From 3517ef6513d1a9585c309f9b099a3345f4b856b0 Mon Sep 17 00:00:00 2001 From: Kirk Baird Date: Wed, 20 Feb 2019 14:16:07 +1100 Subject: [PATCH 001/144] Initialise fuzzing for ssz --- eth2/utils/ssz/fuzz/.gitignore | 4 ++++ eth2/utils/ssz/fuzz/Cargo.toml | 22 +++++++++++++++++++ .../ssz/fuzz/fuzz_targets/fuzz_target_1.rs | 7 ++++++ 3 files changed, 33 insertions(+) create mode 100644 eth2/utils/ssz/fuzz/.gitignore create mode 100644 eth2/utils/ssz/fuzz/Cargo.toml create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_1.rs diff --git a/eth2/utils/ssz/fuzz/.gitignore b/eth2/utils/ssz/fuzz/.gitignore new file mode 100644 index 0000000000..572e03bdf3 --- /dev/null +++ b/eth2/utils/ssz/fuzz/.gitignore @@ -0,0 +1,4 @@ + +target +corpus +artifacts diff --git a/eth2/utils/ssz/fuzz/Cargo.toml b/eth2/utils/ssz/fuzz/Cargo.toml new file mode 100644 index 0000000000..9c0a17f0d3 --- /dev/null +++ b/eth2/utils/ssz/fuzz/Cargo.toml @@ -0,0 +1,22 @@ + +[package] +name = "ssz-fuzz" +version = "0.0.1" +authors = ["Automatically generated"] +publish = false + +[package.metadata] +cargo-fuzz = true + +[dependencies.ssz] +path = ".." +[dependencies.libfuzzer-sys] +git = "https://github.com/rust-fuzz/libfuzzer-sys.git" + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "fuzz_target_1" +path = "fuzz_targets/fuzz_target_1.rs" diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_1.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_1.rs new file mode 100644 index 0000000000..1ca6f957d9 --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_1.rs @@ -0,0 +1,7 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ssz; + +fuzz_target!(|data: &[u8]| { + // fuzzed code goes here +}); From 52347d8e6d4c21f7c4ffebfa9b8e35a58c69ef2d Mon Sep 17 00:00:00 2001 From: Kirk Baird Date: Wed, 20 Feb 2019 14:46:25 +1100 Subject: [PATCH 002/144] Write a fuzz test --- eth2/utils/ssz/fuzz/Cargo.toml | 4 ++-- eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_1.rs | 7 ------- eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8.rs | 10 ++++++++++ 3 files changed, 12 insertions(+), 9 deletions(-) delete mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_1.rs create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8.rs diff --git a/eth2/utils/ssz/fuzz/Cargo.toml b/eth2/utils/ssz/fuzz/Cargo.toml index 9c0a17f0d3..9ffff016c4 100644 --- a/eth2/utils/ssz/fuzz/Cargo.toml +++ b/eth2/utils/ssz/fuzz/Cargo.toml @@ -18,5 +18,5 @@ git = "https://github.com/rust-fuzz/libfuzzer-sys.git" members = ["."] [[bin]] -name = "fuzz_target_1" -path = "fuzz_targets/fuzz_target_1.rs" +name = "fuzz_target_u8" +path = "fuzz_targets/fuzz_target_u8.rs" diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_1.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_1.rs deleted file mode 100644 index 1ca6f957d9..0000000000 --- a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_1.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] -#[macro_use] extern crate libfuzzer_sys; -extern crate ssz; - -fuzz_target!(|data: &[u8]| { - // fuzzed code goes here -}); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8.rs new file mode 100644 index 0000000000..6a8fd7673e --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8.rs @@ -0,0 +1,10 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ssz; + +use ssz::{DecodeError, Decodable, Encodable}; + +// Fuzz ssz_decode(u8) +fuzz_target!(|data: &[u8]| { + let result: Result<(u8, usize), DecodeError> = Decodable::ssz_decode(data, 0); +}); From 38abcc4a240f3878c44203c783af57c66303838e Mon Sep 17 00:00:00 2001 From: Kirk Baird Date: Wed, 20 Feb 2019 15:03:32 +1100 Subject: [PATCH 003/144] Fuzz test for u8 fails --- eth2/utils/ssz/fuzz/Cargo.toml | 4 ++++ .../ssz/fuzz/fuzz_targets/fuzz_target_u16.rs | 19 +++++++++++++++++++ .../ssz/fuzz/fuzz_targets/fuzz_target_u8.rs | 9 +++++++++ 3 files changed, 32 insertions(+) create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u16.rs diff --git a/eth2/utils/ssz/fuzz/Cargo.toml b/eth2/utils/ssz/fuzz/Cargo.toml index 9ffff016c4..b640cc5f0e 100644 --- a/eth2/utils/ssz/fuzz/Cargo.toml +++ b/eth2/utils/ssz/fuzz/Cargo.toml @@ -20,3 +20,7 @@ members = ["."] [[bin]] name = "fuzz_target_u8" path = "fuzz_targets/fuzz_target_u8.rs" + +[[bin]] +name = "fuzz_target_u16" +path = "fuzz_targets/fuzz_target_u16.rs" diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u16.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u16.rs new file mode 100644 index 0000000000..8bf2be8a47 --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u16.rs @@ -0,0 +1,19 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ssz; + +use ssz::{DecodeError, Decodable, Encodable}; + +// Fuzz ssz_decode(u8) +fuzz_target!(|data: &[u8]| { + let result: Result<(u16, usize), DecodeError> = Decodable::ssz_decode(data, 0); + if data.len() > 1 { + // Valid result + let (number_u16, index) = result.unwrap(); + assert_eq!(index, 2); + // TODO: add test for number? + } else { + // Length of 0 or 1 should return error + assert_eq!(result, Err(DecodeError::TooShort)); + } +}); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8.rs index 6a8fd7673e..afab5eab51 100644 --- a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8.rs +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8.rs @@ -7,4 +7,13 @@ use ssz::{DecodeError, Decodable, Encodable}; // Fuzz ssz_decode(u8) fuzz_target!(|data: &[u8]| { let result: Result<(u8, usize), DecodeError> = Decodable::ssz_decode(data, 0); + if data.len() > 0 { + // Should have valid result + let (number_u8, index) = result.unwrap(); + assert_eq!(number_u8, data[0]); + assert_eq!(index, 2); + } else { + // Length of 0 should return error + assert_eq!(result, Err(DecodeError::TooShort)); + } }); From 532a854f8efbb313c75ec236acd74a283290ede3 Mon Sep 17 00:00:00 2001 From: mehdi Date: Wed, 20 Feb 2019 15:30:58 +1100 Subject: [PATCH 004/144] Fixing minor bug in assert statement --- eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8.rs index afab5eab51..0320b7c109 100644 --- a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8.rs +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8.rs @@ -11,7 +11,7 @@ fuzz_target!(|data: &[u8]| { // Should have valid result let (number_u8, index) = result.unwrap(); assert_eq!(number_u8, data[0]); - assert_eq!(index, 2); + assert_eq!(index, 1); } else { // Length of 0 should return error assert_eq!(result, Err(DecodeError::TooShort)); From d5c4771f0a3810d50334cadcaef75606065974f3 Mon Sep 17 00:00:00 2001 From: Kirk Baird Date: Wed, 20 Feb 2019 15:34:15 +1100 Subject: [PATCH 005/144] Fuzz test decodes from u8 to u64 --- eth2/utils/ssz/fuzz/Cargo.toml | 16 +++++++--- ...arget_u16.rs => fuzz_target_u16_decode.rs} | 11 ++++--- .../fuzz_targets/fuzz_target_u32_decode.rs | 22 +++++++++++++ .../fuzz_targets/fuzz_target_u64_decode.rs | 31 +++++++++++++++++++ ..._target_u8.rs => fuzz_target_u8_decode.rs} | 10 +++--- 5 files changed, 78 insertions(+), 12 deletions(-) rename eth2/utils/ssz/fuzz/fuzz_targets/{fuzz_target_u16.rs => fuzz_target_u16_decode.rs} (59%) create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u32_decode.rs create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u64_decode.rs rename eth2/utils/ssz/fuzz/fuzz_targets/{fuzz_target_u8.rs => fuzz_target_u8_decode.rs} (66%) diff --git a/eth2/utils/ssz/fuzz/Cargo.toml b/eth2/utils/ssz/fuzz/Cargo.toml index b640cc5f0e..d0455a556e 100644 --- a/eth2/utils/ssz/fuzz/Cargo.toml +++ b/eth2/utils/ssz/fuzz/Cargo.toml @@ -18,9 +18,17 @@ git = "https://github.com/rust-fuzz/libfuzzer-sys.git" members = ["."] [[bin]] -name = "fuzz_target_u8" -path = "fuzz_targets/fuzz_target_u8.rs" +name = "fuzz_target_u8_decode" +path = "fuzz_targets/fuzz_target_u8_decode.rs" [[bin]] -name = "fuzz_target_u16" -path = "fuzz_targets/fuzz_target_u16.rs" +name = "fuzz_target_u16_decode" +path = "fuzz_targets/fuzz_target_u16_decode.rs" + +[[bin]] +name = "fuzz_target_u32_decode" +path = "fuzz_targets/fuzz_target_u32_decode.rs" + +[[bin]] +name = "fuzz_target_u64_decode" +path = "fuzz_targets/fuzz_target_u64_decode.rs" diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u16.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u16_decode.rs similarity index 59% rename from eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u16.rs rename to eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u16_decode.rs index 8bf2be8a47..73395f3af6 100644 --- a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u16.rs +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u16_decode.rs @@ -2,16 +2,19 @@ #[macro_use] extern crate libfuzzer_sys; extern crate ssz; -use ssz::{DecodeError, Decodable, Encodable}; +use ssz::{DecodeError, Decodable}; -// Fuzz ssz_decode(u8) +// Fuzz ssz_decode() fuzz_target!(|data: &[u8]| { let result: Result<(u16, usize), DecodeError> = Decodable::ssz_decode(data, 0); - if data.len() > 1 { + if data.len() >= 2 { // Valid result let (number_u16, index) = result.unwrap(); assert_eq!(index, 2); - // TODO: add test for number? + // TODO: change to little endian bytes + // https://github.com/sigp/lighthouse/issues/215 + let val = u16::from_be_bytes([data[0], data[1]]); + assert_eq!(number_u16, val); } else { // Length of 0 or 1 should return error assert_eq!(result, Err(DecodeError::TooShort)); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u32_decode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u32_decode.rs new file mode 100644 index 0000000000..e99bf2fad9 --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u32_decode.rs @@ -0,0 +1,22 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ssz; + +use ssz::{DecodeError, Decodable}; + +// Fuzz ssz_decode() +fuzz_target!(|data: &[u8]| { + let result: Result<(u32, usize), DecodeError> = Decodable::ssz_decode(data, 0); + if data.len() >= 4 { + // Valid result + let (number_u32, index) = result.unwrap(); + assert_eq!(index, 4); + // TODO: change to little endian bytes + // https://github.com/sigp/lighthouse/issues/215 + let val = u32::from_be_bytes([data[0], data[1], data[2], data[3]]); + assert_eq!(number_u32, val); + } else { + // Length less then 4 should return error + assert_eq!(result, Err(DecodeError::TooShort)); + } +}); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u64_decode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u64_decode.rs new file mode 100644 index 0000000000..9e13ab604d --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u64_decode.rs @@ -0,0 +1,31 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ssz; + +use ssz::{DecodeError, Decodable}; + +// Fuzz ssz_decode() +fuzz_target!(|data: &[u8]| { + let result: Result<(u64, usize), DecodeError> = Decodable::ssz_decode(data, 0); + if data.len() >= 8 { + // Valid result + let (number_u64, index) = result.unwrap(); + assert_eq!(index, 8); + // TODO: change to little endian bytes + // https://github.com/sigp/lighthouse/issues/215 + let val = u64::from_be_bytes([ + data[0], + data[1], + data[2], + data[3], + data[4], + data[5], + data[6], + data[7], + ]); + assert_eq!(number_u64, val); + } else { + // Length less then 4 should return error + assert_eq!(result, Err(DecodeError::TooShort)); + } +}); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8_decode.rs similarity index 66% rename from eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8.rs rename to eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8_decode.rs index afab5eab51..296b6fa3d3 100644 --- a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8.rs +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8_decode.rs @@ -2,16 +2,18 @@ #[macro_use] extern crate libfuzzer_sys; extern crate ssz; -use ssz::{DecodeError, Decodable, Encodable}; +use ssz::{DecodeError, Decodable}; -// Fuzz ssz_decode(u8) +// Fuzz ssz_decode() fuzz_target!(|data: &[u8]| { let result: Result<(u8, usize), DecodeError> = Decodable::ssz_decode(data, 0); - if data.len() > 0 { + if data.len() >= 1 { // Should have valid result let (number_u8, index) = result.unwrap(); + // TODO: change to little endian bytes + // https://github.com/sigp/lighthouse/issues/215 assert_eq!(number_u8, data[0]); - assert_eq!(index, 2); + assert_eq!(index, 1); } else { // Length of 0 should return error assert_eq!(result, Err(DecodeError::TooShort)); From b98db3773ed560bb12da54df787da620136c328b Mon Sep 17 00:00:00 2001 From: Kirk Baird Date: Wed, 20 Feb 2019 16:15:30 +1100 Subject: [PATCH 006/144] Fuzz test ssz_encode for u8 to u64 --- eth2/utils/ssz/fuzz/Cargo.toml | 16 ++++++++ .../fuzz_targets/fuzz_target_u16_encode.rs | 22 ++++++++++ .../fuzz_targets/fuzz_target_u32_encode.rs | 22 ++++++++++ .../fuzz_targets/fuzz_target_u64_decode.rs | 2 +- .../fuzz_targets/fuzz_target_u64_encode.rs | 40 +++++++++++++++++++ .../fuzz_targets/fuzz_target_u8_decode.rs | 2 +- .../fuzz_targets/fuzz_target_u8_encode.rs | 22 ++++++++++ 7 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u16_encode.rs create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u32_encode.rs create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u64_encode.rs create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8_encode.rs diff --git a/eth2/utils/ssz/fuzz/Cargo.toml b/eth2/utils/ssz/fuzz/Cargo.toml index d0455a556e..5af3275cd8 100644 --- a/eth2/utils/ssz/fuzz/Cargo.toml +++ b/eth2/utils/ssz/fuzz/Cargo.toml @@ -21,14 +21,30 @@ members = ["."] name = "fuzz_target_u8_decode" path = "fuzz_targets/fuzz_target_u8_decode.rs" +[[bin]] +name = "fuzz_target_u8_encode" +path = "fuzz_targets/fuzz_target_u8_encode.rs" + [[bin]] name = "fuzz_target_u16_decode" path = "fuzz_targets/fuzz_target_u16_decode.rs" +[[bin]] +name = "fuzz_target_u16_encode" +path = "fuzz_targets/fuzz_target_u16_encode.rs" + [[bin]] name = "fuzz_target_u32_decode" path = "fuzz_targets/fuzz_target_u32_decode.rs" +[[bin]] +name = "fuzz_target_u32_encode" +path = "fuzz_targets/fuzz_target_u32_encode.rs" + [[bin]] name = "fuzz_target_u64_decode" path = "fuzz_targets/fuzz_target_u64_decode.rs" + +[[bin]] +name = "fuzz_target_u64_encode" +path = "fuzz_targets/fuzz_target_u64_encode.rs" diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u16_encode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u16_encode.rs new file mode 100644 index 0000000000..ce8a51845c --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u16_encode.rs @@ -0,0 +1,22 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ssz; + +use ssz::SszStream; + +// Fuzz ssz_encode (via ssz_append) +fuzz_target!(|data: &[u8]| { + let mut ssz = SszStream::new(); + let mut number_u16 = 0; + if data.len() >= 2 { + number_u16 = u16::from_be_bytes([data[0], data[1]]); + } + + ssz.append(&number_u16); + let ssz = ssz.drain(); + + // TODO: change to little endian bytes + // https://github.com/sigp/lighthouse/issues/215 + assert_eq!(ssz.len(), 2); + assert_eq!(number_u16, u16::from_be_bytes([ssz[0], ssz[1]])); +}); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u32_encode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u32_encode.rs new file mode 100644 index 0000000000..c71bcecaf6 --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u32_encode.rs @@ -0,0 +1,22 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ssz; + +use ssz::SszStream; + +// Fuzz ssz_encode (via ssz_append) +fuzz_target!(|data: &[u8]| { + let mut ssz = SszStream::new(); + let mut number_u32 = 0; + if data.len() >= 4 { + number_u32 = u32::from_be_bytes([data[0], data[1], data[2], data[3]]); + } + + ssz.append(&number_u32); + let ssz = ssz.drain(); + + // TODO: change to little endian bytes + // https://github.com/sigp/lighthouse/issues/215 + assert_eq!(ssz.len(), 4); + assert_eq!(number_u32, u32::from_be_bytes([ssz[0], ssz[1], ssz[2], ssz[3]])); +}); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u64_decode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u64_decode.rs index 9e13ab604d..63eb60f55b 100644 --- a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u64_decode.rs +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u64_decode.rs @@ -25,7 +25,7 @@ fuzz_target!(|data: &[u8]| { ]); assert_eq!(number_u64, val); } else { - // Length less then 4 should return error + // Length less then 8 should return error assert_eq!(result, Err(DecodeError::TooShort)); } }); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u64_encode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u64_encode.rs new file mode 100644 index 0000000000..68616e0da8 --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u64_encode.rs @@ -0,0 +1,40 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ssz; + +use ssz::SszStream; + +// Fuzz ssz_encode (via ssz_append) +fuzz_target!(|data: &[u8]| { + let mut ssz = SszStream::new(); + let mut number_u64 = 0; + if data.len() >= 8 { + number_u64 = u64::from_be_bytes([ + data[0], + data[1], + data[2], + data[3], + data[4], + data[5], + data[6], + data[7], + ]); + } + + ssz.append(&number_u64); + let ssz = ssz.drain(); + + // TODO: change to little endian bytes + // https://github.com/sigp/lighthouse/issues/215 + assert_eq!(ssz.len(), 8); + assert_eq!(number_u64, u64::from_be_bytes([ + ssz[0], + ssz[1], + ssz[2], + ssz[3], + ssz[4], + ssz[5], + ssz[6], + ssz[7], + ])); +}); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8_decode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8_decode.rs index 296b6fa3d3..6f17a4c859 100644 --- a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8_decode.rs +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8_decode.rs @@ -12,8 +12,8 @@ fuzz_target!(|data: &[u8]| { let (number_u8, index) = result.unwrap(); // TODO: change to little endian bytes // https://github.com/sigp/lighthouse/issues/215 - assert_eq!(number_u8, data[0]); assert_eq!(index, 1); + assert_eq!(number_u8, data[0]); } else { // Length of 0 should return error assert_eq!(result, Err(DecodeError::TooShort)); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8_encode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8_encode.rs new file mode 100644 index 0000000000..a135f2cd53 --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_u8_encode.rs @@ -0,0 +1,22 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ssz; + +use ssz::SszStream; + +// Fuzz ssz_encode (via ssz_append) +fuzz_target!(|data: &[u8]| { + let mut ssz = SszStream::new(); + let mut number_u8 = 0; + if data.len() >= 1 { + number_u8 = data[0]; + } + + ssz.append(&number_u8); + let ssz = ssz.drain(); + + // TODO: change to little endian bytes + // https://github.com/sigp/lighthouse/issues/215 + assert_eq!(number_u8, ssz[0]); + assert_eq!(ssz.len(), 1); +}); From 00e5b571662cb2015d6977df1b812d0a5b17d876 Mon Sep 17 00:00:00 2001 From: Kirk Baird Date: Wed, 20 Feb 2019 16:43:30 +1100 Subject: [PATCH 007/144] Fuzz test ssz_encode and ssz_decode for usize --- eth2/utils/ssz/fuzz/Cargo.toml | 8 ++++ .../fuzz_targets/fuzz_target_usize_decode.rs | 32 +++++++++++++++ .../fuzz_targets/fuzz_target_usize_encode.rs | 40 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_usize_decode.rs create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_usize_encode.rs diff --git a/eth2/utils/ssz/fuzz/Cargo.toml b/eth2/utils/ssz/fuzz/Cargo.toml index 5af3275cd8..6a65fb5e2d 100644 --- a/eth2/utils/ssz/fuzz/Cargo.toml +++ b/eth2/utils/ssz/fuzz/Cargo.toml @@ -48,3 +48,11 @@ path = "fuzz_targets/fuzz_target_u64_decode.rs" [[bin]] name = "fuzz_target_u64_encode" path = "fuzz_targets/fuzz_target_u64_encode.rs" + +[[bin]] +name = "fuzz_target_usize_decode" +path = "fuzz_targets/fuzz_target_usize_decode.rs" + +[[bin]] +name = "fuzz_target_usize_encode" +path = "fuzz_targets/fuzz_target_usize_encode.rs" diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_usize_decode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_usize_decode.rs new file mode 100644 index 0000000000..1458bfae98 --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_usize_decode.rs @@ -0,0 +1,32 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ssz; + +use ssz::{DecodeError, Decodable}; + +// Fuzz ssz_decode() +fuzz_target!(|data: &[u8]| { + // Note: we assume architecture is 64 bit -> usize == 64 bits + let result: Result<(usize, usize), DecodeError> = Decodable::ssz_decode(data, 0); + if data.len() >= 8 { + // Valid result + let (number_usize, index) = result.unwrap(); + assert_eq!(index, 8); + // TODO: change to little endian bytes + // https://github.com/sigp/lighthouse/issues/215 + let val = u64::from_be_bytes([ + data[0], + data[1], + data[2], + data[3], + data[4], + data[5], + data[6], + data[7], + ]); + assert_eq!(number_usize, val as usize); + } else { + // Length less then 8 should return error + assert_eq!(result, Err(DecodeError::TooShort)); + } +}); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_usize_encode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_usize_encode.rs new file mode 100644 index 0000000000..d5aa9751f3 --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_usize_encode.rs @@ -0,0 +1,40 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ssz; + +use ssz::SszStream; + +// Fuzz ssz_encode (via ssz_append) +fuzz_target!(|data: &[u8]| { + let mut ssz = SszStream::new(); + let mut number_usize = 0; + if data.len() >= 8 { + number_usize = u64::from_be_bytes([ + data[0], + data[1], + data[2], + data[3], + data[4], + data[5], + data[6], + data[7], + ]) as usize; + } + + ssz.append(&number_usize); + let ssz = ssz.drain(); + + // TODO: change to little endian bytes + // https://github.com/sigp/lighthouse/issues/215 + assert_eq!(ssz.len(), 8); + assert_eq!(number_usize, u64::from_be_bytes([ + ssz[0], + ssz[1], + ssz[2], + ssz[3], + ssz[4], + ssz[5], + ssz[6], + ssz[7], + ]) as usize); +}); From 274bdd491dcfa0d74b6ceb51f0b63d03cb14cce3 Mon Sep 17 00:00:00 2001 From: Kirk Baird Date: Thu, 21 Feb 2019 13:43:09 +1100 Subject: [PATCH 008/144] Fuzz for address and Hash256 --- eth2/utils/ssz/fuzz/Cargo.toml | 27 ++++++++++++++++++ .../fuzz_target_address_decode.rs | 21 ++++++++++++++ .../fuzz_target_address_encode.rs | 20 +++++++++++++ .../fuzz_targets/fuzz_target_bool_decode.rs | 28 +++++++++++++++++++ .../fuzz_targets/fuzz_target_bool_encode.rs | 22 +++++++++++++++ .../fuzz_target_hash256_decode.rs | 21 ++++++++++++++ .../fuzz_target_hash256_encode.rs | 20 +++++++++++++ 7 files changed, 159 insertions(+) create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_address_decode.rs create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_address_encode.rs create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_bool_decode.rs create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_bool_encode.rs create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_hash256_decode.rs create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_hash256_encode.rs diff --git a/eth2/utils/ssz/fuzz/Cargo.toml b/eth2/utils/ssz/fuzz/Cargo.toml index 6a65fb5e2d..081afdcb95 100644 --- a/eth2/utils/ssz/fuzz/Cargo.toml +++ b/eth2/utils/ssz/fuzz/Cargo.toml @@ -8,6 +8,9 @@ publish = false [package.metadata] cargo-fuzz = true +[dependencies] +ethereum-types = "0.4.0" + [dependencies.ssz] path = ".." [dependencies.libfuzzer-sys] @@ -17,6 +20,14 @@ git = "https://github.com/rust-fuzz/libfuzzer-sys.git" [workspace] members = ["."] +[[bin]] +name = "fuzz_target_bool_decode" +path = "fuzz_targets/fuzz_target_bool_decode.rs" + +[[bin]] +name = "fuzz_target_bool_encode" +path = "fuzz_targets/fuzz_target_bool_encode.rs" + [[bin]] name = "fuzz_target_u8_decode" path = "fuzz_targets/fuzz_target_u8_decode.rs" @@ -56,3 +67,19 @@ path = "fuzz_targets/fuzz_target_usize_decode.rs" [[bin]] name = "fuzz_target_usize_encode" path = "fuzz_targets/fuzz_target_usize_encode.rs" + +[[bin]] +name = "fuzz_target_hash256_decode" +path = "fuzz_targets/fuzz_target_hash256_decode.rs" + +[[bin]] +name = "fuzz_target_hash256_encode" +path = "fuzz_targets/fuzz_target_hash256_encode.rs" + +[[bin]] +name = "fuzz_target_address_decode" +path = "fuzz_targets/fuzz_target_address_decode.rs" + +[[bin]] +name = "fuzz_target_address_encode" +path = "fuzz_targets/fuzz_target_address_encode.rs" diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_address_decode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_address_decode.rs new file mode 100644 index 0000000000..c49be500a1 --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_address_decode.rs @@ -0,0 +1,21 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ethereum_types; +extern crate ssz; + +use ethereum_types::Address; +use ssz::{DecodeError, Decodable}; + +// Fuzz ssz_decode() +fuzz_target!(|data: &[u8]| { + let result: Result<(Address, usize), DecodeError> = Decodable::ssz_decode(data, 0); + if data.len() >= 20 { + // Should have valid result + let (address, index) = result.unwrap(); + assert_eq!(index, 20); + assert_eq!(address, Address::from_slice(&data[..20])); + } else { + // Length of less than 32 should return error + assert_eq!(result, Err(DecodeError::TooShort)); + } +}); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_address_encode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_address_encode.rs new file mode 100644 index 0000000000..0e51e00acb --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_address_encode.rs @@ -0,0 +1,20 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ethereum_types; +extern crate ssz; + +use ethereum_types::Address; +use ssz::SszStream; + +// Fuzz ssz_encode (via ssz_append) +fuzz_target!(|data: &[u8]| { + let mut ssz = SszStream::new(); + if data.len() >= 20 { + let hash = Address::from_slice(&data[..20]); + ssz.append(&hash); + let ssz = ssz.drain(); + + assert_eq!(data[..20], ssz[..20]); + assert_eq!(ssz.len(), 20); + } +}); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_bool_decode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_bool_decode.rs new file mode 100644 index 0000000000..4fb1052b14 --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_bool_decode.rs @@ -0,0 +1,28 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ssz; + +use ssz::{DecodeError, Decodable}; + +// Fuzz ssz_decode() +fuzz_target!(|data: &[u8]| { + let result: Result<(bool, usize), DecodeError> = Decodable::ssz_decode(data, 0); + if data.len() >= 1 { + // TODO: change to little endian bytes + // https://github.com/sigp/lighthouse/issues/215 + if data[0] == u8::pow(2,7) { + let (val_bool, index) = result.unwrap(); + assert!(val_bool); + assert_eq!(index, 1); + } else if data[0] == 0 { + let (val_bool, index) = result.unwrap(); + assert!(!val_bool); + assert_eq!(index, 1); + } else { + assert_eq!(result, Err(DecodeError::Invalid)); + } + } else { + // Length of 0 should return error + assert_eq!(result, Err(DecodeError::TooShort)); + } +}); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_bool_encode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_bool_encode.rs new file mode 100644 index 0000000000..4f344cb7de --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_bool_encode.rs @@ -0,0 +1,22 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ssz; + +use ssz::SszStream; + +// Fuzz ssz_encode (via ssz_append) +fuzz_target!(|data: &[u8]| { + let mut ssz = SszStream::new(); + let mut val_bool = 0; + if data.len() >= 1 { + val_bool = data[0] % u8::pow(2, 6); + } + + ssz.append(&val_bool); + let ssz = ssz.drain(); + + // TODO: change to little endian bytes + // https://github.com/sigp/lighthouse/issues/215 + assert_eq!(val_bool, ssz[0] % u8::pow(2, 6)); + assert_eq!(ssz.len(), 1); +}); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_hash256_decode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_hash256_decode.rs new file mode 100644 index 0000000000..e4ccc56a4a --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_hash256_decode.rs @@ -0,0 +1,21 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ethereum_types; +extern crate ssz; + +use ethereum_types::H256; +use ssz::{DecodeError, Decodable}; + +// Fuzz ssz_decode() +fuzz_target!(|data: &[u8]| { + let result: Result<(H256, usize), DecodeError> = Decodable::ssz_decode(data, 0); + if data.len() >= 32 { + // Should have valid result + let (hash, index) = result.unwrap(); + assert_eq!(index, 32); + assert_eq!(hash, H256::from_slice(&data[..32])); + } else { + // Length of less than 32 should return error + assert_eq!(result, Err(DecodeError::TooShort)); + } +}); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_hash256_encode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_hash256_encode.rs new file mode 100644 index 0000000000..537d9cdf96 --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_hash256_encode.rs @@ -0,0 +1,20 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ethereum_types; +extern crate ssz; + +use ethereum_types::H256; +use ssz::SszStream; + +// Fuzz ssz_encode (via ssz_append) +fuzz_target!(|data: &[u8]| { + let mut ssz = SszStream::new(); + if data.len() >= 32 { + let hash = H256::from_slice(&data[..32]); + ssz.append(&hash); + let ssz = ssz.drain(); + + assert_eq!(data[..32], ssz[..32]); + assert_eq!(ssz.len(), 32); + } +}); From 68017b66fdbcc1ea5b19876252ce813d20cab60c Mon Sep 17 00:00:00 2001 From: Kirk Baird Date: Thu, 21 Feb 2019 14:23:21 +1100 Subject: [PATCH 009/144] Fuzzing for Vec --- eth2/utils/ssz/fuzz/Cargo.toml | 8 +++++++ .../fuzz_targets/fuzz_target_vec_decode.rs | 21 +++++++++++++++++++ .../fuzz_targets/fuzz_target_vec_encode.rs | 15 +++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_decode.rs create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_encode.rs diff --git a/eth2/utils/ssz/fuzz/Cargo.toml b/eth2/utils/ssz/fuzz/Cargo.toml index 081afdcb95..c76cbbbdef 100644 --- a/eth2/utils/ssz/fuzz/Cargo.toml +++ b/eth2/utils/ssz/fuzz/Cargo.toml @@ -83,3 +83,11 @@ path = "fuzz_targets/fuzz_target_address_decode.rs" [[bin]] name = "fuzz_target_address_encode" path = "fuzz_targets/fuzz_target_address_encode.rs" + +[[bin]] +name = "fuzz_target_vec_decode" +path = "fuzz_targets/fuzz_target_vec_decode.rs" + +[[bin]] +name = "fuzz_target_vec_encode" +path = "fuzz_targets/fuzz_target_vec_encode.rs" diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_decode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_decode.rs new file mode 100644 index 0000000000..048d19ee57 --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_decode.rs @@ -0,0 +1,21 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ethereum_types; +extern crate ssz; + +use ethereum_types::{Address, H256}; +use ssz::{DecodeError, Decodable}; + +// Fuzz ssz_decode() +fuzz_target!(|data: &[u8]| { + let _result: Result<(Vec, usize), DecodeError> = Decodable::ssz_decode(data, 0); + /* + let _result: Result<(Vec, usize), DecodeError> = Decodable::ssz_decode(data, 0); + let _result: Result<(Vec, usize), DecodeError> = Decodable::ssz_decode(data, 0); + let _result: Result<(Vec, usize), DecodeError> = Decodable::ssz_decode(data, 0); + let _result: Result<(Vec, usize), DecodeError> = Decodable::ssz_decode(data, 0); + let _result: Result<(Vec
, usize), DecodeError> = Decodable::ssz_decode(data, 0); + let _result: Result<(Vec, usize), DecodeError> = Decodable::ssz_decode(data, 0); + let _result: Result<(Vec, usize), DecodeError> = Decodable::ssz_decode(data, 0); + */ +}); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_encode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_encode.rs new file mode 100644 index 0000000000..6980e5d206 --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_encode.rs @@ -0,0 +1,15 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ethereum_types; +extern crate ssz; + +use ethereum_types::{Address, H256}; +use ssz::SszStream; + +// Fuzz ssz_decode() +fuzz_target!(|data: &[u8]| { + + let mut ssz = SszStream::new(); + let data_vec = data.to_vec(); + ssz.append(&data_vec); +}); From ab1dc7bfceceb45a8d272bdfdeab870531fce285 Mon Sep 17 00:00:00 2001 From: Kirk Baird Date: Fri, 22 Feb 2019 16:50:14 +1100 Subject: [PATCH 010/144] Add simple fuzz tests for hashing and boolean-bitfield --- eth2/utils/boolean-bitfield/fuzz/.gitignore | 4 +++ eth2/utils/boolean-bitfield/fuzz/Cargo.toml | 33 +++++++++++++++++++ .../fuzz_targets/fuzz_target_from_bytes.rs | 9 +++++ .../fuzz_targets/fuzz_target_ssz_decode.rs | 11 +++++++ .../fuzz_targets/fuzz_target_ssz_encode.rs | 13 ++++++++ eth2/utils/hashing/fuzz/.gitignore | 4 +++ eth2/utils/hashing/fuzz/Cargo.toml | 22 +++++++++++++ .../fuzz/fuzz_targets/fuzz_target_hash.rs | 9 +++++ 8 files changed, 105 insertions(+) create mode 100644 eth2/utils/boolean-bitfield/fuzz/.gitignore create mode 100644 eth2/utils/boolean-bitfield/fuzz/Cargo.toml create mode 100644 eth2/utils/boolean-bitfield/fuzz/fuzz_targets/fuzz_target_from_bytes.rs create mode 100644 eth2/utils/boolean-bitfield/fuzz/fuzz_targets/fuzz_target_ssz_decode.rs create mode 100644 eth2/utils/boolean-bitfield/fuzz/fuzz_targets/fuzz_target_ssz_encode.rs create mode 100644 eth2/utils/hashing/fuzz/.gitignore create mode 100644 eth2/utils/hashing/fuzz/Cargo.toml create mode 100644 eth2/utils/hashing/fuzz/fuzz_targets/fuzz_target_hash.rs diff --git a/eth2/utils/boolean-bitfield/fuzz/.gitignore b/eth2/utils/boolean-bitfield/fuzz/.gitignore new file mode 100644 index 0000000000..572e03bdf3 --- /dev/null +++ b/eth2/utils/boolean-bitfield/fuzz/.gitignore @@ -0,0 +1,4 @@ + +target +corpus +artifacts diff --git a/eth2/utils/boolean-bitfield/fuzz/Cargo.toml b/eth2/utils/boolean-bitfield/fuzz/Cargo.toml new file mode 100644 index 0000000000..9769fc50ea --- /dev/null +++ b/eth2/utils/boolean-bitfield/fuzz/Cargo.toml @@ -0,0 +1,33 @@ + +[package] +name = "boolean-bitfield-fuzz" +version = "0.0.1" +authors = ["Automatically generated"] +publish = false + +[package.metadata] +cargo-fuzz = true + +[dependencies] +ssz = { path = "../../ssz" } + +[dependencies.boolean-bitfield] +path = ".." +[dependencies.libfuzzer-sys] +git = "https://github.com/rust-fuzz/libfuzzer-sys.git" + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "fuzz_target_from_bytes" +path = "fuzz_targets/fuzz_target_from_bytes.rs" + +[[bin]] +name = "fuzz_target_ssz_decode" +path = "fuzz_targets/fuzz_target_ssz_decode.rs" + +[[bin]] +name = "fuzz_target_ssz_encode" +path = "fuzz_targets/fuzz_target_ssz_encode.rs" diff --git a/eth2/utils/boolean-bitfield/fuzz/fuzz_targets/fuzz_target_from_bytes.rs b/eth2/utils/boolean-bitfield/fuzz/fuzz_targets/fuzz_target_from_bytes.rs new file mode 100644 index 0000000000..0c71c6d68c --- /dev/null +++ b/eth2/utils/boolean-bitfield/fuzz/fuzz_targets/fuzz_target_from_bytes.rs @@ -0,0 +1,9 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate boolean_bitfield; + +use boolean_bitfield::BooleanBitfield; + +fuzz_target!(|data: &[u8]| { + let _result = BooleanBitfield::from_bytes(data); +}); diff --git a/eth2/utils/boolean-bitfield/fuzz/fuzz_targets/fuzz_target_ssz_decode.rs b/eth2/utils/boolean-bitfield/fuzz/fuzz_targets/fuzz_target_ssz_decode.rs new file mode 100644 index 0000000000..14ddbb0a99 --- /dev/null +++ b/eth2/utils/boolean-bitfield/fuzz/fuzz_targets/fuzz_target_ssz_decode.rs @@ -0,0 +1,11 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate boolean_bitfield; +extern crate ssz; + +use boolean_bitfield::BooleanBitfield; +use ssz::{Decodable, DecodeError}; + +fuzz_target!(|data: &[u8]| { + let result: Result<(BooleanBitfield, usize), DecodeError> = <_>::ssz_decode(data, 0); +}); diff --git a/eth2/utils/boolean-bitfield/fuzz/fuzz_targets/fuzz_target_ssz_encode.rs b/eth2/utils/boolean-bitfield/fuzz/fuzz_targets/fuzz_target_ssz_encode.rs new file mode 100644 index 0000000000..0626e5db7a --- /dev/null +++ b/eth2/utils/boolean-bitfield/fuzz/fuzz_targets/fuzz_target_ssz_encode.rs @@ -0,0 +1,13 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate boolean_bitfield; +extern crate ssz; + +use boolean_bitfield::BooleanBitfield; +use ssz::SszStream; + +fuzz_target!(|data: &[u8]| { + let bitfield = BooleanBitfield::from_bytes(data); + let mut ssz = SszStream::new(); + ssz.append(&bitfield); +}); diff --git a/eth2/utils/hashing/fuzz/.gitignore b/eth2/utils/hashing/fuzz/.gitignore new file mode 100644 index 0000000000..572e03bdf3 --- /dev/null +++ b/eth2/utils/hashing/fuzz/.gitignore @@ -0,0 +1,4 @@ + +target +corpus +artifacts diff --git a/eth2/utils/hashing/fuzz/Cargo.toml b/eth2/utils/hashing/fuzz/Cargo.toml new file mode 100644 index 0000000000..57e0172eb6 --- /dev/null +++ b/eth2/utils/hashing/fuzz/Cargo.toml @@ -0,0 +1,22 @@ + +[package] +name = "hashing-fuzz" +version = "0.0.1" +authors = ["Automatically generated"] +publish = false + +[package.metadata] +cargo-fuzz = true + +[dependencies.hashing] +path = ".." +[dependencies.libfuzzer-sys] +git = "https://github.com/rust-fuzz/libfuzzer-sys.git" + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "fuzz_target_hash" +path = "fuzz_targets/fuzz_target_hash.rs" diff --git a/eth2/utils/hashing/fuzz/fuzz_targets/fuzz_target_hash.rs b/eth2/utils/hashing/fuzz/fuzz_targets/fuzz_target_hash.rs new file mode 100644 index 0000000000..dd78d1ac86 --- /dev/null +++ b/eth2/utils/hashing/fuzz/fuzz_targets/fuzz_target_hash.rs @@ -0,0 +1,9 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate hashing; + +use hashing::hash; + +fuzz_target!(|data: &[u8]| { + let _result = hash(data); +}); From 08b803b6e7d24ddb85eb4e6678647509eb562af0 Mon Sep 17 00:00:00 2001 From: Kirk Baird Date: Thu, 7 Mar 2019 17:50:00 +1100 Subject: [PATCH 011/144] Modifications to fuzz tests --- eth2/utils/ssz/fuzz/Cargo.toml | 12 ++++++++++++ .../fuzz_targets/fuzz_target_vec_address_decode.rs | 12 ++++++++++++ .../fuzz/fuzz_targets/fuzz_target_vec_bool_decode.rs | 10 ++++++++++ .../ssz/fuzz/fuzz_targets/fuzz_target_vec_decode.rs | 9 --------- .../ssz/fuzz/fuzz_targets/fuzz_target_vec_encode.rs | 2 +- .../fuzz/fuzz_targets/fuzz_target_vec_u64_decode.rs | 10 ++++++++++ 6 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_address_decode.rs create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_bool_decode.rs create mode 100644 eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_u64_decode.rs diff --git a/eth2/utils/ssz/fuzz/Cargo.toml b/eth2/utils/ssz/fuzz/Cargo.toml index c76cbbbdef..055d031a09 100644 --- a/eth2/utils/ssz/fuzz/Cargo.toml +++ b/eth2/utils/ssz/fuzz/Cargo.toml @@ -88,6 +88,18 @@ path = "fuzz_targets/fuzz_target_address_encode.rs" name = "fuzz_target_vec_decode" path = "fuzz_targets/fuzz_target_vec_decode.rs" +[[bin]] +name = "fuzz_target_vec_address_decode" +path = "fuzz_targets/fuzz_target_vec_address_decode.rs" + +[[bin]] +name = "fuzz_target_vec_u64_decode" +path = "fuzz_targets/fuzz_target_vec_u64_decode.rs" + +[[bin]] +name = "fuzz_target_vec_bool_decode" +path = "fuzz_targets/fuzz_target_vec_bool_decode.rs" + [[bin]] name = "fuzz_target_vec_encode" path = "fuzz_targets/fuzz_target_vec_encode.rs" diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_address_decode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_address_decode.rs new file mode 100644 index 0000000000..6c686df1a9 --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_address_decode.rs @@ -0,0 +1,12 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ethereum_types; +extern crate ssz; + +use ethereum_types::{Address}; +use ssz::{DecodeError, Decodable}; + +// Fuzz ssz_decode() +fuzz_target!(|data: &[u8]| { + let _result: Result<(Vec
, usize), DecodeError> = Decodable::ssz_decode(data, 0); +}); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_bool_decode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_bool_decode.rs new file mode 100644 index 0000000000..25017ef259 --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_bool_decode.rs @@ -0,0 +1,10 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ssz; + +use ssz::{DecodeError, Decodable}; + +// Fuzz ssz_decode() +fuzz_target!(|data: &[u8]| { + let _result: Result<(Vec, usize), DecodeError> = Decodable::ssz_decode(data, 0); +}); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_decode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_decode.rs index 048d19ee57..cc1dc09f51 100644 --- a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_decode.rs +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_decode.rs @@ -9,13 +9,4 @@ use ssz::{DecodeError, Decodable}; // Fuzz ssz_decode() fuzz_target!(|data: &[u8]| { let _result: Result<(Vec, usize), DecodeError> = Decodable::ssz_decode(data, 0); - /* - let _result: Result<(Vec, usize), DecodeError> = Decodable::ssz_decode(data, 0); - let _result: Result<(Vec, usize), DecodeError> = Decodable::ssz_decode(data, 0); - let _result: Result<(Vec, usize), DecodeError> = Decodable::ssz_decode(data, 0); - let _result: Result<(Vec, usize), DecodeError> = Decodable::ssz_decode(data, 0); - let _result: Result<(Vec
, usize), DecodeError> = Decodable::ssz_decode(data, 0); - let _result: Result<(Vec, usize), DecodeError> = Decodable::ssz_decode(data, 0); - let _result: Result<(Vec, usize), DecodeError> = Decodable::ssz_decode(data, 0); - */ }); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_encode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_encode.rs index 6980e5d206..39500b7828 100644 --- a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_encode.rs +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_encode.rs @@ -6,7 +6,7 @@ extern crate ssz; use ethereum_types::{Address, H256}; use ssz::SszStream; -// Fuzz ssz_decode() +// Fuzz ssz_encode() fuzz_target!(|data: &[u8]| { let mut ssz = SszStream::new(); diff --git a/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_u64_decode.rs b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_u64_decode.rs new file mode 100644 index 0000000000..ee25a6378a --- /dev/null +++ b/eth2/utils/ssz/fuzz/fuzz_targets/fuzz_target_vec_u64_decode.rs @@ -0,0 +1,10 @@ +#![no_main] +#[macro_use] extern crate libfuzzer_sys; +extern crate ssz; + +use ssz::{DecodeError, Decodable}; + +// Fuzz ssz_decode() +fuzz_target!(|data: &[u8]| { + let _result: Result<(Vec, usize), DecodeError> = Decodable::ssz_decode(data, 0); +}); From 9a964be58baeb9510183181736533f511eb239c5 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 8 Mar 2019 10:50:12 +1100 Subject: [PATCH 012/144] Update test_harness clap args structure Prepares it for adding a new subcommand --- .../beacon_chain/test_harness/src/bin.rs | 55 ++++++------------- .../beacon_chain/test_harness/src/run_test.rs | 36 ++++++++++++ 2 files changed, 52 insertions(+), 39 deletions(-) create mode 100644 beacon_node/beacon_chain/test_harness/src/run_test.rs diff --git a/beacon_node/beacon_chain/test_harness/src/bin.rs b/beacon_node/beacon_chain/test_harness/src/bin.rs index 283cb0dfa3..3769788d8b 100644 --- a/beacon_node/beacon_chain/test_harness/src/bin.rs +++ b/beacon_node/beacon_chain/test_harness/src/bin.rs @@ -1,10 +1,9 @@ -use clap::{App, Arg}; +use clap::{App, Arg, SubCommand}; use env_logger::{Builder, Env}; -use std::{fs::File, io::prelude::*}; -use test_case::TestCase; -use yaml_rust::YamlLoader; +use run_test::run_test; mod beacon_chain_harness; +mod run_test; mod test_case; mod validator_harness; @@ -15,13 +14,6 @@ fn main() { .version("0.0.1") .author("Sigma Prime ") .about("Runs `test_harness` using a YAML test_case.") - .arg( - Arg::with_name("yaml") - .long("yaml") - .value_name("FILE") - .help("YAML file test_case.") - .required(true), - ) .arg( Arg::with_name("log") .long("log-level") @@ -31,39 +23,24 @@ fn main() { .default_value("debug") .required(true), ) + .subcommand( + SubCommand::with_name("run_test") + .about("Executes a YAML test specification") + .arg( + Arg::with_name("yaml") + .long("yaml") + .value_name("FILE") + .help("YAML file test_case.") + .required(true), + ), + ) .get_matches(); if let Some(log_level) = matches.value_of("log") { Builder::from_env(Env::default().default_filter_or(log_level)).init(); } - if let Some(yaml_file) = matches.value_of("yaml") { - let docs = { - let mut file = File::open(yaml_file).unwrap(); - - let mut yaml_str = String::new(); - file.read_to_string(&mut yaml_str).unwrap(); - - YamlLoader::load_from_str(&yaml_str).unwrap() - }; - - for doc in &docs { - // For each `test_cases` YAML in the document, build a `TestCase`, execute it and - // assert that the execution result matches the test_case description. - // - // In effect, for each `test_case` a new `BeaconChainHarness` is created from genesis - // and a new `BeaconChain` is built as per the test_case. - // - // After the `BeaconChain` has been built out as per the test_case, a dump of all blocks - // and states in the chain is obtained and checked against the `results` specified in - // the `test_case`. - // - // If any of the expectations in the results are not met, the process - // panics with a message. - for test_case in doc["test_cases"].as_vec().unwrap() { - let test_case = TestCase::from_yaml(test_case); - test_case.assert_result_valid(test_case.execute()) - } - } + if let Some(matches) = matches.subcommand_matches("run_test") { + run_test(matches); } } diff --git a/beacon_node/beacon_chain/test_harness/src/run_test.rs b/beacon_node/beacon_chain/test_harness/src/run_test.rs new file mode 100644 index 0000000000..1a816afe0f --- /dev/null +++ b/beacon_node/beacon_chain/test_harness/src/run_test.rs @@ -0,0 +1,36 @@ +use crate::test_case::TestCase; +use clap::ArgMatches; +use std::{fs::File, io::prelude::*}; +use yaml_rust::YamlLoader; + +pub fn run_test(matches: &ArgMatches) { + if let Some(yaml_file) = matches.value_of("yaml") { + let docs = { + let mut file = File::open(yaml_file).unwrap(); + + let mut yaml_str = String::new(); + file.read_to_string(&mut yaml_str).unwrap(); + + YamlLoader::load_from_str(&yaml_str).unwrap() + }; + + for doc in &docs { + // For each `test_cases` YAML in the document, build a `TestCase`, execute it and + // assert that the execution result matches the test_case description. + // + // In effect, for each `test_case` a new `BeaconChainHarness` is created from genesis + // and a new `BeaconChain` is built as per the test_case. + // + // After the `BeaconChain` has been built out as per the test_case, a dump of all blocks + // and states in the chain is obtained and checked against the `results` specified in + // the `test_case`. + // + // If any of the expectations in the results are not met, the process + // panics with a message. + for test_case in doc["test_cases"].as_vec().unwrap() { + let test_case = TestCase::from_yaml(test_case); + test_case.assert_result_valid(test_case.execute()) + } + } + } +} From b98f514d6812161ff2f794e20c3f4b19ea84462f Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 8 Mar 2019 10:50:43 +1100 Subject: [PATCH 013/144] Break BeaconChainHarness validator gen into fn Prepares for allowing for loading from file --- .../test_harness/src/beacon_chain_harness.rs | 40 ++------------ .../beacon_chain_harness/generate_deposits.rs | 54 +++++++++++++++++++ 2 files changed, 59 insertions(+), 35 deletions(-) create mode 100644 beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index f220619ce9..95899e23b4 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -1,12 +1,12 @@ use super::ValidatorHarness; use beacon_chain::{BeaconChain, BlockProcessingOutcome}; pub use beacon_chain::{BeaconChainError, CheckPoint}; -use bls::{create_proof_of_possession, get_withdrawal_credentials}; use db::{ stores::{BeaconBlockStore, BeaconStateStore}, MemoryDB, }; use fork_choice::BitwiseLMDGhost; +use generate_deposits::generate_deposits_with_random_keypairs; use log::debug; use rayon::prelude::*; use slot_clock::TestingSlotClock; @@ -15,6 +15,8 @@ use std::iter::FromIterator; use std::sync::Arc; use types::*; +mod generate_deposits; + /// The beacon chain harness simulates a single beacon node with `validator_count` validators connected /// to it. Each validator is provided a borrow to the beacon chain, where it may read /// information and submit blocks/attestations for processing. @@ -47,40 +49,8 @@ impl BeaconChainHarness { block_hash: Hash256::zero(), }; - debug!("Generating validator keypairs..."); - - let keypairs: Vec = (0..validator_count) - .collect::>() - .par_iter() - .map(|_| Keypair::random()) - .collect(); - - debug!("Creating validator deposits..."); - - let initial_validator_deposits = keypairs - .par_iter() - .map(|keypair| Deposit { - branch: vec![], // branch verification is not specified. - index: 0, // index verification is not specified. - deposit_data: DepositData { - amount: 32_000_000_000, // 32 ETH (in Gwei) - timestamp: genesis_time - 1, - deposit_input: DepositInput { - pubkey: keypair.pk.clone(), - // Validator can withdraw using their main keypair. - withdrawal_credentials: Hash256::from_slice( - &get_withdrawal_credentials( - &keypair.pk, - spec.bls_withdrawal_prefix_byte, - )[..], - ), - proof_of_possession: create_proof_of_possession(&keypair), - }, - }, - }) - .collect(); - - debug!("Creating the BeaconChain..."); + let (keypairs, initial_validator_deposits) = + generate_deposits_with_random_keypairs(validator_count, genesis_time, &spec); // Create the Beacon Chain let beacon_chain = Arc::new( diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs new file mode 100644 index 0000000000..39924fb678 --- /dev/null +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs @@ -0,0 +1,54 @@ +use bls::{create_proof_of_possession, get_withdrawal_credentials}; +use log::debug; +use rayon::prelude::*; +use types::*; + +/// Generates `validator_count` deposits using randomly generated keypairs and some default specs +/// for the deposits. +pub fn generate_deposits_with_random_keypairs( + validator_count: usize, + genesis_time: u64, + spec: &ChainSpec, +) -> (Vec, Vec) { + debug!( + "Generating {} random validator keypairs...", + validator_count + ); + + let keypairs: Vec = (0..validator_count) + .collect::>() + .par_iter() + .map(|_| Keypair::random()) + .collect(); + + debug!( + "Generating {} validator deposits from random keypairs...", + validator_count + ); + + let initial_validator_deposits = + keypairs + .par_iter() + .map(|keypair| Deposit { + branch: vec![], // branch verification is not specified. + index: 0, // index verification is not specified. + deposit_data: DepositData { + amount: 32_000_000_000, // 32 ETH (in Gwei) + timestamp: genesis_time - 1, + deposit_input: DepositInput { + pubkey: keypair.pk.clone(), + // Validator can withdraw using their main keypair. + withdrawal_credentials: Hash256::from_slice( + &get_withdrawal_credentials( + &keypair.pk, + spec.bls_withdrawal_prefix_byte, + )[..], + ), + proof_of_possession: create_proof_of_possession(&keypair), + }, + }, + }) + .collect(); + + (keypairs, initial_validator_deposits) +} From 3b6431b4b457c00c1b92b50bd32b54834ad47996 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 8 Mar 2019 13:15:41 +1100 Subject: [PATCH 014/144] Impl serde ser and deser for bls keypairs --- eth2/utils/bls/Cargo.toml | 1 + eth2/utils/bls/src/keypair.rs | 3 ++- eth2/utils/bls/src/lib.rs | 1 + eth2/utils/bls/src/public_key.rs | 16 +++++++++++++++- eth2/utils/bls/src/secret_key.rs | 27 ++++++++++++++++++++++++++- eth2/utils/bls/src/signature.rs | 17 ++++++++++++++++- 6 files changed, 61 insertions(+), 4 deletions(-) diff --git a/eth2/utils/bls/Cargo.toml b/eth2/utils/bls/Cargo.toml index 7a436307b5..5ac38595ae 100644 --- a/eth2/utils/bls/Cargo.toml +++ b/eth2/utils/bls/Cargo.toml @@ -9,4 +9,5 @@ bls-aggregates = { git = "https://github.com/sigp/signature-schemes", tag = "0.5 hashing = { path = "../hashing" } hex = "0.3" serde = "1.0" +serde_derive = "1.0" ssz = { path = "../ssz" } diff --git a/eth2/utils/bls/src/keypair.rs b/eth2/utils/bls/src/keypair.rs index 1cce9c10e7..d60a2fc253 100644 --- a/eth2/utils/bls/src/keypair.rs +++ b/eth2/utils/bls/src/keypair.rs @@ -1,6 +1,7 @@ use super::{PublicKey, SecretKey}; +use serde_derive::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Keypair { pub sk: SecretKey, pub pk: PublicKey, diff --git a/eth2/utils/bls/src/lib.rs b/eth2/utils/bls/src/lib.rs index bb109b0a1b..8b3f8b2ba4 100644 --- a/eth2/utils/bls/src/lib.rs +++ b/eth2/utils/bls/src/lib.rs @@ -6,6 +6,7 @@ mod aggregate_signature; mod keypair; mod public_key; mod secret_key; +mod serde_vistors; mod signature; pub use crate::aggregate_public_key::AggregatePublicKey; diff --git a/eth2/utils/bls/src/public_key.rs b/eth2/utils/bls/src/public_key.rs index c7fd526a06..3ab2b60bbb 100644 --- a/eth2/utils/bls/src/public_key.rs +++ b/eth2/utils/bls/src/public_key.rs @@ -1,6 +1,8 @@ +use super::serde_vistors::HexVisitor; use super::SecretKey; use bls_aggregates::PublicKey as RawPublicKey; use hex::encode as hex_encode; +use serde::de::{Deserialize, Deserializer}; use serde::ser::{Serialize, Serializer}; use ssz::{ decode_ssz_list, hash, ssz_encode, Decodable, DecodeError, Encodable, SszStream, TreeHash, @@ -61,7 +63,19 @@ impl Serialize for PublicKey { where S: Serializer, { - serializer.serialize_bytes(&ssz_encode(self)) + serializer.serialize_str(&hex_encode(ssz_encode(self))) + } +} + +impl<'de> Deserialize<'de> for PublicKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let bytes = deserializer.deserialize_str(HexVisitor)?; + let (pubkey, _) = <_>::ssz_decode(&bytes[..], 0) + .map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?; + Ok(pubkey) } } diff --git a/eth2/utils/bls/src/secret_key.rs b/eth2/utils/bls/src/secret_key.rs index f2d54f4ac3..06c9683897 100644 --- a/eth2/utils/bls/src/secret_key.rs +++ b/eth2/utils/bls/src/secret_key.rs @@ -1,5 +1,9 @@ +use super::serde_vistors::HexVisitor; use bls_aggregates::{DecodeError as BlsDecodeError, SecretKey as RawSecretKey}; -use ssz::{decode_ssz_list, Decodable, DecodeError, Encodable, SszStream, TreeHash}; +use hex::encode as hex_encode; +use serde::de::{Deserialize, Deserializer}; +use serde::ser::{Serialize, Serializer}; +use ssz::{decode_ssz_list, ssz_encode, Decodable, DecodeError, Encodable, SszStream, TreeHash}; /// A single BLS signature. /// @@ -40,6 +44,27 @@ impl Decodable for SecretKey { } } +impl Serialize for SecretKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&hex_encode(ssz_encode(self))) + } +} + +impl<'de> Deserialize<'de> for SecretKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let bytes = deserializer.deserialize_str(HexVisitor)?; + let (pubkey, _) = <_>::ssz_decode(&bytes[..], 0) + .map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?; + Ok(pubkey) + } +} + impl TreeHash for SecretKey { fn hash_tree_root_internal(&self) -> Vec { self.0.as_bytes().clone() diff --git a/eth2/utils/bls/src/signature.rs b/eth2/utils/bls/src/signature.rs index c0c31ef27b..86c54cba70 100644 --- a/eth2/utils/bls/src/signature.rs +++ b/eth2/utils/bls/src/signature.rs @@ -1,5 +1,8 @@ +use super::serde_vistors::HexVisitor; use super::{PublicKey, SecretKey}; use bls_aggregates::Signature as RawSignature; +use hex::encode as hex_encode; +use serde::de::{Deserialize, Deserializer}; use serde::ser::{Serialize, Serializer}; use ssz::{ decode_ssz_list, hash, ssz_encode, Decodable, DecodeError, Encodable, SszStream, TreeHash, @@ -83,7 +86,19 @@ impl Serialize for Signature { where S: Serializer, { - serializer.serialize_bytes(&ssz_encode(self)) + serializer.serialize_str(&hex_encode(ssz_encode(self))) + } +} + +impl<'de> Deserialize<'de> for Signature { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let bytes = deserializer.deserialize_str(HexVisitor)?; + let (pubkey, _) = <_>::ssz_decode(&bytes[..], 0) + .map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?; + Ok(pubkey) } } From 7ddbdc15bb8f460c70bd148d12e538f5bbca4c1a Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 8 Mar 2019 13:16:18 +1100 Subject: [PATCH 015/144] Impl serde deser for types::Deposit --- eth2/types/src/deposit.rs | 4 ++-- eth2/types/src/deposit_data.rs | 4 ++-- eth2/types/src/deposit_input.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/eth2/types/src/deposit.rs b/eth2/types/src/deposit.rs index 2e69ea599b..91c6ef2ac8 100644 --- a/eth2/types/src/deposit.rs +++ b/eth2/types/src/deposit.rs @@ -1,14 +1,14 @@ use super::{DepositData, Hash256}; use crate::test_utils::TestRandom; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode, TreeHash}; use test_random_derive::TestRandom; /// A deposit to potentially become a beacon chain validator. /// /// Spec v0.4.0 -#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)] pub struct Deposit { pub branch: Vec, pub index: u64, diff --git a/eth2/types/src/deposit_data.rs b/eth2/types/src/deposit_data.rs index 1eb2722a90..61b82f4b3d 100644 --- a/eth2/types/src/deposit_data.rs +++ b/eth2/types/src/deposit_data.rs @@ -1,14 +1,14 @@ use super::DepositInput; use crate::test_utils::TestRandom; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode, TreeHash}; use test_random_derive::TestRandom; /// Data generated by the deposit contract. /// /// Spec v0.4.0 -#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)] pub struct DepositData { pub amount: u64, pub timestamp: u64, diff --git a/eth2/types/src/deposit_input.rs b/eth2/types/src/deposit_input.rs index c4c79c3d1c..32f57ab6e6 100644 --- a/eth2/types/src/deposit_input.rs +++ b/eth2/types/src/deposit_input.rs @@ -2,14 +2,14 @@ use super::Hash256; use crate::test_utils::TestRandom; use bls::{PublicKey, Signature}; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode, TreeHash}; use test_random_derive::TestRandom; /// The data supplied by the user to the deposit contract. /// /// Spec v0.4.0 -#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)] pub struct DepositInput { pub pubkey: PublicKey, pub withdrawal_credentials: Hash256, From 2f484db82c40880d31fc1d42cbc78ac94c5f0cdb Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 8 Mar 2019 13:16:33 +1100 Subject: [PATCH 016/144] Expose `Signature` in `types` crate --- eth2/types/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth2/types/src/lib.rs b/eth2/types/src/lib.rs index 9bf60f2c92..76fcb43edd 100644 --- a/eth2/types/src/lib.rs +++ b/eth2/types/src/lib.rs @@ -72,4 +72,4 @@ pub type AttesterMap = HashMap<(u64, u64), Vec>; /// Maps a slot to a block proposer. pub type ProposerMap = HashMap; -pub use bls::{AggregatePublicKey, AggregateSignature, Keypair, PublicKey, Signature}; +pub use bls::{AggregatePublicKey, AggregateSignature, Keypair, PublicKey, SecretKey, Signature}; From ec9e0bbddfc1896eaa4d5e8466d5a5fb465e3044 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 8 Mar 2019 13:18:02 +1100 Subject: [PATCH 017/144] Allow test_harness to load validators from file Also adds a command to test_harness binary to generate validators --- .../beacon_chain/test_harness/.gitignore | 1 + .../beacon_chain/test_harness/Cargo.toml | 2 + .../test_harness/src/beacon_chain_harness.rs | 21 +++++-- .../beacon_chain_harness/generate_deposits.rs | 18 ++++-- .../load_deposits_from_file.rs | 38 ++++++++++++ .../beacon_chain/test_harness/src/bin.rs | 58 +++++++++++++++++++ .../beacon_chain/test_harness/src/prepare.rs | 35 +++++++++++ .../beacon_chain/test_harness/src/run_test.rs | 7 ++- .../test_harness/src/test_case.rs | 5 +- 9 files changed, 174 insertions(+), 11 deletions(-) create mode 100644 beacon_node/beacon_chain/test_harness/.gitignore create mode 100644 beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/load_deposits_from_file.rs create mode 100644 beacon_node/beacon_chain/test_harness/src/prepare.rs diff --git a/beacon_node/beacon_chain/test_harness/.gitignore b/beacon_node/beacon_chain/test_harness/.gitignore new file mode 100644 index 0000000000..5f605cba08 --- /dev/null +++ b/beacon_node/beacon_chain/test_harness/.gitignore @@ -0,0 +1 @@ +validators/ diff --git a/beacon_node/beacon_chain/test_harness/Cargo.toml b/beacon_node/beacon_chain/test_harness/Cargo.toml index bd7a58270b..448934eb3f 100644 --- a/beacon_node/beacon_chain/test_harness/Cargo.toml +++ b/beacon_node/beacon_chain/test_harness/Cargo.toml @@ -33,12 +33,14 @@ failure = "0.1" failure_derive = "0.1" fork_choice = { path = "../../../eth2/fork_choice" } hashing = { path = "../../../eth2/utils/hashing" } +int_to_bytes = { path = "../../../eth2/utils/int_to_bytes" } log = "0.4" env_logger = "0.6.0" rayon = "1.0" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" +serde_yaml = "0.8" slot_clock = { path = "../../../eth2/utils/slot_clock" } ssz = { path = "../../../eth2/utils/ssz" } types = { path = "../../../eth2/types" } diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index 95899e23b4..3eabfcb1fd 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -6,16 +6,20 @@ use db::{ MemoryDB, }; use fork_choice::BitwiseLMDGhost; -use generate_deposits::generate_deposits_with_random_keypairs; use log::debug; use rayon::prelude::*; use slot_clock::TestingSlotClock; use std::collections::HashSet; use std::iter::FromIterator; +use std::path::Path; use std::sync::Arc; use types::*; mod generate_deposits; +mod load_deposits_from_file; + +pub use generate_deposits::generate_deposits_with_deterministic_keypairs; +pub use load_deposits_from_file::load_deposits_from_file; /// The beacon chain harness simulates a single beacon node with `validator_count` validators connected /// to it. Each validator is provided a borrow to the beacon chain, where it may read @@ -37,7 +41,7 @@ impl BeaconChainHarness { /// /// - A keypair, `BlockProducer` and `Attester` for each validator. /// - A new BeaconChain struct where the given validators are in the genesis. - pub fn new(spec: ChainSpec, validator_count: usize) -> Self { + pub fn new(spec: ChainSpec, validator_count: usize, validators_dir: Option<&Path>) -> Self { let db = Arc::new(MemoryDB::open()); let block_store = Arc::new(BeaconBlockStore::new(db.clone())); let state_store = Arc::new(BeaconStateStore::new(db.clone())); @@ -49,8 +53,17 @@ impl BeaconChainHarness { block_hash: Hash256::zero(), }; - let (keypairs, initial_validator_deposits) = - generate_deposits_with_random_keypairs(validator_count, genesis_time, &spec); + let (keypairs, initial_validator_deposits) = if let Some(path) = validators_dir { + let keypairs_path = path.join("keypairs.yaml"); + let deposits_path = path.join("deposits.yaml"); + load_deposits_from_file( + validator_count, + &keypairs_path.as_path(), + &deposits_path.as_path(), + ) + } else { + generate_deposits_with_deterministic_keypairs(validator_count, genesis_time, &spec) + }; // Create the Beacon Chain let beacon_chain = Arc::new( diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs index 39924fb678..b07168df97 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs @@ -1,11 +1,16 @@ use bls::{create_proof_of_possession, get_withdrawal_credentials}; +use int_to_bytes::int_to_bytes48; use log::debug; use rayon::prelude::*; use types::*; -/// Generates `validator_count` deposits using randomly generated keypairs and some default specs -/// for the deposits. -pub fn generate_deposits_with_random_keypairs( +/// Generates `validator_count` deposits using keypairs where the secret key is the index of the +/// validator. +/// +/// For example, the first validator has a secret key of `int_to_bytes48(1)`, the second has +/// `int_to_bytes48(2)` and so on. (We skip `0` as it generates a weird looking public key and is +/// probably invalid). +pub fn generate_deposits_with_deterministic_keypairs( validator_count: usize, genesis_time: u64, spec: &ChainSpec, @@ -18,7 +23,12 @@ pub fn generate_deposits_with_random_keypairs( let keypairs: Vec = (0..validator_count) .collect::>() .par_iter() - .map(|_| Keypair::random()) + .map(|&i| { + let secret = int_to_bytes48(i as u64 + 1); + let sk = SecretKey::from_bytes(&secret).unwrap(); + let pk = PublicKey::from_secret_key(&sk); + Keypair { sk, pk } + }) .collect(); debug!( diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/load_deposits_from_file.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/load_deposits_from_file.rs new file mode 100644 index 0000000000..9cba3d3c49 --- /dev/null +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/load_deposits_from_file.rs @@ -0,0 +1,38 @@ +use log::debug; +use serde_yaml; +use std::fs::File; +use std::path::Path; +use types::*; + +pub fn load_deposits_from_file( + validator_count: usize, + keypairs_path: &Path, + deposits_path: &Path, +) -> (Vec, Vec) { + debug!("Loading keypairs from file..."); + let keypairs_file = File::open(keypairs_path).unwrap(); + let mut keypairs: Vec = serde_yaml::from_reader(&keypairs_file).unwrap(); + + debug!("Loading deposits from file..."); + let deposits_file = File::open(deposits_path).unwrap(); + let mut deposits: Vec = serde_yaml::from_reader(&deposits_file).unwrap(); + + assert!( + keypairs.len() >= validator_count, + "Unable to load {} keypairs from file ({} available)", + validator_count, + keypairs.len() + ); + + assert!( + deposits.len() >= validator_count, + "Unable to load {} deposits from file ({} available)", + validator_count, + deposits.len() + ); + + keypairs.truncate(validator_count); + deposits.truncate(validator_count); + + (keypairs, deposits) +} diff --git a/beacon_node/beacon_chain/test_harness/src/bin.rs b/beacon_node/beacon_chain/test_harness/src/bin.rs index 3769788d8b..0a02264a3b 100644 --- a/beacon_node/beacon_chain/test_harness/src/bin.rs +++ b/beacon_node/beacon_chain/test_harness/src/bin.rs @@ -1,8 +1,11 @@ use clap::{App, Arg, SubCommand}; use env_logger::{Builder, Env}; +use prepare::prepare; use run_test::run_test; +use types::ChainSpec; mod beacon_chain_harness; +mod prepare; mod run_test; mod test_case; mod validator_harness; @@ -17,12 +20,22 @@ fn main() { .arg( Arg::with_name("log") .long("log-level") + .short("l") .value_name("LOG_LEVEL") .help("Logging level.") .possible_values(&["error", "warn", "info", "debug", "trace"]) .default_value("debug") .required(true), ) + .arg( + Arg::with_name("spec") + .long("spec") + .short("s") + .value_name("SPECIFICATION") + .help("ChainSpec instantiation.") + .possible_values(&["foundation", "few_validators"]) + .default_value("foundation"), + ) .subcommand( SubCommand::with_name("run_test") .about("Executes a YAML test specification") @@ -32,6 +45,41 @@ fn main() { .value_name("FILE") .help("YAML file test_case.") .required(true), + ) + .arg( + Arg::with_name("validators_dir") + .long("validators-dir") + .short("v") + .value_name("VALIDATORS_DIR") + .help("A directory with validator deposits and keypair YAML."), + ), + ) + .subcommand( + SubCommand::with_name("prepare") + .about("Builds validator YAML files for faster tests.") + .arg( + Arg::with_name("validator_count") + .long("validator_count") + .short("n") + .value_name("VALIDATOR_COUNT") + .help("Number of validators to generate.") + .required(true), + ) + .arg( + Arg::with_name("genesis_time") + .long("genesis_time") + .short("t") + .value_name("GENESIS_TIME") + .help("Time for validator deposits.") + .required(true), + ) + .arg( + Arg::with_name("output_dir") + .long("output_dir") + .short("d") + .value_name("GENESIS_TIME") + .help("Output directory for generated YAML.") + .default_value("validators"), ), ) .get_matches(); @@ -40,7 +88,17 @@ fn main() { Builder::from_env(Env::default().default_filter_or(log_level)).init(); } + let spec = match matches.value_of("spec") { + Some("foundation") => ChainSpec::foundation(), + Some("few_validators") => ChainSpec::few_validators(), + _ => unreachable!(), // Has a default value, should always exist. + }; + if let Some(matches) = matches.subcommand_matches("run_test") { run_test(matches); } + + if let Some(matches) = matches.subcommand_matches("prepare") { + prepare(matches, &spec); + } } diff --git a/beacon_node/beacon_chain/test_harness/src/prepare.rs b/beacon_node/beacon_chain/test_harness/src/prepare.rs new file mode 100644 index 0000000000..160b0f7ee9 --- /dev/null +++ b/beacon_node/beacon_chain/test_harness/src/prepare.rs @@ -0,0 +1,35 @@ +use crate::beacon_chain_harness::generate_deposits_with_deterministic_keypairs; +use clap::{value_t, ArgMatches}; +use log::debug; +use serde_yaml; +use std::path::Path; +use std::{fs, fs::File}; +use types::*; + +const KEYPAIRS_FILE: &str = "keypairs.yaml"; +const DEPOSITS_FILE: &str = "deposits.yaml"; + +pub fn prepare(matches: &ArgMatches, spec: &ChainSpec) { + let validator_count = value_t!(matches.value_of("validator_count"), usize) + .expect("Validator count is required argument"); + let genesis_time = + value_t!(matches.value_of("genesis_time"), u64).expect("Genesis time is required argument"); + let output_dir = matches + .value_of("output_dir") + .expect("Output dir has a default value."); + + let (keypairs, deposits) = + generate_deposits_with_deterministic_keypairs(validator_count, genesis_time, &spec); + + debug!("Created keypairs and deposits, writing to file..."); + + fs::create_dir_all(Path::new(output_dir)).unwrap(); + + let keypairs_path = Path::new(output_dir).join(KEYPAIRS_FILE); + let keypairs_file = File::create(keypairs_path).unwrap(); + serde_yaml::to_writer(keypairs_file, &keypairs).unwrap(); + + let deposits_path = Path::new(output_dir).join(DEPOSITS_FILE); + let deposits_file = File::create(deposits_path).unwrap(); + serde_yaml::to_writer(deposits_file, &deposits).unwrap(); +} diff --git a/beacon_node/beacon_chain/test_harness/src/run_test.rs b/beacon_node/beacon_chain/test_harness/src/run_test.rs index 1a816afe0f..51a993bd7c 100644 --- a/beacon_node/beacon_chain/test_harness/src/run_test.rs +++ b/beacon_node/beacon_chain/test_harness/src/run_test.rs @@ -1,5 +1,6 @@ use crate::test_case::TestCase; use clap::ArgMatches; +use std::path::Path; use std::{fs::File, io::prelude::*}; use yaml_rust::YamlLoader; @@ -15,6 +16,10 @@ pub fn run_test(matches: &ArgMatches) { }; for doc in &docs { + let validators_dir = matches + .value_of("validators_dir") + .and_then(|dir_str| Some(Path::new(dir_str))); + // For each `test_cases` YAML in the document, build a `TestCase`, execute it and // assert that the execution result matches the test_case description. // @@ -29,7 +34,7 @@ pub fn run_test(matches: &ArgMatches) { // panics with a message. for test_case in doc["test_cases"].as_vec().unwrap() { let test_case = TestCase::from_yaml(test_case); - test_case.assert_result_valid(test_case.execute()) + test_case.assert_result_valid(test_case.execute(validators_dir)) } } } diff --git a/beacon_node/beacon_chain/test_harness/src/test_case.rs b/beacon_node/beacon_chain/test_harness/src/test_case.rs index b2709edfc9..e7b2defe66 100644 --- a/beacon_node/beacon_chain/test_harness/src/test_case.rs +++ b/beacon_node/beacon_chain/test_harness/src/test_case.rs @@ -6,6 +6,7 @@ use beacon_chain::CheckPoint; use bls::{create_proof_of_possession, get_withdrawal_credentials}; use log::{info, warn}; use ssz::SignedRoot; +use std::path::Path; use types::*; use types::{ @@ -70,7 +71,7 @@ impl TestCase { /// Executes the test case, returning an `ExecutionResult`. #[allow(clippy::cyclomatic_complexity)] - pub fn execute(&self) -> ExecutionResult { + pub fn execute(&self, validators_dir: Option<&Path>) -> ExecutionResult { let spec = self.spec(); let validator_count = self.config.deposits_for_chain_start; let slots = self.config.num_slots; @@ -80,7 +81,7 @@ impl TestCase { validator_count ); - let mut harness = BeaconChainHarness::new(spec, validator_count); + let mut harness = BeaconChainHarness::new(spec, validator_count, validators_dir); info!("Starting simulation across {} slots...", slots); From 5c1458ba46832911a32fb2df1e6fbcbb79298a6f Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 8 Mar 2019 13:19:37 +1100 Subject: [PATCH 018/144] Add bls serde_vistors file --- eth2/utils/bls/src/serde_vistors.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 eth2/utils/bls/src/serde_vistors.rs diff --git a/eth2/utils/bls/src/serde_vistors.rs b/eth2/utils/bls/src/serde_vistors.rs new file mode 100644 index 0000000000..55eadb8833 --- /dev/null +++ b/eth2/utils/bls/src/serde_vistors.rs @@ -0,0 +1,20 @@ +use hex; +use serde::de::{self, Visitor}; +use std::fmt; + +pub struct HexVisitor; + +impl<'de> Visitor<'de> for HexVisitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a hex string (without 0x prefix)") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Ok(hex::decode(value).map_err(|e| de::Error::custom(format!("invalid hex ({:?})", e)))?) + } +} From e76b5e1c3a0bc32dc0843fcd12d0395528f604b3 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 8 Mar 2019 15:06:13 +1100 Subject: [PATCH 019/144] Re-work deposit generation for memory efficiency Helps ensure that variables are dropped after they're finished being used. --- .../test_harness/src/beacon_chain_harness.rs | 6 +++-- .../beacon_chain_harness/generate_deposits.rs | 23 +++++++++++-------- .../beacon_chain/test_harness/src/prepare.rs | 20 ++++++++++++---- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index 3eabfcb1fd..8ec07a9946 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -18,7 +18,7 @@ use types::*; mod generate_deposits; mod load_deposits_from_file; -pub use generate_deposits::generate_deposits_with_deterministic_keypairs; +pub use generate_deposits::{generate_deposits_from_keypairs, generate_deterministic_keypairs}; pub use load_deposits_from_file::load_deposits_from_file; /// The beacon chain harness simulates a single beacon node with `validator_count` validators connected @@ -62,7 +62,9 @@ impl BeaconChainHarness { &deposits_path.as_path(), ) } else { - generate_deposits_with_deterministic_keypairs(validator_count, genesis_time, &spec) + let keypairs = generate_deterministic_keypairs(validator_count); + let deposits = generate_deposits_from_keypairs(&keypairs, genesis_time, &spec); + (keypairs, deposits) }; // Create the Beacon Chain diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs index b07168df97..f2d68d6440 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs @@ -4,19 +4,15 @@ use log::debug; use rayon::prelude::*; use types::*; -/// Generates `validator_count` deposits using keypairs where the secret key is the index of the +/// Generates `validator_count` keypairs where the secret key is the index of the /// validator. /// /// For example, the first validator has a secret key of `int_to_bytes48(1)`, the second has /// `int_to_bytes48(2)` and so on. (We skip `0` as it generates a weird looking public key and is /// probably invalid). -pub fn generate_deposits_with_deterministic_keypairs( - validator_count: usize, - genesis_time: u64, - spec: &ChainSpec, -) -> (Vec, Vec) { +pub fn generate_deterministic_keypairs(validator_count: usize) -> Vec { debug!( - "Generating {} random validator keypairs...", + "Generating {} deterministic validator keypairs...", validator_count ); @@ -31,9 +27,18 @@ pub fn generate_deposits_with_deterministic_keypairs( }) .collect(); + keypairs +} + +/// Generates a `Deposit` for each keypairs +pub fn generate_deposits_from_keypairs( + keypairs: &[Keypair], + genesis_time: u64, + spec: &ChainSpec, +) -> Vec { debug!( "Generating {} validator deposits from random keypairs...", - validator_count + keypairs.len() ); let initial_validator_deposits = @@ -60,5 +65,5 @@ pub fn generate_deposits_with_deterministic_keypairs( }) .collect(); - (keypairs, initial_validator_deposits) + initial_validator_deposits } diff --git a/beacon_node/beacon_chain/test_harness/src/prepare.rs b/beacon_node/beacon_chain/test_harness/src/prepare.rs index 160b0f7ee9..0015229559 100644 --- a/beacon_node/beacon_chain/test_harness/src/prepare.rs +++ b/beacon_node/beacon_chain/test_harness/src/prepare.rs @@ -1,4 +1,6 @@ -use crate::beacon_chain_harness::generate_deposits_with_deterministic_keypairs; +use crate::beacon_chain_harness::{ + generate_deposits_from_keypairs, generate_deterministic_keypairs, +}; use clap::{value_t, ArgMatches}; use log::debug; use serde_yaml; @@ -18,17 +20,27 @@ pub fn prepare(matches: &ArgMatches, spec: &ChainSpec) { .value_of("output_dir") .expect("Output dir has a default value."); - let (keypairs, deposits) = - generate_deposits_with_deterministic_keypairs(validator_count, genesis_time, &spec); - debug!("Created keypairs and deposits, writing to file..."); fs::create_dir_all(Path::new(output_dir)).unwrap(); + // Ensure that keypairs is dropped before writing deposits, this provides a big memory saving + // for large validator_counts. + let deposits = { + let keypairs = generate_deterministic_keypairs(validator_count); + write_keypairs(output_dir, &keypairs); + generate_deposits_from_keypairs(&keypairs, genesis_time, &spec) + }; + write_deposits(output_dir, &deposits); +} + +fn write_keypairs(output_dir: &str, keypairs: &[Keypair]) { let keypairs_path = Path::new(output_dir).join(KEYPAIRS_FILE); let keypairs_file = File::create(keypairs_path).unwrap(); serde_yaml::to_writer(keypairs_file, &keypairs).unwrap(); +} +fn write_deposits(output_dir: &str, deposits: &[Deposit]) { let deposits_path = Path::new(output_dir).join(DEPOSITS_FILE); let deposits_file = File::create(deposits_path).unwrap(); serde_yaml::to_writer(deposits_file, &deposits).unwrap(); From 6efe2ad3e3c4cf78f0f6cf7218a6718451ba3a0a Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 8 Mar 2019 15:09:33 +1100 Subject: [PATCH 020/144] Add debug logs to test_harness prepare --- beacon_node/beacon_chain/test_harness/src/prepare.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/beacon_node/beacon_chain/test_harness/src/prepare.rs b/beacon_node/beacon_chain/test_harness/src/prepare.rs index 0015229559..0a4f4c34f4 100644 --- a/beacon_node/beacon_chain/test_harness/src/prepare.rs +++ b/beacon_node/beacon_chain/test_harness/src/prepare.rs @@ -27,10 +27,14 @@ pub fn prepare(matches: &ArgMatches, spec: &ChainSpec) { // Ensure that keypairs is dropped before writing deposits, this provides a big memory saving // for large validator_counts. let deposits = { + debug!("Creating {} keypairs...", validator_count); let keypairs = generate_deterministic_keypairs(validator_count); + debug!("Writing {} keypairs to file...", validator_count); write_keypairs(output_dir, &keypairs); + debug!("Creating {} deposits to file...", validator_count); generate_deposits_from_keypairs(&keypairs, genesis_time, &spec) }; + debug!("Writing {} deposits to file...", validator_count); write_deposits(output_dir, &deposits); } From 4b21252ce4777627fd50108bd2d9f75cec4bed29 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 8 Mar 2019 15:33:45 +1100 Subject: [PATCH 021/144] Refactor BeaconChain and BeaconState genesis Now it more easily supports using pre-build validator registries. --- beacon_node/beacon_chain/src/beacon_chain.rs | 19 +-- .../test_harness/src/beacon_chain_harness.rs | 16 +- eth2/types/src/beacon_state.rs | 42 ++--- eth2/types/src/beacon_state/builder.rs | 154 ++++++------------ 4 files changed, 83 insertions(+), 148 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 3d2efa8ae0..b0e84e1e12 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -73,31 +73,18 @@ where F: ForkChoice, { /// Instantiate a new Beacon Chain, from genesis. - #[allow(clippy::too_many_arguments)] // Will be re-factored in the coming weeks. - pub fn genesis( + pub fn from_genesis( state_store: Arc>, block_store: Arc>, slot_clock: U, - genesis_time: u64, - latest_eth1_data: Eth1Data, - initial_validator_deposits: Vec, + mut genesis_state: BeaconState, + genesis_block: BeaconBlock, spec: ChainSpec, fork_choice: F, ) -> Result { - if initial_validator_deposits.is_empty() { - return Err(Error::InsufficientValidators); - } - - let mut genesis_state = BeaconState::genesis( - genesis_time, - initial_validator_deposits, - latest_eth1_data, - &spec, - )?; let state_root = genesis_state.canonical_root(); state_store.put(&state_root, &ssz_encode(&genesis_state)[..])?; - let genesis_block = BeaconBlock::genesis(state_root, &spec); let block_root = genesis_block.canonical_root(); block_store.put(&block_root, &ssz_encode(&genesis_block)[..])?; diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index 8ec07a9946..c41f6fa1ef 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -9,11 +9,12 @@ use fork_choice::BitwiseLMDGhost; use log::debug; use rayon::prelude::*; use slot_clock::TestingSlotClock; +use ssz::TreeHash; use std::collections::HashSet; use std::iter::FromIterator; use std::path::Path; use std::sync::Arc; -use types::*; +use types::{beacon_state::BeaconStateBuilder, *}; mod generate_deposits; mod load_deposits_from_file; @@ -67,15 +68,20 @@ impl BeaconChainHarness { (keypairs, deposits) }; + let mut state_builder = BeaconStateBuilder::new(genesis_time, latest_eth1_data, &spec); + state_builder.process_initial_deposits(&initial_validator_deposits, &spec); + let genesis_state = state_builder.build(&spec).unwrap(); + let state_root = Hash256::from_slice(&genesis_state.hash_tree_root()); + let genesis_block = BeaconBlock::genesis(state_root, &spec); + // Create the Beacon Chain let beacon_chain = Arc::new( - BeaconChain::genesis( + BeaconChain::from_genesis( state_store.clone(), block_store.clone(), slot_clock, - genesis_time, - latest_eth1_data, - initial_validator_deposits, + genesis_state, + genesis_block, spec.clone(), fork_choice, ) diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 809408b326..f3d5335271 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -114,18 +114,13 @@ pub struct BeaconState { impl BeaconState { /// Produce the first state of the Beacon Chain. - pub fn genesis_without_validators( - genesis_time: u64, - latest_eth1_data: Eth1Data, - spec: &ChainSpec, - ) -> Result { - debug!("Creating genesis state (without validator processing)."); + pub fn genesis(genesis_time: u64, latest_eth1_data: Eth1Data, spec: &ChainSpec) -> BeaconState { let initial_crosslink = Crosslink { epoch: spec.genesis_epoch, crosslink_data_root: spec.zero_hash, }; - Ok(BeaconState { + BeaconState { /* * Misc */ @@ -188,19 +183,15 @@ impl BeaconState { */ cache_index_offset: 0, caches: vec![EpochCache::empty(); CACHED_EPOCHS], - }) + } } /// Produce the first state of the Beacon Chain. - pub fn genesis( - genesis_time: u64, + pub fn process_initial_deposits( + &mut self, initial_validator_deposits: Vec, - latest_eth1_data: Eth1Data, spec: &ChainSpec, - ) -> Result { - let mut genesis_state = - BeaconState::genesis_without_validators(genesis_time, latest_eth1_data, spec)?; - + ) -> Result<(), Error> { debug!("Processing genesis deposits..."); let deposit_data = initial_validator_deposits @@ -208,29 +199,28 @@ impl BeaconState { .map(|deposit| &deposit.deposit_data) .collect(); - genesis_state.process_deposits(deposit_data, spec); + self.process_deposits(deposit_data, spec); trace!("Processed genesis deposits."); - for validator_index in 0..genesis_state.validator_registry.len() { - if genesis_state.get_effective_balance(validator_index, spec) >= spec.max_deposit_amount - { - genesis_state.activate_validator(validator_index, true, spec); + for validator_index in 0..self.validator_registry.len() { + if self.get_effective_balance(validator_index, spec) >= spec.max_deposit_amount { + self.activate_validator(validator_index, true, spec); } } - genesis_state.deposit_index = initial_validator_deposits.len() as u64; + self.deposit_index = initial_validator_deposits.len() as u64; let genesis_active_index_root = hash_tree_root(get_active_validator_indices( - &genesis_state.validator_registry, + &self.validator_registry, spec.genesis_epoch, )); - genesis_state.latest_active_index_roots = + self.latest_active_index_roots = vec![genesis_active_index_root; spec.latest_active_index_roots_length]; - genesis_state.current_shuffling_seed = - genesis_state.generate_seed(spec.genesis_epoch, spec)?; - Ok(genesis_state) + self.current_shuffling_seed = self.generate_seed(spec.genesis_epoch, spec)?; + + Ok(()) } /// Returns the `hash_tree_root` of the state. diff --git a/eth2/types/src/beacon_state/builder.rs b/eth2/types/src/beacon_state/builder.rs index 0be297db74..4bb5e2cc62 100644 --- a/eth2/types/src/beacon_state/builder.rs +++ b/eth2/types/src/beacon_state/builder.rs @@ -1,5 +1,9 @@ +use super::BeaconStateError; use crate::*; +use crate::{validator_registry::get_active_validator_indices, *}; use bls::create_proof_of_possession; +use rayon::prelude::*; +use ssz::TreeHash; /// Builds a `BeaconState` for use in testing or benchmarking. /// @@ -16,128 +20,73 @@ use bls::create_proof_of_possession; /// Step (4) produces a clone of the BeaconState and doesn't consume the `BeaconStateBuilder` to /// allow access to `self.keypairs` and `self.spec`. pub struct BeaconStateBuilder { - pub validator_count: usize, - pub state: Option, - pub genesis_time: u64, - pub latest_eth1_data: Eth1Data, - pub spec: ChainSpec, - pub keypairs: Vec, + pub state: BeaconState, } impl BeaconStateBuilder { /// Create a new builder with the given number of validators. - pub fn new(validator_count: usize) -> Self { - let genesis_time = 10_000_000; - - let latest_eth1_data = Eth1Data { - deposit_root: Hash256::zero(), - block_hash: Hash256::zero(), - }; - - let spec = ChainSpec::foundation(); - + /// + /// Spec v0.4.0 + pub fn new(genesis_time: u64, latest_eth1_data: Eth1Data, spec: &ChainSpec) -> Self { Self { - validator_count, - state: None, - genesis_time, - latest_eth1_data, - spec, - keypairs: vec![], + state: BeaconState::genesis(genesis_time, latest_eth1_data, spec), } } + /// Produce the first state of the Beacon Chain. + /// + /// Spec v0.4.0 + pub fn process_initial_deposits( + &mut self, + initial_validator_deposits: &[Deposit], + spec: &ChainSpec, + ) { + let deposit_data = initial_validator_deposits + .par_iter() + .map(|deposit| &deposit.deposit_data) + .collect(); + + self.state.process_deposits(deposit_data, spec); + + for validator_index in 0..self.state.validator_registry.len() { + if self.state.get_effective_balance(validator_index, spec) >= spec.max_deposit_amount { + self.state.activate_validator(validator_index, true, spec); + } + } + + self.state.deposit_index = initial_validator_deposits.len() as u64; + } + /// Builds a `BeaconState` using the `BeaconState::genesis(..)` function. /// /// Each validator is assigned a unique, randomly-generated keypair and all /// proof-of-possessions are verified during genesis. - pub fn build(&mut self) -> Result<(), BeaconStateError> { - self.keypairs = (0..self.validator_count) - .collect::>() - .iter() - .map(|_| Keypair::random()) - .collect(); + /// + /// Spec v0.4.0 + pub fn build(mut self, spec: &ChainSpec) -> Result { + let genesis_active_index_root = + get_active_validator_indices(&self.state.validator_registry, spec.genesis_epoch) + .hash_tree_root(); - let initial_validator_deposits = self - .keypairs - .iter() - .map(|keypair| Deposit { - branch: vec![], // branch verification is not specified. - index: 0, // index verification is not specified. - deposit_data: DepositData { - amount: 32_000_000_000, // 32 ETH (in Gwei) - timestamp: self.genesis_time - 1, - deposit_input: DepositInput { - pubkey: keypair.pk.clone(), - withdrawal_credentials: Hash256::zero(), // Withdrawal not possible. - proof_of_possession: create_proof_of_possession(&keypair), - }, - }, - }) - .collect(); + self.state.latest_active_index_roots = vec![ + Hash256::from_slice(&genesis_active_index_root); + spec.latest_active_index_roots_length + ]; - let state = BeaconState::genesis( - self.genesis_time, - initial_validator_deposits, - self.latest_eth1_data.clone(), - &self.spec, - )?; + self.state.current_shuffling_seed = self.state.generate_seed(spec.genesis_epoch, spec)?; - self.state = Some(state); - - Ok(()) - } - - /// Builds a `BeaconState` using the `BeaconState::genesis(..)` function, without supplying any - /// validators. Instead validators are added to the state post-genesis. - /// - /// One keypair is randomly generated and all validators are assigned this same keypair. - /// Proof-of-possessions are not created (or validated). - /// - /// This function runs orders of magnitude faster than `Self::build()`, however it will be - /// erroneous for functions which use a validators public key as an identifier (e.g., - /// deposits). - pub fn build_fast(&mut self) -> Result<(), BeaconStateError> { - let common_keypair = Keypair::random(); - - let mut validator_registry = Vec::with_capacity(self.validator_count); - let mut validator_balances = Vec::with_capacity(self.validator_count); - self.keypairs = Vec::with_capacity(self.validator_count); - - for _ in 0..self.validator_count { - self.keypairs.push(common_keypair.clone()); - validator_balances.push(32_000_000_000); - validator_registry.push(Validator { - pubkey: common_keypair.pk.clone(), - withdrawal_credentials: Hash256::zero(), - activation_epoch: self.spec.genesis_epoch, - ..Validator::default() - }) - } - - let state = BeaconState { - validator_registry, - validator_balances, - ..BeaconState::genesis( - self.genesis_time, - vec![], - self.latest_eth1_data.clone(), - &self.spec, - )? - }; - - self.state = Some(state); - - Ok(()) + Ok(self.state) } + /* /// Sets the `BeaconState` to be in the last slot of the given epoch. /// /// Sets all justification/finalization parameters to be be as "perfect" as possible (i.e., /// highest justified and finalized slots, full justification bitfield, etc). - pub fn teleport_to_end_of_epoch(&mut self, epoch: Epoch) { - let state = self.state.as_mut().expect("Genesis required"); + pub fn teleport_to_end_of_epoch(&mut self, epoch: Epoch, spec: &ChainSpec) { + let state = &mut self.state; - let slot = epoch.end_slot(self.spec.slots_per_epoch); + let slot = epoch.end_slot(spec.slots_per_epoch); state.slot = slot; state.validator_registry_update_epoch = epoch - 1; @@ -159,7 +108,7 @@ impl BeaconStateBuilder { /// /// These attestations should be fully conducive to justification and finalization. pub fn insert_attestations(&mut self) { - let state = self.state.as_mut().expect("Genesis required"); + let state = &mut self.state; state .build_epoch_cache(RelativeEpoch::Previous, &self.spec) @@ -198,8 +147,10 @@ impl BeaconStateBuilder { pub fn cloned_state(&self) -> BeaconState { self.state.as_ref().expect("Genesis required").clone() } + */ } +/* /// Builds a valid PendingAttestation with full participation for some committee. fn committee_to_pending_attestation( state: &BeaconState, @@ -261,3 +212,4 @@ fn committee_to_pending_attestation( inclusion_slot: slot, } } +*/ From ddac7540bc5f687b5e9aeee6b918c72f711f7b07 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 8 Mar 2019 16:10:21 +1100 Subject: [PATCH 022/144] Allow test_harness to load validators from file. --- .../test_harness/src/beacon_chain_harness.rs | 45 ++++++++++++---- .../beacon_chain/test_harness/src/prepare.rs | 52 +++++++++++++------ eth2/types/Cargo.toml | 1 + eth2/types/src/beacon_state/builder.rs | 40 +++++++++++--- eth2/types/src/slot_epoch.rs | 6 +-- eth2/types/src/validator.rs | 4 +- 6 files changed, 109 insertions(+), 39 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index c41f6fa1ef..1ebe4dc745 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -11,6 +11,7 @@ use rayon::prelude::*; use slot_clock::TestingSlotClock; use ssz::TreeHash; use std::collections::HashSet; +use std::fs::File; use std::iter::FromIterator; use std::path::Path; use std::sync::Arc; @@ -54,22 +55,44 @@ impl BeaconChainHarness { block_hash: Hash256::zero(), }; - let (keypairs, initial_validator_deposits) = if let Some(path) = validators_dir { - let keypairs_path = path.join("keypairs.yaml"); - let deposits_path = path.join("deposits.yaml"); - load_deposits_from_file( - validator_count, - &keypairs_path.as_path(), - &deposits_path.as_path(), - ) + let mut state_builder = BeaconStateBuilder::new(genesis_time, latest_eth1_data, &spec); + + // If a `validators_dir` is specified, load the keypairs and validators from YAML files. + // + // Otherwise, build all the keypairs and initial validator deposits manually. + // + // It is _much_ faster to load from YAML, however it does skip all the initial processing + // and verification of `Deposits`, so it is a slightly less comprehensive test. + let keypairs = if let Some(path) = validators_dir { + debug!("Loading validator keypairs from file..."); + let keypairs_file = File::open(path.join("keypairs.yaml")).unwrap(); + let mut keypairs: Vec = serde_yaml::from_reader(&keypairs_file).unwrap(); + keypairs.truncate(validator_count); + + debug!("Loading validators from file..."); + let validators_file = File::open(path.join("validators.yaml")).unwrap(); + let mut validators: Vec = serde_yaml::from_reader(&validators_file).unwrap(); + validators.truncate(validator_count); + + let balances = vec![32_000_000_000; validator_count]; + + state_builder.import_existing_validators( + validators, + balances, + validator_count as u64, + &spec, + ); + + keypairs } else { + debug!("Generating validator keypairs..."); let keypairs = generate_deterministic_keypairs(validator_count); + debug!("Generating initial validator deposits..."); let deposits = generate_deposits_from_keypairs(&keypairs, genesis_time, &spec); - (keypairs, deposits) + state_builder.process_initial_deposits(&deposits, &spec); + keypairs }; - let mut state_builder = BeaconStateBuilder::new(genesis_time, latest_eth1_data, &spec); - state_builder.process_initial_deposits(&initial_validator_deposits, &spec); let genesis_state = state_builder.build(&spec).unwrap(); let state_root = Hash256::from_slice(&genesis_state.hash_tree_root()); let genesis_block = BeaconBlock::genesis(state_root, &spec); diff --git a/beacon_node/beacon_chain/test_harness/src/prepare.rs b/beacon_node/beacon_chain/test_harness/src/prepare.rs index 0a4f4c34f4..36a99317fd 100644 --- a/beacon_node/beacon_chain/test_harness/src/prepare.rs +++ b/beacon_node/beacon_chain/test_harness/src/prepare.rs @@ -1,6 +1,5 @@ -use crate::beacon_chain_harness::{ - generate_deposits_from_keypairs, generate_deterministic_keypairs, -}; +use crate::beacon_chain_harness::generate_deterministic_keypairs; +use bls::get_withdrawal_credentials; use clap::{value_t, ArgMatches}; use log::debug; use serde_yaml; @@ -9,33 +8,52 @@ use std::{fs, fs::File}; use types::*; const KEYPAIRS_FILE: &str = "keypairs.yaml"; -const DEPOSITS_FILE: &str = "deposits.yaml"; +const VALIDATORS_FILE: &str = "validators.yaml"; pub fn prepare(matches: &ArgMatches, spec: &ChainSpec) { let validator_count = value_t!(matches.value_of("validator_count"), usize) .expect("Validator count is required argument"); - let genesis_time = - value_t!(matches.value_of("genesis_time"), u64).expect("Genesis time is required argument"); let output_dir = matches .value_of("output_dir") .expect("Output dir has a default value."); - debug!("Created keypairs and deposits, writing to file..."); + debug!("Created keypairs and validators, writing to file..."); fs::create_dir_all(Path::new(output_dir)).unwrap(); - // Ensure that keypairs is dropped before writing deposits, this provides a big memory saving + // Ensure that keypairs is dropped before writing validators, this provides a big memory saving // for large validator_counts. - let deposits = { + let validators: Vec = { debug!("Creating {} keypairs...", validator_count); let keypairs = generate_deterministic_keypairs(validator_count); debug!("Writing {} keypairs to file...", validator_count); write_keypairs(output_dir, &keypairs); - debug!("Creating {} deposits to file...", validator_count); - generate_deposits_from_keypairs(&keypairs, genesis_time, &spec) + debug!("Creating {} validators...", validator_count); + keypairs + .iter() + .map(|keypair| generate_validator(&keypair, spec)) + .collect() }; - debug!("Writing {} deposits to file...", validator_count); - write_deposits(output_dir, &deposits); + + debug!("Writing {} validators to file...", validator_count); + write_validators(output_dir, &validators); +} + +fn generate_validator(keypair: &Keypair, spec: &ChainSpec) -> Validator { + let withdrawal_credentials = Hash256::from_slice(&get_withdrawal_credentials( + &keypair.pk, + spec.bls_withdrawal_prefix_byte, + )); + + Validator { + pubkey: keypair.pk.clone(), + withdrawal_credentials, + activation_epoch: spec.far_future_epoch, + exit_epoch: spec.far_future_epoch, + withdrawable_epoch: spec.far_future_epoch, + initiated_exit: false, + slashed: false, + } } fn write_keypairs(output_dir: &str, keypairs: &[Keypair]) { @@ -44,8 +62,8 @@ fn write_keypairs(output_dir: &str, keypairs: &[Keypair]) { serde_yaml::to_writer(keypairs_file, &keypairs).unwrap(); } -fn write_deposits(output_dir: &str, deposits: &[Deposit]) { - let deposits_path = Path::new(output_dir).join(DEPOSITS_FILE); - let deposits_file = File::create(deposits_path).unwrap(); - serde_yaml::to_writer(deposits_file, &deposits).unwrap(); +fn write_validators(output_dir: &str, validators: &[Validator]) { + let validators_path = Path::new(output_dir).join(VALIDATORS_FILE); + let validators_file = File::create(validators_path).unwrap(); + serde_yaml::to_writer(validators_file, &validators).unwrap(); } diff --git a/eth2/types/Cargo.toml b/eth2/types/Cargo.toml index ea1343dbab..e2930040d7 100644 --- a/eth2/types/Cargo.toml +++ b/eth2/types/Cargo.toml @@ -17,6 +17,7 @@ rand = "0.5.5" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" +serde_yaml = "0.8" slog = "^2.2.3" ssz = { path = "../utils/ssz" } ssz_derive = { path = "../utils/ssz_derive" } diff --git a/eth2/types/src/beacon_state/builder.rs b/eth2/types/src/beacon_state/builder.rs index 4bb5e2cc62..f6d7b39009 100644 --- a/eth2/types/src/beacon_state/builder.rs +++ b/eth2/types/src/beacon_state/builder.rs @@ -33,7 +33,7 @@ impl BeaconStateBuilder { } } - /// Produce the first state of the Beacon Chain. + /// Process deposit objects. /// /// Spec v0.4.0 pub fn process_initial_deposits( @@ -48,19 +48,47 @@ impl BeaconStateBuilder { self.state.process_deposits(deposit_data, spec); + self.activate_genesis_validators(spec); + + self.state.deposit_index = initial_validator_deposits.len() as u64; + } + + fn activate_genesis_validators(&mut self, spec: &ChainSpec) { for validator_index in 0..self.state.validator_registry.len() { if self.state.get_effective_balance(validator_index, spec) >= spec.max_deposit_amount { self.state.activate_validator(validator_index, true, spec); } } - - self.state.deposit_index = initial_validator_deposits.len() as u64; } - /// Builds a `BeaconState` using the `BeaconState::genesis(..)` function. + /// Instantiate the validator registry from a YAML file. /// - /// Each validator is assigned a unique, randomly-generated keypair and all - /// proof-of-possessions are verified during genesis. + /// This skips a lot of signing and verification, useful for fast test setups. + /// + /// Spec v0.4.0 + pub fn import_existing_validators( + &mut self, + validators: Vec, + initial_balances: Vec, + deposit_index: u64, + spec: &ChainSpec, + ) { + self.state.validator_registry = validators; + + assert_eq!( + self.state.validator_registry.len(), + initial_balances.len(), + "Not enough balances for validators" + ); + + self.state.validator_balances = initial_balances; + + self.activate_genesis_validators(spec); + + self.state.deposit_index = deposit_index; + } + + /// Updates the final state variables and returns a fully built genesis state. /// /// Spec v0.4.0 pub fn build(mut self, spec: &ChainSpec) -> Result { diff --git a/eth2/types/src/slot_epoch.rs b/eth2/types/src/slot_epoch.rs index 7753027a61..2af7f5196c 100644 --- a/eth2/types/src/slot_epoch.rs +++ b/eth2/types/src/slot_epoch.rs @@ -12,7 +12,7 @@ use crate::slot_height::SlotHeight; /// may lead to programming errors which are not detected by the compiler. use crate::test_utils::TestRandom; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use slog; use ssz::{hash, ssz_encode, Decodable, DecodeError, Encodable, SszStream, TreeHash}; use std::cmp::{Ord, Ordering}; @@ -21,10 +21,10 @@ use std::hash::{Hash, Hasher}; use std::iter::Iterator; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, Sub, SubAssign}; -#[derive(Eq, Debug, Clone, Copy, Default, Serialize)] +#[derive(Eq, Debug, Clone, Copy, Default, Serialize, Deserialize)] pub struct Slot(u64); -#[derive(Eq, Debug, Clone, Copy, Default, Serialize)] +#[derive(Eq, Debug, Clone, Copy, Default, Serialize, Deserialize)] pub struct Epoch(u64); impl_common!(Slot); diff --git a/eth2/types/src/validator.rs b/eth2/types/src/validator.rs index 43701ca054..59f6c58264 100644 --- a/eth2/types/src/validator.rs +++ b/eth2/types/src/validator.rs @@ -1,13 +1,13 @@ use crate::{test_utils::TestRandom, Epoch, Hash256, PublicKey}; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode, TreeHash}; use test_random_derive::TestRandom; /// Information about a `BeaconChain` validator. /// /// Spec v0.4.0 -#[derive(Debug, Clone, PartialEq, Serialize, Encode, Decode, TestRandom, TreeHash)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom, TreeHash)] pub struct Validator { pub pubkey: PublicKey, pub withdrawal_credentials: Hash256, From d4f3bab68dad9f3192d99d395ca4f65481d2c62a Mon Sep 17 00:00:00 2001 From: mjkeating Date: Fri, 8 Mar 2019 15:24:07 -0800 Subject: [PATCH 023/144] Updated TreeHash logic as per revised spec --- eth2/types/src/attestation.rs | 4 +- eth2/types/src/attestation_data.rs | 4 +- .../src/attestation_data_and_custody_bit.rs | 4 +- eth2/types/src/attester_slashing.rs | 4 +- eth2/types/src/beacon_block.rs | 4 +- eth2/types/src/beacon_block_body.rs | 4 +- eth2/types/src/beacon_state.rs | 54 +++++++------- eth2/types/src/beacon_state/tests.rs | 4 +- eth2/types/src/crosslink.rs | 4 +- eth2/types/src/deposit.rs | 4 +- eth2/types/src/deposit_data.rs | 4 +- eth2/types/src/deposit_input.rs | 4 +- eth2/types/src/eth1_data.rs | 4 +- eth2/types/src/eth1_data_vote.rs | 4 +- eth2/types/src/fork.rs | 4 +- eth2/types/src/pending_attestation.rs | 4 +- eth2/types/src/proposal.rs | 4 +- eth2/types/src/proposer_slashing.rs | 4 +- eth2/types/src/shard_reassignment_record.rs | 4 +- eth2/types/src/slashable_attestation.rs | 4 +- eth2/types/src/slot_epoch_macros.rs | 8 +-- eth2/types/src/test_utils/macros.rs | 4 +- eth2/types/src/transfer.rs | 4 +- eth2/types/src/validator.rs | 4 +- eth2/types/src/voluntary_exit.rs | 4 +- eth2/utils/bls/src/aggregate_signature.rs | 2 +- eth2/utils/bls/src/public_key.rs | 2 +- eth2/utils/bls/src/secret_key.rs | 2 +- eth2/utils/bls/src/signature.rs | 2 +- eth2/utils/boolean-bitfield/src/lib.rs | 4 +- eth2/utils/ssz/src/impl_tree_hash.rs | 26 +++---- eth2/utils/ssz/src/tree_hash.rs | 71 +++++++------------ eth2/utils/ssz_derive/src/lib.rs | 6 +- 33 files changed, 126 insertions(+), 143 deletions(-) diff --git a/eth2/types/src/attestation.rs b/eth2/types/src/attestation.rs index 03ef8ce481..67bff3d209 100644 --- a/eth2/types/src/attestation.rs +++ b/eth2/types/src/attestation.rs @@ -35,11 +35,11 @@ mod tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = Attestation::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/attestation_data.rs b/eth2/types/src/attestation_data.rs index 1dfadfb1d6..5899ab52d4 100644 --- a/eth2/types/src/attestation_data.rs +++ b/eth2/types/src/attestation_data.rs @@ -53,11 +53,11 @@ mod tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = AttestationData::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/attestation_data_and_custody_bit.rs b/eth2/types/src/attestation_data_and_custody_bit.rs index 83018c1943..e9cf4bb672 100644 --- a/eth2/types/src/attestation_data_and_custody_bit.rs +++ b/eth2/types/src/attestation_data_and_custody_bit.rs @@ -42,11 +42,11 @@ mod test { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = AttestationDataAndCustodyBit::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/attester_slashing.rs b/eth2/types/src/attester_slashing.rs index 1cb6719606..7b25e94c48 100644 --- a/eth2/types/src/attester_slashing.rs +++ b/eth2/types/src/attester_slashing.rs @@ -35,11 +35,11 @@ mod tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = AttesterSlashing::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/beacon_block.rs b/eth2/types/src/beacon_block.rs index 2e1e24ef79..6c2b158885 100644 --- a/eth2/types/src/beacon_block.rs +++ b/eth2/types/src/beacon_block.rs @@ -69,11 +69,11 @@ mod tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = BeaconBlock::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/beacon_block_body.rs b/eth2/types/src/beacon_block_body.rs index e7dec2e4b7..2a43f289ce 100644 --- a/eth2/types/src/beacon_block_body.rs +++ b/eth2/types/src/beacon_block_body.rs @@ -36,11 +36,11 @@ mod tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = BeaconBlockBody::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 809408b326..bbba5d70bc 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -1262,42 +1262,42 @@ impl Decodable for BeaconState { } impl TreeHash for BeaconState { - fn hash_tree_root_internal(&self) -> Vec { + fn hash_tree_root(&self) -> Vec { let mut result: Vec = vec![]; - result.append(&mut self.slot.hash_tree_root_internal()); - result.append(&mut self.genesis_time.hash_tree_root_internal()); - result.append(&mut self.fork.hash_tree_root_internal()); - result.append(&mut self.validator_registry.hash_tree_root_internal()); - result.append(&mut self.validator_balances.hash_tree_root_internal()); + result.append(&mut self.slot.hash_tree_root()); + result.append(&mut self.genesis_time.hash_tree_root()); + result.append(&mut self.fork.hash_tree_root()); + result.append(&mut self.validator_registry.hash_tree_root()); + result.append(&mut self.validator_balances.hash_tree_root()); result.append( &mut self .validator_registry_update_epoch - .hash_tree_root_internal(), + .hash_tree_root(), ); - result.append(&mut self.latest_randao_mixes.hash_tree_root_internal()); + result.append(&mut self.latest_randao_mixes.hash_tree_root()); result.append( &mut self .previous_shuffling_start_shard - .hash_tree_root_internal(), + .hash_tree_root(), ); - result.append(&mut self.current_shuffling_start_shard.hash_tree_root_internal()); - result.append(&mut self.previous_shuffling_epoch.hash_tree_root_internal()); - result.append(&mut self.current_shuffling_epoch.hash_tree_root_internal()); - result.append(&mut self.previous_shuffling_seed.hash_tree_root_internal()); - result.append(&mut self.current_shuffling_seed.hash_tree_root_internal()); - result.append(&mut self.previous_justified_epoch.hash_tree_root_internal()); - result.append(&mut self.justified_epoch.hash_tree_root_internal()); - result.append(&mut self.justification_bitfield.hash_tree_root_internal()); - result.append(&mut self.finalized_epoch.hash_tree_root_internal()); - result.append(&mut self.latest_crosslinks.hash_tree_root_internal()); - result.append(&mut self.latest_block_roots.hash_tree_root_internal()); - result.append(&mut self.latest_active_index_roots.hash_tree_root_internal()); - result.append(&mut self.latest_slashed_balances.hash_tree_root_internal()); - result.append(&mut self.latest_attestations.hash_tree_root_internal()); - result.append(&mut self.batched_block_roots.hash_tree_root_internal()); - result.append(&mut self.latest_eth1_data.hash_tree_root_internal()); - result.append(&mut self.eth1_data_votes.hash_tree_root_internal()); - result.append(&mut self.deposit_index.hash_tree_root_internal()); + result.append(&mut self.current_shuffling_start_shard.hash_tree_root()); + result.append(&mut self.previous_shuffling_epoch.hash_tree_root()); + result.append(&mut self.current_shuffling_epoch.hash_tree_root()); + result.append(&mut self.previous_shuffling_seed.hash_tree_root()); + result.append(&mut self.current_shuffling_seed.hash_tree_root()); + result.append(&mut self.previous_justified_epoch.hash_tree_root()); + result.append(&mut self.justified_epoch.hash_tree_root()); + result.append(&mut self.justification_bitfield.hash_tree_root()); + result.append(&mut self.finalized_epoch.hash_tree_root()); + result.append(&mut self.latest_crosslinks.hash_tree_root()); + result.append(&mut self.latest_block_roots.hash_tree_root()); + result.append(&mut self.latest_active_index_roots.hash_tree_root()); + result.append(&mut self.latest_slashed_balances.hash_tree_root()); + result.append(&mut self.latest_attestations.hash_tree_root()); + result.append(&mut self.batched_block_roots.hash_tree_root()); + result.append(&mut self.latest_eth1_data.hash_tree_root()); + result.append(&mut self.eth1_data_votes.hash_tree_root()); + result.append(&mut self.deposit_index.hash_tree_root()); hash(&result) } } diff --git a/eth2/types/src/beacon_state/tests.rs b/eth2/types/src/beacon_state/tests.rs index 40bfd146ce..eca54bf369 100644 --- a/eth2/types/src/beacon_state/tests.rs +++ b/eth2/types/src/beacon_state/tests.rs @@ -72,11 +72,11 @@ pub fn test_ssz_round_trip() { } #[test] -pub fn test_hash_tree_root_internal() { +pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = BeaconState::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/crosslink.rs b/eth2/types/src/crosslink.rs index f49195a75d..ed31a80d5d 100644 --- a/eth2/types/src/crosslink.rs +++ b/eth2/types/src/crosslink.rs @@ -34,11 +34,11 @@ mod tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = Crosslink::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/deposit.rs b/eth2/types/src/deposit.rs index 2e69ea599b..dcd82e5501 100644 --- a/eth2/types/src/deposit.rs +++ b/eth2/types/src/deposit.rs @@ -33,11 +33,11 @@ mod tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = Deposit::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/deposit_data.rs b/eth2/types/src/deposit_data.rs index 1eb2722a90..d90b07b098 100644 --- a/eth2/types/src/deposit_data.rs +++ b/eth2/types/src/deposit_data.rs @@ -33,11 +33,11 @@ mod tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = DepositData::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/deposit_input.rs b/eth2/types/src/deposit_input.rs index c4c79c3d1c..1dda64ce21 100644 --- a/eth2/types/src/deposit_input.rs +++ b/eth2/types/src/deposit_input.rs @@ -34,11 +34,11 @@ mod tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = DepositInput::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/eth1_data.rs b/eth2/types/src/eth1_data.rs index 2c817ca385..88f91e3a92 100644 --- a/eth2/types/src/eth1_data.rs +++ b/eth2/types/src/eth1_data.rs @@ -32,11 +32,11 @@ mod tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = Eth1Data::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/eth1_data_vote.rs b/eth2/types/src/eth1_data_vote.rs index 898145575d..bd8266ce37 100644 --- a/eth2/types/src/eth1_data_vote.rs +++ b/eth2/types/src/eth1_data_vote.rs @@ -32,11 +32,11 @@ mod tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = Eth1DataVote::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/fork.rs b/eth2/types/src/fork.rs index 0acd6da906..9cf6ae3960 100644 --- a/eth2/types/src/fork.rs +++ b/eth2/types/src/fork.rs @@ -44,11 +44,11 @@ mod tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = Fork::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/pending_attestation.rs b/eth2/types/src/pending_attestation.rs index 0430d18baf..c1293546e2 100644 --- a/eth2/types/src/pending_attestation.rs +++ b/eth2/types/src/pending_attestation.rs @@ -34,11 +34,11 @@ mod tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = PendingAttestation::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/proposal.rs b/eth2/types/src/proposal.rs index b1fd737a09..dda544a19c 100644 --- a/eth2/types/src/proposal.rs +++ b/eth2/types/src/proposal.rs @@ -37,11 +37,11 @@ mod tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = Proposal::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/proposer_slashing.rs b/eth2/types/src/proposer_slashing.rs index f86e7f3a88..307a4b0a0c 100644 --- a/eth2/types/src/proposer_slashing.rs +++ b/eth2/types/src/proposer_slashing.rs @@ -37,11 +37,11 @@ mod tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = ProposerSlashing::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/shard_reassignment_record.rs b/eth2/types/src/shard_reassignment_record.rs index f5dfa86762..d8595b69d7 100644 --- a/eth2/types/src/shard_reassignment_record.rs +++ b/eth2/types/src/shard_reassignment_record.rs @@ -29,11 +29,11 @@ mod tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = ShardReassignmentRecord::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/slashable_attestation.rs b/eth2/types/src/slashable_attestation.rs index 20ba76cdb9..2c4bde8db4 100644 --- a/eth2/types/src/slashable_attestation.rs +++ b/eth2/types/src/slashable_attestation.rs @@ -132,11 +132,11 @@ mod tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = SlashableAttestation::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/slot_epoch_macros.rs b/eth2/types/src/slot_epoch_macros.rs index 2148b6cc2f..4b2332bafc 100644 --- a/eth2/types/src/slot_epoch_macros.rs +++ b/eth2/types/src/slot_epoch_macros.rs @@ -207,9 +207,9 @@ macro_rules! impl_ssz { } impl TreeHash for $type { - fn hash_tree_root_internal(&self) -> Vec { + fn hash_tree_root(&self) -> Vec { let mut result: Vec = vec![]; - result.append(&mut self.0.hash_tree_root_internal()); + result.append(&mut self.0.hash_tree_root()); hash(&result) } } @@ -543,11 +543,11 @@ macro_rules! ssz_tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = $type::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/test_utils/macros.rs b/eth2/types/src/test_utils/macros.rs index b7c0a65226..f5b2fd87cb 100644 --- a/eth2/types/src/test_utils/macros.rs +++ b/eth2/types/src/test_utils/macros.rs @@ -17,14 +17,14 @@ macro_rules! ssz_tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; use ssz::TreeHash; let mut rng = XorShiftRng::from_seed([42; 16]); let original = $type::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/transfer.rs b/eth2/types/src/transfer.rs index 0382dee11b..b3c283fa2d 100644 --- a/eth2/types/src/transfer.rs +++ b/eth2/types/src/transfer.rs @@ -39,11 +39,11 @@ mod tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = Transfer::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/validator.rs b/eth2/types/src/validator.rs index 43701ca054..578c4db76b 100644 --- a/eth2/types/src/validator.rs +++ b/eth2/types/src/validator.rs @@ -91,11 +91,11 @@ mod tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = Validator::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/types/src/voluntary_exit.rs b/eth2/types/src/voluntary_exit.rs index 58c3ae4c27..36b5597f07 100644 --- a/eth2/types/src/voluntary_exit.rs +++ b/eth2/types/src/voluntary_exit.rs @@ -34,11 +34,11 @@ mod tests { } #[test] - pub fn test_hash_tree_root_internal() { + pub fn test_hash_tree_root() { let mut rng = XorShiftRng::from_seed([42; 16]); let original = VoluntaryExit::random_for_test(&mut rng); - let result = original.hash_tree_root_internal(); + let result = original.hash_tree_root(); assert_eq!(result.len(), 32); // TODO: Add further tests diff --git a/eth2/utils/bls/src/aggregate_signature.rs b/eth2/utils/bls/src/aggregate_signature.rs index 2d8776353f..3ebb6f15e8 100644 --- a/eth2/utils/bls/src/aggregate_signature.rs +++ b/eth2/utils/bls/src/aggregate_signature.rs @@ -95,7 +95,7 @@ impl Serialize for AggregateSignature { } impl TreeHash for AggregateSignature { - fn hash_tree_root_internal(&self) -> Vec { + fn hash_tree_root(&self) -> Vec { hash(&self.0.as_bytes()) } } diff --git a/eth2/utils/bls/src/public_key.rs b/eth2/utils/bls/src/public_key.rs index c7fd526a06..0c2ad81bb4 100644 --- a/eth2/utils/bls/src/public_key.rs +++ b/eth2/utils/bls/src/public_key.rs @@ -66,7 +66,7 @@ impl Serialize for PublicKey { } impl TreeHash for PublicKey { - fn hash_tree_root_internal(&self) -> Vec { + fn hash_tree_root(&self) -> Vec { hash(&self.0.as_bytes()) } } diff --git a/eth2/utils/bls/src/secret_key.rs b/eth2/utils/bls/src/secret_key.rs index f2d54f4ac3..4ff9f8684d 100644 --- a/eth2/utils/bls/src/secret_key.rs +++ b/eth2/utils/bls/src/secret_key.rs @@ -41,7 +41,7 @@ impl Decodable for SecretKey { } impl TreeHash for SecretKey { - fn hash_tree_root_internal(&self) -> Vec { + fn hash_tree_root(&self) -> Vec { self.0.as_bytes().clone() } } diff --git a/eth2/utils/bls/src/signature.rs b/eth2/utils/bls/src/signature.rs index c0c31ef27b..23b0c08346 100644 --- a/eth2/utils/bls/src/signature.rs +++ b/eth2/utils/bls/src/signature.rs @@ -73,7 +73,7 @@ impl Decodable for Signature { } impl TreeHash for Signature { - fn hash_tree_root_internal(&self) -> Vec { + fn hash_tree_root(&self) -> Vec { hash(&self.0.as_bytes()) } } diff --git a/eth2/utils/boolean-bitfield/src/lib.rs b/eth2/utils/boolean-bitfield/src/lib.rs index fb3a78e7a6..a0fce1f0a4 100644 --- a/eth2/utils/boolean-bitfield/src/lib.rs +++ b/eth2/utils/boolean-bitfield/src/lib.rs @@ -187,8 +187,8 @@ impl Serialize for BooleanBitfield { } impl ssz::TreeHash for BooleanBitfield { - fn hash_tree_root_internal(&self) -> Vec { - self.to_bytes().hash_tree_root_internal() + fn hash_tree_root(&self) -> Vec { + self.to_bytes().hash_tree_root() } } diff --git a/eth2/utils/ssz/src/impl_tree_hash.rs b/eth2/utils/ssz/src/impl_tree_hash.rs index 54bd7c1392..03976f6375 100644 --- a/eth2/utils/ssz/src/impl_tree_hash.rs +++ b/eth2/utils/ssz/src/impl_tree_hash.rs @@ -3,55 +3,55 @@ use super::{merkle_hash, ssz_encode, TreeHash}; use hashing::hash; impl TreeHash for u8 { - fn hash_tree_root_internal(&self) -> Vec { + fn hash_tree_root(&self) -> Vec { ssz_encode(self) } } impl TreeHash for u16 { - fn hash_tree_root_internal(&self) -> Vec { + fn hash_tree_root(&self) -> Vec { ssz_encode(self) } } impl TreeHash for u32 { - fn hash_tree_root_internal(&self) -> Vec { + fn hash_tree_root(&self) -> Vec { ssz_encode(self) } } impl TreeHash for u64 { - fn hash_tree_root_internal(&self) -> Vec { + fn hash_tree_root(&self) -> Vec { ssz_encode(self) } } impl TreeHash for usize { - fn hash_tree_root_internal(&self) -> Vec { + fn hash_tree_root(&self) -> Vec { ssz_encode(self) } } impl TreeHash for bool { - fn hash_tree_root_internal(&self) -> Vec { + fn hash_tree_root(&self) -> Vec { ssz_encode(self) } } impl TreeHash for Address { - fn hash_tree_root_internal(&self) -> Vec { + fn hash_tree_root(&self) -> Vec { ssz_encode(self) } } impl TreeHash for H256 { - fn hash_tree_root_internal(&self) -> Vec { + fn hash_tree_root(&self) -> Vec { ssz_encode(self) } } impl TreeHash for [u8] { - fn hash_tree_root_internal(&self) -> Vec { + fn hash_tree_root(&self) -> Vec { if self.len() > 32 { return hash(&self); } @@ -63,12 +63,12 @@ impl TreeHash for Vec where T: TreeHash, { - /// Returns the merkle_hash of a list of hash_tree_root_internal values created + /// Returns the merkle_hash of a list of hash_tree_root values created /// from the given list. /// Note: A byte vector, Vec, must be converted to a slice (as_slice()) /// to be handled properly (i.e. hashed) as byte array. - fn hash_tree_root_internal(&self) -> Vec { - let mut tree_hashes = self.iter().map(|x| x.hash_tree_root_internal()).collect(); + fn hash_tree_root(&self) -> Vec { + let mut tree_hashes = self.iter().map(|x| x.hash_tree_root()).collect(); merkle_hash(&mut tree_hashes) } } @@ -79,7 +79,7 @@ mod tests { #[test] fn test_impl_tree_hash_vec() { - let result = vec![1u32, 2, 3, 4, 5, 6, 7].hash_tree_root_internal(); + let result = vec![1u32, 2, 3, 4, 5, 6, 7].hash_tree_root(); assert_eq!(result.len(), 32); } } diff --git a/eth2/utils/ssz/src/tree_hash.rs b/eth2/utils/ssz/src/tree_hash.rs index 7c1ab35e93..85e56924c2 100644 --- a/eth2/utils/ssz/src/tree_hash.rs +++ b/eth2/utils/ssz/src/tree_hash.rs @@ -1,44 +1,31 @@ use hashing::hash; -const SSZ_CHUNK_SIZE: usize = 128; +const BYTES_PER_CHUNK: usize = 32; const HASHSIZE: usize = 32; pub trait TreeHash { - fn hash_tree_root_internal(&self) -> Vec; - fn hash_tree_root(&self) -> Vec { - let mut result = self.hash_tree_root_internal(); - zpad(&mut result, HASHSIZE); - result - } + fn hash_tree_root(&self) -> Vec; } /// Returns a 32 byte hash of 'list' - a vector of byte vectors. /// Note that this will consume 'list'. pub fn merkle_hash(list: &mut Vec>) -> Vec { // flatten list - let (mut chunk_size, mut chunkz) = list_to_blob(list); + let mut chunkz = list_to_blob(list); // get data_len as bytes. It will hashed will the merkle root let mut datalen = list.len().to_le_bytes().to_vec(); zpad(&mut datalen, 32); - // Tree-hash + // merklelize while chunkz.len() > HASHSIZE { let mut new_chunkz: Vec = Vec::new(); - for two_chunks in chunkz.chunks(chunk_size * 2) { - if two_chunks.len() == chunk_size { - // Odd number of chunks - let mut c = two_chunks.to_vec(); - c.append(&mut vec![0; SSZ_CHUNK_SIZE]); - new_chunkz.append(&mut hash(&c)); - } else { - // Hash two chuncks together - new_chunkz.append(&mut hash(two_chunks)); - } + for two_chunks in chunkz.chunks(BYTES_PER_CHUNK * 2) { + // Hash two chuncks together + new_chunkz.append(&mut hash(two_chunks)); } - chunk_size = HASHSIZE; chunkz = new_chunkz; } @@ -46,17 +33,13 @@ pub fn merkle_hash(list: &mut Vec>) -> Vec { hash(&chunkz) } -fn list_to_blob(list: &mut Vec>) -> (usize, Vec) { - let chunk_size = if list.is_empty() || list[0].len() < SSZ_CHUNK_SIZE { - SSZ_CHUNK_SIZE - } else { - list[0].len() - }; - +fn list_to_blob(list: &mut Vec>) -> Vec { + // pack - fit as many many items per chunk as we can and then + // right pad to BYTES_PER_CHUNCK let (items_per_chunk, chunk_count) = if list.is_empty() { (1, 1) } else { - let items_per_chunk = SSZ_CHUNK_SIZE / list[0].len(); + let items_per_chunk = BYTES_PER_CHUNK / list[0].len(); let chunk_count = list.len() / items_per_chunk; (items_per_chunk, chunk_count) }; @@ -64,20 +47,20 @@ fn list_to_blob(list: &mut Vec>) -> (usize, Vec) { let mut chunkz = Vec::new(); if list.is_empty() { // handle and empty list - chunkz.append(&mut vec![0; SSZ_CHUNK_SIZE]); - } else if list[0].len() <= SSZ_CHUNK_SIZE { + chunkz.append(&mut vec![0; BYTES_PER_CHUNK * 2]); + } else if list[0].len() <= BYTES_PER_CHUNK { // just create a blob here; we'll divide into // chunked slices when we merklize - let mut chunk = Vec::with_capacity(chunk_size); + let mut chunk = Vec::with_capacity(BYTES_PER_CHUNK); let mut item_count_in_chunk = 0; - chunkz.reserve(chunk_count * chunk_size); + chunkz.reserve(chunk_count * BYTES_PER_CHUNK); for item in list.iter_mut() { item_count_in_chunk += 1; chunk.append(item); // completed chunk? if item_count_in_chunk == items_per_chunk { - zpad(&mut chunk, chunk_size); + zpad(&mut chunk, BYTES_PER_CHUNK); chunkz.append(&mut chunk); item_count_in_chunk = 0; } @@ -85,18 +68,18 @@ fn list_to_blob(list: &mut Vec>) -> (usize, Vec) { // left-over uncompleted chunk? if item_count_in_chunk != 0 { - zpad(&mut chunk, chunk_size); + zpad(&mut chunk, BYTES_PER_CHUNK); chunkz.append(&mut chunk); } - } else { - // chunks larger than SSZ_CHUNK_SIZE - chunkz.reserve(chunk_count * chunk_size); - for item in list.iter_mut() { - chunkz.append(item); - } } - (chunk_size, chunkz) + // extend the number of chunks to a power of two if necessary + if !chunk_count.is_power_of_two() { + let zero_chunks_count = chunk_count.next_power_of_two() - chunk_count; + chunkz.append(&mut vec![0; zero_chunks_count * BYTES_PER_CHUNK]); + } + + chunkz } /// right pads with zeros making 'bytes' 'size' in length @@ -112,9 +95,9 @@ mod tests { #[test] fn test_merkle_hash() { - let data1 = vec![1; 100]; - let data2 = vec![2; 100]; - let data3 = vec![3; 100]; + let data1 = vec![1; 32]; + let data2 = vec![2; 32]; + let data3 = vec![3; 32]; let mut list = vec![data1, data2, data3]; let result = merkle_hash(&mut list); diff --git a/eth2/utils/ssz_derive/src/lib.rs b/eth2/utils/ssz_derive/src/lib.rs index 0d2e17f768..a7802a274f 100644 --- a/eth2/utils/ssz_derive/src/lib.rs +++ b/eth2/utils/ssz_derive/src/lib.rs @@ -146,10 +146,10 @@ pub fn ssz_tree_hash_derive(input: TokenStream) -> TokenStream { let output = quote! { impl ssz::TreeHash for #name { - fn hash_tree_root_internal(&self) -> Vec { + fn hash_tree_root(&self) -> Vec { let mut list: Vec> = Vec::new(); #( - list.push(self.#field_idents.hash_tree_root_internal()); + list.push(self.#field_idents.hash_tree_root()); )* ssz::merkle_hash(&mut list) @@ -224,7 +224,7 @@ pub fn ssz_signed_root_derive(input: TokenStream) -> TokenStream { fn signed_root(&self) -> Vec { let mut list: Vec> = Vec::new(); #( - list.push(self.#field_idents.hash_tree_root_internal()); + list.push(self.#field_idents.hash_tree_root()); )* ssz::merkle_hash(&mut list) From 63743a962c60ca5100054ad7aa38e8300f8ef9cd Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 9 Mar 2019 10:37:41 +1100 Subject: [PATCH 024/144] Add per-epoch benchmarks, optimise function. --- Cargo.toml | 1 + eth2/state_processing/Cargo.toml | 2 + eth2/state_processing/benches/benches.rs | 289 +++++++++++++++-- .../benching_utils/Cargo.toml | 17 + .../benching_utils/src/lib.rs | 195 +++++++++++ .../src/per_epoch_processing.rs | 302 +++++++++++------- .../src/per_epoch_processing/attester_sets.rs | 4 +- .../src/per_epoch_processing/errors.rs | 2 + .../src/per_epoch_processing/tests.rs | 15 +- eth2/types/src/beacon_state/helpers.rs | 2 +- 10 files changed, 670 insertions(+), 159 deletions(-) create mode 100644 eth2/state_processing/benching_utils/Cargo.toml create mode 100644 eth2/state_processing/benching_utils/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index c5aae7f43f..8f4dbb2688 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "eth2/block_proposer", "eth2/fork_choice", "eth2/state_processing", + "eth2/state_processing/benching_utils", "eth2/types", "eth2/utils/bls", "eth2/utils/boolean-bitfield", diff --git a/eth2/state_processing/Cargo.toml b/eth2/state_processing/Cargo.toml index c51ce8372f..962d23a778 100644 --- a/eth2/state_processing/Cargo.toml +++ b/eth2/state_processing/Cargo.toml @@ -11,9 +11,11 @@ harness = false [dev-dependencies] criterion = "0.2" env_logger = "0.6.0" +benching_utils = { path = "./benching_utils" } [dependencies] bls = { path = "../utils/bls" } +fnv = "1.0" hashing = { path = "../utils/hashing" } int_to_bytes = { path = "../utils/int_to_bytes" } integer-sqrt = "0.1" diff --git a/eth2/state_processing/benches/benches.rs b/eth2/state_processing/benches/benches.rs index 682259eef5..5c064a08ff 100644 --- a/eth2/state_processing/benches/benches.rs +++ b/eth2/state_processing/benches/benches.rs @@ -1,60 +1,291 @@ use criterion::Criterion; use criterion::{black_box, criterion_group, criterion_main, Benchmark}; +use state_processing::{ + per_epoch_processing, + per_epoch_processing::{ + calculate_active_validator_indices, calculate_attester_sets, clean_attestations, + process_crosslinks, process_eth1_data, process_justification, + process_rewards_and_penalities, process_validator_registry, update_active_tree_index_roots, + update_latest_slashed_balances, + }, +}; // use env_logger::{Builder, Env}; -use state_processing::SlotProcessable; -use types::beacon_state::BeaconStateBuilder; -use types::*; +use benching_utils::BeaconStateBencher; +use types::{validator_registry::get_active_validator_indices, *}; fn epoch_processing(c: &mut Criterion) { // Builder::from_env(Env::default().default_filter_or("debug")).init(); + // + let spec = ChainSpec::foundation(); - let mut builder = BeaconStateBuilder::new(16_384); + let validator_count = 16_384; - builder.build_fast().unwrap(); - builder.teleport_to_end_of_epoch(builder.spec.genesis_epoch + 4); - - let mut state = builder.cloned_state(); + let mut builder = BeaconStateBencher::new(validator_count, &spec); + builder.teleport_to_end_of_epoch(spec.genesis_epoch + 4, &spec); + builder.insert_attestations(&spec); + let mut state = builder.build(); // Build all the caches so the following state does _not_ include the cache-building time. state - .build_epoch_cache(RelativeEpoch::Previous, &builder.spec) + .build_epoch_cache(RelativeEpoch::Previous, &spec) .unwrap(); state - .build_epoch_cache(RelativeEpoch::Current, &builder.spec) - .unwrap(); - state - .build_epoch_cache(RelativeEpoch::Next, &builder.spec) + .build_epoch_cache(RelativeEpoch::Current, &spec) .unwrap(); + state.build_epoch_cache(RelativeEpoch::Next, &spec).unwrap(); - let cached_state = state.clone(); + // Assert that the state has the maximum possible attestations. + let committees_per_epoch = spec.get_epoch_committee_count(validator_count); + let committees_per_slot = committees_per_epoch / spec.slots_per_epoch; + let previous_epoch_attestations = committees_per_epoch; + let current_epoch_attestations = + committees_per_slot * (spec.slots_per_epoch - spec.min_attestation_inclusion_delay); + assert_eq!( + state.latest_attestations.len() as u64, + previous_epoch_attestations + current_epoch_attestations + ); - // Drop all the caches so the following state includes the cache-building time. - state.drop_cache(RelativeEpoch::Previous); - state.drop_cache(RelativeEpoch::Current); - state.drop_cache(RelativeEpoch::Next); + // Assert that each attestation in the state has full participation. + let committee_size = validator_count / committees_per_epoch as usize; + for a in &state.latest_attestations { + assert_eq!(a.aggregation_bitfield.num_set_bits(), committee_size); + } - let cacheless_state = state; + // Assert that we will run the first arm of process_rewards_and_penalities + let epochs_since_finality = state.next_epoch(&spec) - state.finalized_epoch; + assert!(epochs_since_finality <= 4); - let spec_a = builder.spec.clone(); - let spec_b = builder.spec.clone(); + bench_epoch_processing(c, &state, &spec, "16k_validators"); +} +fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSpec, desc: &str) { + let state_clone = state.clone(); + let spec_clone = spec.clone(); c.bench( - "epoch processing", - Benchmark::new("with pre-built caches", move |b| { + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("full run", move |b| { b.iter_with_setup( - || cached_state.clone(), - |mut state| black_box(state.per_slot_processing(Hash256::zero(), &spec_a).unwrap()), + || state_clone.clone(), + |mut state| black_box(per_epoch_processing(&mut state, &spec_clone).unwrap()), ) }) .sample_size(10), ); + let state_clone = state.clone(); + let spec_clone = spec.clone(); c.bench( - "epoch processing", - Benchmark::new("without pre-built caches", move |b| { + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("calculate_active_validator_indices", move |b| { b.iter_with_setup( - || cacheless_state.clone(), - |mut state| black_box(state.per_slot_processing(Hash256::zero(), &spec_b).unwrap()), + || state_clone.clone(), + |mut state| black_box(calculate_active_validator_indices(&mut state, &spec_clone)), + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + let active_validator_indices = calculate_active_validator_indices(&state, &spec); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("calculate_current_total_balance", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |state| { + black_box(state.get_total_balance(&active_validator_indices[..], &spec_clone)) + }, + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("calculate_previous_total_balance", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |state| { + black_box(state.get_total_balance( + &get_active_validator_indices( + &state.validator_registry, + state.previous_epoch(&spec_clone), + )[..], + &spec_clone, + )) + }, + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("process_eth1_data", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| black_box(process_eth1_data(&mut state, &spec_clone)), + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("calculate_attester_sets", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| black_box(calculate_attester_sets(&mut state, &spec_clone).unwrap()), + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + let previous_epoch = state.previous_epoch(&spec); + let attesters = calculate_attester_sets(&state, &spec).unwrap(); + let active_validator_indices = calculate_active_validator_indices(&state, &spec); + let current_total_balance = state.get_total_balance(&active_validator_indices[..], &spec); + let previous_total_balance = state.get_total_balance( + &get_active_validator_indices(&state.validator_registry, previous_epoch)[..], + &spec, + ); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("process_justification", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| { + black_box(process_justification( + &mut state, + current_total_balance, + previous_total_balance, + attesters.previous_epoch_boundary.balance, + attesters.current_epoch_boundary.balance, + &spec_clone, + )) + }, + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("process_crosslinks", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| black_box(process_crosslinks(&mut state, &spec_clone).unwrap()), + ) + }) + .sample_size(10), + ); + + let mut state_clone = state.clone(); + let spec_clone = spec.clone(); + let previous_epoch = state.previous_epoch(&spec); + let attesters = calculate_attester_sets(&state, &spec).unwrap(); + let active_validator_indices = calculate_active_validator_indices(&state, &spec); + let previous_total_balance = state.get_total_balance( + &get_active_validator_indices(&state.validator_registry, previous_epoch)[..], + &spec, + ); + let winning_root_for_shards = process_crosslinks(&mut state_clone, &spec).unwrap(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("process_rewards_and_penalties", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| { + black_box( + process_rewards_and_penalities( + &mut state, + &active_validator_indices, + &attesters, + previous_total_balance, + &winning_root_for_shards, + &spec_clone, + ) + .unwrap(), + ) + }, + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("process_ejections", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| black_box(state.process_ejections(&spec_clone)), + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("process_validator_registry", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| black_box(process_validator_registry(&mut state, &spec_clone)), + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("update_active_tree_index_roots", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| { + black_box(update_active_tree_index_roots(&mut state, &spec_clone).unwrap()) + }, + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("update_latest_slashed_balances", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| black_box(update_latest_slashed_balances(&mut state, &spec_clone)), + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("clean_attestations", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| black_box(clean_attestations(&mut state, &spec_clone)), ) }) .sample_size(10), diff --git a/eth2/state_processing/benching_utils/Cargo.toml b/eth2/state_processing/benching_utils/Cargo.toml new file mode 100644 index 0000000000..00815406a0 --- /dev/null +++ b/eth2/state_processing/benching_utils/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "benching_utils" +version = "0.1.0" +authors = ["Paul Hauner "] +edition = "2018" + +[dependencies] +bls = { path = "../../utils/bls" } +hashing = { path = "../../utils/hashing" } +int_to_bytes = { path = "../../utils/int_to_bytes" } +integer-sqrt = "0.1" +log = "0.4" +merkle_proof = { path = "../../utils/merkle_proof" } +ssz = { path = "../../utils/ssz" } +ssz_derive = { path = "../../utils/ssz_derive" } +types = { path = "../../types" } +rayon = "1.0" diff --git a/eth2/state_processing/benching_utils/src/lib.rs b/eth2/state_processing/benching_utils/src/lib.rs new file mode 100644 index 0000000000..c70b7828a7 --- /dev/null +++ b/eth2/state_processing/benching_utils/src/lib.rs @@ -0,0 +1,195 @@ +use bls::get_withdrawal_credentials; +use int_to_bytes::int_to_bytes48; +use rayon::prelude::*; +use types::beacon_state::BeaconStateBuilder; +use types::*; + +pub struct BeaconStateBencher { + state: BeaconState, +} + +impl BeaconStateBencher { + pub fn new(validator_count: usize, spec: &ChainSpec) -> Self { + let keypairs: Vec = (0..validator_count) + .collect::>() + .par_iter() + .map(|&i| { + let secret = int_to_bytes48(i as u64 + 1); + let sk = SecretKey::from_bytes(&secret).unwrap(); + let pk = PublicKey::from_secret_key(&sk); + Keypair { sk, pk } + }) + .collect(); + + let validators = keypairs + .iter() + .map(|keypair| { + let withdrawal_credentials = Hash256::from_slice(&get_withdrawal_credentials( + &keypair.pk, + spec.bls_withdrawal_prefix_byte, + )); + + Validator { + pubkey: keypair.pk.clone(), + withdrawal_credentials, + activation_epoch: spec.far_future_epoch, + exit_epoch: spec.far_future_epoch, + withdrawable_epoch: spec.far_future_epoch, + initiated_exit: false, + slashed: false, + } + }) + .collect(); + + let mut state_builder = BeaconStateBuilder::new( + 0, + Eth1Data { + deposit_root: Hash256::zero(), + block_hash: Hash256::zero(), + }, + spec, + ); + + let balances = vec![32_000_000_000; validator_count]; + + state_builder.import_existing_validators( + validators, + balances, + validator_count as u64, + spec, + ); + + Self { + state: state_builder.build(spec).unwrap(), + } + } + + pub fn build(self) -> BeaconState { + self.state + } + + /// Sets the `BeaconState` to be in the last slot of the given epoch. + /// + /// Sets all justification/finalization parameters to be be as "perfect" as possible (i.e., + /// highest justified and finalized slots, full justification bitfield, etc). + pub fn teleport_to_end_of_epoch(&mut self, epoch: Epoch, spec: &ChainSpec) { + let state = &mut self.state; + + let slot = epoch.end_slot(spec.slots_per_epoch); + + state.slot = slot; + state.validator_registry_update_epoch = epoch - 1; + + state.previous_shuffling_epoch = epoch - 1; + state.current_shuffling_epoch = epoch; + + state.previous_shuffling_seed = Hash256::from_low_u64_le(0); + state.current_shuffling_seed = Hash256::from_low_u64_le(1); + + state.previous_justified_epoch = epoch - 2; + state.justified_epoch = epoch - 1; + state.justification_bitfield = u64::max_value(); + state.finalized_epoch = epoch - 1; + } + + /// Creates a full set of attestations for the `BeaconState`. Each attestation has full + /// participation from its committee and references the expected beacon_block hashes. + /// + /// These attestations should be fully conducive to justification and finalization. + pub fn insert_attestations(&mut self, spec: &ChainSpec) { + let state = &mut self.state; + + state + .build_epoch_cache(RelativeEpoch::Previous, spec) + .unwrap(); + state + .build_epoch_cache(RelativeEpoch::Current, spec) + .unwrap(); + + let current_epoch = state.current_epoch(spec); + let previous_epoch = state.previous_epoch(spec); + + let first_slot = previous_epoch.start_slot(spec.slots_per_epoch).as_u64(); + let last_slot = current_epoch.end_slot(spec.slots_per_epoch).as_u64() + - spec.min_attestation_inclusion_delay; + let last_slot = std::cmp::min(state.slot.as_u64(), last_slot); + + for slot in first_slot..last_slot + 1 { + let slot = Slot::from(slot); + + let committees = state + .get_crosslink_committees_at_slot(slot, spec) + .unwrap() + .clone(); + + for (committee, shard) in committees { + state + .latest_attestations + .push(committee_to_pending_attestation( + state, &committee, shard, slot, spec, + )) + } + } + } +} + +fn committee_to_pending_attestation( + state: &BeaconState, + committee: &[usize], + shard: u64, + slot: Slot, + spec: &ChainSpec, +) -> PendingAttestation { + let current_epoch = state.current_epoch(spec); + let previous_epoch = state.previous_epoch(spec); + + let mut aggregation_bitfield = Bitfield::new(); + let mut custody_bitfield = Bitfield::new(); + + for (i, _) in committee.iter().enumerate() { + aggregation_bitfield.set(i, true); + custody_bitfield.set(i, true); + } + + let is_previous_epoch = + state.slot.epoch(spec.slots_per_epoch) != slot.epoch(spec.slots_per_epoch); + + let justified_epoch = if is_previous_epoch { + state.previous_justified_epoch + } else { + state.justified_epoch + }; + + let epoch_boundary_root = if is_previous_epoch { + *state + .get_block_root(previous_epoch.start_slot(spec.slots_per_epoch), spec) + .unwrap() + } else { + *state + .get_block_root(current_epoch.start_slot(spec.slots_per_epoch), spec) + .unwrap() + }; + + let justified_block_root = *state + .get_block_root(justified_epoch.start_slot(spec.slots_per_epoch), spec) + .unwrap(); + + PendingAttestation { + aggregation_bitfield, + data: AttestationData { + slot, + shard, + beacon_block_root: *state.get_block_root(slot, spec).unwrap(), + epoch_boundary_root, + crosslink_data_root: Hash256::zero(), + latest_crosslink: Crosslink { + epoch: slot.epoch(spec.slots_per_epoch), + crosslink_data_root: Hash256::zero(), + }, + justified_epoch, + justified_block_root, + }, + custody_bitfield, + inclusion_slot: slot + spec.min_attestation_inclusion_delay, + } +} diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index bd8aca3c4f..99275bd10c 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -1,11 +1,12 @@ use attester_sets::AttesterSets; use errors::EpochProcessingError as Error; +use fnv::FnvHashSet; use inclusion_distance::{inclusion_distance, inclusion_slot}; use integer_sqrt::IntegerSquareRoot; use log::debug; use rayon::prelude::*; use ssz::TreeHash; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::iter::FromIterator; use types::{validator_registry::get_active_validator_indices, *}; use winning_root::{winning_root, WinningRoot}; @@ -17,9 +18,7 @@ pub mod tests; pub mod winning_root; pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { - let current_epoch = state.current_epoch(spec); let previous_epoch = state.previous_epoch(spec); - let next_epoch = state.next_epoch(spec); debug!( "Starting per-epoch processing on epoch {}...", @@ -31,14 +30,12 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result state.build_epoch_cache(RelativeEpoch::Current, spec)?; state.build_epoch_cache(RelativeEpoch::Next, spec)?; - let attesters = AttesterSets::new(&state, spec)?; + let attesters = calculate_attester_sets(&state, spec)?; - let active_validator_indices = get_active_validator_indices( - &state.validator_registry, - state.slot.epoch(spec.slots_per_epoch), - ); + let active_validator_indices = calculate_active_validator_indices(&state, spec); let current_total_balance = state.get_total_balance(&active_validator_indices[..], spec); + let previous_total_balance = state.get_total_balance( &get_active_validator_indices(&state.validator_registry, previous_epoch)[..], spec, @@ -59,11 +56,9 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result let winning_root_for_shards = process_crosslinks(state, spec)?; // Rewards and Penalities - let active_validator_indices_hashset: HashSet = - HashSet::from_iter(active_validator_indices.iter().cloned()); process_rewards_and_penalities( state, - active_validator_indices_hashset, + &active_validator_indices, &attesters, previous_total_balance, &winning_root_for_shards, @@ -77,27 +72,9 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result process_validator_registry(state, spec)?; // Final updates - let active_tree_root = get_active_validator_indices( - &state.validator_registry, - next_epoch + Epoch::from(spec.activation_exit_delay), - ) - .hash_tree_root(); - state.latest_active_index_roots[(next_epoch.as_usize() - + spec.activation_exit_delay as usize) - % spec.latest_active_index_roots_length] = Hash256::from_slice(&active_tree_root[..]); - - state.latest_slashed_balances[next_epoch.as_usize() % spec.latest_slashed_exit_length] = - state.latest_slashed_balances[current_epoch.as_usize() % spec.latest_slashed_exit_length]; - state.latest_randao_mixes[next_epoch.as_usize() % spec.latest_randao_mixes_length] = state - .get_randao_mix(current_epoch, spec) - .and_then(|x| Some(*x)) - .ok_or_else(|| Error::NoRandaoSeed)?; - state.latest_attestations = state - .latest_attestations - .iter() - .filter(|a| a.data.slot.epoch(spec.slots_per_epoch) >= current_epoch) - .cloned() - .collect(); + update_active_tree_index_roots(state, spec)?; + update_latest_slashed_balances(state, spec); + clean_attestations(state, spec); // Rotate the epoch caches to suit the epoch transition. state.advance_caches(); @@ -107,8 +84,22 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result Ok(()) } +pub fn calculate_active_validator_indices(state: &BeaconState, spec: &ChainSpec) -> Vec { + get_active_validator_indices( + &state.validator_registry, + state.slot.epoch(spec.slots_per_epoch), + ) +} + +pub fn calculate_attester_sets( + state: &BeaconState, + spec: &ChainSpec, +) -> Result { + AttesterSets::new(&state, spec) +} + /// Spec v0.4.0 -fn process_eth1_data(state: &mut BeaconState, spec: &ChainSpec) { +pub fn process_eth1_data(state: &mut BeaconState, spec: &ChainSpec) { let next_epoch = state.next_epoch(spec); let voting_period = spec.epochs_per_eth1_voting_period; @@ -123,7 +114,7 @@ fn process_eth1_data(state: &mut BeaconState, spec: &ChainSpec) { } /// Spec v0.4.0 -fn process_justification( +pub fn process_justification( state: &mut BeaconState, current_total_balance: u64, previous_total_balance: u64, @@ -201,7 +192,7 @@ fn process_justification( pub type WinningRootHashSet = HashMap; -fn process_crosslinks( +pub fn process_crosslinks( state: &mut BeaconState, spec: &ChainSpec, ) -> Result { @@ -260,9 +251,9 @@ fn process_crosslinks( } /// Spec v0.4.0 -fn process_rewards_and_penalities( +pub fn process_rewards_and_penalities( state: &mut BeaconState, - active_validator_indices: HashSet, + active_validator_indices: &[usize], attesters: &AttesterSets, previous_total_balance: u64, winning_root_for_shards: &WinningRootHashSet, @@ -270,6 +261,9 @@ fn process_rewards_and_penalities( ) -> Result<(), Error> { let next_epoch = state.next_epoch(spec); + let active_validator_indices: FnvHashSet = + FnvHashSet::from_iter(active_validator_indices.iter().cloned()); + let previous_epoch_attestations: Vec<&PendingAttestation> = state .latest_attestations .par_iter() @@ -281,95 +275,126 @@ fn process_rewards_and_penalities( if base_reward_quotient == 0 { return Err(Error::BaseRewardQuotientIsZero); } + if previous_total_balance == 0 { + return Err(Error::PreviousTotalBalanceIsZero); + } // Justification and finalization let epochs_since_finality = next_epoch - state.finalized_epoch; if epochs_since_finality <= 4 { - for index in 0..state.validator_balances.len() { - let base_reward = state.base_reward(index, base_reward_quotient, spec); + state.validator_balances = state + .validator_balances + .par_iter() + .enumerate() + .map(|(index, &balance)| { + let mut balance = balance; + let base_reward = state.base_reward(index, base_reward_quotient, spec); - // Expected FFG source - if attesters.previous_epoch.indices.contains(&index) { - safe_add_assign!( - state.validator_balances[index], - base_reward * attesters.previous_epoch.balance / previous_total_balance - ); - } else if active_validator_indices.contains(&index) { - safe_sub_assign!(state.validator_balances[index], base_reward); - } - - // Expected FFG target - if attesters.previous_epoch_boundary.indices.contains(&index) { - safe_add_assign!( - state.validator_balances[index], - base_reward * attesters.previous_epoch_boundary.balance - / previous_total_balance - ); - } else if active_validator_indices.contains(&index) { - safe_sub_assign!(state.validator_balances[index], base_reward); - } - - // Expected beacon chain head - if attesters.previous_epoch_head.indices.contains(&index) { - safe_add_assign!( - state.validator_balances[index], - base_reward * attesters.previous_epoch_head.balance / previous_total_balance - ); - } else if active_validator_indices.contains(&index) { - safe_sub_assign!(state.validator_balances[index], base_reward); - } - } - - // Inclusion distance - for &index in &attesters.previous_epoch.indices { - let base_reward = state.base_reward(index, base_reward_quotient, spec); - let inclusion_distance = - inclusion_distance(state, &previous_epoch_attestations, index, spec)?; - - safe_add_assign!( - state.validator_balances[index], - base_reward * spec.min_attestation_inclusion_delay / inclusion_distance - ) - } - } else { - for index in 0..state.validator_balances.len() { - let inactivity_penalty = - state.inactivity_penalty(index, epochs_since_finality, base_reward_quotient, spec); - - if active_validator_indices.contains(&index) { - if !attesters.previous_epoch.indices.contains(&index) { - safe_sub_assign!(state.validator_balances[index], inactivity_penalty); - } - if !attesters.previous_epoch_boundary.indices.contains(&index) { - safe_sub_assign!(state.validator_balances[index], inactivity_penalty); - } - if !attesters.previous_epoch_head.indices.contains(&index) { - safe_sub_assign!(state.validator_balances[index], inactivity_penalty); - } - - if state.validator_registry[index].slashed { - let base_reward = state.base_reward(index, base_reward_quotient, spec); - safe_sub_assign!( - state.validator_balances[index], - 2 * inactivity_penalty + base_reward + // Expected FFG source + if attesters.previous_epoch.indices.contains(&index) { + safe_add_assign!( + balance, + base_reward * attesters.previous_epoch.balance / previous_total_balance ); + } else if active_validator_indices.contains(&index) { + safe_sub_assign!(balance, base_reward); } - } - } - for &index in &attesters.previous_epoch.indices { - let base_reward = state.base_reward(index, base_reward_quotient, spec); - let inclusion_distance = - inclusion_distance(state, &previous_epoch_attestations, index, spec)?; + // Expected FFG target + if attesters.previous_epoch_boundary.indices.contains(&index) { + safe_add_assign!( + balance, + base_reward * attesters.previous_epoch_boundary.balance + / previous_total_balance + ); + } else if active_validator_indices.contains(&index) { + safe_sub_assign!(balance, base_reward); + } - safe_sub_assign!( - state.validator_balances[index], - base_reward - - base_reward * spec.min_attestation_inclusion_delay / inclusion_distance - ); - } + // Expected beacon chain head + if attesters.previous_epoch_head.indices.contains(&index) { + safe_add_assign!( + balance, + base_reward * attesters.previous_epoch_head.balance + / previous_total_balance + ); + } else if active_validator_indices.contains(&index) { + safe_sub_assign!(balance, base_reward); + }; + + if attesters.previous_epoch.indices.contains(&index) { + let base_reward = state.base_reward(index, base_reward_quotient, spec); + let inclusion_distance = + inclusion_distance(state, &previous_epoch_attestations, index, spec); + + if let Ok(inclusion_distance) = inclusion_distance { + if inclusion_distance > 0 { + safe_add_assign!( + balance, + base_reward * spec.min_attestation_inclusion_delay + / inclusion_distance + ) + } + } + } + + balance + }) + .collect(); + } else { + state.validator_balances = state + .validator_balances + .par_iter() + .enumerate() + .map(|(index, &balance)| { + let mut balance = balance; + + let inactivity_penalty = state.inactivity_penalty( + index, + epochs_since_finality, + base_reward_quotient, + spec, + ); + + if active_validator_indices.contains(&index) { + if !attesters.previous_epoch.indices.contains(&index) { + safe_sub_assign!(balance, inactivity_penalty); + } + if !attesters.previous_epoch_boundary.indices.contains(&index) { + safe_sub_assign!(balance, inactivity_penalty); + } + if !attesters.previous_epoch_head.indices.contains(&index) { + safe_sub_assign!(balance, inactivity_penalty); + } + + if state.validator_registry[index].slashed { + let base_reward = state.base_reward(index, base_reward_quotient, spec); + safe_sub_assign!(balance, 2 * inactivity_penalty + base_reward); + } + } + + if attesters.previous_epoch.indices.contains(&index) { + let base_reward = state.base_reward(index, base_reward_quotient, spec); + let inclusion_distance = + inclusion_distance(state, &previous_epoch_attestations, index, spec); + + if let Ok(inclusion_distance) = inclusion_distance { + if inclusion_distance > 0 { + safe_sub_assign!( + balance, + base_reward + - base_reward * spec.min_attestation_inclusion_delay + / inclusion_distance + ); + } + } + } + + balance + }) + .collect(); } // Attestation inclusion @@ -413,8 +438,8 @@ fn process_rewards_and_penalities( if let Some(winning_root) = winning_root_for_shards.get(&shard) { // Hash set de-dedups and (hopefully) offers a speed improvement from faster // lookups. - let attesting_validator_indices: HashSet = - HashSet::from_iter(winning_root.attesting_validator_indices.iter().cloned()); + let attesting_validator_indices: FnvHashSet = + FnvHashSet::from_iter(winning_root.attesting_validator_indices.iter().cloned()); for &index in &crosslink_committee { let base_reward = state.base_reward(index, base_reward_quotient, spec); @@ -444,7 +469,7 @@ fn process_rewards_and_penalities( } // Spec v0.4.0 -fn process_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { +pub fn process_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { let current_epoch = state.current_epoch(spec); let next_epoch = state.next_epoch(spec); @@ -489,3 +514,44 @@ fn process_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> Resu Ok(()) } + +// Spec v0.4.0 +pub fn update_active_tree_index_roots( + state: &mut BeaconState, + spec: &ChainSpec, +) -> Result<(), Error> { + let next_epoch = state.next_epoch(spec); + + let active_tree_root = get_active_validator_indices( + &state.validator_registry, + next_epoch + Epoch::from(spec.activation_exit_delay), + ) + .hash_tree_root(); + + state.latest_active_index_roots[(next_epoch.as_usize() + + spec.activation_exit_delay as usize) + % spec.latest_active_index_roots_length] = Hash256::from_slice(&active_tree_root[..]); + + Ok(()) +} + +// Spec v0.4.0 +pub fn update_latest_slashed_balances(state: &mut BeaconState, spec: &ChainSpec) { + let current_epoch = state.current_epoch(spec); + let next_epoch = state.next_epoch(spec); + + state.latest_slashed_balances[next_epoch.as_usize() % spec.latest_slashed_exit_length] = + state.latest_slashed_balances[current_epoch.as_usize() % spec.latest_slashed_exit_length]; +} + +// Spec v0.4.0 +pub fn clean_attestations(state: &mut BeaconState, spec: &ChainSpec) { + let current_epoch = state.current_epoch(spec); + + state.latest_attestations = state + .latest_attestations + .iter() + .filter(|a| a.data.slot.epoch(spec.slots_per_epoch) >= current_epoch) + .cloned() + .collect(); +} diff --git a/eth2/state_processing/src/per_epoch_processing/attester_sets.rs b/eth2/state_processing/src/per_epoch_processing/attester_sets.rs index 2b674e1bcd..1252d80572 100644 --- a/eth2/state_processing/src/per_epoch_processing/attester_sets.rs +++ b/eth2/state_processing/src/per_epoch_processing/attester_sets.rs @@ -1,9 +1,9 @@ -use std::collections::HashSet; +use fnv::FnvHashSet; use types::*; #[derive(Default)] pub struct Attesters { - pub indices: HashSet, + pub indices: FnvHashSet, pub balance: u64, } diff --git a/eth2/state_processing/src/per_epoch_processing/errors.rs b/eth2/state_processing/src/per_epoch_processing/errors.rs index 51e9b253cd..7d8a5800d8 100644 --- a/eth2/state_processing/src/per_epoch_processing/errors.rs +++ b/eth2/state_processing/src/per_epoch_processing/errors.rs @@ -6,6 +6,8 @@ pub enum EpochProcessingError { NoBlockRoots, BaseRewardQuotientIsZero, NoRandaoSeed, + PreviousTotalBalanceIsZero, + InclusionDistanceZero, BeaconStateError(BeaconStateError), InclusionError(InclusionError), } diff --git a/eth2/state_processing/src/per_epoch_processing/tests.rs b/eth2/state_processing/src/per_epoch_processing/tests.rs index 627df858b7..8ff6879043 100644 --- a/eth2/state_processing/src/per_epoch_processing/tests.rs +++ b/eth2/state_processing/src/per_epoch_processing/tests.rs @@ -1,21 +1,18 @@ #![cfg(test)] use crate::per_epoch_processing; +use benching_utils::BeaconStateBencher; use env_logger::{Builder, Env}; -use types::beacon_state::BeaconStateBuilder; use types::*; #[test] fn runs_without_error() { Builder::from_env(Env::default().default_filter_or("error")).init(); - let mut builder = BeaconStateBuilder::new(8); - builder.spec = ChainSpec::few_validators(); + let spec = ChainSpec::few_validators(); - builder.build().unwrap(); - builder.teleport_to_end_of_epoch(builder.spec.genesis_epoch + 4); + let mut builder = BeaconStateBencher::new(8, &spec); + builder.teleport_to_end_of_epoch(spec.genesis_epoch + 4, &spec); + let mut state = builder.build(); - let mut state = builder.cloned_state(); - - let spec = &builder.spec; - per_epoch_processing(&mut state, spec).unwrap(); + per_epoch_processing(&mut state, &spec).unwrap(); } diff --git a/eth2/types/src/beacon_state/helpers.rs b/eth2/types/src/beacon_state/helpers.rs index c93b16f76e..adae7bab49 100644 --- a/eth2/types/src/beacon_state/helpers.rs +++ b/eth2/types/src/beacon_state/helpers.rs @@ -11,7 +11,7 @@ pub fn verify_bitfield_length(bitfield: &Bitfield, committee_size: usize) -> boo } for i in committee_size..(bitfield.num_bytes() * 8) { - if bitfield.get(i).expect("Impossible due to previous check.") { + if bitfield.get(i).unwrap_or(false) { return false; } } From a77d1885a1ab2ab72b63f725754f5594ed0d33ff Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 9 Mar 2019 10:39:05 +1100 Subject: [PATCH 025/144] Refactor BeaconStateBuilder Made it a production-only struct. All the testing stuff can be done with BeaconStateBencher --- .../test_harness/src/beacon_chain_harness.rs | 56 ++++++++++++++----- .../test_harness/src/test_case.rs | 2 +- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index 1ebe4dc745..d2274ac693 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -1,6 +1,7 @@ use super::ValidatorHarness; use beacon_chain::{BeaconChain, BlockProcessingOutcome}; pub use beacon_chain::{BeaconChainError, CheckPoint}; +use bls::get_withdrawal_credentials; use db::{ stores::{BeaconBlockStore, BeaconStateStore}, MemoryDB, @@ -43,7 +44,12 @@ impl BeaconChainHarness { /// /// - A keypair, `BlockProducer` and `Attester` for each validator. /// - A new BeaconChain struct where the given validators are in the genesis. - pub fn new(spec: ChainSpec, validator_count: usize, validators_dir: Option<&Path>) -> Self { + pub fn new( + spec: ChainSpec, + validator_count: usize, + validators_dir: Option<&Path>, + skip_deposit_verification: bool, + ) -> Self { let db = Arc::new(MemoryDB::open()); let block_store = Arc::new(BeaconBlockStore::new(db.clone())); let state_store = Arc::new(BeaconStateStore::new(db.clone())); @@ -57,22 +63,47 @@ impl BeaconChainHarness { let mut state_builder = BeaconStateBuilder::new(genesis_time, latest_eth1_data, &spec); - // If a `validators_dir` is specified, load the keypairs and validators from YAML files. + // If a `validators_dir` is specified, load the keypairs a YAML file. // - // Otherwise, build all the keypairs and initial validator deposits manually. - // - // It is _much_ faster to load from YAML, however it does skip all the initial processing - // and verification of `Deposits`, so it is a slightly less comprehensive test. + // Otherwise, generate them deterministically where the first validator has a secret key of + // `1`, etc. let keypairs = if let Some(path) = validators_dir { debug!("Loading validator keypairs from file..."); let keypairs_file = File::open(path.join("keypairs.yaml")).unwrap(); let mut keypairs: Vec = serde_yaml::from_reader(&keypairs_file).unwrap(); keypairs.truncate(validator_count); + keypairs + } else { + debug!("Generating validator keypairs..."); + generate_deterministic_keypairs(validator_count) + }; - debug!("Loading validators from file..."); - let validators_file = File::open(path.join("validators.yaml")).unwrap(); - let mut validators: Vec = serde_yaml::from_reader(&validators_file).unwrap(); - validators.truncate(validator_count); + // Skipping deposit verification means directly generating `Validator` records, instead + // of generating `Deposit` objects, verifying them and converting them into `Validator` + // records. + // + // It is much faster to skip deposit verification, however it does not test the initial + // validator induction part of beacon chain genesis. + if skip_deposit_verification { + let validators = keypairs + .iter() + .map(|keypair| { + let withdrawal_credentials = Hash256::from_slice(&get_withdrawal_credentials( + &keypair.pk, + spec.bls_withdrawal_prefix_byte, + )); + + Validator { + pubkey: keypair.pk.clone(), + withdrawal_credentials, + activation_epoch: spec.far_future_epoch, + exit_epoch: spec.far_future_epoch, + withdrawable_epoch: spec.far_future_epoch, + initiated_exit: false, + slashed: false, + } + }) + .collect(); let balances = vec![32_000_000_000; validator_count]; @@ -82,15 +113,10 @@ impl BeaconChainHarness { validator_count as u64, &spec, ); - - keypairs } else { - debug!("Generating validator keypairs..."); - let keypairs = generate_deterministic_keypairs(validator_count); debug!("Generating initial validator deposits..."); let deposits = generate_deposits_from_keypairs(&keypairs, genesis_time, &spec); state_builder.process_initial_deposits(&deposits, &spec); - keypairs }; let genesis_state = state_builder.build(&spec).unwrap(); diff --git a/beacon_node/beacon_chain/test_harness/src/test_case.rs b/beacon_node/beacon_chain/test_harness/src/test_case.rs index e7b2defe66..7bc7161a8b 100644 --- a/beacon_node/beacon_chain/test_harness/src/test_case.rs +++ b/beacon_node/beacon_chain/test_harness/src/test_case.rs @@ -81,7 +81,7 @@ impl TestCase { validator_count ); - let mut harness = BeaconChainHarness::new(spec, validator_count, validators_dir); + let mut harness = BeaconChainHarness::new(spec, validator_count, validators_dir, true); info!("Starting simulation across {} slots...", slots); From ca5d9658ce66ab9e6bd32c4eec8e87eb412cf900 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 9 Mar 2019 10:45:28 +1100 Subject: [PATCH 026/144] Move epoch processing benches into separate file --- eth2/state_processing/benches/benches.rs | 296 +----------------- .../benches/epoch_processing_benches.rs | 294 +++++++++++++++++ 2 files changed, 298 insertions(+), 292 deletions(-) create mode 100644 eth2/state_processing/benches/epoch_processing_benches.rs diff --git a/eth2/state_processing/benches/benches.rs b/eth2/state_processing/benches/benches.rs index 5c064a08ff..e42e91fb44 100644 --- a/eth2/state_processing/benches/benches.rs +++ b/eth2/state_processing/benches/benches.rs @@ -1,296 +1,8 @@ -use criterion::Criterion; -use criterion::{black_box, criterion_group, criterion_main, Benchmark}; -use state_processing::{ - per_epoch_processing, - per_epoch_processing::{ - calculate_active_validator_indices, calculate_attester_sets, clean_attestations, - process_crosslinks, process_eth1_data, process_justification, - process_rewards_and_penalities, process_validator_registry, update_active_tree_index_roots, - update_latest_slashed_balances, - }, -}; -// use env_logger::{Builder, Env}; -use benching_utils::BeaconStateBencher; -use types::{validator_registry::get_active_validator_indices, *}; +use criterion::{criterion_group, criterion_main}; -fn epoch_processing(c: &mut Criterion) { - // Builder::from_env(Env::default().default_filter_or("debug")).init(); - // - let spec = ChainSpec::foundation(); +mod epoch_processing_benches; - let validator_count = 16_384; +use epoch_processing_benches::epoch_processing_16k_validators; - let mut builder = BeaconStateBencher::new(validator_count, &spec); - builder.teleport_to_end_of_epoch(spec.genesis_epoch + 4, &spec); - builder.insert_attestations(&spec); - let mut state = builder.build(); - - // Build all the caches so the following state does _not_ include the cache-building time. - state - .build_epoch_cache(RelativeEpoch::Previous, &spec) - .unwrap(); - state - .build_epoch_cache(RelativeEpoch::Current, &spec) - .unwrap(); - state.build_epoch_cache(RelativeEpoch::Next, &spec).unwrap(); - - // Assert that the state has the maximum possible attestations. - let committees_per_epoch = spec.get_epoch_committee_count(validator_count); - let committees_per_slot = committees_per_epoch / spec.slots_per_epoch; - let previous_epoch_attestations = committees_per_epoch; - let current_epoch_attestations = - committees_per_slot * (spec.slots_per_epoch - spec.min_attestation_inclusion_delay); - assert_eq!( - state.latest_attestations.len() as u64, - previous_epoch_attestations + current_epoch_attestations - ); - - // Assert that each attestation in the state has full participation. - let committee_size = validator_count / committees_per_epoch as usize; - for a in &state.latest_attestations { - assert_eq!(a.aggregation_bitfield.num_set_bits(), committee_size); - } - - // Assert that we will run the first arm of process_rewards_and_penalities - let epochs_since_finality = state.next_epoch(&spec) - state.finalized_epoch; - assert!(epochs_since_finality <= 4); - - bench_epoch_processing(c, &state, &spec, "16k_validators"); -} - -fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSpec, desc: &str) { - let state_clone = state.clone(); - let spec_clone = spec.clone(); - c.bench( - &format!("epoch_process_with_caches_{}", desc), - Benchmark::new("full run", move |b| { - b.iter_with_setup( - || state_clone.clone(), - |mut state| black_box(per_epoch_processing(&mut state, &spec_clone).unwrap()), - ) - }) - .sample_size(10), - ); - - let state_clone = state.clone(); - let spec_clone = spec.clone(); - c.bench( - &format!("epoch_process_with_caches_{}", desc), - Benchmark::new("calculate_active_validator_indices", move |b| { - b.iter_with_setup( - || state_clone.clone(), - |mut state| black_box(calculate_active_validator_indices(&mut state, &spec_clone)), - ) - }) - .sample_size(10), - ); - - let state_clone = state.clone(); - let spec_clone = spec.clone(); - let active_validator_indices = calculate_active_validator_indices(&state, &spec); - c.bench( - &format!("epoch_process_with_caches_{}", desc), - Benchmark::new("calculate_current_total_balance", move |b| { - b.iter_with_setup( - || state_clone.clone(), - |state| { - black_box(state.get_total_balance(&active_validator_indices[..], &spec_clone)) - }, - ) - }) - .sample_size(10), - ); - - let state_clone = state.clone(); - let spec_clone = spec.clone(); - c.bench( - &format!("epoch_process_with_caches_{}", desc), - Benchmark::new("calculate_previous_total_balance", move |b| { - b.iter_with_setup( - || state_clone.clone(), - |state| { - black_box(state.get_total_balance( - &get_active_validator_indices( - &state.validator_registry, - state.previous_epoch(&spec_clone), - )[..], - &spec_clone, - )) - }, - ) - }) - .sample_size(10), - ); - - let state_clone = state.clone(); - let spec_clone = spec.clone(); - c.bench( - &format!("epoch_process_with_caches_{}", desc), - Benchmark::new("process_eth1_data", move |b| { - b.iter_with_setup( - || state_clone.clone(), - |mut state| black_box(process_eth1_data(&mut state, &spec_clone)), - ) - }) - .sample_size(10), - ); - - let state_clone = state.clone(); - let spec_clone = spec.clone(); - c.bench( - &format!("epoch_process_with_caches_{}", desc), - Benchmark::new("calculate_attester_sets", move |b| { - b.iter_with_setup( - || state_clone.clone(), - |mut state| black_box(calculate_attester_sets(&mut state, &spec_clone).unwrap()), - ) - }) - .sample_size(10), - ); - - let state_clone = state.clone(); - let spec_clone = spec.clone(); - let previous_epoch = state.previous_epoch(&spec); - let attesters = calculate_attester_sets(&state, &spec).unwrap(); - let active_validator_indices = calculate_active_validator_indices(&state, &spec); - let current_total_balance = state.get_total_balance(&active_validator_indices[..], &spec); - let previous_total_balance = state.get_total_balance( - &get_active_validator_indices(&state.validator_registry, previous_epoch)[..], - &spec, - ); - c.bench( - &format!("epoch_process_with_caches_{}", desc), - Benchmark::new("process_justification", move |b| { - b.iter_with_setup( - || state_clone.clone(), - |mut state| { - black_box(process_justification( - &mut state, - current_total_balance, - previous_total_balance, - attesters.previous_epoch_boundary.balance, - attesters.current_epoch_boundary.balance, - &spec_clone, - )) - }, - ) - }) - .sample_size(10), - ); - - let state_clone = state.clone(); - let spec_clone = spec.clone(); - c.bench( - &format!("epoch_process_with_caches_{}", desc), - Benchmark::new("process_crosslinks", move |b| { - b.iter_with_setup( - || state_clone.clone(), - |mut state| black_box(process_crosslinks(&mut state, &spec_clone).unwrap()), - ) - }) - .sample_size(10), - ); - - let mut state_clone = state.clone(); - let spec_clone = spec.clone(); - let previous_epoch = state.previous_epoch(&spec); - let attesters = calculate_attester_sets(&state, &spec).unwrap(); - let active_validator_indices = calculate_active_validator_indices(&state, &spec); - let previous_total_balance = state.get_total_balance( - &get_active_validator_indices(&state.validator_registry, previous_epoch)[..], - &spec, - ); - let winning_root_for_shards = process_crosslinks(&mut state_clone, &spec).unwrap(); - c.bench( - &format!("epoch_process_with_caches_{}", desc), - Benchmark::new("process_rewards_and_penalties", move |b| { - b.iter_with_setup( - || state_clone.clone(), - |mut state| { - black_box( - process_rewards_and_penalities( - &mut state, - &active_validator_indices, - &attesters, - previous_total_balance, - &winning_root_for_shards, - &spec_clone, - ) - .unwrap(), - ) - }, - ) - }) - .sample_size(10), - ); - - let state_clone = state.clone(); - let spec_clone = spec.clone(); - c.bench( - &format!("epoch_process_with_caches_{}", desc), - Benchmark::new("process_ejections", move |b| { - b.iter_with_setup( - || state_clone.clone(), - |mut state| black_box(state.process_ejections(&spec_clone)), - ) - }) - .sample_size(10), - ); - - let state_clone = state.clone(); - let spec_clone = spec.clone(); - c.bench( - &format!("epoch_process_with_caches_{}", desc), - Benchmark::new("process_validator_registry", move |b| { - b.iter_with_setup( - || state_clone.clone(), - |mut state| black_box(process_validator_registry(&mut state, &spec_clone)), - ) - }) - .sample_size(10), - ); - - let state_clone = state.clone(); - let spec_clone = spec.clone(); - c.bench( - &format!("epoch_process_with_caches_{}", desc), - Benchmark::new("update_active_tree_index_roots", move |b| { - b.iter_with_setup( - || state_clone.clone(), - |mut state| { - black_box(update_active_tree_index_roots(&mut state, &spec_clone).unwrap()) - }, - ) - }) - .sample_size(10), - ); - - let state_clone = state.clone(); - let spec_clone = spec.clone(); - c.bench( - &format!("epoch_process_with_caches_{}", desc), - Benchmark::new("update_latest_slashed_balances", move |b| { - b.iter_with_setup( - || state_clone.clone(), - |mut state| black_box(update_latest_slashed_balances(&mut state, &spec_clone)), - ) - }) - .sample_size(10), - ); - - let state_clone = state.clone(); - let spec_clone = spec.clone(); - c.bench( - &format!("epoch_process_with_caches_{}", desc), - Benchmark::new("clean_attestations", move |b| { - b.iter_with_setup( - || state_clone.clone(), - |mut state| black_box(clean_attestations(&mut state, &spec_clone)), - ) - }) - .sample_size(10), - ); -} - -criterion_group!(benches, epoch_processing,); +criterion_group!(benches, epoch_processing_16k_validators); criterion_main!(benches); diff --git a/eth2/state_processing/benches/epoch_processing_benches.rs b/eth2/state_processing/benches/epoch_processing_benches.rs new file mode 100644 index 0000000000..9411804559 --- /dev/null +++ b/eth2/state_processing/benches/epoch_processing_benches.rs @@ -0,0 +1,294 @@ +use benching_utils::BeaconStateBencher; +use criterion::Criterion; +use criterion::{black_box, Benchmark}; +use state_processing::{ + per_epoch_processing, + per_epoch_processing::{ + calculate_active_validator_indices, calculate_attester_sets, clean_attestations, + process_crosslinks, process_eth1_data, process_justification, + process_rewards_and_penalities, process_validator_registry, update_active_tree_index_roots, + update_latest_slashed_balances, + }, +}; +use types::{validator_registry::get_active_validator_indices, *}; + +/// Run the benchmarking suite on a foundation spec with 16,384 validators. +pub fn epoch_processing_16k_validators(c: &mut Criterion) { + let spec = ChainSpec::foundation(); + + let validator_count = 16_384; + + let mut builder = BeaconStateBencher::new(validator_count, &spec); + builder.teleport_to_end_of_epoch(spec.genesis_epoch + 4, &spec); + builder.insert_attestations(&spec); + let mut state = builder.build(); + + // Build all the caches so the following state does _not_ include the cache-building time. + state + .build_epoch_cache(RelativeEpoch::Previous, &spec) + .unwrap(); + state + .build_epoch_cache(RelativeEpoch::Current, &spec) + .unwrap(); + state.build_epoch_cache(RelativeEpoch::Next, &spec).unwrap(); + + // Assert that the state has the maximum possible attestations. + let committees_per_epoch = spec.get_epoch_committee_count(validator_count); + let committees_per_slot = committees_per_epoch / spec.slots_per_epoch; + let previous_epoch_attestations = committees_per_epoch; + let current_epoch_attestations = + committees_per_slot * (spec.slots_per_epoch - spec.min_attestation_inclusion_delay); + assert_eq!( + state.latest_attestations.len() as u64, + previous_epoch_attestations + current_epoch_attestations + ); + + // Assert that each attestation in the state has full participation. + let committee_size = validator_count / committees_per_epoch as usize; + for a in &state.latest_attestations { + assert_eq!(a.aggregation_bitfield.num_set_bits(), committee_size); + } + + // Assert that we will run the first arm of process_rewards_and_penalities + let epochs_since_finality = state.next_epoch(&spec) - state.finalized_epoch; + assert!(epochs_since_finality <= 4); + + bench_epoch_processing(c, &state, &spec, "16k_validators"); +} + +/// Run the detailed benchmarking suite on the given `BeaconState`. +/// +/// `desc` will be added to the title of each bench. +fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSpec, desc: &str) { + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("full run", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| black_box(per_epoch_processing(&mut state, &spec_clone).unwrap()), + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("calculate_active_validator_indices", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| black_box(calculate_active_validator_indices(&mut state, &spec_clone)), + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + let active_validator_indices = calculate_active_validator_indices(&state, &spec); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("calculate_current_total_balance", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |state| { + black_box(state.get_total_balance(&active_validator_indices[..], &spec_clone)) + }, + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("calculate_previous_total_balance", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |state| { + black_box(state.get_total_balance( + &get_active_validator_indices( + &state.validator_registry, + state.previous_epoch(&spec_clone), + )[..], + &spec_clone, + )) + }, + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("process_eth1_data", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| black_box(process_eth1_data(&mut state, &spec_clone)), + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("calculate_attester_sets", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| black_box(calculate_attester_sets(&mut state, &spec_clone).unwrap()), + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + let previous_epoch = state.previous_epoch(&spec); + let attesters = calculate_attester_sets(&state, &spec).unwrap(); + let active_validator_indices = calculate_active_validator_indices(&state, &spec); + let current_total_balance = state.get_total_balance(&active_validator_indices[..], &spec); + let previous_total_balance = state.get_total_balance( + &get_active_validator_indices(&state.validator_registry, previous_epoch)[..], + &spec, + ); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("process_justification", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| { + black_box(process_justification( + &mut state, + current_total_balance, + previous_total_balance, + attesters.previous_epoch_boundary.balance, + attesters.current_epoch_boundary.balance, + &spec_clone, + )) + }, + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("process_crosslinks", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| black_box(process_crosslinks(&mut state, &spec_clone).unwrap()), + ) + }) + .sample_size(10), + ); + + let mut state_clone = state.clone(); + let spec_clone = spec.clone(); + let previous_epoch = state.previous_epoch(&spec); + let attesters = calculate_attester_sets(&state, &spec).unwrap(); + let active_validator_indices = calculate_active_validator_indices(&state, &spec); + let previous_total_balance = state.get_total_balance( + &get_active_validator_indices(&state.validator_registry, previous_epoch)[..], + &spec, + ); + let winning_root_for_shards = process_crosslinks(&mut state_clone, &spec).unwrap(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("process_rewards_and_penalties", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| { + black_box( + process_rewards_and_penalities( + &mut state, + &active_validator_indices, + &attesters, + previous_total_balance, + &winning_root_for_shards, + &spec_clone, + ) + .unwrap(), + ) + }, + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("process_ejections", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| black_box(state.process_ejections(&spec_clone)), + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("process_validator_registry", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| black_box(process_validator_registry(&mut state, &spec_clone)), + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("update_active_tree_index_roots", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| { + black_box(update_active_tree_index_roots(&mut state, &spec_clone).unwrap()) + }, + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("update_latest_slashed_balances", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| black_box(update_latest_slashed_balances(&mut state, &spec_clone)), + ) + }) + .sample_size(10), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("clean_attestations", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| black_box(clean_attestations(&mut state, &spec_clone)), + ) + }) + .sample_size(10), + ); +} From 7db2b5187689f7b6cd1d80994f2a62ba304dd1fe Mon Sep 17 00:00:00 2001 From: mjkeating Date: Fri, 8 Mar 2019 16:37:01 -0800 Subject: [PATCH 027/144] ran cargo fmt --- eth2/types/src/beacon_state.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index bbba5d70bc..c68aa53c37 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -1269,17 +1269,9 @@ impl TreeHash for BeaconState { result.append(&mut self.fork.hash_tree_root()); result.append(&mut self.validator_registry.hash_tree_root()); result.append(&mut self.validator_balances.hash_tree_root()); - result.append( - &mut self - .validator_registry_update_epoch - .hash_tree_root(), - ); + result.append(&mut self.validator_registry_update_epoch.hash_tree_root()); result.append(&mut self.latest_randao_mixes.hash_tree_root()); - result.append( - &mut self - .previous_shuffling_start_shard - .hash_tree_root(), - ); + result.append(&mut self.previous_shuffling_start_shard.hash_tree_root()); result.append(&mut self.current_shuffling_start_shard.hash_tree_root()); result.append(&mut self.previous_shuffling_epoch.hash_tree_root()); result.append(&mut self.current_shuffling_epoch.hash_tree_root()); From 73ebb4bc2eb4e4326fc9bc8adf8ce3749d094728 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 9 Mar 2019 14:11:49 +1100 Subject: [PATCH 028/144] Add incomplete per-block processing benchmarks Still needs to fill block with operations --- eth2/state_processing/benches/benches.rs | 9 +- .../benches/block_processing_benches.rs | 276 ++++++++++++++++++ .../benches/epoch_processing_benches.rs | 23 +- .../src/beacon_block_bencher.rs | 46 +++ .../src/beacon_state_bencher.rs | 213 ++++++++++++++ .../benching_utils/src/lib.rs | 198 +------------ .../src/per_block_processing.rs | 18 +- .../src/per_epoch_processing/tests.rs | 7 +- eth2/types/src/beacon_block.rs | 16 +- 9 files changed, 583 insertions(+), 223 deletions(-) create mode 100644 eth2/state_processing/benches/block_processing_benches.rs create mode 100644 eth2/state_processing/benching_utils/src/beacon_block_bencher.rs create mode 100644 eth2/state_processing/benching_utils/src/beacon_state_bencher.rs diff --git a/eth2/state_processing/benches/benches.rs b/eth2/state_processing/benches/benches.rs index e42e91fb44..52b939a696 100644 --- a/eth2/state_processing/benches/benches.rs +++ b/eth2/state_processing/benches/benches.rs @@ -1,8 +1,11 @@ use criterion::{criterion_group, criterion_main}; +mod block_processing_benches; mod epoch_processing_benches; -use epoch_processing_benches::epoch_processing_16k_validators; - -criterion_group!(benches, epoch_processing_16k_validators); +criterion_group!( + benches, + // epoch_processing_benches::epoch_processing_16k_validators, + block_processing_benches::block_processing_16k_validators, +); criterion_main!(benches); diff --git a/eth2/state_processing/benches/block_processing_benches.rs b/eth2/state_processing/benches/block_processing_benches.rs new file mode 100644 index 0000000000..b5fdaa5bdd --- /dev/null +++ b/eth2/state_processing/benches/block_processing_benches.rs @@ -0,0 +1,276 @@ +use benching_utils::{BeaconBlockBencher, BeaconStateBencher}; +use criterion::Criterion; +use criterion::{black_box, Benchmark}; +use state_processing::{ + per_block_processing, + per_block_processing::{ + process_attestations, process_attester_slashings, process_deposits, process_eth1_data, + process_exits, process_proposer_slashings, process_randao, process_transfers, + verify_block_signature, + }, +}; +use types::*; + +/// Run the benchmarking suite on a foundation spec with 16,384 validators. +pub fn block_processing_16k_validators(c: &mut Criterion) { + let spec = ChainSpec::foundation(); + + let validator_count = 16_384; + + let (state, keypairs) = build_state(validator_count, &spec); + let block = build_block(&state, &keypairs, &spec); + + bench_block_processing( + c, + &block, + &state, + &spec, + &format!("{}_validators", validator_count), + ); +} + +fn build_state(validator_count: usize, spec: &ChainSpec) -> (BeaconState, Vec) { + let mut builder = BeaconStateBencher::new(validator_count, &spec); + + // Set the state to be just before an epoch transition. + let target_slot = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch); + builder.teleport_to_slot(target_slot, &spec); + + // Builds all caches; benches will not contain shuffling/committee building times. + builder.build_caches(&spec).unwrap(); + + builder.build() +} + +fn build_block(state: &BeaconState, keypairs: &[Keypair], spec: &ChainSpec) -> BeaconBlock { + let mut builder = BeaconBlockBencher::new(spec); + + builder.set_slot(state.slot); + + let proposer_index = state.get_beacon_proposer_index(state.slot, spec).unwrap(); + let keypair = &keypairs[proposer_index]; + + builder.set_randao_reveal(&keypair.sk, &state.fork, spec); + + builder.build(&keypair.sk, &state.fork, spec) +} + +/// Run the detailed benchmarking suite on the given `BeaconState`. +/// +/// `desc` will be added to the title of each bench. +fn bench_block_processing( + c: &mut Criterion, + initial_block: &BeaconBlock, + initial_state: &BeaconState, + initial_spec: &ChainSpec, + desc: &str, +) { + let state = initial_state.clone(); + let block = initial_block.clone(); + let spec = initial_spec.clone(); + c.bench( + &format!("block_processing_{}", desc), + Benchmark::new("verify_block_signature", move |b| { + b.iter_with_setup( + || state.clone(), + |mut state| black_box(verify_block_signature(&mut state, &block, &spec).unwrap()), + ) + }) + .sample_size(10), + ); + + let state = initial_state.clone(); + let block = initial_block.clone(); + let spec = initial_spec.clone(); + c.bench( + &format!("block_processing_{}", desc), + Benchmark::new("process_randao", move |b| { + b.iter_with_setup( + || state.clone(), + |mut state| black_box(process_randao(&mut state, &block, &spec).unwrap()), + ) + }) + .sample_size(10), + ); + + let state = initial_state.clone(); + let block = initial_block.clone(); + c.bench( + &format!("block_processing_{}", desc), + Benchmark::new("process_eth1_data", move |b| { + b.iter_with_setup( + || state.clone(), + |mut state| black_box(process_eth1_data(&mut state, &block.eth1_data).unwrap()), + ) + }) + .sample_size(10), + ); + + let state = initial_state.clone(); + let block = initial_block.clone(); + let spec = initial_spec.clone(); + c.bench( + &format!("block_processing_{}", desc), + Benchmark::new("process_proposer_slashings", move |b| { + b.iter_with_setup( + || state.clone(), + |mut state| { + black_box( + process_proposer_slashings( + &mut state, + &block.body.proposer_slashings, + &spec, + ) + .unwrap(), + ) + }, + ) + }) + .sample_size(10), + ); + + let state = initial_state.clone(); + let block = initial_block.clone(); + let spec = initial_spec.clone(); + c.bench( + &format!("block_processing_{}", desc), + Benchmark::new("process_attester_slashings", move |b| { + b.iter_with_setup( + || state.clone(), + |mut state| { + black_box( + process_attester_slashings( + &mut state, + &block.body.attester_slashings, + &spec, + ) + .unwrap(), + ) + }, + ) + }) + .sample_size(10), + ); + + let state = initial_state.clone(); + let block = initial_block.clone(); + let spec = initial_spec.clone(); + c.bench( + &format!("block_processing_{}", desc), + Benchmark::new("process_attestations", move |b| { + b.iter_with_setup( + || state.clone(), + |mut state| { + black_box( + process_attestations(&mut state, &block.body.attestations, &spec).unwrap(), + ) + }, + ) + }) + .sample_size(10), + ); + + let state = initial_state.clone(); + let block = initial_block.clone(); + let spec = initial_spec.clone(); + c.bench( + &format!("block_processing_{}", desc), + Benchmark::new("process_deposits", move |b| { + b.iter_with_setup( + || state.clone(), + |mut state| { + black_box(process_deposits(&mut state, &block.body.deposits, &spec).unwrap()) + }, + ) + }) + .sample_size(10), + ); + + let state = initial_state.clone(); + let block = initial_block.clone(); + let spec = initial_spec.clone(); + c.bench( + &format!("block_processing_{}", desc), + Benchmark::new("process_exits", move |b| { + b.iter_with_setup( + || state.clone(), + |mut state| { + black_box( + process_exits(&mut state, &block.body.voluntary_exits, &spec).unwrap(), + ) + }, + ) + }) + .sample_size(10), + ); + + let state = initial_state.clone(); + let block = initial_block.clone(); + let spec = initial_spec.clone(); + c.bench( + &format!("block_processing_{}", desc), + Benchmark::new("process_transfers", move |b| { + b.iter_with_setup( + || state.clone(), + |mut state| { + black_box(process_transfers(&mut state, &block.body.transfers, &spec).unwrap()) + }, + ) + }) + .sample_size(10), + ); + + let state = initial_state.clone(); + let block = initial_block.clone(); + let spec = initial_spec.clone(); + c.bench( + &format!("block_processing_{}", desc), + Benchmark::new("per_block_processing", move |b| { + b.iter_with_setup( + || state.clone(), + |mut state| black_box(per_block_processing(&mut state, &block, &spec).unwrap()), + ) + }) + .sample_size(10), + ); + + let mut state = initial_state.clone(); + state.drop_cache(RelativeEpoch::Previous); + let spec = initial_spec.clone(); + c.bench( + &format!("block_processing_{}", desc), + Benchmark::new("build_previous_state_epoch_cache", move |b| { + b.iter_with_setup( + || state.clone(), + |mut state| { + black_box( + state + .build_epoch_cache(RelativeEpoch::Previous, &spec) + .unwrap(), + ) + }, + ) + }) + .sample_size(10), + ); + + let mut state = initial_state.clone(); + state.drop_cache(RelativeEpoch::Current); + let spec = initial_spec.clone(); + c.bench( + &format!("block_processing_{}", desc), + Benchmark::new("build_current_state_epoch_cache", move |b| { + b.iter_with_setup( + || state.clone(), + |mut state| { + black_box( + state + .build_epoch_cache(RelativeEpoch::Current, &spec) + .unwrap(), + ) + }, + ) + }) + .sample_size(10), + ); +} diff --git a/eth2/state_processing/benches/epoch_processing_benches.rs b/eth2/state_processing/benches/epoch_processing_benches.rs index 9411804559..149d8f28e7 100644 --- a/eth2/state_processing/benches/epoch_processing_benches.rs +++ b/eth2/state_processing/benches/epoch_processing_benches.rs @@ -19,18 +19,19 @@ pub fn epoch_processing_16k_validators(c: &mut Criterion) { let validator_count = 16_384; let mut builder = BeaconStateBencher::new(validator_count, &spec); - builder.teleport_to_end_of_epoch(spec.genesis_epoch + 4, &spec); - builder.insert_attestations(&spec); - let mut state = builder.build(); - // Build all the caches so the following state does _not_ include the cache-building time. - state - .build_epoch_cache(RelativeEpoch::Previous, &spec) - .unwrap(); - state - .build_epoch_cache(RelativeEpoch::Current, &spec) - .unwrap(); - state.build_epoch_cache(RelativeEpoch::Next, &spec).unwrap(); + // Set the state to be just before an epoch transition. + let target_slot = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch); + builder.teleport_to_slot(target_slot, &spec); + + // Builds all caches; benches will not contain shuffling/committee building times. + builder.build_caches(&spec).unwrap(); + + // Inserts one attestation with full participation for each committee able to include an + // attestation in this state. + builder.insert_attestations(&spec); + + let (state, _keypairs) = builder.build(); // Assert that the state has the maximum possible attestations. let committees_per_epoch = spec.get_epoch_committee_count(validator_count); diff --git a/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs b/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs new file mode 100644 index 0000000000..67b7ccc9db --- /dev/null +++ b/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs @@ -0,0 +1,46 @@ +use ssz::{SignedRoot, TreeHash}; +use types::*; + +pub struct BeaconBlockBencher { + block: BeaconBlock, +} + +impl BeaconBlockBencher { + pub fn new(spec: &ChainSpec) -> Self { + Self { + block: BeaconBlock::genesis(spec.zero_hash, spec), + } + } + + pub fn set_slot(&mut self, slot: Slot) { + self.block.slot = slot; + } + + /// Signs the block. + pub fn sign(&mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) { + let proposal = self.block.proposal(spec); + let message = proposal.signed_root(); + let epoch = self.block.slot.epoch(spec.slots_per_epoch); + let domain = spec.get_domain(epoch, Domain::Proposal, fork); + self.block.signature = Signature::new(&message, domain, sk); + } + + /// Sets the randao to be a signature across the blocks epoch. + pub fn set_randao_reveal(&mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) { + let epoch = self.block.slot.epoch(spec.slots_per_epoch); + let message = epoch.hash_tree_root(); + let domain = spec.get_domain(epoch, Domain::Randao, fork); + self.block.randao_reveal = Signature::new(&message, domain, sk); + } + + /// Signs and returns the block, consuming the builder. + pub fn build(mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) -> BeaconBlock { + self.sign(sk, fork, spec); + self.block + } + + /// Returns the block, consuming the builder. + pub fn build_without_signing(self) -> BeaconBlock { + self.block + } +} diff --git a/eth2/state_processing/benching_utils/src/beacon_state_bencher.rs b/eth2/state_processing/benching_utils/src/beacon_state_bencher.rs new file mode 100644 index 0000000000..0ee4a75e95 --- /dev/null +++ b/eth2/state_processing/benching_utils/src/beacon_state_bencher.rs @@ -0,0 +1,213 @@ +use bls::get_withdrawal_credentials; +use int_to_bytes::int_to_bytes48; +use rayon::prelude::*; +use types::beacon_state::BeaconStateBuilder; +use types::*; + +pub struct BeaconStateBencher { + state: BeaconState, + keypairs: Vec, +} + +impl BeaconStateBencher { + pub fn new(validator_count: usize, spec: &ChainSpec) -> Self { + let keypairs: Vec = (0..validator_count) + .collect::>() + .par_iter() + .map(|&i| { + let secret = int_to_bytes48(i as u64 + 1); + let sk = SecretKey::from_bytes(&secret).unwrap(); + let pk = PublicKey::from_secret_key(&sk); + Keypair { sk, pk } + }) + .collect(); + + let validators = keypairs + .iter() + .map(|keypair| { + let withdrawal_credentials = Hash256::from_slice(&get_withdrawal_credentials( + &keypair.pk, + spec.bls_withdrawal_prefix_byte, + )); + + Validator { + pubkey: keypair.pk.clone(), + withdrawal_credentials, + activation_epoch: spec.far_future_epoch, + exit_epoch: spec.far_future_epoch, + withdrawable_epoch: spec.far_future_epoch, + initiated_exit: false, + slashed: false, + } + }) + .collect(); + + let mut state_builder = BeaconStateBuilder::new( + 0, + Eth1Data { + deposit_root: Hash256::zero(), + block_hash: Hash256::zero(), + }, + spec, + ); + + let balances = vec![32_000_000_000; validator_count]; + + state_builder.import_existing_validators( + validators, + balances, + validator_count as u64, + spec, + ); + + Self { + state: state_builder.build(spec).unwrap(), + keypairs, + } + } + + pub fn build(self) -> (BeaconState, Vec) { + (self.state, self.keypairs) + } + + pub fn build_caches(&mut self, spec: &ChainSpec) -> Result<(), BeaconStateError> { + let state = &mut self.state; + + state.build_epoch_cache(RelativeEpoch::Previous, &spec)?; + state.build_epoch_cache(RelativeEpoch::Current, &spec)?; + state.build_epoch_cache(RelativeEpoch::Next, &spec)?; + + Ok(()) + } + + /// Sets the `BeaconState` to be in a slot, calling `teleport_to_epoch` to update the epoch. + pub fn teleport_to_slot(&mut self, slot: Slot, spec: &ChainSpec) { + self.teleport_to_epoch(slot.epoch(spec.slots_per_epoch), spec); + self.state.slot = slot; + } + + /// Sets the `BeaconState` to be in the first slot of the given epoch. + /// + /// Sets all justification/finalization parameters to be be as "perfect" as possible (i.e., + /// highest justified and finalized slots, full justification bitfield, etc). + fn teleport_to_epoch(&mut self, epoch: Epoch, spec: &ChainSpec) { + let state = &mut self.state; + + let slot = epoch.start_slot(spec.slots_per_epoch); + + state.slot = slot; + state.validator_registry_update_epoch = epoch - 1; + + state.previous_shuffling_epoch = epoch - 1; + state.current_shuffling_epoch = epoch; + + state.previous_shuffling_seed = Hash256::from_low_u64_le(0); + state.current_shuffling_seed = Hash256::from_low_u64_le(1); + + state.previous_justified_epoch = epoch - 2; + state.justified_epoch = epoch - 1; + state.justification_bitfield = u64::max_value(); + state.finalized_epoch = epoch - 1; + } + + /// Creates a full set of attestations for the `BeaconState`. Each attestation has full + /// participation from its committee and references the expected beacon_block hashes. + /// + /// These attestations should be fully conducive to justification and finalization. + pub fn insert_attestations(&mut self, spec: &ChainSpec) { + let state = &mut self.state; + + state + .build_epoch_cache(RelativeEpoch::Previous, spec) + .unwrap(); + state + .build_epoch_cache(RelativeEpoch::Current, spec) + .unwrap(); + + let current_epoch = state.current_epoch(spec); + let previous_epoch = state.previous_epoch(spec); + + let first_slot = previous_epoch.start_slot(spec.slots_per_epoch).as_u64(); + let last_slot = current_epoch.end_slot(spec.slots_per_epoch).as_u64() + - spec.min_attestation_inclusion_delay; + let last_slot = std::cmp::min(state.slot.as_u64(), last_slot); + + for slot in first_slot..last_slot + 1 { + let slot = Slot::from(slot); + + let committees = state + .get_crosslink_committees_at_slot(slot, spec) + .unwrap() + .clone(); + + for (committee, shard) in committees { + state + .latest_attestations + .push(committee_to_pending_attestation( + state, &committee, shard, slot, spec, + )) + } + } + } +} + +fn committee_to_pending_attestation( + state: &BeaconState, + committee: &[usize], + shard: u64, + slot: Slot, + spec: &ChainSpec, +) -> PendingAttestation { + let current_epoch = state.current_epoch(spec); + let previous_epoch = state.previous_epoch(spec); + + let mut aggregation_bitfield = Bitfield::new(); + let mut custody_bitfield = Bitfield::new(); + + for (i, _) in committee.iter().enumerate() { + aggregation_bitfield.set(i, true); + custody_bitfield.set(i, true); + } + + let is_previous_epoch = + state.slot.epoch(spec.slots_per_epoch) != slot.epoch(spec.slots_per_epoch); + + let justified_epoch = if is_previous_epoch { + state.previous_justified_epoch + } else { + state.justified_epoch + }; + + let epoch_boundary_root = if is_previous_epoch { + *state + .get_block_root(previous_epoch.start_slot(spec.slots_per_epoch), spec) + .unwrap() + } else { + *state + .get_block_root(current_epoch.start_slot(spec.slots_per_epoch), spec) + .unwrap() + }; + + let justified_block_root = *state + .get_block_root(justified_epoch.start_slot(spec.slots_per_epoch), spec) + .unwrap(); + + PendingAttestation { + aggregation_bitfield, + data: AttestationData { + slot, + shard, + beacon_block_root: *state.get_block_root(slot, spec).unwrap(), + epoch_boundary_root, + crosslink_data_root: Hash256::zero(), + latest_crosslink: Crosslink { + epoch: slot.epoch(spec.slots_per_epoch), + crosslink_data_root: Hash256::zero(), + }, + justified_epoch, + justified_block_root, + }, + custody_bitfield, + inclusion_slot: slot + spec.min_attestation_inclusion_delay, + } +} diff --git a/eth2/state_processing/benching_utils/src/lib.rs b/eth2/state_processing/benching_utils/src/lib.rs index c70b7828a7..ba95488143 100644 --- a/eth2/state_processing/benching_utils/src/lib.rs +++ b/eth2/state_processing/benching_utils/src/lib.rs @@ -1,195 +1,5 @@ -use bls::get_withdrawal_credentials; -use int_to_bytes::int_to_bytes48; -use rayon::prelude::*; -use types::beacon_state::BeaconStateBuilder; -use types::*; +mod beacon_block_bencher; +mod beacon_state_bencher; -pub struct BeaconStateBencher { - state: BeaconState, -} - -impl BeaconStateBencher { - pub fn new(validator_count: usize, spec: &ChainSpec) -> Self { - let keypairs: Vec = (0..validator_count) - .collect::>() - .par_iter() - .map(|&i| { - let secret = int_to_bytes48(i as u64 + 1); - let sk = SecretKey::from_bytes(&secret).unwrap(); - let pk = PublicKey::from_secret_key(&sk); - Keypair { sk, pk } - }) - .collect(); - - let validators = keypairs - .iter() - .map(|keypair| { - let withdrawal_credentials = Hash256::from_slice(&get_withdrawal_credentials( - &keypair.pk, - spec.bls_withdrawal_prefix_byte, - )); - - Validator { - pubkey: keypair.pk.clone(), - withdrawal_credentials, - activation_epoch: spec.far_future_epoch, - exit_epoch: spec.far_future_epoch, - withdrawable_epoch: spec.far_future_epoch, - initiated_exit: false, - slashed: false, - } - }) - .collect(); - - let mut state_builder = BeaconStateBuilder::new( - 0, - Eth1Data { - deposit_root: Hash256::zero(), - block_hash: Hash256::zero(), - }, - spec, - ); - - let balances = vec![32_000_000_000; validator_count]; - - state_builder.import_existing_validators( - validators, - balances, - validator_count as u64, - spec, - ); - - Self { - state: state_builder.build(spec).unwrap(), - } - } - - pub fn build(self) -> BeaconState { - self.state - } - - /// Sets the `BeaconState` to be in the last slot of the given epoch. - /// - /// Sets all justification/finalization parameters to be be as "perfect" as possible (i.e., - /// highest justified and finalized slots, full justification bitfield, etc). - pub fn teleport_to_end_of_epoch(&mut self, epoch: Epoch, spec: &ChainSpec) { - let state = &mut self.state; - - let slot = epoch.end_slot(spec.slots_per_epoch); - - state.slot = slot; - state.validator_registry_update_epoch = epoch - 1; - - state.previous_shuffling_epoch = epoch - 1; - state.current_shuffling_epoch = epoch; - - state.previous_shuffling_seed = Hash256::from_low_u64_le(0); - state.current_shuffling_seed = Hash256::from_low_u64_le(1); - - state.previous_justified_epoch = epoch - 2; - state.justified_epoch = epoch - 1; - state.justification_bitfield = u64::max_value(); - state.finalized_epoch = epoch - 1; - } - - /// Creates a full set of attestations for the `BeaconState`. Each attestation has full - /// participation from its committee and references the expected beacon_block hashes. - /// - /// These attestations should be fully conducive to justification and finalization. - pub fn insert_attestations(&mut self, spec: &ChainSpec) { - let state = &mut self.state; - - state - .build_epoch_cache(RelativeEpoch::Previous, spec) - .unwrap(); - state - .build_epoch_cache(RelativeEpoch::Current, spec) - .unwrap(); - - let current_epoch = state.current_epoch(spec); - let previous_epoch = state.previous_epoch(spec); - - let first_slot = previous_epoch.start_slot(spec.slots_per_epoch).as_u64(); - let last_slot = current_epoch.end_slot(spec.slots_per_epoch).as_u64() - - spec.min_attestation_inclusion_delay; - let last_slot = std::cmp::min(state.slot.as_u64(), last_slot); - - for slot in first_slot..last_slot + 1 { - let slot = Slot::from(slot); - - let committees = state - .get_crosslink_committees_at_slot(slot, spec) - .unwrap() - .clone(); - - for (committee, shard) in committees { - state - .latest_attestations - .push(committee_to_pending_attestation( - state, &committee, shard, slot, spec, - )) - } - } - } -} - -fn committee_to_pending_attestation( - state: &BeaconState, - committee: &[usize], - shard: u64, - slot: Slot, - spec: &ChainSpec, -) -> PendingAttestation { - let current_epoch = state.current_epoch(spec); - let previous_epoch = state.previous_epoch(spec); - - let mut aggregation_bitfield = Bitfield::new(); - let mut custody_bitfield = Bitfield::new(); - - for (i, _) in committee.iter().enumerate() { - aggregation_bitfield.set(i, true); - custody_bitfield.set(i, true); - } - - let is_previous_epoch = - state.slot.epoch(spec.slots_per_epoch) != slot.epoch(spec.slots_per_epoch); - - let justified_epoch = if is_previous_epoch { - state.previous_justified_epoch - } else { - state.justified_epoch - }; - - let epoch_boundary_root = if is_previous_epoch { - *state - .get_block_root(previous_epoch.start_slot(spec.slots_per_epoch), spec) - .unwrap() - } else { - *state - .get_block_root(current_epoch.start_slot(spec.slots_per_epoch), spec) - .unwrap() - }; - - let justified_block_root = *state - .get_block_root(justified_epoch.start_slot(spec.slots_per_epoch), spec) - .unwrap(); - - PendingAttestation { - aggregation_bitfield, - data: AttestationData { - slot, - shard, - beacon_block_root: *state.get_block_root(slot, spec).unwrap(), - epoch_boundary_root, - crosslink_data_root: Hash256::zero(), - latest_crosslink: Crosslink { - epoch: slot.epoch(spec.slots_per_epoch), - crosslink_data_root: Hash256::zero(), - }, - justified_epoch, - justified_block_root, - }, - custody_bitfield, - inclusion_slot: slot + spec.min_attestation_inclusion_delay, - } -} +pub use beacon_block_bencher::BeaconBlockBencher; +pub use beacon_state_bencher::BeaconStateBencher; diff --git a/eth2/state_processing/src/per_block_processing.rs b/eth2/state_processing/src/per_block_processing.rs index 1ab1eed71b..149e0bf790 100644 --- a/eth2/state_processing/src/per_block_processing.rs +++ b/eth2/state_processing/src/per_block_processing.rs @@ -1,7 +1,6 @@ use self::verify_proposer_slashing::verify_proposer_slashing; use errors::{BlockInvalid as Invalid, BlockProcessingError as Error, IntoWithIndex}; use hashing::hash; -use log::debug; use ssz::{ssz_encode, SignedRoot, TreeHash}; use types::*; @@ -70,22 +69,21 @@ fn per_block_processing_signature_optional( // Verify that `block.slot == state.slot`. verify!(block.slot == state.slot, Invalid::StateSlotMismatch); - // Ensure the current epoch cache is built. + // Ensure the current and previous epoch cache is built. state.build_epoch_cache(RelativeEpoch::Current, spec)?; + state.build_epoch_cache(RelativeEpoch::Previous, spec)?; if should_verify_block_signature { verify_block_signature(&state, &block, &spec)?; } process_randao(&mut state, &block, &spec)?; process_eth1_data(&mut state, &block.eth1_data)?; - process_proposer_slashings(&mut state, &block.body.proposer_slashings[..], spec)?; - process_attester_slashings(&mut state, &block.body.attester_slashings[..], spec)?; - process_attestations(&mut state, &block.body.attestations[..], spec)?; - process_deposits(&mut state, &block.body.deposits[..], spec)?; - process_exits(&mut state, &block.body.voluntary_exits[..], spec)?; - process_transfers(&mut state, &block.body.transfers[..], spec)?; - - debug!("per_block_processing complete."); + process_proposer_slashings(&mut state, &block.body.proposer_slashings, spec)?; + process_attester_slashings(&mut state, &block.body.attester_slashings, spec)?; + process_attestations(&mut state, &block.body.attestations, spec)?; + process_deposits(&mut state, &block.body.deposits, spec)?; + process_exits(&mut state, &block.body.voluntary_exits, spec)?; + process_transfers(&mut state, &block.body.transfers, spec)?; Ok(()) } diff --git a/eth2/state_processing/src/per_epoch_processing/tests.rs b/eth2/state_processing/src/per_epoch_processing/tests.rs index 8ff6879043..f3c68a173f 100644 --- a/eth2/state_processing/src/per_epoch_processing/tests.rs +++ b/eth2/state_processing/src/per_epoch_processing/tests.rs @@ -11,8 +11,11 @@ fn runs_without_error() { let spec = ChainSpec::few_validators(); let mut builder = BeaconStateBencher::new(8, &spec); - builder.teleport_to_end_of_epoch(spec.genesis_epoch + 4, &spec); - let mut state = builder.build(); + + let target_slot = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch); + builder.teleport_to_slot(target_slot, &spec); + + let (mut state, _keypairs) = builder.build(); per_epoch_processing(&mut state, &spec).unwrap(); } diff --git a/eth2/types/src/beacon_block.rs b/eth2/types/src/beacon_block.rs index 2e1e24ef79..9e1b3f7aef 100644 --- a/eth2/types/src/beacon_block.rs +++ b/eth2/types/src/beacon_block.rs @@ -1,9 +1,9 @@ use crate::test_utils::TestRandom; -use crate::{BeaconBlockBody, ChainSpec, Eth1Data, Hash256, Slot}; +use crate::{BeaconBlockBody, ChainSpec, Eth1Data, Hash256, Proposal, Slot}; use bls::Signature; use rand::RngCore; use serde_derive::Serialize; -use ssz::TreeHash; +use ssz::{SignedRoot, TreeHash}; use ssz_derive::{Decode, Encode, SignedRoot, TreeHash}; use test_random_derive::TestRandom; @@ -33,7 +33,6 @@ impl BeaconBlock { deposit_root: spec.zero_hash, block_hash: spec.zero_hash, }, - signature: spec.empty_signature.clone(), body: BeaconBlockBody { proposer_slashings: vec![], attester_slashings: vec![], @@ -42,6 +41,7 @@ impl BeaconBlock { voluntary_exits: vec![], transfers: vec![], }, + signature: spec.empty_signature.clone(), } } @@ -49,6 +49,16 @@ impl BeaconBlock { pub fn canonical_root(&self) -> Hash256 { Hash256::from_slice(&self.hash_tree_root()[..]) } + + /// Returns an unsigned proposal for block. + pub fn proposal(&self, spec: &ChainSpec) -> Proposal { + Proposal { + slot: self.slot, + shard: spec.beacon_chain_shard_number, + block_root: Hash256::from_slice(&self.signed_root()), + signature: spec.empty_signature.clone(), + } + } } #[cfg(test)] From f8ec1e0cfa381610c8f19adda6a0a47bca3da427 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 9 Mar 2019 18:56:01 +1100 Subject: [PATCH 029/144] Add slashings and attestations to per block benching --- .../benches/block_processing_benches.rs | 84 +++++- .../src/beacon_block_bencher.rs | 241 +++++++++++++++++- eth2/types/src/attester_slashing/builder.rs | 1 + 3 files changed, 322 insertions(+), 4 deletions(-) diff --git a/eth2/state_processing/benches/block_processing_benches.rs b/eth2/state_processing/benches/block_processing_benches.rs index b5fdaa5bdd..75943b1ad8 100644 --- a/eth2/state_processing/benches/block_processing_benches.rs +++ b/eth2/state_processing/benches/block_processing_benches.rs @@ -20,6 +20,39 @@ pub fn block_processing_16k_validators(c: &mut Criterion) { let (state, keypairs) = build_state(validator_count, &spec); let block = build_block(&state, &keypairs, &spec); + assert_eq!( + block.body.proposer_slashings.len(), + spec.max_proposer_slashings as usize, + "The block should have the maximum possible proposer slashings" + ); + + assert_eq!( + block.body.attester_slashings.len(), + spec.max_attester_slashings as usize, + "The block should have the maximum possible attester slashings" + ); + + for attester_slashing in &block.body.attester_slashings { + let len_1 = attester_slashing + .slashable_attestation_1 + .validator_indices + .len(); + let len_2 = attester_slashing + .slashable_attestation_1 + .validator_indices + .len(); + assert!( + (len_1 == len_2) && (len_2 == spec.max_indices_per_slashable_vote as usize), + "Each attester slashing should have the maximum possible validator indices" + ); + } + + assert_eq!( + block.body.attestations.len(), + spec.max_attestations as usize, + "The block should have the maximum possible attestations." + ); + bench_block_processing( c, &block, @@ -52,6 +85,45 @@ fn build_block(state: &BeaconState, keypairs: &[Keypair], spec: &ChainSpec) -> B builder.set_randao_reveal(&keypair.sk, &state.fork, spec); + // Insert the maximum possible number of `ProposerSlashing` objects. + for validator_index in 0..spec.max_proposer_slashings { + builder.insert_proposer_slashing( + validator_index, + &keypairs[validator_index as usize].sk, + &state.fork, + spec, + ); + } + + // Insert the maximum possible number of `AttesterSlashing` objects + let number_of_slashable_attesters = + spec.max_indices_per_slashable_vote * spec.max_attester_slashings; + let all_attester_slashing_indices: Vec = (spec.max_proposer_slashings + ..(spec.max_proposer_slashings + number_of_slashable_attesters)) + .collect(); + let attester_slashing_groups: Vec<&[u64]> = all_attester_slashing_indices + .chunks(spec.max_indices_per_slashable_vote as usize) + .collect(); + for attester_slashing_group in attester_slashing_groups { + let attester_slashing_keypairs: Vec<&SecretKey> = attester_slashing_group + .iter() + .map(|&validator_index| &keypairs[validator_index as usize].sk) + .collect(); + + builder.insert_attester_slashing( + &attester_slashing_group, + &attester_slashing_keypairs, + &state.fork, + spec, + ); + } + + // Insert the maximum possible number of `Attestation` objects. + let all_secret_keys: Vec<&SecretKey> = keypairs.iter().map(|keypair| &keypair.sk).collect(); + builder + .fill_with_attestations(state, &all_secret_keys, spec) + .unwrap(); + builder.build(&keypair.sk, &state.fork, spec) } @@ -153,7 +225,10 @@ fn bench_block_processing( ); let state = initial_state.clone(); - let block = initial_block.clone(); + let mut block = initial_block.clone(); + // Slashings will invalidate the attestations. + block.body.proposer_slashings = vec![]; + block.body.attester_slashings = vec![]; let spec = initial_spec.clone(); c.bench( &format!("block_processing_{}", desc), @@ -221,11 +296,14 @@ fn bench_block_processing( ); let state = initial_state.clone(); - let block = initial_block.clone(); + let mut block = initial_block.clone(); + // Slashings will invalidate the attestations. + block.body.proposer_slashings = vec![]; + block.body.attester_slashings = vec![]; let spec = initial_spec.clone(); c.bench( &format!("block_processing_{}", desc), - Benchmark::new("per_block_processing", move |b| { + Benchmark::new("per_block_processing_no_slashings", move |b| { b.iter_with_setup( || state.clone(), |mut state| black_box(per_block_processing(&mut state, &block, &spec).unwrap()), diff --git a/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs b/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs index 67b7ccc9db..989dbd9292 100644 --- a/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs +++ b/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs @@ -1,5 +1,8 @@ +use rayon::prelude::*; use ssz::{SignedRoot, TreeHash}; -use types::*; +use types::{ + attester_slashing::AttesterSlashingBuilder, proposer_slashing::ProposerSlashingBuilder, *, +}; pub struct BeaconBlockBencher { block: BeaconBlock, @@ -33,6 +36,114 @@ impl BeaconBlockBencher { self.block.randao_reveal = Signature::new(&message, domain, sk); } + /// Inserts a signed, valid `ProposerSlashing` for the validator. + pub fn insert_proposer_slashing( + &mut self, + validator_index: u64, + secret_key: &SecretKey, + fork: &Fork, + spec: &ChainSpec, + ) { + let proposer_slashing = build_proposer_slashing(validator_index, secret_key, fork, spec); + self.block.body.proposer_slashings.push(proposer_slashing); + } + + /// Inserts a signed, valid `AttesterSlashing` for each validator index in `validator_indices`. + pub fn insert_attester_slashing( + &mut self, + validator_indices: &[u64], + secret_keys: &[&SecretKey], + fork: &Fork, + spec: &ChainSpec, + ) { + let attester_slashing = + build_double_vote_attester_slashing(validator_indices, secret_keys, fork, spec); + self.block.body.attester_slashings.push(attester_slashing); + } + + /// Fills the block with as many attestations as possible. + /// + /// Note: this will not perform well when `jepoch_committees_count % slots_per_epoch != 0` + pub fn fill_with_attestations( + &mut self, + state: &BeaconState, + secret_keys: &[&SecretKey], + spec: &ChainSpec, + ) -> Result<(), BeaconStateError> { + let mut slot = self.block.slot - spec.min_attestation_inclusion_delay; + let mut attestations_added = 0; + + // Stores the following (in order): + // + // - The slot of the committee. + // - A list of all validators in the committee. + // - A list of all validators in the committee that should sign the attestation. + // - The shard of the committee. + let mut committees: Vec<(Slot, Vec, Vec, u64)> = vec![]; + + // Loop backwards through slots gathering each committee, until: + // + // - The slot is too old to be included in a block at this slot. + // - The `MAX_ATTESTATIONS`. + loop { + if attestations_added == spec.max_attestations { + break; + } + if state.slot >= slot + spec.slots_per_epoch { + break; + } + + for (committee, shard) in state.get_crosslink_committees_at_slot(slot, spec)? { + committees.push((slot, committee.clone(), committee.clone(), *shard)) + } + + attestations_added += 1; + slot -= 1; + } + + // Loop through all the committees, splitting each one in half until we have + // `MAX_ATTESTATIONS` committees. + loop { + if committees.len() >= spec.max_attestations as usize { + break; + } + + for index in 0..committees.len() { + if committees.len() >= spec.max_attestations as usize { + break; + } + + let (slot, committee, mut signing_validators, shard) = committees[index].clone(); + + let new_signing_validators = + signing_validators.split_off(signing_validators.len() / 2); + + committees[index] = (slot, committee.clone(), signing_validators, shard); + committees.push((slot, committee, new_signing_validators, shard)); + } + } + + let mut attestations: Vec = committees + .par_iter() + .map(|(slot, committee, signing_validators, shard)| { + committee_to_attestation( + state, + &committee, + signing_validators, + secret_keys, + *shard, + *slot, + &state.fork, + spec, + ) + }) + .collect(); + + self.block.body.attestations.append(&mut attestations); + + Ok(()) + } + /// Signs and returns the block, consuming the builder. pub fn build(mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) -> BeaconBlock { self.sign(sk, fork, spec); @@ -44,3 +155,131 @@ impl BeaconBlockBencher { self.block } } + +/// Builds an `ProposerSlashing` for some `validator_index`. +/// +/// Signs the message using a `BeaconChainHarness`. +fn build_proposer_slashing( + validator_index: u64, + secret_key: &SecretKey, + fork: &Fork, + spec: &ChainSpec, +) -> ProposerSlashing { + let signer = |_validator_index: u64, message: &[u8], epoch: Epoch, domain: Domain| { + let domain = spec.get_domain(epoch, domain, fork); + Signature::new(message, domain, secret_key) + }; + + ProposerSlashingBuilder::double_vote(validator_index, signer, spec) +} + +/// Builds an `AttesterSlashing` for some `validator_indices`. +/// +/// Signs the message using a `BeaconChainHarness`. +fn build_double_vote_attester_slashing( + validator_indices: &[u64], + secret_keys: &[&SecretKey], + fork: &Fork, + spec: &ChainSpec, +) -> AttesterSlashing { + let signer = |validator_index: u64, message: &[u8], epoch: Epoch, domain: Domain| { + let key_index = validator_indices + .iter() + .position(|&i| i == validator_index) + .expect("Unable to find attester slashing key"); + let domain = spec.get_domain(epoch, domain, fork); + Signature::new(message, domain, secret_keys[key_index]) + }; + + AttesterSlashingBuilder::double_vote(validator_indices, signer) +} + +/// Convert some committee into a valid `Attestation`. +/// +/// Note: `committee` must be the full committee for the attestation. `signing_validators` is a +/// list of validator indices that should sign the attestation. +fn committee_to_attestation( + state: &BeaconState, + committee: &[usize], + signing_validators: &[usize], + secret_keys: &[&SecretKey], + shard: u64, + slot: Slot, + fork: &Fork, + spec: &ChainSpec, +) -> Attestation { + let current_epoch = state.current_epoch(spec); + let previous_epoch = state.previous_epoch(spec); + + let is_previous_epoch = + state.slot.epoch(spec.slots_per_epoch) != slot.epoch(spec.slots_per_epoch); + + let justified_epoch = if is_previous_epoch { + state.previous_justified_epoch + } else { + state.justified_epoch + }; + + let epoch_boundary_root = if is_previous_epoch { + *state + .get_block_root(previous_epoch.start_slot(spec.slots_per_epoch), spec) + .unwrap() + } else { + *state + .get_block_root(current_epoch.start_slot(spec.slots_per_epoch), spec) + .unwrap() + }; + + let justified_block_root = *state + .get_block_root(justified_epoch.start_slot(spec.slots_per_epoch), spec) + .unwrap(); + + let data = AttestationData { + slot, + shard, + beacon_block_root: *state.get_block_root(slot, spec).unwrap(), + epoch_boundary_root, + crosslink_data_root: Hash256::zero(), + latest_crosslink: state.latest_crosslinks[shard as usize].clone(), + justified_epoch, + justified_block_root, + }; + + let mut aggregate_signature = AggregateSignature::new(); + let mut aggregation_bitfield = Bitfield::new(); + let mut custody_bitfield = Bitfield::new(); + + let message = AttestationDataAndCustodyBit { + data: data.clone(), + custody_bit: false, + } + .hash_tree_root(); + + let domain = spec.get_domain( + data.slot.epoch(spec.slots_per_epoch), + Domain::Attestation, + fork, + ); + + for (i, validator_index) in committee.iter().enumerate() { + custody_bitfield.set(i, false); + + if signing_validators + .iter() + .any(|&signer| *validator_index == signer) + { + aggregation_bitfield.set(i, true); + let signature = Signature::new(&message, domain, secret_keys[*validator_index]); + aggregate_signature.add(&signature); + } else { + aggregation_bitfield.set(i, false); + } + } + + Attestation { + aggregation_bitfield, + data, + custody_bitfield, + aggregate_signature, + } +} diff --git a/eth2/types/src/attester_slashing/builder.rs b/eth2/types/src/attester_slashing/builder.rs index 05301f30b8..8edf4ed65c 100644 --- a/eth2/types/src/attester_slashing/builder.rs +++ b/eth2/types/src/attester_slashing/builder.rs @@ -66,6 +66,7 @@ impl AttesterSlashingBuilder { let add_signatures = |attestation: &mut SlashableAttestation| { for (i, validator_index) in validator_indices.iter().enumerate() { + attestation.custody_bitfield.set(i, false); let attestation_data_and_custody_bit = AttestationDataAndCustodyBit { data: attestation.data.clone(), custody_bit: attestation.custody_bitfield.get(i).unwrap(), From 4bf2490163a224fb0e1dcdedc50af14557f77b3d Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 9 Mar 2019 18:59:32 +1100 Subject: [PATCH 030/144] Re-organise types::test_utils dir --- eth2/types/src/test_utils/mod.rs | 56 +------------------ eth2/types/src/test_utils/test_random.rs | 55 ++++++++++++++++++ .../test_utils/{ => test_random}/address.rs | 0 .../{ => test_random}/aggregate_signature.rs | 0 .../test_utils/{ => test_random}/bitfield.rs | 2 +- .../test_utils/{ => test_random}/hash256.rs | 0 .../{ => test_random}/public_key.rs | 0 .../{ => test_random}/secret_key.rs | 0 .../test_utils/{ => test_random}/signature.rs | 0 9 files changed, 58 insertions(+), 55 deletions(-) create mode 100644 eth2/types/src/test_utils/test_random.rs rename eth2/types/src/test_utils/{ => test_random}/address.rs (100%) rename eth2/types/src/test_utils/{ => test_random}/aggregate_signature.rs (100%) rename eth2/types/src/test_utils/{ => test_random}/bitfield.rs (90%) rename eth2/types/src/test_utils/{ => test_random}/hash256.rs (100%) rename eth2/types/src/test_utils/{ => test_random}/public_key.rs (100%) rename eth2/types/src/test_utils/{ => test_random}/secret_key.rs (100%) rename eth2/types/src/test_utils/{ => test_random}/signature.rs (100%) diff --git a/eth2/types/src/test_utils/mod.rs b/eth2/types/src/test_utils/mod.rs index 82e060fcaf..1e88ab34f5 100644 --- a/eth2/types/src/test_utils/mod.rs +++ b/eth2/types/src/test_utils/mod.rs @@ -1,55 +1,3 @@ -use rand::RngCore; +mod test_random; -pub use rand::{prng::XorShiftRng, SeedableRng}; - -pub mod address; -pub mod aggregate_signature; -pub mod bitfield; -pub mod hash256; -pub mod public_key; -pub mod secret_key; -pub mod signature; - -pub trait TestRandom -where - T: RngCore, -{ - fn random_for_test(rng: &mut T) -> Self; -} - -impl TestRandom for bool { - fn random_for_test(rng: &mut T) -> Self { - (rng.next_u32() % 2) == 1 - } -} - -impl TestRandom for u64 { - fn random_for_test(rng: &mut T) -> Self { - rng.next_u64() - } -} - -impl TestRandom for u32 { - fn random_for_test(rng: &mut T) -> Self { - rng.next_u32() - } -} - -impl TestRandom for usize { - fn random_for_test(rng: &mut T) -> Self { - rng.next_u32() as usize - } -} - -impl TestRandom for Vec -where - U: TestRandom, -{ - fn random_for_test(rng: &mut T) -> Self { - vec![ - ::random_for_test(rng), - ::random_for_test(rng), - ::random_for_test(rng), - ] - } -} +pub use test_random::TestRandom; diff --git a/eth2/types/src/test_utils/test_random.rs b/eth2/types/src/test_utils/test_random.rs new file mode 100644 index 0000000000..841d129a04 --- /dev/null +++ b/eth2/types/src/test_utils/test_random.rs @@ -0,0 +1,55 @@ +use rand::RngCore; + +pub use rand::{prng::XorShiftRng, SeedableRng}; + +mod address; +mod aggregate_signature; +mod bitfield; +mod hash256; +mod public_key; +mod secret_key; +mod signature; + +pub trait TestRandom +where + T: RngCore, +{ + fn random_for_test(rng: &mut T) -> Self; +} + +impl TestRandom for bool { + fn random_for_test(rng: &mut T) -> Self { + (rng.next_u32() % 2) == 1 + } +} + +impl TestRandom for u64 { + fn random_for_test(rng: &mut T) -> Self { + rng.next_u64() + } +} + +impl TestRandom for u32 { + fn random_for_test(rng: &mut T) -> Self { + rng.next_u32() + } +} + +impl TestRandom for usize { + fn random_for_test(rng: &mut T) -> Self { + rng.next_u32() as usize + } +} + +impl TestRandom for Vec +where + U: TestRandom, +{ + fn random_for_test(rng: &mut T) -> Self { + vec![ + ::random_for_test(rng), + ::random_for_test(rng), + ::random_for_test(rng), + ] + } +} diff --git a/eth2/types/src/test_utils/address.rs b/eth2/types/src/test_utils/test_random/address.rs similarity index 100% rename from eth2/types/src/test_utils/address.rs rename to eth2/types/src/test_utils/test_random/address.rs diff --git a/eth2/types/src/test_utils/aggregate_signature.rs b/eth2/types/src/test_utils/test_random/aggregate_signature.rs similarity index 100% rename from eth2/types/src/test_utils/aggregate_signature.rs rename to eth2/types/src/test_utils/test_random/aggregate_signature.rs diff --git a/eth2/types/src/test_utils/bitfield.rs b/eth2/types/src/test_utils/test_random/bitfield.rs similarity index 90% rename from eth2/types/src/test_utils/bitfield.rs rename to eth2/types/src/test_utils/test_random/bitfield.rs index 15011edd9c..9748458f11 100644 --- a/eth2/types/src/test_utils/bitfield.rs +++ b/eth2/types/src/test_utils/test_random/bitfield.rs @@ -1,5 +1,5 @@ -use super::super::Bitfield; use super::TestRandom; +use crate::Bitfield; use rand::RngCore; impl TestRandom for Bitfield { diff --git a/eth2/types/src/test_utils/hash256.rs b/eth2/types/src/test_utils/test_random/hash256.rs similarity index 100% rename from eth2/types/src/test_utils/hash256.rs rename to eth2/types/src/test_utils/test_random/hash256.rs diff --git a/eth2/types/src/test_utils/public_key.rs b/eth2/types/src/test_utils/test_random/public_key.rs similarity index 100% rename from eth2/types/src/test_utils/public_key.rs rename to eth2/types/src/test_utils/test_random/public_key.rs diff --git a/eth2/types/src/test_utils/secret_key.rs b/eth2/types/src/test_utils/test_random/secret_key.rs similarity index 100% rename from eth2/types/src/test_utils/secret_key.rs rename to eth2/types/src/test_utils/test_random/secret_key.rs diff --git a/eth2/types/src/test_utils/signature.rs b/eth2/types/src/test_utils/test_random/signature.rs similarity index 100% rename from eth2/types/src/test_utils/signature.rs rename to eth2/types/src/test_utils/test_random/signature.rs From 62ab782ee2da6d9189edbf406936364e55eb458e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 9 Mar 2019 20:09:02 +1100 Subject: [PATCH 031/144] Add TestingAttestationBuilder --- .../src/beacon_block_bencher.rs | 113 ++--------------- eth2/types/src/test_utils/mod.rs | 3 + eth2/types/src/test_utils/test_random.rs | 2 - .../test_utils/testing_attestation_builder.rs | 117 ++++++++++++++++++ 4 files changed, 132 insertions(+), 103 deletions(-) create mode 100644 eth2/types/src/test_utils/testing_attestation_builder.rs diff --git a/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs b/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs index 989dbd9292..5e7fddb558 100644 --- a/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs +++ b/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs @@ -1,7 +1,8 @@ use rayon::prelude::*; use ssz::{SignedRoot, TreeHash}; use types::{ - attester_slashing::AttesterSlashingBuilder, proposer_slashing::ProposerSlashingBuilder, *, + attester_slashing::AttesterSlashingBuilder, proposer_slashing::ProposerSlashingBuilder, + test_utils::TestingAttestationBuilder, *, }; pub struct BeaconBlockBencher { @@ -126,16 +127,16 @@ impl BeaconBlockBencher { let mut attestations: Vec = committees .par_iter() .map(|(slot, committee, signing_validators, shard)| { - committee_to_attestation( - state, - &committee, - signing_validators, - secret_keys, - *shard, - *slot, - &state.fork, - spec, - ) + let mut builder = + TestingAttestationBuilder::new(state, committee, *slot, *shard, spec); + + let signing_secret_keys: Vec<&SecretKey> = signing_validators + .iter() + .map(|validator_index| secret_keys[*validator_index]) + .collect(); + builder.sign(signing_validators, &signing_secret_keys, &state.fork, spec); + + builder.build() }) .collect(); @@ -193,93 +194,3 @@ fn build_double_vote_attester_slashing( AttesterSlashingBuilder::double_vote(validator_indices, signer) } - -/// Convert some committee into a valid `Attestation`. -/// -/// Note: `committee` must be the full committee for the attestation. `signing_validators` is a -/// list of validator indices that should sign the attestation. -fn committee_to_attestation( - state: &BeaconState, - committee: &[usize], - signing_validators: &[usize], - secret_keys: &[&SecretKey], - shard: u64, - slot: Slot, - fork: &Fork, - spec: &ChainSpec, -) -> Attestation { - let current_epoch = state.current_epoch(spec); - let previous_epoch = state.previous_epoch(spec); - - let is_previous_epoch = - state.slot.epoch(spec.slots_per_epoch) != slot.epoch(spec.slots_per_epoch); - - let justified_epoch = if is_previous_epoch { - state.previous_justified_epoch - } else { - state.justified_epoch - }; - - let epoch_boundary_root = if is_previous_epoch { - *state - .get_block_root(previous_epoch.start_slot(spec.slots_per_epoch), spec) - .unwrap() - } else { - *state - .get_block_root(current_epoch.start_slot(spec.slots_per_epoch), spec) - .unwrap() - }; - - let justified_block_root = *state - .get_block_root(justified_epoch.start_slot(spec.slots_per_epoch), spec) - .unwrap(); - - let data = AttestationData { - slot, - shard, - beacon_block_root: *state.get_block_root(slot, spec).unwrap(), - epoch_boundary_root, - crosslink_data_root: Hash256::zero(), - latest_crosslink: state.latest_crosslinks[shard as usize].clone(), - justified_epoch, - justified_block_root, - }; - - let mut aggregate_signature = AggregateSignature::new(); - let mut aggregation_bitfield = Bitfield::new(); - let mut custody_bitfield = Bitfield::new(); - - let message = AttestationDataAndCustodyBit { - data: data.clone(), - custody_bit: false, - } - .hash_tree_root(); - - let domain = spec.get_domain( - data.slot.epoch(spec.slots_per_epoch), - Domain::Attestation, - fork, - ); - - for (i, validator_index) in committee.iter().enumerate() { - custody_bitfield.set(i, false); - - if signing_validators - .iter() - .any(|&signer| *validator_index == signer) - { - aggregation_bitfield.set(i, true); - let signature = Signature::new(&message, domain, secret_keys[*validator_index]); - aggregate_signature.add(&signature); - } else { - aggregation_bitfield.set(i, false); - } - } - - Attestation { - aggregation_bitfield, - data, - custody_bitfield, - aggregate_signature, - } -} diff --git a/eth2/types/src/test_utils/mod.rs b/eth2/types/src/test_utils/mod.rs index 1e88ab34f5..6138940a26 100644 --- a/eth2/types/src/test_utils/mod.rs +++ b/eth2/types/src/test_utils/mod.rs @@ -1,3 +1,6 @@ mod test_random; +mod testing_attestation_builder; +pub use rand::{prng::XorShiftRng, SeedableRng}; pub use test_random::TestRandom; +pub use testing_attestation_builder::TestingAttestationBuilder; diff --git a/eth2/types/src/test_utils/test_random.rs b/eth2/types/src/test_utils/test_random.rs index 841d129a04..3b172463e4 100644 --- a/eth2/types/src/test_utils/test_random.rs +++ b/eth2/types/src/test_utils/test_random.rs @@ -1,7 +1,5 @@ use rand::RngCore; -pub use rand::{prng::XorShiftRng, SeedableRng}; - mod address; mod aggregate_signature; mod bitfield; diff --git a/eth2/types/src/test_utils/testing_attestation_builder.rs b/eth2/types/src/test_utils/testing_attestation_builder.rs new file mode 100644 index 0000000000..f52edadfef --- /dev/null +++ b/eth2/types/src/test_utils/testing_attestation_builder.rs @@ -0,0 +1,117 @@ +use crate::*; +use ssz::TreeHash; + +pub struct TestingAttestationBuilder { + committee: Vec, + attestation: Attestation, +} + +impl TestingAttestationBuilder { + pub fn new( + state: &BeaconState, + committee: &[usize], + slot: Slot, + shard: u64, + spec: &ChainSpec, + ) -> Self { + let current_epoch = state.current_epoch(spec); + let previous_epoch = state.previous_epoch(spec); + + let is_previous_epoch = + state.slot.epoch(spec.slots_per_epoch) != slot.epoch(spec.slots_per_epoch); + + let justified_epoch = if is_previous_epoch { + state.previous_justified_epoch + } else { + state.justified_epoch + }; + + let epoch_boundary_root = if is_previous_epoch { + *state + .get_block_root(previous_epoch.start_slot(spec.slots_per_epoch), spec) + .unwrap() + } else { + *state + .get_block_root(current_epoch.start_slot(spec.slots_per_epoch), spec) + .unwrap() + }; + + let justified_block_root = *state + .get_block_root(justified_epoch.start_slot(spec.slots_per_epoch), spec) + .unwrap(); + + let mut aggregation_bitfield = Bitfield::new(); + let mut custody_bitfield = Bitfield::new(); + + for (i, _) in committee.iter().enumerate() { + custody_bitfield.set(i, false); + aggregation_bitfield.set(i, false); + } + + let attestation = Attestation { + aggregation_bitfield, + data: AttestationData { + slot, + shard, + beacon_block_root: *state.get_block_root(slot, spec).unwrap(), + epoch_boundary_root, + crosslink_data_root: Hash256::zero(), + latest_crosslink: state.latest_crosslinks[shard as usize].clone(), + justified_epoch, + justified_block_root, + }, + custody_bitfield, + aggregate_signature: AggregateSignature::new(), + }; + + Self { + attestation, + committee: committee.to_vec(), + } + } + + pub fn sign( + &mut self, + signing_validators: &[usize], + secret_keys: &[&SecretKey], + fork: &Fork, + spec: &ChainSpec, + ) { + assert_eq!( + signing_validators.len(), + secret_keys.len(), + "Must be a key for each validator" + ); + + for (key_index, validator_index) in signing_validators.iter().enumerate() { + let committee_index = self + .committee + .iter() + .position(|v| *v == *validator_index) + .expect("Signing validator not in attestation committee"); + + self.attestation + .aggregation_bitfield + .set(committee_index, true); + + let message = AttestationDataAndCustodyBit { + data: self.attestation.data.clone(), + custody_bit: false, + } + .hash_tree_root(); + + let domain = spec.get_domain( + self.attestation.data.slot.epoch(spec.slots_per_epoch), + Domain::Attestation, + fork, + ); + + let signature = Signature::new(&message, domain, secret_keys[key_index]); + self.attestation.aggregate_signature.add(&signature) + } + } + + pub fn build(self) -> Attestation { + self.attestation + } +} From 6250c81bb9d2699fa17e1f111365ad731dcd1ed5 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 9 Mar 2019 20:09:17 +1100 Subject: [PATCH 032/144] Fix bug in attestation verification We were ensuring that a validator was present on the aggregation bitfield before adding their signature to the agg pub --- .../src/per_block_processing/errors.rs | 2 + .../validate_attestation.rs | 72 ++++++++++++------- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/eth2/state_processing/src/per_block_processing/errors.rs b/eth2/state_processing/src/per_block_processing/errors.rs index b97d8bacc5..7e71a9b75b 100644 --- a/eth2/state_processing/src/per_block_processing/errors.rs +++ b/eth2/state_processing/src/per_block_processing/errors.rs @@ -147,6 +147,8 @@ pub enum AttestationInvalid { /// /// (attestation_data_shard, attestation_data_slot) NoCommitteeForShard(u64, Slot), + /// The validator index was unknown. + UnknownValidator(u64), /// The attestation signature verification failed. BadSignature, /// The shard block root was not set to zero. This is a phase 0 requirement. diff --git a/eth2/state_processing/src/per_block_processing/validate_attestation.rs b/eth2/state_processing/src/per_block_processing/validate_attestation.rs index 54bd2d332b..b153608504 100644 --- a/eth2/state_processing/src/per_block_processing/validate_attestation.rs +++ b/eth2/state_processing/src/per_block_processing/validate_attestation.rs @@ -159,18 +159,16 @@ fn validate_attestation_signature_optional( if verify_signature { let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch); - verify!( - verify_attestation_signature( - state, - committee, - attestation_epoch, - &attestation.custody_bitfield, - &attestation.data, - &attestation.aggregate_signature, - spec - ), - Invalid::BadSignature - ); + verify_attestation_signature( + state, + committee, + attestation_epoch, + &attestation.aggregation_bitfield, + &attestation.custody_bitfield, + &attestation.data, + &attestation.aggregate_signature, + spec, + )?; } // [TO BE REMOVED IN PHASE 1] Verify that `attestation.data.crosslink_data_root == ZERO_HASH`. @@ -195,30 +193,45 @@ fn verify_attestation_signature( state: &BeaconState, committee: &[usize], attestation_epoch: Epoch, + aggregation_bitfield: &Bitfield, custody_bitfield: &Bitfield, attestation_data: &AttestationData, aggregate_signature: &AggregateSignature, spec: &ChainSpec, -) -> bool { +) -> Result<(), Error> { let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2]; let mut message_exists = vec![false; 2]; for (i, v) in committee.iter().enumerate() { - let custody_bit = match custody_bitfield.get(i) { - Ok(bit) => bit, - // Invalidate signature if custody_bitfield.len() < committee - Err(_) => return false, - }; + let validator_signed = aggregation_bitfield.get(i).map_err(|_| { + Error::Invalid(Invalid::BadAggregationBitfieldLength( + committee.len(), + aggregation_bitfield.len(), + )) + })?; - message_exists[custody_bit as usize] = true; + if validator_signed { + let custody_bit: bool = match custody_bitfield.get(i) { + Ok(bit) => bit, + // Invalidate signature if custody_bitfield.len() < committee + Err(_) => { + return Err(Error::Invalid(Invalid::BadCustodyBitfieldLength( + committee.len(), + custody_bitfield.len(), + ))); + } + }; - match state.validator_registry.get(*v as usize) { - Some(validator) => { - aggregate_pubs[custody_bit as usize].add(&validator.pubkey); - } - // Invalidate signature if validator index is unknown. - None => return false, - }; + message_exists[custody_bit as usize] = true; + + match state.validator_registry.get(*v as usize) { + Some(validator) => { + aggregate_pubs[custody_bit as usize].add(&validator.pubkey); + } + // Return error if validator index is unknown. + None => return Err(Error::BeaconStateError(BeaconStateError::UnknownValidator)), + }; + } } // Message when custody bitfield is `false` @@ -251,5 +264,10 @@ fn verify_attestation_signature( let domain = spec.get_domain(attestation_epoch, Domain::Attestation, &state.fork); - aggregate_signature.verify_multiple(&messages[..], domain, &keys[..]) + verify!( + aggregate_signature.verify_multiple(&messages[..], domain, &keys[..]), + Invalid::BadSignature + ); + + Ok(()) } From c1e386a0b16f9fd2c921524cec0f6362acd8ac75 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 9 Mar 2019 22:10:47 +1100 Subject: [PATCH 033/144] Add deposits, transfers and exits to benches --- .../benches/block_processing_benches.rs | 96 ++++++++++++++----- .../src/beacon_block_bencher.rs | 57 ++++++++++- eth2/types/src/test_utils/mod.rs | 6 ++ .../src/test_utils/testing_deposit_builder.rs | 48 ++++++++++ .../test_utils/testing_transfer_builder.rs | 37 +++++++ .../testing_voluntary_exit_builder.rs | 29 ++++++ 6 files changed, 248 insertions(+), 25 deletions(-) create mode 100644 eth2/types/src/test_utils/testing_deposit_builder.rs create mode 100644 eth2/types/src/test_utils/testing_transfer_builder.rs create mode 100644 eth2/types/src/test_utils/testing_voluntary_exit_builder.rs diff --git a/eth2/state_processing/benches/block_processing_benches.rs b/eth2/state_processing/benches/block_processing_benches.rs index 75943b1ad8..2ff2e7413f 100644 --- a/eth2/state_processing/benches/block_processing_benches.rs +++ b/eth2/state_processing/benches/block_processing_benches.rs @@ -17,8 +17,8 @@ pub fn block_processing_16k_validators(c: &mut Criterion) { let validator_count = 16_384; - let (state, keypairs) = build_state(validator_count, &spec); - let block = build_block(&state, &keypairs, &spec); + let (mut state, keypairs) = build_state(validator_count, &spec); + let block = build_block(&mut state, &keypairs, &spec); assert_eq!( block.body.proposer_slashings.len(), @@ -53,6 +53,24 @@ pub fn block_processing_16k_validators(c: &mut Criterion) { "The block should have the maximum possible attestations." ); + assert_eq!( + block.body.deposits.len(), + spec.max_deposits as usize, + "The block should have the maximum possible deposits." + ); + + assert_eq!( + block.body.voluntary_exits.len(), + spec.max_voluntary_exits as usize, + "The block should have the maximum possible voluntary exits." + ); + + assert_eq!( + block.body.transfers.len(), + spec.max_transfers as usize, + "The block should have the maximum possible transfers." + ); + bench_block_processing( c, &block, @@ -75,7 +93,7 @@ fn build_state(validator_count: usize, spec: &ChainSpec) -> (BeaconState, Vec BeaconBlock { +fn build_block(state: &mut BeaconState, keypairs: &[Keypair], spec: &ChainSpec) -> BeaconBlock { let mut builder = BeaconBlockBencher::new(spec); builder.set_slot(state.slot); @@ -85,8 +103,13 @@ fn build_block(state: &BeaconState, keypairs: &[Keypair], spec: &ChainSpec) -> B builder.set_randao_reveal(&keypair.sk, &state.fork, spec); + // Used as a stream of validator indices for use in slashings, exits, etc. + let mut validators_iter = (0..keypairs.len() as u64).into_iter(); + // Insert the maximum possible number of `ProposerSlashing` objects. - for validator_index in 0..spec.max_proposer_slashings { + for _ in 0..spec.max_proposer_slashings { + let validator_index = validators_iter.next().expect("Insufficient validators."); + builder.insert_proposer_slashing( validator_index, &keypairs[validator_index as usize].sk, @@ -96,26 +119,18 @@ fn build_block(state: &BeaconState, keypairs: &[Keypair], spec: &ChainSpec) -> B } // Insert the maximum possible number of `AttesterSlashing` objects - let number_of_slashable_attesters = - spec.max_indices_per_slashable_vote * spec.max_attester_slashings; - let all_attester_slashing_indices: Vec = (spec.max_proposer_slashings - ..(spec.max_proposer_slashings + number_of_slashable_attesters)) - .collect(); - let attester_slashing_groups: Vec<&[u64]> = all_attester_slashing_indices - .chunks(spec.max_indices_per_slashable_vote as usize) - .collect(); - for attester_slashing_group in attester_slashing_groups { - let attester_slashing_keypairs: Vec<&SecretKey> = attester_slashing_group - .iter() - .map(|&validator_index| &keypairs[validator_index as usize].sk) - .collect(); + for _ in 0..spec.max_attester_slashings { + let mut attesters: Vec = vec![]; + let mut secret_keys: Vec<&SecretKey> = vec![]; - builder.insert_attester_slashing( - &attester_slashing_group, - &attester_slashing_keypairs, - &state.fork, - spec, - ); + for _ in 0..spec.max_indices_per_slashable_vote { + let validator_index = validators_iter.next().expect("Insufficient validators."); + + attesters.push(validator_index); + secret_keys.push(&keypairs[validator_index as usize].sk); + } + + builder.insert_attester_slashing(&attesters, &secret_keys, &state.fork, spec); } // Insert the maximum possible number of `Attestation` objects. @@ -124,6 +139,41 @@ fn build_block(state: &BeaconState, keypairs: &[Keypair], spec: &ChainSpec) -> B .fill_with_attestations(state, &all_secret_keys, spec) .unwrap(); + // Insert the maximum possible number of `Deposit` objects. + for i in 0..spec.max_deposits { + builder.insert_deposit(32_000_000_000, state.deposit_index + i, spec); + } + + // Insert the maximum possible number of `Exit` objects. + for _ in 0..spec.max_voluntary_exits { + let validator_index = validators_iter.next().expect("Insufficient validators."); + + builder.insert_exit( + state, + validator_index, + &keypairs[validator_index as usize].sk, + spec, + ); + } + + // Insert the maximum possible number of `Transfer` objects. + for _ in 0..spec.max_transfers { + let validator_index = validators_iter.next().expect("Insufficient validators."); + + // Manually set the validator to be withdrawn. + state.validator_registry[validator_index as usize].withdrawable_epoch = + state.previous_epoch(spec); + + builder.insert_transfer( + state, + validator_index, + validator_index, + 1, + keypairs[validator_index as usize].clone(), + spec, + ); + } + builder.build(&keypair.sk, &state.fork, spec) } diff --git a/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs b/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs index 5e7fddb558..3eafdc0c9a 100644 --- a/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs +++ b/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs @@ -1,8 +1,13 @@ use rayon::prelude::*; use ssz::{SignedRoot, TreeHash}; use types::{ - attester_slashing::AttesterSlashingBuilder, proposer_slashing::ProposerSlashingBuilder, - test_utils::TestingAttestationBuilder, *, + attester_slashing::AttesterSlashingBuilder, + proposer_slashing::ProposerSlashingBuilder, + test_utils::{ + TestingAttestationBuilder, TestingDepositBuilder, TestingTransferBuilder, + TestingVoluntaryExitBuilder, + }, + *, }; pub struct BeaconBlockBencher { @@ -145,6 +150,54 @@ impl BeaconBlockBencher { Ok(()) } + /// Insert a `Valid` deposit into the state. + pub fn insert_deposit(&mut self, amount: u64, index: u64, spec: &ChainSpec) { + let keypair = Keypair::random(); + + let mut builder = TestingDepositBuilder::new(amount); + builder.set_index(index); + builder.sign(&keypair, spec); + + self.block.body.deposits.push(builder.build()) + } + + /// Insert a `Valid` exit into the state. + pub fn insert_exit( + &mut self, + state: &BeaconState, + validator_index: u64, + secret_key: &SecretKey, + spec: &ChainSpec, + ) { + let mut builder = TestingVoluntaryExitBuilder::new( + state.slot.epoch(spec.slots_per_epoch), + validator_index, + ); + + builder.sign(secret_key, &state.fork, spec); + + self.block.body.voluntary_exits.push(builder.build()) + } + + /// Insert a `Valid` transfer into the state. + /// + /// Note: this will set the validator to be withdrawable by directly modifying the state + /// validator registry. This _may_ cause problems historic hashes, etc. + pub fn insert_transfer( + &mut self, + state: &BeaconState, + from: u64, + to: u64, + amount: u64, + keypair: Keypair, + spec: &ChainSpec, + ) { + let mut builder = TestingTransferBuilder::new(from, to, amount, state.slot); + builder.sign(keypair, &state.fork, spec); + + self.block.body.transfers.push(builder.build()) + } + /// Signs and returns the block, consuming the builder. pub fn build(mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) -> BeaconBlock { self.sign(sk, fork, spec); diff --git a/eth2/types/src/test_utils/mod.rs b/eth2/types/src/test_utils/mod.rs index 6138940a26..2145f684ad 100644 --- a/eth2/types/src/test_utils/mod.rs +++ b/eth2/types/src/test_utils/mod.rs @@ -1,6 +1,12 @@ mod test_random; mod testing_attestation_builder; +mod testing_deposit_builder; +mod testing_transfer_builder; +mod testing_voluntary_exit_builder; pub use rand::{prng::XorShiftRng, SeedableRng}; pub use test_random::TestRandom; pub use testing_attestation_builder::TestingAttestationBuilder; +pub use testing_deposit_builder::TestingDepositBuilder; +pub use testing_transfer_builder::TestingTransferBuilder; +pub use testing_voluntary_exit_builder::TestingVoluntaryExitBuilder; diff --git a/eth2/types/src/test_utils/testing_deposit_builder.rs b/eth2/types/src/test_utils/testing_deposit_builder.rs new file mode 100644 index 0000000000..c7eadcfd18 --- /dev/null +++ b/eth2/types/src/test_utils/testing_deposit_builder.rs @@ -0,0 +1,48 @@ +use crate::*; +use bls::{create_proof_of_possession, get_withdrawal_credentials}; + +pub struct TestingDepositBuilder { + deposit: Deposit, +} + +impl TestingDepositBuilder { + pub fn new(amount: u64) -> Self { + let keypair = Keypair::random(); + + let deposit = Deposit { + branch: vec![], + index: 0, + deposit_data: DepositData { + amount, + timestamp: 1, + deposit_input: DepositInput { + pubkey: keypair.pk, + withdrawal_credentials: Hash256::zero(), + proof_of_possession: Signature::empty_signature(), + }, + }, + }; + + Self { deposit } + } + + pub fn set_index(&mut self, index: u64) { + self.deposit.index = index; + } + + pub fn sign(&mut self, keypair: &Keypair, spec: &ChainSpec) { + self.deposit.deposit_data.deposit_input.pubkey = keypair.pk.clone(); + self.deposit.deposit_data.deposit_input.proof_of_possession = + create_proof_of_possession(&keypair); + self.deposit + .deposit_data + .deposit_input + .withdrawal_credentials = Hash256::from_slice( + &get_withdrawal_credentials(&keypair.pk, spec.bls_withdrawal_prefix_byte)[..], + ); + } + + pub fn build(self) -> Deposit { + self.deposit + } +} diff --git a/eth2/types/src/test_utils/testing_transfer_builder.rs b/eth2/types/src/test_utils/testing_transfer_builder.rs new file mode 100644 index 0000000000..a8a6d7a17c --- /dev/null +++ b/eth2/types/src/test_utils/testing_transfer_builder.rs @@ -0,0 +1,37 @@ +use crate::*; +use ssz::SignedRoot; + +pub struct TestingTransferBuilder { + transfer: Transfer, +} + +impl TestingTransferBuilder { + pub fn new(from: u64, to: u64, amount: u64, slot: Slot) -> Self { + let keypair = Keypair::random(); + + let mut transfer = Transfer { + from, + to, + amount, + fee: 0, + slot, + pubkey: keypair.pk, + signature: Signature::empty_signature(), + }; + + Self { transfer } + } + + pub fn sign(&mut self, keypair: Keypair, fork: &Fork, spec: &ChainSpec) { + self.transfer.pubkey = keypair.pk; + let message = self.transfer.signed_root(); + let epoch = self.transfer.slot.epoch(spec.slots_per_epoch); + let domain = spec.get_domain(epoch, Domain::Transfer, fork); + + self.transfer.signature = Signature::new(&message, domain, &keypair.sk); + } + + pub fn build(self) -> Transfer { + self.transfer + } +} diff --git a/eth2/types/src/test_utils/testing_voluntary_exit_builder.rs b/eth2/types/src/test_utils/testing_voluntary_exit_builder.rs new file mode 100644 index 0000000000..92ef4484ea --- /dev/null +++ b/eth2/types/src/test_utils/testing_voluntary_exit_builder.rs @@ -0,0 +1,29 @@ +use crate::*; +use ssz::SignedRoot; + +pub struct TestingVoluntaryExitBuilder { + exit: VoluntaryExit, +} + +impl TestingVoluntaryExitBuilder { + pub fn new(epoch: Epoch, validator_index: u64) -> Self { + let exit = VoluntaryExit { + epoch, + validator_index, + signature: Signature::empty_signature(), + }; + + Self { exit } + } + + pub fn sign(&mut self, secret_key: &SecretKey, fork: &Fork, spec: &ChainSpec) { + let message = self.exit.signed_root(); + let domain = spec.get_domain(self.exit.epoch, Domain::Exit, fork); + + self.exit.signature = Signature::new(&message, domain, secret_key); + } + + pub fn build(self) -> VoluntaryExit { + self.exit + } +} From 90d00773cb0af9bf6cfd6656a317df17d8f6165e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 10 Mar 2019 08:30:36 +1100 Subject: [PATCH 034/144] Add slashings back into per-block processing. I thought they would invalidate the attestations but I was wrong. --- .../benches/block_processing_benches.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/eth2/state_processing/benches/block_processing_benches.rs b/eth2/state_processing/benches/block_processing_benches.rs index 2ff2e7413f..26c62d0d3d 100644 --- a/eth2/state_processing/benches/block_processing_benches.rs +++ b/eth2/state_processing/benches/block_processing_benches.rs @@ -275,10 +275,7 @@ fn bench_block_processing( ); let state = initial_state.clone(); - let mut block = initial_block.clone(); - // Slashings will invalidate the attestations. - block.body.proposer_slashings = vec![]; - block.body.attester_slashings = vec![]; + let block = initial_block.clone(); let spec = initial_spec.clone(); c.bench( &format!("block_processing_{}", desc), @@ -346,14 +343,11 @@ fn bench_block_processing( ); let state = initial_state.clone(); - let mut block = initial_block.clone(); - // Slashings will invalidate the attestations. - block.body.proposer_slashings = vec![]; - block.body.attester_slashings = vec![]; + let block = initial_block.clone(); let spec = initial_spec.clone(); c.bench( &format!("block_processing_{}", desc), - Benchmark::new("per_block_processing_no_slashings", move |b| { + Benchmark::new("per_block_processing", move |b| { b.iter_with_setup( || state.clone(), |mut state| black_box(per_block_processing(&mut state, &block, &spec).unwrap()), From 5f3da0732f076d62278f88f15b468c5012a21836 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 10 Mar 2019 08:31:40 +1100 Subject: [PATCH 035/144] Fix attestations bug in block builder. It was previously producing too many attestations in some scenarios. --- .../benching_utils/src/beacon_block_bencher.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs b/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs index 3eafdc0c9a..46e822baa8 100644 --- a/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs +++ b/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs @@ -92,18 +92,20 @@ impl BeaconBlockBencher { // - The slot is too old to be included in a block at this slot. // - The `MAX_ATTESTATIONS`. loop { - if attestations_added == spec.max_attestations { - break; - } if state.slot >= slot + spec.slots_per_epoch { break; } for (committee, shard) in state.get_crosslink_committees_at_slot(slot, spec)? { - committees.push((slot, committee.clone(), committee.clone(), *shard)) + if attestations_added >= spec.max_attestations { + break; + } + + committees.push((slot, committee.clone(), committee.clone(), *shard)); + + attestations_added += 1; } - attestations_added += 1; slot -= 1; } From 1ca99b8c4c7d9afc5602e32e9c689583bdeb3b56 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 10 Mar 2019 08:33:17 +1100 Subject: [PATCH 036/144] Optimise deposits processing. --- .../src/per_block_processing.rs | 129 +++++++++++++----- .../src/per_block_processing/errors.rs | 5 + .../verify_attester_slashing.rs | 19 ++- .../per_block_processing/verify_deposit.rs | 61 ++++++++- eth2/types/src/beacon_state.rs | 6 +- 5 files changed, 182 insertions(+), 38 deletions(-) diff --git a/eth2/state_processing/src/per_block_processing.rs b/eth2/state_processing/src/per_block_processing.rs index 149e0bf790..c446dcd853 100644 --- a/eth2/state_processing/src/per_block_processing.rs +++ b/eth2/state_processing/src/per_block_processing.rs @@ -1,12 +1,17 @@ use self::verify_proposer_slashing::verify_proposer_slashing; use errors::{BlockInvalid as Invalid, BlockProcessingError as Error, IntoWithIndex}; use hashing::hash; +use rayon::prelude::*; use ssz::{ssz_encode, SignedRoot, TreeHash}; use types::*; -pub use self::verify_attester_slashing::verify_attester_slashing; +pub use self::verify_attester_slashing::{ + gather_attester_slashing_indices, verify_attester_slashing, +}; pub use validate_attestation::{validate_attestation, validate_attestation_without_signature}; -pub use verify_deposit::verify_deposit; +pub use verify_deposit::{ + build_public_key_hashmap, get_existing_validator_index, verify_deposit, verify_deposit_index, +}; pub use verify_exit::verify_exit; pub use verify_transfer::{execute_transfer, verify_transfer}; @@ -226,9 +231,17 @@ pub fn process_proposer_slashings( proposer_slashings.len() as u64 <= spec.max_proposer_slashings, Invalid::MaxProposerSlashingsExceeded ); - for (i, proposer_slashing) in proposer_slashings.iter().enumerate() { - verify_proposer_slashing(proposer_slashing, &state, spec) - .map_err(|e| e.into_with_index(i))?; + + // Verify proposer slashings in parallel. + proposer_slashings + .par_iter() + .enumerate() + .try_for_each(|(i, proposer_slashing)| { + verify_proposer_slashing(proposer_slashing, &state, spec) + .map_err(|e| e.into_with_index(i)) + })?; + + for proposer_slashing in proposer_slashings { state.slash_validator(proposer_slashing.proposer_index as usize, spec)?; } @@ -250,8 +263,19 @@ pub fn process_attester_slashings( attester_slashings.len() as u64 <= spec.max_attester_slashings, Invalid::MaxAttesterSlashingsExceed ); + + // Verify attester slashings in parallel. + attester_slashings + .par_iter() + .enumerate() + .try_for_each(|(i, attester_slashing)| { + verify_attester_slashing(&state, &attester_slashing, spec) + .map_err(|e| e.into_with_index(i)) + })?; + + // Gather the slashable indices and update the state in series. for (i, attester_slashing) in attester_slashings.iter().enumerate() { - let slashable_indices = verify_attester_slashing(&state, &attester_slashing, spec) + let slashable_indices = gather_attester_slashing_indices(&state, &attester_slashing) .map_err(|e| e.into_with_index(i))?; for i in slashable_indices { state.slash_validator(i as usize, spec)?; @@ -276,14 +300,20 @@ pub fn process_attestations( attestations.len() as u64 <= spec.max_attestations, Invalid::MaxAttestationsExceeded ); - for (i, attestation) in attestations.iter().enumerate() { - // Build the previous epoch cache only if required by an attestation. - if attestation.data.slot.epoch(spec.slots_per_epoch) == state.previous_epoch(spec) { - state.build_epoch_cache(RelativeEpoch::Previous, spec)?; - } - validate_attestation(state, attestation, spec).map_err(|e| e.into_with_index(i))?; + // Ensure the previous epoch cache exists. + state.build_epoch_cache(RelativeEpoch::Previous, spec)?; + // Verify attestations in parallel. + attestations + .par_iter() + .enumerate() + .try_for_each(|(i, attestation)| { + validate_attestation(state, attestation, spec).map_err(|e| e.into_with_index(i)) + })?; + + // Update the state in series. + for attestation in attestations { let pending_attestation = PendingAttestation { data: attestation.data.clone(), aggregation_bitfield: attestation.aggregation_bitfield.clone(), @@ -311,24 +341,53 @@ pub fn process_deposits( deposits.len() as u64 <= spec.max_deposits, Invalid::MaxDepositsExceeded ); - for (i, deposit) in deposits.iter().enumerate() { - verify_deposit(state, deposit, VERIFY_DEPOSIT_MERKLE_PROOFS, spec) - .map_err(|e| e.into_with_index(i))?; - state - .process_deposit( - deposit.deposit_data.deposit_input.pubkey.clone(), - deposit.deposit_data.amount, - deposit - .deposit_data - .deposit_input - .proof_of_possession - .clone(), - deposit.deposit_data.deposit_input.withdrawal_credentials, - None, - spec, - ) - .map_err(|_| Error::Invalid(Invalid::DepositProcessingFailed(i)))?; + // Verify deposits in parallel. + deposits + .par_iter() + .enumerate() + .try_for_each(|(i, deposit)| { + verify_deposit(state, deposit, VERIFY_DEPOSIT_MERKLE_PROOFS, spec) + .map_err(|e| e.into_with_index(i)) + })?; + + let public_key_to_index_hashmap = build_public_key_hashmap(&state); + + // Check `state.deposit_index` and update the state in series. + for (i, deposit) in deposits.iter().enumerate() { + verify_deposit_index(state, deposit).map_err(|e| e.into_with_index(i))?; + + // Get an `Option` where `u64` is the validator index if this deposit public key + // already exists in the beacon_state. + // + // This function also verifies the withdrawal credentials. + let validator_index = + get_existing_validator_index(state, deposit, &public_key_to_index_hashmap) + .map_err(|e| e.into_with_index(i))?; + + let deposit_data = &deposit.deposit_data; + let deposit_input = &deposit.deposit_data.deposit_input; + + if let Some(index) = validator_index { + // Update the existing validator balance. + safe_add_assign!( + state.validator_balances[index as usize], + deposit_data.amount + ); + } else { + // Create a new validator. + let validator = Validator { + pubkey: deposit_input.pubkey.clone(), + withdrawal_credentials: deposit_input.withdrawal_credentials.clone(), + activation_epoch: spec.far_future_epoch, + exit_epoch: spec.far_future_epoch, + withdrawable_epoch: spec.far_future_epoch, + initiated_exit: false, + slashed: false, + }; + state.validator_registry.push(validator); + state.validator_balances.push(deposit_data.amount); + } state.deposit_index += 1; } @@ -351,9 +410,17 @@ pub fn process_exits( voluntary_exits.len() as u64 <= spec.max_voluntary_exits, Invalid::MaxExitsExceeded ); - for (i, exit) in voluntary_exits.iter().enumerate() { - verify_exit(&state, exit, spec).map_err(|e| e.into_with_index(i))?; + // Verify exits in parallel. + voluntary_exits + .par_iter() + .enumerate() + .try_for_each(|(i, exit)| { + verify_exit(&state, exit, spec).map_err(|e| e.into_with_index(i)) + })?; + + // Update the state in series. + for exit in voluntary_exits { state.initiate_validator_exit(exit.validator_index as usize); } diff --git a/eth2/state_processing/src/per_block_processing/errors.rs b/eth2/state_processing/src/per_block_processing/errors.rs index 7e71a9b75b..f64a3f8aa3 100644 --- a/eth2/state_processing/src/per_block_processing/errors.rs +++ b/eth2/state_processing/src/per_block_processing/errors.rs @@ -294,6 +294,11 @@ pub enum DepositInvalid { /// /// (state_index, deposit_index) BadIndex(u64, u64), + /// The proof-of-possession does not match the given pubkey. + BadProofOfPossession, + /// The withdrawal credentials for the depositing validator did not match the withdrawal + /// credentials of an existing validator with the same public key. + BadWithdrawalCredentials, /// The specified `branch` and `index` did not form a valid proof that the deposit is included /// in the eth1 deposit root. BadMerkleProof, diff --git a/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs b/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs index 71ac97469b..2970712c51 100644 --- a/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs +++ b/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs @@ -12,7 +12,7 @@ pub fn verify_attester_slashing( state: &BeaconState, attester_slashing: &AttesterSlashing, spec: &ChainSpec, -) -> Result, Error> { +) -> Result<(), Error> { let slashable_attestation_1 = &attester_slashing.slashable_attestation_1; let slashable_attestation_2 = &attester_slashing.slashable_attestation_2; @@ -31,6 +31,21 @@ pub fn verify_attester_slashing( verify_slashable_attestation(state, &slashable_attestation_2, spec) .map_err(|e| Error::Invalid(Invalid::SlashableAttestation2Invalid(e.into())))?; + Ok(()) +} + +/// For a given attester slashing, return the indices able to be slashed. +/// +/// Returns Ok(indices) if `indices.len() > 0`. +/// +/// Spec v0.4.0 +pub fn gather_attester_slashing_indices( + state: &BeaconState, + attester_slashing: &AttesterSlashing, +) -> Result, Error> { + let slashable_attestation_1 = &attester_slashing.slashable_attestation_1; + let slashable_attestation_2 = &attester_slashing.slashable_attestation_2; + let mut slashable_indices = vec![]; for i in &slashable_attestation_1.validator_indices { let validator = state @@ -38,7 +53,7 @@ pub fn verify_attester_slashing( .get(*i as usize) .ok_or_else(|| Error::Invalid(Invalid::UnknownValidator(*i)))?; - if slashable_attestation_1.validator_indices.contains(&i) & !validator.slashed { + if slashable_attestation_2.validator_indices.contains(&i) & !validator.slashed { slashable_indices.push(*i); } } diff --git a/eth2/state_processing/src/per_block_processing/verify_deposit.rs b/eth2/state_processing/src/per_block_processing/verify_deposit.rs index 69dae1533d..0cf2a078f9 100644 --- a/eth2/state_processing/src/per_block_processing/verify_deposit.rs +++ b/eth2/state_processing/src/per_block_processing/verify_deposit.rs @@ -1,15 +1,22 @@ use super::errors::{DepositInvalid as Invalid, DepositValidationError as Error}; +use bls::verify_proof_of_possession; use hashing::hash; use merkle_proof::verify_merkle_proof; use ssz::ssz_encode; use ssz_derive::Encode; +use std::collections::HashMap; use types::*; +pub type PublicKeyValidatorIndexHashmap = HashMap; + /// Indicates if a `Deposit` is valid to be included in a block in the current epoch of the given /// state. /// /// Returns `Ok(())` if the `Deposit` is valid, otherwise indicates the reason for invalidity. /// +/// This function _does not_ check `state.deposit_index` so this function may be run in parallel. +/// See the `verify_deposit_index` function for this. +/// /// Note: this function is incomplete. /// /// Spec v0.4.0 @@ -20,8 +27,14 @@ pub fn verify_deposit( spec: &ChainSpec, ) -> Result<(), Error> { verify!( - deposit.index == state.deposit_index, - Invalid::BadIndex(state.deposit_index, deposit.index) + // TODO: update proof of possession. + // + // https://github.com/sigp/lighthouse/issues/239 + verify_proof_of_possession( + &deposit.deposit_data.deposit_input.proof_of_possession, + &deposit.deposit_data.deposit_input.pubkey + ), + Invalid::BadProofOfPossession ); if verify_merkle_branch { @@ -34,6 +47,50 @@ pub fn verify_deposit( Ok(()) } +/// Verify that the `Deposit` index is correct. +/// +/// Spec v0.4.0 +pub fn verify_deposit_index(state: &BeaconState, deposit: &Deposit) -> Result<(), Error> { + verify!( + deposit.index == state.deposit_index, + Invalid::BadIndex(state.deposit_index, deposit.index) + ); + + Ok(()) +} + +pub fn build_public_key_hashmap(state: &BeaconState) -> PublicKeyValidatorIndexHashmap { + let mut hashmap = HashMap::with_capacity(state.validator_registry.len()); + + for (i, validator) in state.validator_registry.iter().enumerate() { + hashmap.insert(validator.pubkey.clone(), i as u64); + } + + hashmap +} + +pub fn get_existing_validator_index( + state: &BeaconState, + deposit: &Deposit, + pubkey_map: &HashMap, +) -> Result, Error> { + let deposit_input = &deposit.deposit_data.deposit_input; + + let validator_index = pubkey_map.get(&deposit_input.pubkey).and_then(|i| Some(*i)); + + match validator_index { + None => Ok(None), + Some(index) => { + verify!( + deposit_input.withdrawal_credentials + == state.validator_registry[index as usize].withdrawal_credentials, + Invalid::BadWithdrawalCredentials + ); + Ok(Some(index)) + } + } +} + /// Verify that a deposit is included in the state's eth1 deposit root. /// /// Spec v0.4.0 diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index f3d5335271..39970b9a7b 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -608,6 +608,9 @@ impl BeaconState { /// this hashmap, each call to `process_deposits` requires an iteration though /// `self.validator_registry`. This becomes highly inefficient at scale. /// + /// TODO: this function also exists in a more optimal form in the `state_processing` crate as + /// `process_deposits`; unify these two functions. + /// /// Spec v0.4.0 pub fn process_deposit( &mut self, @@ -618,10 +621,7 @@ impl BeaconState { pubkey_map: Option<&HashMap>, spec: &ChainSpec, ) -> Result { - // TODO: update proof of possession to function written above ( - // requires bls::create_proof_of_possession to be updated // - // https://github.com/sigp/lighthouse/issues/239 if !verify_proof_of_possession(&proof_of_possession, &pubkey) { return Err(()); } From e7fba3a4735842b73e4c8a54e8f0119f5127ef3e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 10 Mar 2019 08:36:49 +1100 Subject: [PATCH 037/144] Process transfers in parallel. --- eth2/state_processing/src/per_block_processing.rs | 9 ++++++++- eth2/types/src/test_utils/testing_transfer_builder.rs | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/eth2/state_processing/src/per_block_processing.rs b/eth2/state_processing/src/per_block_processing.rs index c446dcd853..d871914d98 100644 --- a/eth2/state_processing/src/per_block_processing.rs +++ b/eth2/state_processing/src/per_block_processing.rs @@ -442,8 +442,15 @@ pub fn process_transfers( transfers.len() as u64 <= spec.max_transfers, Invalid::MaxTransfersExceed ); + + transfers + .par_iter() + .enumerate() + .try_for_each(|(i, transfer)| { + verify_transfer(&state, transfer, spec).map_err(|e| e.into_with_index(i)) + })?; + for (i, transfer) in transfers.iter().enumerate() { - verify_transfer(&state, transfer, spec).map_err(|e| e.into_with_index(i))?; execute_transfer(state, transfer, spec).map_err(|e| e.into_with_index(i))?; } diff --git a/eth2/types/src/test_utils/testing_transfer_builder.rs b/eth2/types/src/test_utils/testing_transfer_builder.rs index a8a6d7a17c..c343e8fd25 100644 --- a/eth2/types/src/test_utils/testing_transfer_builder.rs +++ b/eth2/types/src/test_utils/testing_transfer_builder.rs @@ -9,7 +9,7 @@ impl TestingTransferBuilder { pub fn new(from: u64, to: u64, amount: u64, slot: Slot) -> Self { let keypair = Keypair::random(); - let mut transfer = Transfer { + let transfer = Transfer { from, to, amount, From 3f988493622ab0221649b91682e4ad8296f86542 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 10 Mar 2019 08:55:45 +1100 Subject: [PATCH 038/144] Optimise attester slashing processing. --- .../src/per_block_processing.rs | 32 ++++++++++++++++--- .../src/per_block_processing/errors.rs | 9 ++++++ .../verify_attester_slashing.rs | 11 ++++--- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/eth2/state_processing/src/per_block_processing.rs b/eth2/state_processing/src/per_block_processing.rs index d871914d98..7b5aafa7f9 100644 --- a/eth2/state_processing/src/per_block_processing.rs +++ b/eth2/state_processing/src/per_block_processing.rs @@ -13,6 +13,7 @@ pub use verify_deposit::{ build_public_key_hashmap, get_existing_validator_index, verify_deposit, verify_deposit_index, }; pub use verify_exit::verify_exit; +pub use verify_slashable_attestation::verify_slashable_attestation; pub use verify_transfer::{execute_transfer, verify_transfer}; pub mod errors; @@ -264,19 +265,40 @@ pub fn process_attester_slashings( Invalid::MaxAttesterSlashingsExceed ); - // Verify attester slashings in parallel. - attester_slashings + // Verify the `SlashableAttestation`s in parallel (these are the resource-consuming objects, not + // the `AttesterSlashing`s themselves). + let mut slashable_attestations: Vec<&SlashableAttestation> = + Vec::with_capacity(attester_slashings.len() * 2); + for attester_slashing in attester_slashings { + slashable_attestations.push(&attester_slashing.slashable_attestation_1); + slashable_attestations.push(&attester_slashing.slashable_attestation_2); + } + + // Verify slashable attestations in parallel. + slashable_attestations .par_iter() .enumerate() - .try_for_each(|(i, attester_slashing)| { - verify_attester_slashing(&state, &attester_slashing, spec) + .try_for_each(|(i, slashable_attestation)| { + verify_slashable_attestation(&state, slashable_attestation, spec) .map_err(|e| e.into_with_index(i)) })?; + let all_slashable_attestations_have_been_checked = true; - // Gather the slashable indices and update the state in series. + // Gather the slashable indices and preform the final verification and update the state in series. for (i, attester_slashing) in attester_slashings.iter().enumerate() { + let should_verify_slashable_attestations = !all_slashable_attestations_have_been_checked; + + verify_attester_slashing( + &state, + &attester_slashing, + should_verify_slashable_attestations, + spec, + ) + .map_err(|e| e.into_with_index(i))?; + let slashable_indices = gather_attester_slashing_indices(&state, &attester_slashing) .map_err(|e| e.into_with_index(i))?; + for i in slashable_indices { state.slash_validator(i as usize, spec)?; } diff --git a/eth2/state_processing/src/per_block_processing/errors.rs b/eth2/state_processing/src/per_block_processing/errors.rs index f64a3f8aa3..a3e3ebad1a 100644 --- a/eth2/state_processing/src/per_block_processing/errors.rs +++ b/eth2/state_processing/src/per_block_processing/errors.rs @@ -76,6 +76,10 @@ pub enum BlockInvalid { MaxExitsExceeded, MaxTransfersExceed, AttestationInvalid(usize, AttestationInvalid), + /// A `SlashableAttestation` inside an `AttesterSlashing` was invalid. + /// + /// To determine the offending `AttesterSlashing` index, divide the error message `usize` by two. + SlashableAttestationInvalid(usize, SlashableAttestationInvalid), AttesterSlashingInvalid(usize, AttesterSlashingInvalid), ProposerSlashingInvalid(usize, ProposerSlashingInvalid), DepositInvalid(usize, DepositInvalid), @@ -235,6 +239,11 @@ impl Into for SlashableAttestationValidationError { } } +impl_into_with_index_without_beacon_error!( + SlashableAttestationValidationError, + SlashableAttestationInvalid +); + /* * `ProposerSlashing` Validation */ diff --git a/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs b/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs index 2970712c51..d126849b63 100644 --- a/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs +++ b/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs @@ -11,6 +11,7 @@ use types::*; pub fn verify_attester_slashing( state: &BeaconState, attester_slashing: &AttesterSlashing, + should_verify_slashable_attestations: bool, spec: &ChainSpec, ) -> Result<(), Error> { let slashable_attestation_1 = &attester_slashing.slashable_attestation_1; @@ -26,10 +27,12 @@ pub fn verify_attester_slashing( Invalid::NotSlashable ); - verify_slashable_attestation(state, &slashable_attestation_1, spec) - .map_err(|e| Error::Invalid(Invalid::SlashableAttestation1Invalid(e.into())))?; - verify_slashable_attestation(state, &slashable_attestation_2, spec) - .map_err(|e| Error::Invalid(Invalid::SlashableAttestation2Invalid(e.into())))?; + if should_verify_slashable_attestations { + verify_slashable_attestation(state, &slashable_attestation_1, spec) + .map_err(|e| Error::Invalid(Invalid::SlashableAttestation1Invalid(e.into())))?; + verify_slashable_attestation(state, &slashable_attestation_2, spec) + .map_err(|e| Error::Invalid(Invalid::SlashableAttestation2Invalid(e.into())))?; + } Ok(()) } From c33e2991760af6aedf58770140d72e31e5c8e010 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 10 Mar 2019 12:56:37 +1100 Subject: [PATCH 039/144] Ensure epoch processing benches get new eth1 data --- eth2/state_processing/benches/block_processing_benches.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/eth2/state_processing/benches/block_processing_benches.rs b/eth2/state_processing/benches/block_processing_benches.rs index 26c62d0d3d..755207f968 100644 --- a/eth2/state_processing/benches/block_processing_benches.rs +++ b/eth2/state_processing/benches/block_processing_benches.rs @@ -174,7 +174,12 @@ fn build_block(state: &mut BeaconState, keypairs: &[Keypair], spec: &ChainSpec) ); } - builder.build(&keypair.sk, &state.fork, spec) + let mut block = builder.build(&keypair.sk, &state.fork, spec); + + // Set the eth1 data to be different from the state. + block.eth1_data.block_hash = Hash256::from_slice(&vec![42; 32]); + + block } /// Run the detailed benchmarking suite on the given `BeaconState`. From e99da31da8be15b4e8a33d76a3334dcb9ca7b3fc Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 10 Mar 2019 12:56:57 +1100 Subject: [PATCH 040/144] Tidy BeaconStateBuilder struct --- eth2/types/src/beacon_state/builder.rs | 153 +------------------------ 1 file changed, 4 insertions(+), 149 deletions(-) diff --git a/eth2/types/src/beacon_state/builder.rs b/eth2/types/src/beacon_state/builder.rs index f6d7b39009..372f0d43d1 100644 --- a/eth2/types/src/beacon_state/builder.rs +++ b/eth2/types/src/beacon_state/builder.rs @@ -1,24 +1,14 @@ use super::BeaconStateError; +use crate::validator_registry::get_active_validator_indices; use crate::*; -use crate::{validator_registry::get_active_validator_indices, *}; -use bls::create_proof_of_possession; use rayon::prelude::*; use ssz::TreeHash; -/// Builds a `BeaconState` for use in testing or benchmarking. +/// Builds a `BeaconState` for use in production. /// -/// Building the `BeaconState` is a three step processes: +/// This struct should not be modified for use in testing scenarios. Use `TestingBeaconStateBuilder` for that purpose. /// -/// 1. Create a new `BeaconStateBuilder`. -/// 2. Call `Self::build()` or `Self::build_fast()` generate a `BeaconState`. -/// 3. (Optional) Use builder functions to modify the `BeaconState`. -/// 4. Call `Self::cloned_state()` to obtain a `BeaconState` cloned from this struct. -/// -/// Step (2) happens prior to step (3) because some functionality requires an existing -/// `BeaconState`. -/// -/// Step (4) produces a clone of the BeaconState and doesn't consume the `BeaconStateBuilder` to -/// allow access to `self.keypairs` and `self.spec`. +/// This struct should remain safe and sensible for production usage. pub struct BeaconStateBuilder { pub state: BeaconState, } @@ -105,139 +95,4 @@ impl BeaconStateBuilder { Ok(self.state) } - - /* - /// Sets the `BeaconState` to be in the last slot of the given epoch. - /// - /// Sets all justification/finalization parameters to be be as "perfect" as possible (i.e., - /// highest justified and finalized slots, full justification bitfield, etc). - pub fn teleport_to_end_of_epoch(&mut self, epoch: Epoch, spec: &ChainSpec) { - let state = &mut self.state; - - let slot = epoch.end_slot(spec.slots_per_epoch); - - state.slot = slot; - state.validator_registry_update_epoch = epoch - 1; - - state.previous_shuffling_epoch = epoch - 1; - state.current_shuffling_epoch = epoch; - - state.previous_shuffling_seed = Hash256::from_low_u64_le(0); - state.current_shuffling_seed = Hash256::from_low_u64_le(1); - - state.previous_justified_epoch = epoch - 2; - state.justified_epoch = epoch - 1; - state.justification_bitfield = u64::max_value(); - state.finalized_epoch = epoch - 1; - } - - /// Creates a full set of attestations for the `BeaconState`. Each attestation has full - /// participation from its committee and references the expected beacon_block hashes. - /// - /// These attestations should be fully conducive to justification and finalization. - pub fn insert_attestations(&mut self) { - let state = &mut self.state; - - state - .build_epoch_cache(RelativeEpoch::Previous, &self.spec) - .unwrap(); - state - .build_epoch_cache(RelativeEpoch::Current, &self.spec) - .unwrap(); - - let current_epoch = state.current_epoch(&self.spec); - let previous_epoch = state.previous_epoch(&self.spec); - let current_epoch_depth = - (state.slot - current_epoch.end_slot(self.spec.slots_per_epoch)).as_usize(); - - let previous_epoch_slots = previous_epoch.slot_iter(self.spec.slots_per_epoch); - let current_epoch_slots = current_epoch - .slot_iter(self.spec.slots_per_epoch) - .take(current_epoch_depth); - - for slot in previous_epoch_slots.chain(current_epoch_slots) { - let committees = state - .get_crosslink_committees_at_slot(slot, &self.spec) - .unwrap() - .clone(); - - for (committee, shard) in committees { - state - .latest_attestations - .push(committee_to_pending_attestation( - state, &committee, shard, slot, &self.spec, - )) - } - } - } - - /// Returns a cloned `BeaconState`. - pub fn cloned_state(&self) -> BeaconState { - self.state.as_ref().expect("Genesis required").clone() - } - */ } - -/* -/// Builds a valid PendingAttestation with full participation for some committee. -fn committee_to_pending_attestation( - state: &BeaconState, - committee: &[usize], - shard: u64, - slot: Slot, - spec: &ChainSpec, -) -> PendingAttestation { - let current_epoch = state.current_epoch(spec); - let previous_epoch = state.previous_epoch(spec); - - let mut aggregation_bitfield = Bitfield::new(); - let mut custody_bitfield = Bitfield::new(); - - for (i, _) in committee.iter().enumerate() { - aggregation_bitfield.set(i, true); - custody_bitfield.set(i, true); - } - - let is_previous_epoch = - state.slot.epoch(spec.slots_per_epoch) != slot.epoch(spec.slots_per_epoch); - - let justified_epoch = if is_previous_epoch { - state.previous_justified_epoch - } else { - state.justified_epoch - }; - - let epoch_boundary_root = if is_previous_epoch { - *state - .get_block_root(previous_epoch.start_slot(spec.slots_per_epoch), spec) - .unwrap() - } else { - *state - .get_block_root(current_epoch.start_slot(spec.slots_per_epoch), spec) - .unwrap() - }; - - let justified_block_root = *state - .get_block_root(justified_epoch.start_slot(spec.slots_per_epoch), &spec) - .unwrap(); - - PendingAttestation { - aggregation_bitfield, - data: AttestationData { - slot, - shard, - beacon_block_root: *state.get_block_root(slot, spec).unwrap(), - epoch_boundary_root, - crosslink_data_root: Hash256::zero(), - latest_crosslink: Crosslink { - epoch: slot.epoch(spec.slots_per_epoch), - crosslink_data_root: Hash256::zero(), - }, - justified_epoch, - justified_block_root, - }, - custody_bitfield, - inclusion_slot: slot, - } -} -*/ From 89fc386264e9fff71d6769d1adbc68c3c02afa92 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 10 Mar 2019 13:38:57 +1100 Subject: [PATCH 041/144] Add extra checks for epoch benches finalization --- .../benches/epoch_processing_benches.rs | 43 ++++++++++++++++--- .../src/beacon_state_bencher.rs | 9 ++-- .../src/per_epoch_processing/attester_sets.rs | 2 +- 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/eth2/state_processing/benches/epoch_processing_benches.rs b/eth2/state_processing/benches/epoch_processing_benches.rs index 149d8f28e7..6f9219658b 100644 --- a/eth2/state_processing/benches/epoch_processing_benches.rs +++ b/eth2/state_processing/benches/epoch_processing_benches.rs @@ -33,7 +33,8 @@ pub fn epoch_processing_16k_validators(c: &mut Criterion) { let (state, _keypairs) = builder.build(); - // Assert that the state has the maximum possible attestations. + // Assert that the state has an attestations for each committee that is able to include an + // attestation in the state. let committees_per_epoch = spec.get_epoch_committee_count(validator_count); let committees_per_slot = committees_per_epoch / spec.slots_per_epoch; let previous_epoch_attestations = committees_per_epoch; @@ -41,18 +42,26 @@ pub fn epoch_processing_16k_validators(c: &mut Criterion) { committees_per_slot * (spec.slots_per_epoch - spec.min_attestation_inclusion_delay); assert_eq!( state.latest_attestations.len() as u64, - previous_epoch_attestations + current_epoch_attestations + previous_epoch_attestations + current_epoch_attestations, + "The state should have an attestation for each committee." ); // Assert that each attestation in the state has full participation. let committee_size = validator_count / committees_per_epoch as usize; for a in &state.latest_attestations { - assert_eq!(a.aggregation_bitfield.num_set_bits(), committee_size); + assert_eq!( + a.aggregation_bitfield.num_set_bits(), + committee_size, + "Each attestation in the state should have full participation" + ); } // Assert that we will run the first arm of process_rewards_and_penalities let epochs_since_finality = state.next_epoch(&spec) - state.finalized_epoch; - assert!(epochs_since_finality <= 4); + assert_eq!( + epochs_since_finality, 4, + "Epochs since finality should be 4" + ); bench_epoch_processing(c, &state, &spec, "16k_validators"); } @@ -239,8 +248,32 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp .sample_size(10), ); - let state_clone = state.clone(); + let mut state_clone = state.clone(); let spec_clone = spec.clone(); + let previous_epoch = state.previous_epoch(&spec); + let attesters = calculate_attester_sets(&state, &spec).unwrap(); + let active_validator_indices = calculate_active_validator_indices(&state, &spec); + let current_total_balance = state.get_total_balance(&active_validator_indices[..], spec); + let previous_total_balance = state.get_total_balance( + &get_active_validator_indices(&state.validator_registry, previous_epoch)[..], + &spec, + ); + assert_eq!( + state_clone.finalized_epoch, state_clone.validator_registry_update_epoch, + "The last registry update should be at the last finalized epoch." + ); + process_justification( + &mut state_clone, + current_total_balance, + previous_total_balance, + attesters.previous_epoch_boundary.balance, + attesters.current_epoch_boundary.balance, + spec, + ); + assert!( + state_clone.finalized_epoch > state_clone.validator_registry_update_epoch, + "The state should have been finalized." + ); c.bench( &format!("epoch_process_with_caches_{}", desc), Benchmark::new("process_validator_registry", move |b| { diff --git a/eth2/state_processing/benching_utils/src/beacon_state_bencher.rs b/eth2/state_processing/benching_utils/src/beacon_state_bencher.rs index 0ee4a75e95..8ad4810e7d 100644 --- a/eth2/state_processing/benching_utils/src/beacon_state_bencher.rs +++ b/eth2/state_processing/benching_utils/src/beacon_state_bencher.rs @@ -96,7 +96,6 @@ impl BeaconStateBencher { let slot = epoch.start_slot(spec.slots_per_epoch); state.slot = slot; - state.validator_registry_update_epoch = epoch - 1; state.previous_shuffling_epoch = epoch - 1; state.current_shuffling_epoch = epoch; @@ -104,10 +103,12 @@ impl BeaconStateBencher { state.previous_shuffling_seed = Hash256::from_low_u64_le(0); state.current_shuffling_seed = Hash256::from_low_u64_le(1); - state.previous_justified_epoch = epoch - 2; - state.justified_epoch = epoch - 1; + state.previous_justified_epoch = epoch - 3; + state.justified_epoch = epoch - 2; state.justification_bitfield = u64::max_value(); - state.finalized_epoch = epoch - 1; + + state.finalized_epoch = epoch - 3; + state.validator_registry_update_epoch = epoch - 3; } /// Creates a full set of attestations for the `BeaconState`. Each attestation has full diff --git a/eth2/state_processing/src/per_epoch_processing/attester_sets.rs b/eth2/state_processing/src/per_epoch_processing/attester_sets.rs index 1252d80572..d82774ac2b 100644 --- a/eth2/state_processing/src/per_epoch_processing/attester_sets.rs +++ b/eth2/state_processing/src/per_epoch_processing/attester_sets.rs @@ -13,7 +13,7 @@ impl Attesters { for i in additional_indices { self.indices.insert(*i); } - self.balance.saturating_add(additional_balance); + self.balance = self.balance.saturating_add(additional_balance); } } From 9cc8e2598fb1ea14e55c72d1e533ecd774217d7d Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 10 Mar 2019 17:48:26 +1100 Subject: [PATCH 042/144] Organise epoch benching file --- .../benches/epoch_processing_benches.rs | 53 ++++++++++--------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/eth2/state_processing/benches/epoch_processing_benches.rs b/eth2/state_processing/benches/epoch_processing_benches.rs index 6f9219658b..19c76fe365 100644 --- a/eth2/state_processing/benches/epoch_processing_benches.rs +++ b/eth2/state_processing/benches/epoch_processing_benches.rs @@ -12,6 +12,9 @@ use state_processing::{ }; use types::{validator_registry::get_active_validator_indices, *}; +pub const BENCHING_SAMPLE_SIZE: usize = 100; +pub const SMALL_BENCHING_SAMPLE_SIZE: usize = 10; + /// Run the benchmarking suite on a foundation spec with 16,384 validators. pub fn epoch_processing_16k_validators(c: &mut Criterion) { let spec = ChainSpec::foundation(); @@ -70,19 +73,6 @@ pub fn epoch_processing_16k_validators(c: &mut Criterion) { /// /// `desc` will be added to the title of each bench. fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSpec, desc: &str) { - let state_clone = state.clone(); - let spec_clone = spec.clone(); - c.bench( - &format!("epoch_process_with_caches_{}", desc), - Benchmark::new("full run", move |b| { - b.iter_with_setup( - || state_clone.clone(), - |mut state| black_box(per_epoch_processing(&mut state, &spec_clone).unwrap()), - ) - }) - .sample_size(10), - ); - let state_clone = state.clone(); let spec_clone = spec.clone(); c.bench( @@ -93,7 +83,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp |mut state| black_box(calculate_active_validator_indices(&mut state, &spec_clone)), ) }) - .sample_size(10), + .sample_size(BENCHING_SAMPLE_SIZE), ); let state_clone = state.clone(); @@ -109,7 +99,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp }, ) }) - .sample_size(10), + .sample_size(BENCHING_SAMPLE_SIZE), ); let state_clone = state.clone(); @@ -130,7 +120,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp }, ) }) - .sample_size(10), + .sample_size(BENCHING_SAMPLE_SIZE), ); let state_clone = state.clone(); @@ -143,7 +133,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp |mut state| black_box(process_eth1_data(&mut state, &spec_clone)), ) }) - .sample_size(10), + .sample_size(BENCHING_SAMPLE_SIZE), ); let state_clone = state.clone(); @@ -156,7 +146,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp |mut state| black_box(calculate_attester_sets(&mut state, &spec_clone).unwrap()), ) }) - .sample_size(10), + .sample_size(BENCHING_SAMPLE_SIZE), ); let state_clone = state.clone(); @@ -199,7 +189,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp |mut state| black_box(process_crosslinks(&mut state, &spec_clone).unwrap()), ) }) - .sample_size(10), + .sample_size(BENCHING_SAMPLE_SIZE), ); let mut state_clone = state.clone(); @@ -232,7 +222,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp }, ) }) - .sample_size(10), + .sample_size(SMALL_BENCHING_SAMPLE_SIZE), ); let state_clone = state.clone(); @@ -245,7 +235,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp |mut state| black_box(state.process_ejections(&spec_clone)), ) }) - .sample_size(10), + .sample_size(BENCHING_SAMPLE_SIZE), ); let mut state_clone = state.clone(); @@ -282,7 +272,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp |mut state| black_box(process_validator_registry(&mut state, &spec_clone)), ) }) - .sample_size(10), + .sample_size(BENCHING_SAMPLE_SIZE), ); let state_clone = state.clone(); @@ -297,7 +287,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp }, ) }) - .sample_size(10), + .sample_size(BENCHING_SAMPLE_SIZE), ); let state_clone = state.clone(); @@ -310,7 +300,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp |mut state| black_box(update_latest_slashed_balances(&mut state, &spec_clone)), ) }) - .sample_size(10), + .sample_size(BENCHING_SAMPLE_SIZE), ); let state_clone = state.clone(); @@ -323,6 +313,19 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp |mut state| black_box(clean_attestations(&mut state, &spec_clone)), ) }) - .sample_size(10), + .sample_size(BENCHING_SAMPLE_SIZE), + ); + + let state_clone = state.clone(); + let spec_clone = spec.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("per_epoch_processing", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |mut state| black_box(per_epoch_processing(&mut state, &spec_clone).unwrap()), + ) + }) + .sample_size(SMALL_BENCHING_SAMPLE_SIZE), ); } From f27b62d410397b5863e763772a4cf47e5498c4df Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 10 Mar 2019 17:49:06 +1100 Subject: [PATCH 043/144] Add optimisation for epoch processing --- .../src/per_epoch_processing.rs | 21 +++++++++++++++++-- .../src/per_epoch_processing/errors.rs | 5 +++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index 99275bd10c..b8504ca7fe 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -1,5 +1,6 @@ use attester_sets::AttesterSets; use errors::EpochProcessingError as Error; +use fnv::FnvHashMap; use fnv::FnvHashSet; use inclusion_distance::{inclusion_distance, inclusion_slot}; use integer_sqrt::IntegerSquareRoot; @@ -398,12 +399,28 @@ pub fn process_rewards_and_penalities( } // Attestation inclusion + let mut inclusion_slots: FnvHashMap = FnvHashMap::default(); + for a in previous_epoch_attestations { + let participants = + state.get_attestation_participants(&a.data, &a.aggregation_bitfield, spec)?; + let inclusion_distance = (a.inclusion_slot - a.data.slot).as_u64(); + for participant in participants { + if let Some((existing_distance, _)) = inclusion_slots.get(&participant) { + if *existing_distance <= inclusion_distance { + continue; + } + } + inclusion_slots.insert(participant, (Slot::from(inclusion_distance), a.data.slot)); + } + } for &index in &attesters.previous_epoch.indices { - let inclusion_slot = inclusion_slot(state, &previous_epoch_attestations[..], index, spec)?; + let (_, inclusion_slot) = inclusion_slots + .get(&index) + .ok_or_else(|| Error::InclusionSlotsInconsistent(index))?; let proposer_index = state - .get_beacon_proposer_index(inclusion_slot, spec) + .get_beacon_proposer_index(*inclusion_slot, spec) .map_err(|_| Error::UnableToDetermineProducer)?; let base_reward = state.base_reward(proposer_index, base_reward_quotient, spec); diff --git a/eth2/state_processing/src/per_epoch_processing/errors.rs b/eth2/state_processing/src/per_epoch_processing/errors.rs index 7d8a5800d8..c60e00cae7 100644 --- a/eth2/state_processing/src/per_epoch_processing/errors.rs +++ b/eth2/state_processing/src/per_epoch_processing/errors.rs @@ -8,6 +8,11 @@ pub enum EpochProcessingError { NoRandaoSeed, PreviousTotalBalanceIsZero, InclusionDistanceZero, + /// Unable to get the inclusion distance for a validator that should have an inclusion + /// distance. This indicates an internal inconsistency. + /// + /// (validator_index) + InclusionSlotsInconsistent(usize), BeaconStateError(BeaconStateError), InclusionError(InclusionError), } From 21d75ef0bd78b808a6d530a4dbb9e82ff01eefdd Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 10 Mar 2019 18:31:14 +1100 Subject: [PATCH 044/144] Add tree hash benches --- eth2/state_processing/benches/benches.rs | 2 +- .../benches/block_processing_benches.rs | 10 ++++++++++ .../benches/epoch_processing_benches.rs | 13 +++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/eth2/state_processing/benches/benches.rs b/eth2/state_processing/benches/benches.rs index 52b939a696..239b782a30 100644 --- a/eth2/state_processing/benches/benches.rs +++ b/eth2/state_processing/benches/benches.rs @@ -5,7 +5,7 @@ mod epoch_processing_benches; criterion_group!( benches, - // epoch_processing_benches::epoch_processing_16k_validators, + epoch_processing_benches::epoch_processing_16k_validators, block_processing_benches::block_processing_16k_validators, ); criterion_main!(benches); diff --git a/eth2/state_processing/benches/block_processing_benches.rs b/eth2/state_processing/benches/block_processing_benches.rs index 755207f968..0b29680828 100644 --- a/eth2/state_processing/benches/block_processing_benches.rs +++ b/eth2/state_processing/benches/block_processing_benches.rs @@ -1,6 +1,7 @@ use benching_utils::{BeaconBlockBencher, BeaconStateBencher}; use criterion::Criterion; use criterion::{black_box, Benchmark}; +use ssz::TreeHash; use state_processing::{ per_block_processing, per_block_processing::{ @@ -400,4 +401,13 @@ fn bench_block_processing( }) .sample_size(10), ); + + let block = initial_block.clone(); + c.bench( + &format!("block_processing_{}", desc), + Benchmark::new("tree_hash_block", move |b| { + b.iter(|| black_box(block.hash_tree_root())) + }) + .sample_size(10), + ); } diff --git a/eth2/state_processing/benches/epoch_processing_benches.rs b/eth2/state_processing/benches/epoch_processing_benches.rs index 19c76fe365..8172ba99a9 100644 --- a/eth2/state_processing/benches/epoch_processing_benches.rs +++ b/eth2/state_processing/benches/epoch_processing_benches.rs @@ -1,6 +1,7 @@ use benching_utils::BeaconStateBencher; use criterion::Criterion; use criterion::{black_box, Benchmark}; +use ssz::TreeHash; use state_processing::{ per_epoch_processing, per_epoch_processing::{ @@ -328,4 +329,16 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp }) .sample_size(SMALL_BENCHING_SAMPLE_SIZE), ); + + let state_clone = state.clone(); + c.bench( + &format!("epoch_process_with_caches_{}", desc), + Benchmark::new("tree_hash_state", move |b| { + b.iter_with_setup( + || state_clone.clone(), + |state| black_box(state.hash_tree_root()), + ) + }) + .sample_size(SMALL_BENCHING_SAMPLE_SIZE), + ); } From 53456a6c79b55799117de324ed7963560244900e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 10 Mar 2019 21:06:40 +1100 Subject: [PATCH 045/144] Remove last inclusion_slot(..) call --- .../src/per_epoch_processing.rs | 89 ++++++++++--------- 1 file changed, 46 insertions(+), 43 deletions(-) diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index b8504ca7fe..bb064ac34f 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -2,7 +2,6 @@ use attester_sets::AttesterSets; use errors::EpochProcessingError as Error; use fnv::FnvHashMap; use fnv::FnvHashSet; -use inclusion_distance::{inclusion_distance, inclusion_slot}; use integer_sqrt::IntegerSquareRoot; use log::debug; use rayon::prelude::*; @@ -280,6 +279,28 @@ pub fn process_rewards_and_penalities( return Err(Error::PreviousTotalBalanceIsZero); } + // Map is ValidatorIndex -> ProposerIndex + let mut inclusion_slots: FnvHashMap = FnvHashMap::default(); + for a in &previous_epoch_attestations { + let participants = + state.get_attestation_participants(&a.data, &a.aggregation_bitfield, spec)?; + let inclusion_distance = (a.inclusion_slot - a.data.slot).as_u64(); + for participant in participants { + if let Some((existing_distance, _)) = inclusion_slots.get(&participant) { + if *existing_distance <= inclusion_distance { + continue; + } + } + let proposer_index = state + .get_beacon_proposer_index(a.data.slot, spec) + .map_err(|_| Error::UnableToDetermineProducer)?; + inclusion_slots.insert( + participant, + (Slot::from(inclusion_distance), proposer_index), + ); + } + } + // Justification and finalization let epochs_since_finality = next_epoch - state.finalized_epoch; @@ -327,17 +348,17 @@ pub fn process_rewards_and_penalities( if attesters.previous_epoch.indices.contains(&index) { let base_reward = state.base_reward(index, base_reward_quotient, spec); - let inclusion_distance = - inclusion_distance(state, &previous_epoch_attestations, index, spec); - if let Ok(inclusion_distance) = inclusion_distance { - if inclusion_distance > 0 { - safe_add_assign!( - balance, - base_reward * spec.min_attestation_inclusion_delay - / inclusion_distance - ) - } + let (inclusion_distance, _) = inclusion_slots + .get(&index) + .expect("Inconsistent inclusion_slots."); + + if *inclusion_distance > 0 { + safe_add_assign!( + balance, + base_reward * spec.min_attestation_inclusion_delay + / inclusion_distance.as_u64() + ) } } @@ -378,18 +399,17 @@ pub fn process_rewards_and_penalities( if attesters.previous_epoch.indices.contains(&index) { let base_reward = state.base_reward(index, base_reward_quotient, spec); - let inclusion_distance = - inclusion_distance(state, &previous_epoch_attestations, index, spec); - if let Ok(inclusion_distance) = inclusion_distance { - if inclusion_distance > 0 { - safe_sub_assign!( - balance, - base_reward - - base_reward * spec.min_attestation_inclusion_delay - / inclusion_distance - ); - } + let (inclusion_distance, _) = inclusion_slots + .get(&index) + .expect("Inconsistent inclusion_slots."); + + if *inclusion_distance > 0 { + safe_add_assign!( + balance, + base_reward * spec.min_attestation_inclusion_delay + / inclusion_distance.as_u64() + ) } } @@ -399,34 +419,17 @@ pub fn process_rewards_and_penalities( } // Attestation inclusion - let mut inclusion_slots: FnvHashMap = FnvHashMap::default(); - for a in previous_epoch_attestations { - let participants = - state.get_attestation_participants(&a.data, &a.aggregation_bitfield, spec)?; - let inclusion_distance = (a.inclusion_slot - a.data.slot).as_u64(); - for participant in participants { - if let Some((existing_distance, _)) = inclusion_slots.get(&participant) { - if *existing_distance <= inclusion_distance { - continue; - } - } - inclusion_slots.insert(participant, (Slot::from(inclusion_distance), a.data.slot)); - } - } + // for &index in &attesters.previous_epoch.indices { - let (_, inclusion_slot) = inclusion_slots + let (_, proposer_index) = inclusion_slots .get(&index) .ok_or_else(|| Error::InclusionSlotsInconsistent(index))?; - let proposer_index = state - .get_beacon_proposer_index(*inclusion_slot, spec) - .map_err(|_| Error::UnableToDetermineProducer)?; - - let base_reward = state.base_reward(proposer_index, base_reward_quotient, spec); + let base_reward = state.base_reward(*proposer_index, base_reward_quotient, spec); safe_add_assign!( - state.validator_balances[proposer_index], + state.validator_balances[*proposer_index], base_reward / spec.attestation_inclusion_reward_quotient ); } From a44d80006a65ae291bded0ee8a0a15db324a56b0 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 10 Mar 2019 21:07:09 +1100 Subject: [PATCH 046/144] Improve allocation in get_attestation_participants --- eth2/types/src/beacon_state.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 39970b9a7b..603ae2f9d2 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -531,12 +531,14 @@ impl BeaconState { return Err(Error::InvalidBitfield); } - let mut participants = vec![]; + let mut participants = Vec::with_capacity(committee.len()); for (i, validator_index) in committee.iter().enumerate() { - if bitfield.get(i).unwrap() { - participants.push(*validator_index); + match bitfield.get(i) { + Ok(bit) if bit == true => participants.push(*validator_index), + _ => {} } } + participants.shrink_to_fit(); Ok(participants) } From 6ae99a146293a76c754e77391b8920ec30e2860a Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 11 Mar 2019 10:56:31 +1100 Subject: [PATCH 047/144] Ensure drop times aren't included in benchmarks Also moves to the new `iter_batched` method on criterion (instead of `iter_with_setup`. --- .../benches/block_processing_benches.rs | 114 ++++++++-------- .../benches/epoch_processing_benches.rs | 123 +++++++++++------- 2 files changed, 142 insertions(+), 95 deletions(-) diff --git a/eth2/state_processing/benches/block_processing_benches.rs b/eth2/state_processing/benches/block_processing_benches.rs index 0b29680828..840fcaeba9 100644 --- a/eth2/state_processing/benches/block_processing_benches.rs +++ b/eth2/state_processing/benches/block_processing_benches.rs @@ -16,7 +16,7 @@ use types::*; pub fn block_processing_16k_validators(c: &mut Criterion) { let spec = ChainSpec::foundation(); - let validator_count = 16_384; + let validator_count = 300_032; let (mut state, keypairs) = build_state(validator_count, &spec); let block = build_block(&mut state, &keypairs, &spec); @@ -199,9 +199,13 @@ fn bench_block_processing( c.bench( &format!("block_processing_{}", desc), Benchmark::new("verify_block_signature", move |b| { - b.iter_with_setup( + b.iter_batched( || state.clone(), - |mut state| black_box(verify_block_signature(&mut state, &block, &spec).unwrap()), + |mut state| { + verify_block_signature(&mut state, &block, &spec).unwrap(); + state + }, + criterion::BatchSize::SmallInput, ) }) .sample_size(10), @@ -213,9 +217,13 @@ fn bench_block_processing( c.bench( &format!("block_processing_{}", desc), Benchmark::new("process_randao", move |b| { - b.iter_with_setup( + b.iter_batched( || state.clone(), - |mut state| black_box(process_randao(&mut state, &block, &spec).unwrap()), + |mut state| { + process_randao(&mut state, &block, &spec).unwrap(); + state + }, + criterion::BatchSize::SmallInput, ) }) .sample_size(10), @@ -226,9 +234,13 @@ fn bench_block_processing( c.bench( &format!("block_processing_{}", desc), Benchmark::new("process_eth1_data", move |b| { - b.iter_with_setup( + b.iter_batched( || state.clone(), - |mut state| black_box(process_eth1_data(&mut state, &block.eth1_data).unwrap()), + |mut state| { + process_eth1_data(&mut state, &block.eth1_data).unwrap(); + state + }, + criterion::BatchSize::SmallInput, ) }) .sample_size(10), @@ -240,18 +252,14 @@ fn bench_block_processing( c.bench( &format!("block_processing_{}", desc), Benchmark::new("process_proposer_slashings", move |b| { - b.iter_with_setup( + b.iter_batched( || state.clone(), |mut state| { - black_box( - process_proposer_slashings( - &mut state, - &block.body.proposer_slashings, - &spec, - ) - .unwrap(), - ) + process_proposer_slashings(&mut state, &block.body.proposer_slashings, &spec) + .unwrap(); + state }, + criterion::BatchSize::SmallInput, ) }) .sample_size(10), @@ -263,18 +271,14 @@ fn bench_block_processing( c.bench( &format!("block_processing_{}", desc), Benchmark::new("process_attester_slashings", move |b| { - b.iter_with_setup( + b.iter_batched( || state.clone(), |mut state| { - black_box( - process_attester_slashings( - &mut state, - &block.body.attester_slashings, - &spec, - ) - .unwrap(), - ) + process_attester_slashings(&mut state, &block.body.attester_slashings, &spec) + .unwrap(); + state }, + criterion::BatchSize::SmallInput, ) }) .sample_size(10), @@ -286,13 +290,13 @@ fn bench_block_processing( c.bench( &format!("block_processing_{}", desc), Benchmark::new("process_attestations", move |b| { - b.iter_with_setup( + b.iter_batched( || state.clone(), |mut state| { - black_box( - process_attestations(&mut state, &block.body.attestations, &spec).unwrap(), - ) + process_attestations(&mut state, &block.body.attestations, &spec).unwrap(); + state }, + criterion::BatchSize::SmallInput, ) }) .sample_size(10), @@ -304,11 +308,13 @@ fn bench_block_processing( c.bench( &format!("block_processing_{}", desc), Benchmark::new("process_deposits", move |b| { - b.iter_with_setup( + b.iter_batched( || state.clone(), |mut state| { - black_box(process_deposits(&mut state, &block.body.deposits, &spec).unwrap()) + process_deposits(&mut state, &block.body.deposits, &spec).unwrap(); + state }, + criterion::BatchSize::SmallInput, ) }) .sample_size(10), @@ -320,13 +326,13 @@ fn bench_block_processing( c.bench( &format!("block_processing_{}", desc), Benchmark::new("process_exits", move |b| { - b.iter_with_setup( + b.iter_batched( || state.clone(), |mut state| { - black_box( - process_exits(&mut state, &block.body.voluntary_exits, &spec).unwrap(), - ) + process_exits(&mut state, &block.body.voluntary_exits, &spec).unwrap(); + state }, + criterion::BatchSize::SmallInput, ) }) .sample_size(10), @@ -338,11 +344,13 @@ fn bench_block_processing( c.bench( &format!("block_processing_{}", desc), Benchmark::new("process_transfers", move |b| { - b.iter_with_setup( + b.iter_batched( || state.clone(), |mut state| { - black_box(process_transfers(&mut state, &block.body.transfers, &spec).unwrap()) + process_transfers(&mut state, &block.body.transfers, &spec).unwrap(); + state }, + criterion::BatchSize::SmallInput, ) }) .sample_size(10), @@ -354,9 +362,13 @@ fn bench_block_processing( c.bench( &format!("block_processing_{}", desc), Benchmark::new("per_block_processing", move |b| { - b.iter_with_setup( + b.iter_batched( || state.clone(), - |mut state| black_box(per_block_processing(&mut state, &block, &spec).unwrap()), + |mut state| { + per_block_processing(&mut state, &block, &spec).unwrap(); + state + }, + criterion::BatchSize::SmallInput, ) }) .sample_size(10), @@ -368,15 +380,15 @@ fn bench_block_processing( c.bench( &format!("block_processing_{}", desc), Benchmark::new("build_previous_state_epoch_cache", move |b| { - b.iter_with_setup( + b.iter_batched( || state.clone(), |mut state| { - black_box( - state - .build_epoch_cache(RelativeEpoch::Previous, &spec) - .unwrap(), - ) + state + .build_epoch_cache(RelativeEpoch::Previous, &spec) + .unwrap(); + state }, + criterion::BatchSize::SmallInput, ) }) .sample_size(10), @@ -388,15 +400,15 @@ fn bench_block_processing( c.bench( &format!("block_processing_{}", desc), Benchmark::new("build_current_state_epoch_cache", move |b| { - b.iter_with_setup( + b.iter_batched( || state.clone(), |mut state| { - black_box( - state - .build_epoch_cache(RelativeEpoch::Current, &spec) - .unwrap(), - ) + state + .build_epoch_cache(RelativeEpoch::Current, &spec) + .unwrap(); + state }, + criterion::BatchSize::SmallInput, ) }) .sample_size(10), diff --git a/eth2/state_processing/benches/epoch_processing_benches.rs b/eth2/state_processing/benches/epoch_processing_benches.rs index 8172ba99a9..e97dfde583 100644 --- a/eth2/state_processing/benches/epoch_processing_benches.rs +++ b/eth2/state_processing/benches/epoch_processing_benches.rs @@ -13,14 +13,14 @@ use state_processing::{ }; use types::{validator_registry::get_active_validator_indices, *}; -pub const BENCHING_SAMPLE_SIZE: usize = 100; +pub const BENCHING_SAMPLE_SIZE: usize = 10; pub const SMALL_BENCHING_SAMPLE_SIZE: usize = 10; /// Run the benchmarking suite on a foundation spec with 16,384 validators. pub fn epoch_processing_16k_validators(c: &mut Criterion) { let spec = ChainSpec::foundation(); - let validator_count = 16_384; + let validator_count = 300_032; let mut builder = BeaconStateBencher::new(validator_count, &spec); @@ -67,7 +67,7 @@ pub fn epoch_processing_16k_validators(c: &mut Criterion) { "Epochs since finality should be 4" ); - bench_epoch_processing(c, &state, &spec, "16k_validators"); + bench_epoch_processing(c, &state, &spec, &format!("{}_validators", validator_count)); } /// Run the detailed benchmarking suite on the given `BeaconState`. @@ -79,9 +79,13 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp c.bench( &format!("epoch_process_with_caches_{}", desc), Benchmark::new("calculate_active_validator_indices", move |b| { - b.iter_with_setup( + b.iter_batched( || state_clone.clone(), - |mut state| black_box(calculate_active_validator_indices(&mut state, &spec_clone)), + |mut state| { + calculate_active_validator_indices(&mut state, &spec_clone); + state + }, + criterion::BatchSize::SmallInput, ) }) .sample_size(BENCHING_SAMPLE_SIZE), @@ -93,11 +97,13 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp c.bench( &format!("epoch_process_with_caches_{}", desc), Benchmark::new("calculate_current_total_balance", move |b| { - b.iter_with_setup( + b.iter_batched( || state_clone.clone(), |state| { - black_box(state.get_total_balance(&active_validator_indices[..], &spec_clone)) + state.get_total_balance(&active_validator_indices[..], &spec_clone); + state }, + criterion::BatchSize::SmallInput, ) }) .sample_size(BENCHING_SAMPLE_SIZE), @@ -108,17 +114,19 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp c.bench( &format!("epoch_process_with_caches_{}", desc), Benchmark::new("calculate_previous_total_balance", move |b| { - b.iter_with_setup( + b.iter_batched( || state_clone.clone(), |state| { - black_box(state.get_total_balance( + state.get_total_balance( &get_active_validator_indices( &state.validator_registry, state.previous_epoch(&spec_clone), )[..], &spec_clone, - )) + ); + state }, + criterion::BatchSize::SmallInput, ) }) .sample_size(BENCHING_SAMPLE_SIZE), @@ -129,9 +137,13 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp c.bench( &format!("epoch_process_with_caches_{}", desc), Benchmark::new("process_eth1_data", move |b| { - b.iter_with_setup( + b.iter_batched( || state_clone.clone(), - |mut state| black_box(process_eth1_data(&mut state, &spec_clone)), + |mut state| { + process_eth1_data(&mut state, &spec_clone); + state + }, + criterion::BatchSize::SmallInput, ) }) .sample_size(BENCHING_SAMPLE_SIZE), @@ -142,9 +154,13 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp c.bench( &format!("epoch_process_with_caches_{}", desc), Benchmark::new("calculate_attester_sets", move |b| { - b.iter_with_setup( + b.iter_batched( || state_clone.clone(), - |mut state| black_box(calculate_attester_sets(&mut state, &spec_clone).unwrap()), + |mut state| { + calculate_attester_sets(&mut state, &spec_clone).unwrap(); + state + }, + criterion::BatchSize::SmallInput, ) }) .sample_size(BENCHING_SAMPLE_SIZE), @@ -163,18 +179,20 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp c.bench( &format!("epoch_process_with_caches_{}", desc), Benchmark::new("process_justification", move |b| { - b.iter_with_setup( + b.iter_batched( || state_clone.clone(), |mut state| { - black_box(process_justification( + process_justification( &mut state, current_total_balance, previous_total_balance, attesters.previous_epoch_boundary.balance, attesters.current_epoch_boundary.balance, &spec_clone, - )) + ); + state }, + criterion::BatchSize::SmallInput, ) }) .sample_size(10), @@ -185,9 +203,10 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp c.bench( &format!("epoch_process_with_caches_{}", desc), Benchmark::new("process_crosslinks", move |b| { - b.iter_with_setup( + b.iter_batched( || state_clone.clone(), |mut state| black_box(process_crosslinks(&mut state, &spec_clone).unwrap()), + criterion::BatchSize::SmallInput, ) }) .sample_size(BENCHING_SAMPLE_SIZE), @@ -206,21 +225,21 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp c.bench( &format!("epoch_process_with_caches_{}", desc), Benchmark::new("process_rewards_and_penalties", move |b| { - b.iter_with_setup( + b.iter_batched( || state_clone.clone(), |mut state| { - black_box( - process_rewards_and_penalities( - &mut state, - &active_validator_indices, - &attesters, - previous_total_balance, - &winning_root_for_shards, - &spec_clone, - ) - .unwrap(), + process_rewards_and_penalities( + &mut state, + &active_validator_indices, + &attesters, + previous_total_balance, + &winning_root_for_shards, + &spec_clone, ) + .unwrap(); + state }, + criterion::BatchSize::SmallInput, ) }) .sample_size(SMALL_BENCHING_SAMPLE_SIZE), @@ -231,9 +250,13 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp c.bench( &format!("epoch_process_with_caches_{}", desc), Benchmark::new("process_ejections", move |b| { - b.iter_with_setup( + b.iter_batched( || state_clone.clone(), - |mut state| black_box(state.process_ejections(&spec_clone)), + |mut state| { + state.process_ejections(&spec_clone); + state + }, + criterion::BatchSize::SmallInput, ) }) .sample_size(BENCHING_SAMPLE_SIZE), @@ -268,9 +291,13 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp c.bench( &format!("epoch_process_with_caches_{}", desc), Benchmark::new("process_validator_registry", move |b| { - b.iter_with_setup( + b.iter_batched( || state_clone.clone(), - |mut state| black_box(process_validator_registry(&mut state, &spec_clone)), + |mut state| { + process_validator_registry(&mut state, &spec_clone).unwrap(); + state + }, + criterion::BatchSize::SmallInput, ) }) .sample_size(BENCHING_SAMPLE_SIZE), @@ -281,11 +308,13 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp c.bench( &format!("epoch_process_with_caches_{}", desc), Benchmark::new("update_active_tree_index_roots", move |b| { - b.iter_with_setup( + b.iter_batched( || state_clone.clone(), |mut state| { - black_box(update_active_tree_index_roots(&mut state, &spec_clone).unwrap()) + update_active_tree_index_roots(&mut state, &spec_clone).unwrap(); + state }, + criterion::BatchSize::SmallInput, ) }) .sample_size(BENCHING_SAMPLE_SIZE), @@ -296,9 +325,13 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp c.bench( &format!("epoch_process_with_caches_{}", desc), Benchmark::new("update_latest_slashed_balances", move |b| { - b.iter_with_setup( + b.iter_batched( || state_clone.clone(), - |mut state| black_box(update_latest_slashed_balances(&mut state, &spec_clone)), + |mut state| { + update_latest_slashed_balances(&mut state, &spec_clone); + state + }, + criterion::BatchSize::SmallInput, ) }) .sample_size(BENCHING_SAMPLE_SIZE), @@ -309,9 +342,13 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp c.bench( &format!("epoch_process_with_caches_{}", desc), Benchmark::new("clean_attestations", move |b| { - b.iter_with_setup( + b.iter_batched( || state_clone.clone(), - |mut state| black_box(clean_attestations(&mut state, &spec_clone)), + |mut state| { + clean_attestations(&mut state, &spec_clone); + state + }, + criterion::BatchSize::SmallInput, ) }) .sample_size(BENCHING_SAMPLE_SIZE), @@ -322,9 +359,10 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp c.bench( &format!("epoch_process_with_caches_{}", desc), Benchmark::new("per_epoch_processing", move |b| { - b.iter_with_setup( + b.iter_batched( || state_clone.clone(), |mut state| black_box(per_epoch_processing(&mut state, &spec_clone).unwrap()), + criterion::BatchSize::SmallInput, ) }) .sample_size(SMALL_BENCHING_SAMPLE_SIZE), @@ -334,10 +372,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp c.bench( &format!("epoch_process_with_caches_{}", desc), Benchmark::new("tree_hash_state", move |b| { - b.iter_with_setup( - || state_clone.clone(), - |state| black_box(state.hash_tree_root()), - ) + b.iter(|| black_box(state_clone.hash_tree_root())) }) .sample_size(SMALL_BENCHING_SAMPLE_SIZE), ); From 0b7082e2b91aa7bc8fa940b50df13ccd0353d977 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 11 Mar 2019 11:17:27 +1100 Subject: [PATCH 048/144] Move `benching_utils` structs into `types` --- Cargo.toml | 1 - eth2/state_processing/Cargo.toml | 1 - .../benches/block_processing_benches.rs | 6 +++--- .../benches/epoch_processing_benches.rs | 4 ++-- eth2/state_processing/benching_utils/Cargo.toml | 17 ----------------- eth2/state_processing/benching_utils/src/lib.rs | 5 ----- .../src/per_epoch_processing/tests.rs | 4 ++-- eth2/types/src/beacon_state/builder.rs | 2 +- eth2/types/src/beacon_state/tests.rs | 17 ++++------------- eth2/types/src/test_utils/mod.rs | 4 ++++ .../test_utils/testing_beacon_block_builder.rs} | 10 +++++----- .../test_utils/testing_beacon_state_builder.rs} | 8 ++++---- 12 files changed, 25 insertions(+), 54 deletions(-) delete mode 100644 eth2/state_processing/benching_utils/Cargo.toml delete mode 100644 eth2/state_processing/benching_utils/src/lib.rs rename eth2/{state_processing/benching_utils/src/beacon_block_bencher.rs => types/src/test_utils/testing_beacon_block_builder.rs} (99%) rename eth2/{state_processing/benching_utils/src/beacon_state_bencher.rs => types/src/test_utils/testing_beacon_state_builder.rs} (98%) diff --git a/Cargo.toml b/Cargo.toml index 8f4dbb2688..c5aae7f43f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,6 @@ members = [ "eth2/block_proposer", "eth2/fork_choice", "eth2/state_processing", - "eth2/state_processing/benching_utils", "eth2/types", "eth2/utils/bls", "eth2/utils/boolean-bitfield", diff --git a/eth2/state_processing/Cargo.toml b/eth2/state_processing/Cargo.toml index 962d23a778..f6692b259e 100644 --- a/eth2/state_processing/Cargo.toml +++ b/eth2/state_processing/Cargo.toml @@ -11,7 +11,6 @@ harness = false [dev-dependencies] criterion = "0.2" env_logger = "0.6.0" -benching_utils = { path = "./benching_utils" } [dependencies] bls = { path = "../utils/bls" } diff --git a/eth2/state_processing/benches/block_processing_benches.rs b/eth2/state_processing/benches/block_processing_benches.rs index 840fcaeba9..3c59e51e51 100644 --- a/eth2/state_processing/benches/block_processing_benches.rs +++ b/eth2/state_processing/benches/block_processing_benches.rs @@ -1,4 +1,3 @@ -use benching_utils::{BeaconBlockBencher, BeaconStateBencher}; use criterion::Criterion; use criterion::{black_box, Benchmark}; use ssz::TreeHash; @@ -10,6 +9,7 @@ use state_processing::{ verify_block_signature, }, }; +use types::test_utils::{TestingBeaconBlockBuilder, TestingBeaconStateBuilder}; use types::*; /// Run the benchmarking suite on a foundation spec with 16,384 validators. @@ -82,7 +82,7 @@ pub fn block_processing_16k_validators(c: &mut Criterion) { } fn build_state(validator_count: usize, spec: &ChainSpec) -> (BeaconState, Vec) { - let mut builder = BeaconStateBencher::new(validator_count, &spec); + let mut builder = TestingBeaconStateBuilder::new(validator_count, &spec); // Set the state to be just before an epoch transition. let target_slot = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch); @@ -95,7 +95,7 @@ fn build_state(validator_count: usize, spec: &ChainSpec) -> (BeaconState, Vec BeaconBlock { - let mut builder = BeaconBlockBencher::new(spec); + let mut builder = TestingBeaconBlockBuilder::new(spec); builder.set_slot(state.slot); diff --git a/eth2/state_processing/benches/epoch_processing_benches.rs b/eth2/state_processing/benches/epoch_processing_benches.rs index e97dfde583..85922fa072 100644 --- a/eth2/state_processing/benches/epoch_processing_benches.rs +++ b/eth2/state_processing/benches/epoch_processing_benches.rs @@ -1,4 +1,3 @@ -use benching_utils::BeaconStateBencher; use criterion::Criterion; use criterion::{black_box, Benchmark}; use ssz::TreeHash; @@ -11,6 +10,7 @@ use state_processing::{ update_latest_slashed_balances, }, }; +use types::test_utils::TestingBeaconStateBuilder; use types::{validator_registry::get_active_validator_indices, *}; pub const BENCHING_SAMPLE_SIZE: usize = 10; @@ -22,7 +22,7 @@ pub fn epoch_processing_16k_validators(c: &mut Criterion) { let validator_count = 300_032; - let mut builder = BeaconStateBencher::new(validator_count, &spec); + let mut builder = TestingBeaconStateBuilder::new(validator_count, &spec); // Set the state to be just before an epoch transition. let target_slot = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch); diff --git a/eth2/state_processing/benching_utils/Cargo.toml b/eth2/state_processing/benching_utils/Cargo.toml deleted file mode 100644 index 00815406a0..0000000000 --- a/eth2/state_processing/benching_utils/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "benching_utils" -version = "0.1.0" -authors = ["Paul Hauner "] -edition = "2018" - -[dependencies] -bls = { path = "../../utils/bls" } -hashing = { path = "../../utils/hashing" } -int_to_bytes = { path = "../../utils/int_to_bytes" } -integer-sqrt = "0.1" -log = "0.4" -merkle_proof = { path = "../../utils/merkle_proof" } -ssz = { path = "../../utils/ssz" } -ssz_derive = { path = "../../utils/ssz_derive" } -types = { path = "../../types" } -rayon = "1.0" diff --git a/eth2/state_processing/benching_utils/src/lib.rs b/eth2/state_processing/benching_utils/src/lib.rs deleted file mode 100644 index ba95488143..0000000000 --- a/eth2/state_processing/benching_utils/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod beacon_block_bencher; -mod beacon_state_bencher; - -pub use beacon_block_bencher::BeaconBlockBencher; -pub use beacon_state_bencher::BeaconStateBencher; diff --git a/eth2/state_processing/src/per_epoch_processing/tests.rs b/eth2/state_processing/src/per_epoch_processing/tests.rs index f3c68a173f..18c888e78c 100644 --- a/eth2/state_processing/src/per_epoch_processing/tests.rs +++ b/eth2/state_processing/src/per_epoch_processing/tests.rs @@ -1,7 +1,7 @@ #![cfg(test)] use crate::per_epoch_processing; -use benching_utils::BeaconStateBencher; use env_logger::{Builder, Env}; +use types::test_utils::TestingBeaconStateBuilder; use types::*; #[test] @@ -10,7 +10,7 @@ fn runs_without_error() { let spec = ChainSpec::few_validators(); - let mut builder = BeaconStateBencher::new(8, &spec); + let mut builder = TestingBeaconStateBuilder::new(8, &spec); let target_slot = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch); builder.teleport_to_slot(target_slot, &spec); diff --git a/eth2/types/src/beacon_state/builder.rs b/eth2/types/src/beacon_state/builder.rs index 372f0d43d1..c36cd11f48 100644 --- a/eth2/types/src/beacon_state/builder.rs +++ b/eth2/types/src/beacon_state/builder.rs @@ -6,7 +6,7 @@ use ssz::TreeHash; /// Builds a `BeaconState` for use in production. /// -/// This struct should not be modified for use in testing scenarios. Use `TestingBeaconStateBuilder` for that purpose. +/// This struct should _not_ be modified for use in testing scenarios. Use `TestingBeaconStateBuilder` for that purpose. /// /// This struct should remain safe and sensible for production usage. pub struct BeaconStateBuilder { diff --git a/eth2/types/src/beacon_state/tests.rs b/eth2/types/src/beacon_state/tests.rs index 40bfd146ce..fc55520bb7 100644 --- a/eth2/types/src/beacon_state/tests.rs +++ b/eth2/types/src/beacon_state/tests.rs @@ -1,29 +1,20 @@ #![cfg(test)] use super::*; +use crate::test_utils::TestingBeaconStateBuilder; use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; use crate::{BeaconState, ChainSpec}; use ssz::{ssz_encode, Decodable}; -#[test] -pub fn can_produce_genesis_block() { - let mut builder = BeaconStateBuilder::new(2); - builder.build().unwrap(); -} - /// Tests that `get_attestation_participants` is consistent with the result of /// get_crosslink_committees_at_slot` with a full bitfield. #[test] pub fn get_attestation_participants_consistency() { let mut rng = XorShiftRng::from_seed([42; 16]); - let mut builder = BeaconStateBuilder::new(8); - builder.spec = ChainSpec::few_validators(); - - builder.build().unwrap(); - - let mut state = builder.cloned_state(); - let spec = builder.spec.clone(); + let spec = ChainSpec::few_validators(); + let builder = TestingBeaconStateBuilder::new(8, &spec); + let (mut state, _keypairs) = builder.build(); state .build_epoch_cache(RelativeEpoch::Previous, &spec) diff --git a/eth2/types/src/test_utils/mod.rs b/eth2/types/src/test_utils/mod.rs index 2145f684ad..01d9668411 100644 --- a/eth2/types/src/test_utils/mod.rs +++ b/eth2/types/src/test_utils/mod.rs @@ -1,5 +1,7 @@ mod test_random; mod testing_attestation_builder; +mod testing_beacon_block_builder; +mod testing_beacon_state_builder; mod testing_deposit_builder; mod testing_transfer_builder; mod testing_voluntary_exit_builder; @@ -7,6 +9,8 @@ mod testing_voluntary_exit_builder; pub use rand::{prng::XorShiftRng, SeedableRng}; pub use test_random::TestRandom; pub use testing_attestation_builder::TestingAttestationBuilder; +pub use testing_beacon_block_builder::TestingBeaconBlockBuilder; +pub use testing_beacon_state_builder::TestingBeaconStateBuilder; pub use testing_deposit_builder::TestingDepositBuilder; pub use testing_transfer_builder::TestingTransferBuilder; pub use testing_voluntary_exit_builder::TestingVoluntaryExitBuilder; diff --git a/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs b/eth2/types/src/test_utils/testing_beacon_block_builder.rs similarity index 99% rename from eth2/state_processing/benching_utils/src/beacon_block_bencher.rs rename to eth2/types/src/test_utils/testing_beacon_block_builder.rs index 46e822baa8..db4d887d40 100644 --- a/eth2/state_processing/benching_utils/src/beacon_block_bencher.rs +++ b/eth2/types/src/test_utils/testing_beacon_block_builder.rs @@ -1,6 +1,4 @@ -use rayon::prelude::*; -use ssz::{SignedRoot, TreeHash}; -use types::{ +use crate::{ attester_slashing::AttesterSlashingBuilder, proposer_slashing::ProposerSlashingBuilder, test_utils::{ @@ -9,12 +7,14 @@ use types::{ }, *, }; +use rayon::prelude::*; +use ssz::{SignedRoot, TreeHash}; -pub struct BeaconBlockBencher { +pub struct TestingBeaconBlockBuilder { block: BeaconBlock, } -impl BeaconBlockBencher { +impl TestingBeaconBlockBuilder { pub fn new(spec: &ChainSpec) -> Self { Self { block: BeaconBlock::genesis(spec.zero_hash, spec), diff --git a/eth2/state_processing/benching_utils/src/beacon_state_bencher.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs similarity index 98% rename from eth2/state_processing/benching_utils/src/beacon_state_bencher.rs rename to eth2/types/src/test_utils/testing_beacon_state_builder.rs index 8ad4810e7d..b3cfea6c09 100644 --- a/eth2/state_processing/benching_utils/src/beacon_state_bencher.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -1,15 +1,15 @@ +use crate::beacon_state::BeaconStateBuilder; +use crate::*; use bls::get_withdrawal_credentials; use int_to_bytes::int_to_bytes48; use rayon::prelude::*; -use types::beacon_state::BeaconStateBuilder; -use types::*; -pub struct BeaconStateBencher { +pub struct TestingBeaconStateBuilder { state: BeaconState, keypairs: Vec, } -impl BeaconStateBencher { +impl TestingBeaconStateBuilder { pub fn new(validator_count: usize, spec: &ChainSpec) -> Self { let keypairs: Vec = (0..validator_count) .collect::>() From 827365cfb0645c3d9eea27baf32d0a197b2d03a0 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 11 Mar 2019 11:33:35 +1100 Subject: [PATCH 049/144] Update fork_choice tests to use new state builder. --- eth2/fork_choice/tests/tests.rs | 43 ++++----------------------------- 1 file changed, 5 insertions(+), 38 deletions(-) diff --git a/eth2/fork_choice/tests/tests.rs b/eth2/fork_choice/tests/tests.rs index a3cab6a7ce..5bf3b7e57d 100644 --- a/eth2/fork_choice/tests/tests.rs +++ b/eth2/fork_choice/tests/tests.rs @@ -12,7 +12,7 @@ extern crate types; extern crate yaml_rust; pub use beacon_chain::BeaconChain; -use bls::{PublicKey, Signature}; +use bls::Signature; use db::stores::{BeaconBlockStore, BeaconStateStore}; use db::MemoryDB; //use env_logger::{Builder, Env}; @@ -21,9 +21,8 @@ use ssz::ssz_encode; use std::collections::HashMap; use std::sync::Arc; use std::{fs::File, io::prelude::*, path::PathBuf}; -use types::{ - BeaconBlock, BeaconBlockBody, BeaconState, ChainSpec, Epoch, Eth1Data, Hash256, Slot, Validator, -}; +use types::test_utils::TestingBeaconStateBuilder; +use types::{BeaconBlock, BeaconBlockBody, ChainSpec, Eth1Data, Hash256, Slot}; use yaml_rust::yaml; // Note: We Assume the block Id's are hex-encoded. @@ -207,8 +206,6 @@ fn setup_inital_state( fork_choice_algo: &ForkChoiceAlgorithm, no_validators: usize, ) -> (Box, Arc>, Hash256) { - let zero_hash = Hash256::zero(); - let db = Arc::new(MemoryDB::open()); let block_store = Arc::new(BeaconBlockStore::new(db.clone())); let state_store = Arc::new(BeaconStateStore::new(db.clone())); @@ -225,40 +222,10 @@ fn setup_inital_state( ForkChoiceAlgorithm::LongestChain => Box::new(LongestChain::new(block_store.clone())), }; - // misc vars for setting up the state - let genesis_time = 1_550_381_159; - - let latest_eth1_data = Eth1Data { - deposit_root: zero_hash.clone(), - block_hash: zero_hash.clone(), - }; - - let initial_validator_deposits = vec![]; let spec = ChainSpec::foundation(); - // create the state - let mut state = BeaconState::genesis( - genesis_time, - initial_validator_deposits, - latest_eth1_data, - &spec, - ) - .unwrap(); - - let default_validator = Validator { - pubkey: PublicKey::default(), - withdrawal_credentials: zero_hash, - activation_epoch: Epoch::from(0u64), - exit_epoch: spec.far_future_epoch, - withdrawable_epoch: spec.far_future_epoch, - initiated_exit: false, - slashed: false, - }; - // activate the validators - for _ in 0..no_validators { - state.validator_registry.push(default_validator.clone()); - state.validator_balances.push(32_000_000_000); - } + let state_builder = TestingBeaconStateBuilder::new(no_validators, &spec); + let (state, _keypairs) = state_builder.build(); let state_root = state.canonical_root(); state_store From 41844841c6e2ff68bab7a95e5664bd361ca37d34 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 11 Mar 2019 11:52:16 +1100 Subject: [PATCH 050/144] Update project tests to use new genesis structure --- .../beacon_chain/test_harness/src/lib.rs | 2 +- .../beacon_chain/test_harness/tests/chain.rs | 4 ++-- beacon_node/src/main.rs | 22 ++++++++++++++----- eth2/types/src/beacon_state/builder.rs | 3 ++- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/src/lib.rs b/beacon_node/beacon_chain/test_harness/src/lib.rs index 0703fd4a5a..f58c1b598d 100644 --- a/beacon_node/beacon_chain/test_harness/src/lib.rs +++ b/beacon_node/beacon_chain/test_harness/src/lib.rs @@ -15,7 +15,7 @@ //! let validator_count = 8; //! let spec = ChainSpec::few_validators(); //! -//! let mut harness = BeaconChainHarness::new(spec, validator_count); +//! let mut harness = BeaconChainHarness::new(spec, validator_count, None, true); //! //! harness.advance_chain_with_block(); //! diff --git a/beacon_node/beacon_chain/test_harness/tests/chain.rs b/beacon_node/beacon_chain/test_harness/tests/chain.rs index e72c3a5aa5..e5a52a314a 100644 --- a/beacon_node/beacon_chain/test_harness/tests/chain.rs +++ b/beacon_node/beacon_chain/test_harness/tests/chain.rs @@ -10,7 +10,7 @@ fn it_can_build_on_genesis_block() { let spec = ChainSpec::few_validators(); let validator_count = 8; - let mut harness = BeaconChainHarness::new(spec, validator_count as usize); + let mut harness = BeaconChainHarness::new(spec, validator_count as usize, None, true); harness.advance_chain_with_block(); } @@ -25,7 +25,7 @@ fn it_can_produce_past_first_epoch_boundary() { debug!("Starting harness build..."); - let mut harness = BeaconChainHarness::new(spec, validator_count); + let mut harness = BeaconChainHarness::new(spec, validator_count, None, true); debug!("Harness built, tests starting.."); diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 072315b6b3..c05438cfb8 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -17,8 +17,12 @@ use db::{ use fork_choice::BitwiseLMDGhost; use slog::{error, info, o, Drain}; use slot_clock::SystemTimeSlotClock; +use ssz::TreeHash; use std::sync::Arc; -use types::{ChainSpec, Deposit, DepositData, DepositInput, Eth1Data, Hash256, Keypair}; +use types::{ + beacon_state::BeaconStateBuilder, BeaconBlock, ChainSpec, Deposit, DepositData, DepositInput, + Eth1Data, Hash256, Keypair, +}; fn main() { let decorator = slog_term::TermDecorator::new().build(); @@ -97,7 +101,8 @@ fn main() { .iter() .map(|_| Keypair::random()) .collect(); - let initial_validator_deposits = keypairs + + let initial_validator_deposits: Vec = keypairs .iter() .map(|keypair| Deposit { branch: vec![], // branch verification is not specified. @@ -114,14 +119,19 @@ fn main() { }) .collect(); + let mut state_builder = BeaconStateBuilder::new(genesis_time, latest_eth1_data, &spec); + state_builder.process_initial_deposits(&initial_validator_deposits, &spec); + let genesis_state = state_builder.build(&spec).unwrap(); + let state_root = Hash256::from_slice(&genesis_state.hash_tree_root()); + let genesis_block = BeaconBlock::genesis(state_root, &spec); + // Genesis chain - let _chain_result = BeaconChain::genesis( + let _chain_result = BeaconChain::from_genesis( state_store.clone(), block_store.clone(), slot_clock, - genesis_time, - latest_eth1_data, - initial_validator_deposits, + genesis_state, + genesis_block, spec, fork_choice, ); diff --git a/eth2/types/src/beacon_state/builder.rs b/eth2/types/src/beacon_state/builder.rs index c36cd11f48..22ca3e6224 100644 --- a/eth2/types/src/beacon_state/builder.rs +++ b/eth2/types/src/beacon_state/builder.rs @@ -53,7 +53,8 @@ impl BeaconStateBuilder { /// Instantiate the validator registry from a YAML file. /// - /// This skips a lot of signing and verification, useful for fast test setups. + /// This skips a lot of signing and verification, useful if signing and verification has been + /// completed previously. /// /// Spec v0.4.0 pub fn import_existing_validators( From df5266988894f248694de1d186b7ecbcb2a88606 Mon Sep 17 00:00:00 2001 From: Kirk Baird Date: Mon, 11 Mar 2019 11:55:09 +1100 Subject: [PATCH 051/144] Add changes of create_proof_of_possession to pull --- beacon_node/src/main.rs | 2 +- eth2/utils/bls/src/lib.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 072315b6b3..eb0b38d5ff 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -108,7 +108,7 @@ fn main() { deposit_input: DepositInput { pubkey: keypair.pk.clone(), withdrawal_credentials: Hash256::zero(), // Withdrawal not possible. - proof_of_possession: create_proof_of_possession(&keypair), + proof_of_possession: create_proof_of_possession(&keypair, Hash256::zero()), }, }, }) diff --git a/eth2/utils/bls/src/lib.rs b/eth2/utils/bls/src/lib.rs index 8b3f8b2ba4..95f993ecb8 100644 --- a/eth2/utils/bls/src/lib.rs +++ b/eth2/utils/bls/src/lib.rs @@ -20,6 +20,7 @@ pub const BLS_AGG_SIG_BYTE_SIZE: usize = 96; use hashing::hash; use ssz::ssz_encode; +use types::{DepositInput, Hash256}; /// For some signature and public key, ensure that the signature message was the public key and it /// was signed by the secret key that corresponds to that public key. @@ -31,7 +32,7 @@ pub fn verify_proof_of_possession(sig: &Signature, pubkey: &PublicKey) -> bool { // TODO: Update this method // https://github.com/sigp/lighthouse/issues/239 -pub fn create_proof_of_possession(keypair: &Keypair) -> Signature { +pub fn create_proof_of_possession(keypair: &Keypair, withdrawal_credentials: &Hash256) -> Signature { Signature::new(&ssz_encode(&keypair.pk), 0, &keypair.sk) } From 36085f63e9a6ed69e85266856f1944bd82162dfd Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 11 Mar 2019 14:52:21 +1100 Subject: [PATCH 052/144] Rename state trans benches --- .../benches/block_processing_benches.rs | 26 ++++++++-------- .../benches/epoch_processing_benches.rs | 30 +++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/eth2/state_processing/benches/block_processing_benches.rs b/eth2/state_processing/benches/block_processing_benches.rs index 3c59e51e51..ea1ada193d 100644 --- a/eth2/state_processing/benches/block_processing_benches.rs +++ b/eth2/state_processing/benches/block_processing_benches.rs @@ -197,7 +197,7 @@ fn bench_block_processing( let block = initial_block.clone(); let spec = initial_spec.clone(); c.bench( - &format!("block_processing_{}", desc), + &format!("{}/block_processing", desc), Benchmark::new("verify_block_signature", move |b| { b.iter_batched( || state.clone(), @@ -215,7 +215,7 @@ fn bench_block_processing( let block = initial_block.clone(); let spec = initial_spec.clone(); c.bench( - &format!("block_processing_{}", desc), + &format!("{}/block_processing", desc), Benchmark::new("process_randao", move |b| { b.iter_batched( || state.clone(), @@ -232,7 +232,7 @@ fn bench_block_processing( let state = initial_state.clone(); let block = initial_block.clone(); c.bench( - &format!("block_processing_{}", desc), + &format!("{}/block_processing", desc), Benchmark::new("process_eth1_data", move |b| { b.iter_batched( || state.clone(), @@ -250,7 +250,7 @@ fn bench_block_processing( let block = initial_block.clone(); let spec = initial_spec.clone(); c.bench( - &format!("block_processing_{}", desc), + &format!("{}/block_processing", desc), Benchmark::new("process_proposer_slashings", move |b| { b.iter_batched( || state.clone(), @@ -269,7 +269,7 @@ fn bench_block_processing( let block = initial_block.clone(); let spec = initial_spec.clone(); c.bench( - &format!("block_processing_{}", desc), + &format!("{}/block_processing", desc), Benchmark::new("process_attester_slashings", move |b| { b.iter_batched( || state.clone(), @@ -288,7 +288,7 @@ fn bench_block_processing( let block = initial_block.clone(); let spec = initial_spec.clone(); c.bench( - &format!("block_processing_{}", desc), + &format!("{}/block_processing", desc), Benchmark::new("process_attestations", move |b| { b.iter_batched( || state.clone(), @@ -306,7 +306,7 @@ fn bench_block_processing( let block = initial_block.clone(); let spec = initial_spec.clone(); c.bench( - &format!("block_processing_{}", desc), + &format!("{}/block_processing", desc), Benchmark::new("process_deposits", move |b| { b.iter_batched( || state.clone(), @@ -324,7 +324,7 @@ fn bench_block_processing( let block = initial_block.clone(); let spec = initial_spec.clone(); c.bench( - &format!("block_processing_{}", desc), + &format!("{}/block_processing", desc), Benchmark::new("process_exits", move |b| { b.iter_batched( || state.clone(), @@ -342,7 +342,7 @@ fn bench_block_processing( let block = initial_block.clone(); let spec = initial_spec.clone(); c.bench( - &format!("block_processing_{}", desc), + &format!("{}/block_processing", desc), Benchmark::new("process_transfers", move |b| { b.iter_batched( || state.clone(), @@ -360,7 +360,7 @@ fn bench_block_processing( let block = initial_block.clone(); let spec = initial_spec.clone(); c.bench( - &format!("block_processing_{}", desc), + &format!("{}/block_processing", desc), Benchmark::new("per_block_processing", move |b| { b.iter_batched( || state.clone(), @@ -378,7 +378,7 @@ fn bench_block_processing( state.drop_cache(RelativeEpoch::Previous); let spec = initial_spec.clone(); c.bench( - &format!("block_processing_{}", desc), + &format!("{}/block_processing", desc), Benchmark::new("build_previous_state_epoch_cache", move |b| { b.iter_batched( || state.clone(), @@ -398,7 +398,7 @@ fn bench_block_processing( state.drop_cache(RelativeEpoch::Current); let spec = initial_spec.clone(); c.bench( - &format!("block_processing_{}", desc), + &format!("{}/block_processing", desc), Benchmark::new("build_current_state_epoch_cache", move |b| { b.iter_batched( || state.clone(), @@ -416,7 +416,7 @@ fn bench_block_processing( let block = initial_block.clone(); c.bench( - &format!("block_processing_{}", desc), + &format!("{}/block_processing", desc), Benchmark::new("tree_hash_block", move |b| { b.iter(|| black_box(block.hash_tree_root())) }) diff --git a/eth2/state_processing/benches/epoch_processing_benches.rs b/eth2/state_processing/benches/epoch_processing_benches.rs index 85922fa072..342889b3f4 100644 --- a/eth2/state_processing/benches/epoch_processing_benches.rs +++ b/eth2/state_processing/benches/epoch_processing_benches.rs @@ -77,7 +77,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let state_clone = state.clone(); let spec_clone = spec.clone(); c.bench( - &format!("epoch_process_with_caches_{}", desc), + &format!("{}/epoch_processing", desc), Benchmark::new("calculate_active_validator_indices", move |b| { b.iter_batched( || state_clone.clone(), @@ -95,7 +95,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let spec_clone = spec.clone(); let active_validator_indices = calculate_active_validator_indices(&state, &spec); c.bench( - &format!("epoch_process_with_caches_{}", desc), + &format!("{}/epoch_processing", desc), Benchmark::new("calculate_current_total_balance", move |b| { b.iter_batched( || state_clone.clone(), @@ -112,7 +112,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let state_clone = state.clone(); let spec_clone = spec.clone(); c.bench( - &format!("epoch_process_with_caches_{}", desc), + &format!("{}/epoch_processing", desc), Benchmark::new("calculate_previous_total_balance", move |b| { b.iter_batched( || state_clone.clone(), @@ -135,7 +135,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let state_clone = state.clone(); let spec_clone = spec.clone(); c.bench( - &format!("epoch_process_with_caches_{}", desc), + &format!("{}/epoch_processing", desc), Benchmark::new("process_eth1_data", move |b| { b.iter_batched( || state_clone.clone(), @@ -152,7 +152,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let state_clone = state.clone(); let spec_clone = spec.clone(); c.bench( - &format!("epoch_process_with_caches_{}", desc), + &format!("{}/epoch_processing", desc), Benchmark::new("calculate_attester_sets", move |b| { b.iter_batched( || state_clone.clone(), @@ -177,7 +177,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp &spec, ); c.bench( - &format!("epoch_process_with_caches_{}", desc), + &format!("{}/epoch_processing", desc), Benchmark::new("process_justification", move |b| { b.iter_batched( || state_clone.clone(), @@ -201,7 +201,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let state_clone = state.clone(); let spec_clone = spec.clone(); c.bench( - &format!("epoch_process_with_caches_{}", desc), + &format!("{}/epoch_processing", desc), Benchmark::new("process_crosslinks", move |b| { b.iter_batched( || state_clone.clone(), @@ -223,7 +223,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp ); let winning_root_for_shards = process_crosslinks(&mut state_clone, &spec).unwrap(); c.bench( - &format!("epoch_process_with_caches_{}", desc), + &format!("{}/epoch_processing", desc), Benchmark::new("process_rewards_and_penalties", move |b| { b.iter_batched( || state_clone.clone(), @@ -248,7 +248,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let state_clone = state.clone(); let spec_clone = spec.clone(); c.bench( - &format!("epoch_process_with_caches_{}", desc), + &format!("{}/epoch_processing", desc), Benchmark::new("process_ejections", move |b| { b.iter_batched( || state_clone.clone(), @@ -289,7 +289,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp "The state should have been finalized." ); c.bench( - &format!("epoch_process_with_caches_{}", desc), + &format!("{}/epoch_processing", desc), Benchmark::new("process_validator_registry", move |b| { b.iter_batched( || state_clone.clone(), @@ -306,7 +306,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let state_clone = state.clone(); let spec_clone = spec.clone(); c.bench( - &format!("epoch_process_with_caches_{}", desc), + &format!("{}/epoch_processing", desc), Benchmark::new("update_active_tree_index_roots", move |b| { b.iter_batched( || state_clone.clone(), @@ -323,7 +323,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let state_clone = state.clone(); let spec_clone = spec.clone(); c.bench( - &format!("epoch_process_with_caches_{}", desc), + &format!("{}/epoch_processing", desc), Benchmark::new("update_latest_slashed_balances", move |b| { b.iter_batched( || state_clone.clone(), @@ -340,7 +340,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let state_clone = state.clone(); let spec_clone = spec.clone(); c.bench( - &format!("epoch_process_with_caches_{}", desc), + &format!("{}/epoch_processing", desc), Benchmark::new("clean_attestations", move |b| { b.iter_batched( || state_clone.clone(), @@ -357,7 +357,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let state_clone = state.clone(); let spec_clone = spec.clone(); c.bench( - &format!("epoch_process_with_caches_{}", desc), + &format!("{}/epoch_processing", desc), Benchmark::new("per_epoch_processing", move |b| { b.iter_batched( || state_clone.clone(), @@ -370,7 +370,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let state_clone = state.clone(); c.bench( - &format!("epoch_process_with_caches_{}", desc), + &format!("{}/epoch_processing", desc), Benchmark::new("tree_hash_state", move |b| { b.iter(|| black_box(state_clone.hash_tree_root())) }) From 191759dad0f662dc29eed2945a942e3fd38c4f75 Mon Sep 17 00:00:00 2001 From: Kirk Baird Date: Mon, 11 Mar 2019 15:18:45 +1100 Subject: [PATCH 053/144] Modify create_proof_of_possession, and verifying proof_of_possession in process_deposits --- .../test_harness/src/beacon_chain_harness.rs | 11 +++- .../beacon_chain_harness/generate_deposits.rs | 36 ++++++------ .../test_harness/src/test_case.rs | 14 ++++- beacon_node/src/main.rs | 20 +++++-- eth2/types/src/beacon_state.rs | 57 +++++++------------ eth2/types/src/deposit_input.rs | 22 ++++++- .../testing_beacon_block_builder.rs | 4 +- .../src/test_utils/testing_deposit_builder.rs | 18 +++--- eth2/utils/bls/src/lib.rs | 6 -- 9 files changed, 104 insertions(+), 84 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index d2274ac693..ea32e177d0 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -115,7 +115,16 @@ impl BeaconChainHarness { ); } else { debug!("Generating initial validator deposits..."); - let deposits = generate_deposits_from_keypairs(&keypairs, genesis_time, &spec); + let deposits = generate_deposits_from_keypairs( + &keypairs, + genesis_time, + spec.get_domain(spec.genesis_epoch, Domain::Deposit, &Fork{ + previous_version: spec.genesis_fork_version, + current_version: spec.genesis_fork_version, + epoch: spec.genesis_epoch, + }), + &spec + ); state_builder.process_initial_deposits(&deposits, &spec); }; diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs index f2d68d6440..2baf8984f0 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs @@ -1,4 +1,4 @@ -use bls::{create_proof_of_possession, get_withdrawal_credentials}; +use bls::get_withdrawal_credentials; use int_to_bytes::int_to_bytes48; use log::debug; use rayon::prelude::*; @@ -34,6 +34,7 @@ pub fn generate_deterministic_keypairs(validator_count: usize) -> Vec { pub fn generate_deposits_from_keypairs( keypairs: &[Keypair], genesis_time: u64, + domain: u64, spec: &ChainSpec, ) -> Vec { debug!( @@ -44,24 +45,23 @@ pub fn generate_deposits_from_keypairs( let initial_validator_deposits = keypairs .par_iter() - .map(|keypair| Deposit { - branch: vec![], // branch verification is not specified. - index: 0, // index verification is not specified. - deposit_data: DepositData { - amount: 32_000_000_000, // 32 ETH (in Gwei) - timestamp: genesis_time - 1, - deposit_input: DepositInput { - pubkey: keypair.pk.clone(), - // Validator can withdraw using their main keypair. - withdrawal_credentials: Hash256::from_slice( - &get_withdrawal_credentials( - &keypair.pk, - spec.bls_withdrawal_prefix_byte, - )[..], - ), - proof_of_possession: create_proof_of_possession(&keypair), + .map(|keypair| { + let withdrawal_credentials = Hash256::from_slice( + &get_withdrawal_credentials(&keypair.pk, spec.bls_withdrawal_prefix_byte)[..]); + Deposit { + branch: vec![], // branch verification is not specified. + index: 0, // index verification is not specified. + deposit_data: DepositData { + amount: 32_000_000_000, // 32 ETH (in Gwei) + timestamp: genesis_time - 1, + deposit_input: DepositInput { + pubkey: keypair.pk.clone(), + // Validator can withdraw using their main keypair. + withdrawal_credentials: withdrawal_credentials.clone(), + proof_of_possession: DepositInput::create_proof_of_possession(&keypair, &withdrawal_credentials, domain), + }, }, - }, + } }) .collect(); diff --git a/beacon_node/beacon_chain/test_harness/src/test_case.rs b/beacon_node/beacon_chain/test_harness/src/test_case.rs index 7bc7161a8b..32a16ff806 100644 --- a/beacon_node/beacon_chain/test_harness/src/test_case.rs +++ b/beacon_node/beacon_chain/test_harness/src/test_case.rs @@ -3,7 +3,7 @@ use crate::beacon_chain_harness::BeaconChainHarness; use beacon_chain::CheckPoint; -use bls::{create_proof_of_possession, get_withdrawal_credentials}; +use bls::get_withdrawal_credentials; use log::{info, warn}; use ssz::SignedRoot; use std::path::Path; @@ -258,11 +258,19 @@ fn build_deposit( index_offset: u64, ) -> (Deposit, Keypair) { let keypair = Keypair::random(); - let proof_of_possession = create_proof_of_possession(&keypair); - let index = harness.beacon_chain.state.read().deposit_index + index_offset; let withdrawal_credentials = Hash256::from_slice( &get_withdrawal_credentials(&keypair.pk, harness.spec.bls_withdrawal_prefix_byte)[..], ); + let proof_of_possession = DepositInput::create_proof_of_possession( + &keypair, + &withdrawal_credentials, + harness.spec.get_domain( + harness.beacon_chain.state.read().current_epoch(&harness.spec), + Domain::Deposit, + &harness.beacon_chain.state.read().fork, + ) + ); + let index = harness.beacon_chain.state.read().deposit_index + index_offset; let deposit = Deposit { // Note: `branch` and `index` will need to be updated once the spec defines their diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 7606da10d8..8fdfa34461 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -8,7 +8,6 @@ use std::path::PathBuf; use crate::config::LighthouseConfig; use crate::rpc::start_server; use beacon_chain::BeaconChain; -use bls::create_proof_of_possession; use clap::{App, Arg}; use db::{ stores::{BeaconBlockStore, BeaconStateStore}, @@ -20,8 +19,8 @@ use slot_clock::SystemTimeSlotClock; use ssz::TreeHash; use std::sync::Arc; use types::{ - beacon_state::BeaconStateBuilder, BeaconBlock, ChainSpec, Deposit, DepositData, DepositInput, - Eth1Data, Hash256, Keypair, + beacon_state::BeaconStateBuilder, BeaconBlock, ChainSpec, Domain, Deposit, DepositData, DepositInput, + Eth1Data, Fork, Hash256, Keypair, }; fn main() { @@ -113,7 +112,20 @@ fn main() { deposit_input: DepositInput { pubkey: keypair.pk.clone(), withdrawal_credentials: Hash256::zero(), // Withdrawal not possible. - proof_of_possession: create_proof_of_possession(&keypair, Hash256::zero()), + proof_of_possession: DepositInput::create_proof_of_possession( + &keypair, + &Hash256::zero(), + spec.get_domain( + // Get domain from genesis fork_version + spec.genesis_epoch, + Domain::Deposit, + &Fork { + previous_version: spec.genesis_fork_version, + current_version: spec.genesis_fork_version, + epoch: spec.genesis_epoch, + } + ), + ), }, }, }) diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 603ae2f9d2..f98b3e47e9 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -1,7 +1,6 @@ use self::epoch_cache::EpochCache; use crate::test_utils::TestRandom; use crate::{validator_registry::get_active_validator_indices, *}; -use bls::verify_proof_of_possession; use helpers::*; use honey_badger_split::SplitExt; use int_to_bytes::int_to_bytes32; @@ -9,7 +8,7 @@ use log::{debug, error, trace}; use rand::RngCore; use rayon::prelude::*; use serde_derive::Serialize; -use ssz::{hash, Decodable, DecodeError, Encodable, SszStream, TreeHash}; +use ssz::{hash, Decodable, DecodeError, Encodable, SignedRoot, SszStream, TreeHash}; use std::collections::HashMap; use swap_or_not_shuffle::shuffle_list; @@ -590,10 +589,8 @@ impl BeaconState { for deposit_data in deposits { let result = self.process_deposit( - deposit_data.deposit_input.pubkey.clone(), + deposit_data.deposit_input.clone(), deposit_data.amount, - deposit_data.deposit_input.proof_of_possession.clone(), - deposit_data.deposit_input.withdrawal_credentials, Some(&pubkey_map), spec, ); @@ -616,18 +613,29 @@ impl BeaconState { /// Spec v0.4.0 pub fn process_deposit( &mut self, - pubkey: PublicKey, + deposit_input: DepositInput, amount: u64, - proof_of_possession: Signature, - withdrawal_credentials: Hash256, pubkey_map: Option<&HashMap>, spec: &ChainSpec, ) -> Result { - // - if !verify_proof_of_possession(&proof_of_possession, &pubkey) { - return Err(()); + + let proof_is_valid = deposit_input.proof_of_possession.verify( + &deposit_input.signed_root(), + spec.get_domain( + self.current_epoch(&spec), + Domain::Deposit, + &self.fork, + ), + &deposit_input.pubkey, + ); + + if !proof_is_valid { + return Err(()) } + let pubkey = deposit_input.pubkey.clone(); + let withdrawal_credentials = deposit_input.withdrawal_credentials.clone(); + let validator_index = if let Some(pubkey_map) = pubkey_map { pubkey_map.get(&pubkey).and_then(|i| Some(*i)) } else { @@ -1055,33 +1063,6 @@ impl BeaconState { self.validator_registry_update_epoch = current_epoch; } - /// Confirm validator owns PublicKey - /// - /// Spec v0.4.0 - pub fn validate_proof_of_possession( - &self, - pubkey: PublicKey, - proof_of_possession: Signature, - withdrawal_credentials: Hash256, - spec: &ChainSpec, - ) -> bool { - let proof_of_possession_data = DepositInput { - pubkey: pubkey.clone(), - withdrawal_credentials, - proof_of_possession: Signature::empty_signature(), - }; - - proof_of_possession.verify( - &proof_of_possession_data.hash_tree_root(), - spec.get_domain( - self.slot.epoch(spec.slots_per_epoch), - Domain::Deposit, - &self.fork, - ), - &pubkey, - ) - } - /// Iterate through the validator registry and eject active validators with balance below /// ``EJECTION_BALANCE``. /// diff --git a/eth2/types/src/deposit_input.rs b/eth2/types/src/deposit_input.rs index 32f57ab6e6..bb2334672a 100644 --- a/eth2/types/src/deposit_input.rs +++ b/eth2/types/src/deposit_input.rs @@ -1,21 +1,37 @@ use super::Hash256; use crate::test_utils::TestRandom; -use bls::{PublicKey, Signature}; +use bls::{Keypair, PublicKey, Signature}; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode, TreeHash}; +use ssz_derive::{Decode, Encode, SignedRoot, TreeHash}; +use ssz::{SignedRoot, TreeHash}; use test_random_derive::TestRandom; /// The data supplied by the user to the deposit contract. /// /// Spec v0.4.0 -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, SignedRoot, TreeHash, TestRandom)] pub struct DepositInput { pub pubkey: PublicKey, pub withdrawal_credentials: Hash256, pub proof_of_possession: Signature, } +impl DepositInput { + /// Generate the 'proof_of_posession' signature for a given DepositInput details. + /// + /// Spec v0.4.0 + pub fn create_proof_of_possession(keypair: &Keypair, withdrawal_credentials: &Hash256, domain: u64) -> Signature { + let signable_deposite_input = DepositInput { + pubkey: keypair.pk.clone(), + withdrawal_credentials: withdrawal_credentials.clone(), + proof_of_possession: Signature::empty_signature(), + }; + let msg = signable_deposite_input.signed_root(); + Signature::new(msg.as_slice(), domain, &keypair.sk) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/eth2/types/src/test_utils/testing_beacon_block_builder.rs b/eth2/types/src/test_utils/testing_beacon_block_builder.rs index db4d887d40..ad3667e41c 100644 --- a/eth2/types/src/test_utils/testing_beacon_block_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_block_builder.rs @@ -153,12 +153,12 @@ impl TestingBeaconBlockBuilder { } /// Insert a `Valid` deposit into the state. - pub fn insert_deposit(&mut self, amount: u64, index: u64, spec: &ChainSpec) { + pub fn insert_deposit(&mut self, amount: u64, index: u64, domain: u64, spec: &ChainSpec) { let keypair = Keypair::random(); let mut builder = TestingDepositBuilder::new(amount); builder.set_index(index); - builder.sign(&keypair, spec); + builder.sign(&keypair, domain, spec); self.block.body.deposits.push(builder.build()) } diff --git a/eth2/types/src/test_utils/testing_deposit_builder.rs b/eth2/types/src/test_utils/testing_deposit_builder.rs index c7eadcfd18..4e754eab0b 100644 --- a/eth2/types/src/test_utils/testing_deposit_builder.rs +++ b/eth2/types/src/test_utils/testing_deposit_builder.rs @@ -1,5 +1,5 @@ use crate::*; -use bls::{create_proof_of_possession, get_withdrawal_credentials}; +use bls::{get_withdrawal_credentials}; pub struct TestingDepositBuilder { deposit: Deposit, @@ -30,16 +30,16 @@ impl TestingDepositBuilder { self.deposit.index = index; } - pub fn sign(&mut self, keypair: &Keypair, spec: &ChainSpec) { + pub fn sign(&mut self, keypair: &Keypair, domain: u64, spec: &ChainSpec) { + let withdrawal_credentials = Hash256::from_slice(&get_withdrawal_credentials(&keypair.pk, spec.bls_withdrawal_prefix_byte)[..]); self.deposit.deposit_data.deposit_input.pubkey = keypair.pk.clone(); + self.deposit.deposit_data.deposit_input.withdrawal_credentials = withdrawal_credentials.clone(); self.deposit.deposit_data.deposit_input.proof_of_possession = - create_proof_of_possession(&keypair); - self.deposit - .deposit_data - .deposit_input - .withdrawal_credentials = Hash256::from_slice( - &get_withdrawal_credentials(&keypair.pk, spec.bls_withdrawal_prefix_byte)[..], - ); + DepositInput::create_proof_of_possession( + &keypair, + &withdrawal_credentials, + domain, + ); } pub fn build(self) -> Deposit { diff --git a/eth2/utils/bls/src/lib.rs b/eth2/utils/bls/src/lib.rs index 95f993ecb8..4888ff5670 100644 --- a/eth2/utils/bls/src/lib.rs +++ b/eth2/utils/bls/src/lib.rs @@ -20,7 +20,6 @@ pub const BLS_AGG_SIG_BYTE_SIZE: usize = 96; use hashing::hash; use ssz::ssz_encode; -use types::{DepositInput, Hash256}; /// For some signature and public key, ensure that the signature message was the public key and it /// was signed by the secret key that corresponds to that public key. @@ -30,11 +29,6 @@ pub fn verify_proof_of_possession(sig: &Signature, pubkey: &PublicKey) -> bool { sig.verify(&ssz_encode(pubkey), 0, &pubkey) } -// TODO: Update this method -// https://github.com/sigp/lighthouse/issues/239 -pub fn create_proof_of_possession(keypair: &Keypair, withdrawal_credentials: &Hash256) -> Signature { - Signature::new(&ssz_encode(&keypair.pk), 0, &keypair.sk) -} /// Returns the withdrawal credentials for a given public key. pub fn get_withdrawal_credentials(pubkey: &PublicKey, prefix_byte: u8) -> Vec { From 25b08f009640b7e223d8947a925f836dadaea50f Mon Sep 17 00:00:00 2001 From: Kirk Baird Date: Mon, 11 Mar 2019 15:22:15 +1100 Subject: [PATCH 054/144] Run cargo fmt --- .../test_harness/src/beacon_chain_harness.rs | 16 ++++--- .../beacon_chain_harness/generate_deposits.rs | 46 ++++++++++--------- .../test_harness/src/test_case.rs | 8 +++- beacon_node/src/main.rs | 6 +-- eth2/types/src/beacon_state.rs | 9 +--- eth2/types/src/deposit_input.rs | 21 +++++++-- .../src/test_utils/testing_deposit_builder.rs | 17 +++---- eth2/utils/bls/src/lib.rs | 1 - 8 files changed, 73 insertions(+), 51 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index ea32e177d0..7a84456b81 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -118,12 +118,16 @@ impl BeaconChainHarness { let deposits = generate_deposits_from_keypairs( &keypairs, genesis_time, - spec.get_domain(spec.genesis_epoch, Domain::Deposit, &Fork{ - previous_version: spec.genesis_fork_version, - current_version: spec.genesis_fork_version, - epoch: spec.genesis_epoch, - }), - &spec + spec.get_domain( + spec.genesis_epoch, + Domain::Deposit, + &Fork { + previous_version: spec.genesis_fork_version, + current_version: spec.genesis_fork_version, + epoch: spec.genesis_epoch, + }, + ), + &spec, ); state_builder.process_initial_deposits(&deposits, &spec); }; diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs index 2baf8984f0..f568f03e55 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs @@ -42,28 +42,32 @@ pub fn generate_deposits_from_keypairs( keypairs.len() ); - let initial_validator_deposits = - keypairs - .par_iter() - .map(|keypair| { - let withdrawal_credentials = Hash256::from_slice( - &get_withdrawal_credentials(&keypair.pk, spec.bls_withdrawal_prefix_byte)[..]); - Deposit { - branch: vec![], // branch verification is not specified. - index: 0, // index verification is not specified. - deposit_data: DepositData { - amount: 32_000_000_000, // 32 ETH (in Gwei) - timestamp: genesis_time - 1, - deposit_input: DepositInput { - pubkey: keypair.pk.clone(), - // Validator can withdraw using their main keypair. - withdrawal_credentials: withdrawal_credentials.clone(), - proof_of_possession: DepositInput::create_proof_of_possession(&keypair, &withdrawal_credentials, domain), - }, + let initial_validator_deposits = keypairs + .par_iter() + .map(|keypair| { + let withdrawal_credentials = Hash256::from_slice( + &get_withdrawal_credentials(&keypair.pk, spec.bls_withdrawal_prefix_byte)[..], + ); + Deposit { + branch: vec![], // branch verification is not specified. + index: 0, // index verification is not specified. + deposit_data: DepositData { + amount: 32_000_000_000, // 32 ETH (in Gwei) + timestamp: genesis_time - 1, + deposit_input: DepositInput { + pubkey: keypair.pk.clone(), + // Validator can withdraw using their main keypair. + withdrawal_credentials: withdrawal_credentials.clone(), + proof_of_possession: DepositInput::create_proof_of_possession( + &keypair, + &withdrawal_credentials, + domain, + ), }, - } - }) - .collect(); + }, + } + }) + .collect(); initial_validator_deposits } diff --git a/beacon_node/beacon_chain/test_harness/src/test_case.rs b/beacon_node/beacon_chain/test_harness/src/test_case.rs index 32a16ff806..a18b4688b4 100644 --- a/beacon_node/beacon_chain/test_harness/src/test_case.rs +++ b/beacon_node/beacon_chain/test_harness/src/test_case.rs @@ -265,10 +265,14 @@ fn build_deposit( &keypair, &withdrawal_credentials, harness.spec.get_domain( - harness.beacon_chain.state.read().current_epoch(&harness.spec), + harness + .beacon_chain + .state + .read() + .current_epoch(&harness.spec), Domain::Deposit, &harness.beacon_chain.state.read().fork, - ) + ), ); let index = harness.beacon_chain.state.read().deposit_index + index_offset; diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 8fdfa34461..c3182c789b 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -19,8 +19,8 @@ use slot_clock::SystemTimeSlotClock; use ssz::TreeHash; use std::sync::Arc; use types::{ - beacon_state::BeaconStateBuilder, BeaconBlock, ChainSpec, Domain, Deposit, DepositData, DepositInput, - Eth1Data, Fork, Hash256, Keypair, + beacon_state::BeaconStateBuilder, BeaconBlock, ChainSpec, Deposit, DepositData, DepositInput, + Domain, Eth1Data, Fork, Hash256, Keypair, }; fn main() { @@ -123,7 +123,7 @@ fn main() { previous_version: spec.genesis_fork_version, current_version: spec.genesis_fork_version, epoch: spec.genesis_epoch, - } + }, ), ), }, diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index f98b3e47e9..f69746dae5 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -618,19 +618,14 @@ impl BeaconState { pubkey_map: Option<&HashMap>, spec: &ChainSpec, ) -> Result { - let proof_is_valid = deposit_input.proof_of_possession.verify( &deposit_input.signed_root(), - spec.get_domain( - self.current_epoch(&spec), - Domain::Deposit, - &self.fork, - ), + spec.get_domain(self.current_epoch(&spec), Domain::Deposit, &self.fork), &deposit_input.pubkey, ); if !proof_is_valid { - return Err(()) + return Err(()); } let pubkey = deposit_input.pubkey.clone(); diff --git a/eth2/types/src/deposit_input.rs b/eth2/types/src/deposit_input.rs index bb2334672a..2a61efd9c1 100644 --- a/eth2/types/src/deposit_input.rs +++ b/eth2/types/src/deposit_input.rs @@ -3,14 +3,25 @@ use crate::test_utils::TestRandom; use bls::{Keypair, PublicKey, Signature}; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; -use ssz_derive::{Decode, Encode, SignedRoot, TreeHash}; use ssz::{SignedRoot, TreeHash}; +use ssz_derive::{Decode, Encode, SignedRoot, TreeHash}; use test_random_derive::TestRandom; /// The data supplied by the user to the deposit contract. /// /// Spec v0.4.0 -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, SignedRoot, TreeHash, TestRandom)] +#[derive( + Debug, + PartialEq, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + SignedRoot, + TreeHash, + TestRandom, +)] pub struct DepositInput { pub pubkey: PublicKey, pub withdrawal_credentials: Hash256, @@ -21,7 +32,11 @@ impl DepositInput { /// Generate the 'proof_of_posession' signature for a given DepositInput details. /// /// Spec v0.4.0 - pub fn create_proof_of_possession(keypair: &Keypair, withdrawal_credentials: &Hash256, domain: u64) -> Signature { + pub fn create_proof_of_possession( + keypair: &Keypair, + withdrawal_credentials: &Hash256, + domain: u64, + ) -> Signature { let signable_deposite_input = DepositInput { pubkey: keypair.pk.clone(), withdrawal_credentials: withdrawal_credentials.clone(), diff --git a/eth2/types/src/test_utils/testing_deposit_builder.rs b/eth2/types/src/test_utils/testing_deposit_builder.rs index 4e754eab0b..80e039a898 100644 --- a/eth2/types/src/test_utils/testing_deposit_builder.rs +++ b/eth2/types/src/test_utils/testing_deposit_builder.rs @@ -1,5 +1,5 @@ use crate::*; -use bls::{get_withdrawal_credentials}; +use bls::get_withdrawal_credentials; pub struct TestingDepositBuilder { deposit: Deposit, @@ -31,15 +31,16 @@ impl TestingDepositBuilder { } pub fn sign(&mut self, keypair: &Keypair, domain: u64, spec: &ChainSpec) { - let withdrawal_credentials = Hash256::from_slice(&get_withdrawal_credentials(&keypair.pk, spec.bls_withdrawal_prefix_byte)[..]); + let withdrawal_credentials = Hash256::from_slice( + &get_withdrawal_credentials(&keypair.pk, spec.bls_withdrawal_prefix_byte)[..], + ); self.deposit.deposit_data.deposit_input.pubkey = keypair.pk.clone(); - self.deposit.deposit_data.deposit_input.withdrawal_credentials = withdrawal_credentials.clone(); + self.deposit + .deposit_data + .deposit_input + .withdrawal_credentials = withdrawal_credentials.clone(); self.deposit.deposit_data.deposit_input.proof_of_possession = - DepositInput::create_proof_of_possession( - &keypair, - &withdrawal_credentials, - domain, - ); + DepositInput::create_proof_of_possession(&keypair, &withdrawal_credentials, domain); } pub fn build(self) -> Deposit { diff --git a/eth2/utils/bls/src/lib.rs b/eth2/utils/bls/src/lib.rs index 4888ff5670..b995b78c9a 100644 --- a/eth2/utils/bls/src/lib.rs +++ b/eth2/utils/bls/src/lib.rs @@ -29,7 +29,6 @@ pub fn verify_proof_of_possession(sig: &Signature, pubkey: &PublicKey) -> bool { sig.verify(&ssz_encode(pubkey), 0, &pubkey) } - /// Returns the withdrawal credentials for a given public key. pub fn get_withdrawal_credentials(pubkey: &PublicKey, prefix_byte: u8) -> Vec { let hashed = hash(&ssz_encode(pubkey)); From 5e5cfb782ef85c7bab95dff70366db849caa10be Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 11 Mar 2019 18:58:49 +1100 Subject: [PATCH 055/144] Add concurrency to TestBeaconStateBuilder Specifically to generating the initial validator objects. --- eth2/types/src/test_utils/testing_beacon_state_builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index b3cfea6c09..e3f3313deb 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -23,7 +23,7 @@ impl TestingBeaconStateBuilder { .collect(); let validators = keypairs - .iter() + .par_iter() .map(|keypair| { let withdrawal_credentials = Hash256::from_slice(&get_withdrawal_credentials( &keypair.pk, From 7e79a2b3d31032e2acf62e6b02a1435b7b1dd646 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 11 Mar 2019 19:01:44 +1100 Subject: [PATCH 056/144] Improve PublicKey Hash impl efficiency Instead of SSZ-encoding, we just use the AMCL tobytes method. --- eth2/utils/bls/src/public_key.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/eth2/utils/bls/src/public_key.rs b/eth2/utils/bls/src/public_key.rs index 3ab2b60bbb..ffe710d2da 100644 --- a/eth2/utils/bls/src/public_key.rs +++ b/eth2/utils/bls/src/public_key.rs @@ -93,7 +93,11 @@ impl PartialEq for PublicKey { impl Hash for PublicKey { fn hash(&self, state: &mut H) { - ssz_encode(self).hash(state) + // Note: this is not necessarily the consensus-ready hash. Instead, it is designed to be + // optimally fast for internal usage. + // + // To hash for consensus purposes, use the SSZ-encoded bytes. + self.0.as_bytes().hash(state) } } From 292991810d7f9d732eb1ba111624fa671f7ee2de Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 11 Mar 2019 19:46:22 +1100 Subject: [PATCH 057/144] Move state processing benches around --- eth2/state_processing/benches/benches.rs | 14 +++++++++----- .../benches/block_processing_benches.rs | 4 +--- .../benches/epoch_processing_benches.rs | 4 +--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/eth2/state_processing/benches/benches.rs b/eth2/state_processing/benches/benches.rs index 239b782a30..516f152158 100644 --- a/eth2/state_processing/benches/benches.rs +++ b/eth2/state_processing/benches/benches.rs @@ -1,11 +1,15 @@ +use criterion::Criterion; use criterion::{criterion_group, criterion_main}; mod block_processing_benches; mod epoch_processing_benches; -criterion_group!( - benches, - epoch_processing_benches::epoch_processing_16k_validators, - block_processing_benches::block_processing_16k_validators, -); +pub const VALIDATOR_COUNT: usize = 300_032; + +pub fn state_processing(c: &mut Criterion) { + block_processing_benches::bench_block_processing_n_validators(c, VALIDATOR_COUNT); + epoch_processing_benches::bench_epoch_processing_n_validators(c, VALIDATOR_COUNT); +} + +criterion_group!(benches, state_processing,); criterion_main!(benches); diff --git a/eth2/state_processing/benches/block_processing_benches.rs b/eth2/state_processing/benches/block_processing_benches.rs index ea1ada193d..06563b05b4 100644 --- a/eth2/state_processing/benches/block_processing_benches.rs +++ b/eth2/state_processing/benches/block_processing_benches.rs @@ -13,11 +13,9 @@ use types::test_utils::{TestingBeaconBlockBuilder, TestingBeaconStateBuilder}; use types::*; /// Run the benchmarking suite on a foundation spec with 16,384 validators. -pub fn block_processing_16k_validators(c: &mut Criterion) { +pub fn bench_block_processing_n_validators(c: &mut Criterion, validator_count: usize) { let spec = ChainSpec::foundation(); - let validator_count = 300_032; - let (mut state, keypairs) = build_state(validator_count, &spec); let block = build_block(&mut state, &keypairs, &spec); diff --git a/eth2/state_processing/benches/epoch_processing_benches.rs b/eth2/state_processing/benches/epoch_processing_benches.rs index 342889b3f4..f7ae136761 100644 --- a/eth2/state_processing/benches/epoch_processing_benches.rs +++ b/eth2/state_processing/benches/epoch_processing_benches.rs @@ -17,11 +17,9 @@ pub const BENCHING_SAMPLE_SIZE: usize = 10; pub const SMALL_BENCHING_SAMPLE_SIZE: usize = 10; /// Run the benchmarking suite on a foundation spec with 16,384 validators. -pub fn epoch_processing_16k_validators(c: &mut Criterion) { +pub fn bench_epoch_processing_n_validators(c: &mut Criterion, validator_count: usize) { let spec = ChainSpec::foundation(); - let validator_count = 300_032; - let mut builder = TestingBeaconStateBuilder::new(validator_count, &spec); // Set the state to be just before an epoch transition. From e81f1c31c9890ae34be093fdbd11523853983902 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 11 Mar 2019 19:47:33 +1100 Subject: [PATCH 058/144] Fix proof-of-possession issues. These were introduced in an earlier commit --- .../benches/block_processing_benches.rs | 2 +- .../per_block_processing/verify_deposit.rs | 16 +++++++------- eth2/types/src/deposit_input.rs | 22 ++++++++++++++++--- .../testing_beacon_block_builder.rs | 10 +++++++-- .../src/test_utils/testing_deposit_builder.rs | 6 ++++- eth2/utils/bls/src/lib.rs | 8 ------- 6 files changed, 41 insertions(+), 23 deletions(-) diff --git a/eth2/state_processing/benches/block_processing_benches.rs b/eth2/state_processing/benches/block_processing_benches.rs index 06563b05b4..9d9a5647b7 100644 --- a/eth2/state_processing/benches/block_processing_benches.rs +++ b/eth2/state_processing/benches/block_processing_benches.rs @@ -140,7 +140,7 @@ fn build_block(state: &mut BeaconState, keypairs: &[Keypair], spec: &ChainSpec) // Insert the maximum possible number of `Deposit` objects. for i in 0..spec.max_deposits { - builder.insert_deposit(32_000_000_000, state.deposit_index + i, spec); + builder.insert_deposit(32_000_000_000, state.deposit_index + i, state, spec); } // Insert the maximum possible number of `Exit` objects. diff --git a/eth2/state_processing/src/per_block_processing/verify_deposit.rs b/eth2/state_processing/src/per_block_processing/verify_deposit.rs index 0cf2a078f9..1aabbb973f 100644 --- a/eth2/state_processing/src/per_block_processing/verify_deposit.rs +++ b/eth2/state_processing/src/per_block_processing/verify_deposit.rs @@ -1,5 +1,4 @@ use super::errors::{DepositInvalid as Invalid, DepositValidationError as Error}; -use bls::verify_proof_of_possession; use hashing::hash; use merkle_proof::verify_merkle_proof; use ssz::ssz_encode; @@ -27,13 +26,14 @@ pub fn verify_deposit( spec: &ChainSpec, ) -> Result<(), Error> { verify!( - // TODO: update proof of possession. - // - // https://github.com/sigp/lighthouse/issues/239 - verify_proof_of_possession( - &deposit.deposit_data.deposit_input.proof_of_possession, - &deposit.deposit_data.deposit_input.pubkey - ), + deposit + .deposit_data + .deposit_input + .validate_proof_of_possession( + state.slot.epoch(spec.slots_per_epoch), + &state.fork, + spec + ), Invalid::BadProofOfPossession ); diff --git a/eth2/types/src/deposit_input.rs b/eth2/types/src/deposit_input.rs index 2a61efd9c1..1b506894d3 100644 --- a/eth2/types/src/deposit_input.rs +++ b/eth2/types/src/deposit_input.rs @@ -1,5 +1,5 @@ -use super::Hash256; use crate::test_utils::TestRandom; +use crate::*; use bls::{Keypair, PublicKey, Signature}; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; @@ -37,14 +37,30 @@ impl DepositInput { withdrawal_credentials: &Hash256, domain: u64, ) -> Signature { - let signable_deposite_input = DepositInput { + let signable_deposit_input = DepositInput { pubkey: keypair.pk.clone(), withdrawal_credentials: withdrawal_credentials.clone(), proof_of_possession: Signature::empty_signature(), }; - let msg = signable_deposite_input.signed_root(); + let msg = signable_deposit_input.signed_root(); + Signature::new(msg.as_slice(), domain, &keypair.sk) } + + /// Verify that proof-of-possession is valid. + /// + /// Spec v0.4.0 + pub fn validate_proof_of_possession( + &self, + epoch: Epoch, + fork: &Fork, + spec: &ChainSpec, + ) -> bool { + let msg = self.signed_root(); + let domain = spec.get_domain(epoch, Domain::Deposit, fork); + + self.proof_of_possession.verify(&msg, domain, &self.pubkey) + } } #[cfg(test)] diff --git a/eth2/types/src/test_utils/testing_beacon_block_builder.rs b/eth2/types/src/test_utils/testing_beacon_block_builder.rs index ad3667e41c..bbdc5046b8 100644 --- a/eth2/types/src/test_utils/testing_beacon_block_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_block_builder.rs @@ -153,12 +153,18 @@ impl TestingBeaconBlockBuilder { } /// Insert a `Valid` deposit into the state. - pub fn insert_deposit(&mut self, amount: u64, index: u64, domain: u64, spec: &ChainSpec) { + pub fn insert_deposit( + &mut self, + amount: u64, + index: u64, + state: &BeaconState, + spec: &ChainSpec, + ) { let keypair = Keypair::random(); let mut builder = TestingDepositBuilder::new(amount); builder.set_index(index); - builder.sign(&keypair, domain, spec); + builder.sign(&keypair, state, spec); self.block.body.deposits.push(builder.build()) } diff --git a/eth2/types/src/test_utils/testing_deposit_builder.rs b/eth2/types/src/test_utils/testing_deposit_builder.rs index 80e039a898..56e81cad0a 100644 --- a/eth2/types/src/test_utils/testing_deposit_builder.rs +++ b/eth2/types/src/test_utils/testing_deposit_builder.rs @@ -30,10 +30,14 @@ impl TestingDepositBuilder { self.deposit.index = index; } - pub fn sign(&mut self, keypair: &Keypair, domain: u64, spec: &ChainSpec) { + pub fn sign(&mut self, keypair: &Keypair, state: &BeaconState, spec: &ChainSpec) { let withdrawal_credentials = Hash256::from_slice( &get_withdrawal_credentials(&keypair.pk, spec.bls_withdrawal_prefix_byte)[..], ); + + let epoch = state.current_epoch(spec); + let domain = spec.get_domain(epoch, Domain::Deposit, &state.fork); + self.deposit.deposit_data.deposit_input.pubkey = keypair.pk.clone(); self.deposit .deposit_data diff --git a/eth2/utils/bls/src/lib.rs b/eth2/utils/bls/src/lib.rs index b995b78c9a..38a1299084 100644 --- a/eth2/utils/bls/src/lib.rs +++ b/eth2/utils/bls/src/lib.rs @@ -21,14 +21,6 @@ pub const BLS_AGG_SIG_BYTE_SIZE: usize = 96; use hashing::hash; use ssz::ssz_encode; -/// For some signature and public key, ensure that the signature message was the public key and it -/// was signed by the secret key that corresponds to that public key. -pub fn verify_proof_of_possession(sig: &Signature, pubkey: &PublicKey) -> bool { - // TODO: replace this function with state.validate_proof_of_possession - // https://github.com/sigp/lighthouse/issues/239 - sig.verify(&ssz_encode(pubkey), 0, &pubkey) -} - /// Returns the withdrawal credentials for a given public key. pub fn get_withdrawal_credentials(pubkey: &PublicKey, prefix_byte: u8) -> Vec { let hashed = hash(&ssz_encode(pubkey)); From 9de6a0c733bd94ed2f0ef3b5216e46c3dd186f05 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 12 Mar 2019 09:57:00 +1100 Subject: [PATCH 059/144] Rename benching files --- ...ck_processing_benches.rs => bench_block_processing.rs} | 0 ...ch_processing_benches.rs => bench_epoch_processing.rs} | 0 eth2/state_processing/benches/benches.rs | 8 ++++---- 3 files changed, 4 insertions(+), 4 deletions(-) rename eth2/state_processing/benches/{block_processing_benches.rs => bench_block_processing.rs} (100%) rename eth2/state_processing/benches/{epoch_processing_benches.rs => bench_epoch_processing.rs} (100%) diff --git a/eth2/state_processing/benches/block_processing_benches.rs b/eth2/state_processing/benches/bench_block_processing.rs similarity index 100% rename from eth2/state_processing/benches/block_processing_benches.rs rename to eth2/state_processing/benches/bench_block_processing.rs diff --git a/eth2/state_processing/benches/epoch_processing_benches.rs b/eth2/state_processing/benches/bench_epoch_processing.rs similarity index 100% rename from eth2/state_processing/benches/epoch_processing_benches.rs rename to eth2/state_processing/benches/bench_epoch_processing.rs diff --git a/eth2/state_processing/benches/benches.rs b/eth2/state_processing/benches/benches.rs index 516f152158..6e54a25f52 100644 --- a/eth2/state_processing/benches/benches.rs +++ b/eth2/state_processing/benches/benches.rs @@ -1,14 +1,14 @@ use criterion::Criterion; use criterion::{criterion_group, criterion_main}; -mod block_processing_benches; -mod epoch_processing_benches; +mod bench_block_processing; +mod bench_epoch_processing; pub const VALIDATOR_COUNT: usize = 300_032; pub fn state_processing(c: &mut Criterion) { - block_processing_benches::bench_block_processing_n_validators(c, VALIDATOR_COUNT); - epoch_processing_benches::bench_epoch_processing_n_validators(c, VALIDATOR_COUNT); + bench_block_processing::bench_block_processing_n_validators(c, VALIDATOR_COUNT); + bench_epoch_processing::bench_epoch_processing_n_validators(c, VALIDATOR_COUNT); } criterion_group!(benches, state_processing,); From f34ae86cde83811806d0322241295f1100cb0d69 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 12 Mar 2019 12:46:44 +1100 Subject: [PATCH 060/144] Add support for loading keypairs from file --- .gitignore | 1 + .../beacon_chain/test_harness/.gitignore | 1 - .../test_harness/src/beacon_chain_harness.rs | 6 +- .../beacon_chain_harness/generate_deposits.rs | 27 ----- .../load_deposits_from_file.rs | 38 ------ .../beacon_chain/test_harness/src/bin.rs | 28 ++--- .../beacon_chain/test_harness/src/gen_keys.rs | 20 ++++ .../beacon_chain/test_harness/src/prepare.rs | 69 ----------- eth2/fork_choice/tests/tests.rs | 2 +- .../benches/bench_block_processing.rs | 17 ++- .../benches/bench_epoch_processing.rs | 9 +- eth2/state_processing/benches/benches.rs | 48 +++++++- .../src/per_epoch_processing/tests.rs | 2 +- eth2/types/src/beacon_state/tests.rs | 2 +- .../generate_deterministic_keypairs.rs | 30 +++++ eth2/types/src/test_utils/keypairs_file.rs | 113 ++++++++++++++++++ eth2/types/src/test_utils/mod.rs | 4 + .../testing_beacon_state_builder.rs | 19 ++- eth2/utils/bls/src/public_key.rs | 11 +- 19 files changed, 264 insertions(+), 183 deletions(-) delete mode 100644 beacon_node/beacon_chain/test_harness/.gitignore delete mode 100644 beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/load_deposits_from_file.rs create mode 100644 beacon_node/beacon_chain/test_harness/src/gen_keys.rs delete mode 100644 beacon_node/beacon_chain/test_harness/src/prepare.rs create mode 100644 eth2/types/src/test_utils/generate_deterministic_keypairs.rs create mode 100644 eth2/types/src/test_utils/keypairs_file.rs diff --git a/.gitignore b/.gitignore index 9050bdab9d..346ef9afae 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ target/ Cargo.lock *.pk *.sk +*.raw_keypairs diff --git a/beacon_node/beacon_chain/test_harness/.gitignore b/beacon_node/beacon_chain/test_harness/.gitignore deleted file mode 100644 index 5f605cba08..0000000000 --- a/beacon_node/beacon_chain/test_harness/.gitignore +++ /dev/null @@ -1 +0,0 @@ -validators/ diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index 7a84456b81..c442c05dbc 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -16,13 +16,11 @@ use std::fs::File; use std::iter::FromIterator; use std::path::Path; use std::sync::Arc; -use types::{beacon_state::BeaconStateBuilder, *}; +use types::{beacon_state::BeaconStateBuilder, test_utils::generate_deterministic_keypairs, *}; mod generate_deposits; -mod load_deposits_from_file; -pub use generate_deposits::{generate_deposits_from_keypairs, generate_deterministic_keypairs}; -pub use load_deposits_from_file::load_deposits_from_file; +pub use generate_deposits::generate_deposits_from_keypairs; /// The beacon chain harness simulates a single beacon node with `validator_count` validators connected /// to it. Each validator is provided a borrow to the beacon chain, where it may read diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs index f568f03e55..bba3aec1cb 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs @@ -1,35 +1,8 @@ use bls::get_withdrawal_credentials; -use int_to_bytes::int_to_bytes48; use log::debug; use rayon::prelude::*; use types::*; -/// Generates `validator_count` keypairs where the secret key is the index of the -/// validator. -/// -/// For example, the first validator has a secret key of `int_to_bytes48(1)`, the second has -/// `int_to_bytes48(2)` and so on. (We skip `0` as it generates a weird looking public key and is -/// probably invalid). -pub fn generate_deterministic_keypairs(validator_count: usize) -> Vec { - debug!( - "Generating {} deterministic validator keypairs...", - validator_count - ); - - let keypairs: Vec = (0..validator_count) - .collect::>() - .par_iter() - .map(|&i| { - let secret = int_to_bytes48(i as u64 + 1); - let sk = SecretKey::from_bytes(&secret).unwrap(); - let pk = PublicKey::from_secret_key(&sk); - Keypair { sk, pk } - }) - .collect(); - - keypairs -} - /// Generates a `Deposit` for each keypairs pub fn generate_deposits_from_keypairs( keypairs: &[Keypair], diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/load_deposits_from_file.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/load_deposits_from_file.rs deleted file mode 100644 index 9cba3d3c49..0000000000 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/load_deposits_from_file.rs +++ /dev/null @@ -1,38 +0,0 @@ -use log::debug; -use serde_yaml; -use std::fs::File; -use std::path::Path; -use types::*; - -pub fn load_deposits_from_file( - validator_count: usize, - keypairs_path: &Path, - deposits_path: &Path, -) -> (Vec, Vec) { - debug!("Loading keypairs from file..."); - let keypairs_file = File::open(keypairs_path).unwrap(); - let mut keypairs: Vec = serde_yaml::from_reader(&keypairs_file).unwrap(); - - debug!("Loading deposits from file..."); - let deposits_file = File::open(deposits_path).unwrap(); - let mut deposits: Vec = serde_yaml::from_reader(&deposits_file).unwrap(); - - assert!( - keypairs.len() >= validator_count, - "Unable to load {} keypairs from file ({} available)", - validator_count, - keypairs.len() - ); - - assert!( - deposits.len() >= validator_count, - "Unable to load {} deposits from file ({} available)", - validator_count, - deposits.len() - ); - - keypairs.truncate(validator_count); - deposits.truncate(validator_count); - - (keypairs, deposits) -} diff --git a/beacon_node/beacon_chain/test_harness/src/bin.rs b/beacon_node/beacon_chain/test_harness/src/bin.rs index 0a02264a3b..d5e43f67a9 100644 --- a/beacon_node/beacon_chain/test_harness/src/bin.rs +++ b/beacon_node/beacon_chain/test_harness/src/bin.rs @@ -1,11 +1,11 @@ use clap::{App, Arg, SubCommand}; use env_logger::{Builder, Env}; -use prepare::prepare; +use gen_keys::gen_keys; use run_test::run_test; use types::ChainSpec; mod beacon_chain_harness; -mod prepare; +mod gen_keys; mod run_test; mod test_case; mod validator_harness; @@ -55,8 +55,8 @@ fn main() { ), ) .subcommand( - SubCommand::with_name("prepare") - .about("Builds validator YAML files for faster tests.") + SubCommand::with_name("gen_keys") + .about("Builds a file of BLS keypairs for faster tests.") .arg( Arg::with_name("validator_count") .long("validator_count") @@ -66,20 +66,12 @@ fn main() { .required(true), ) .arg( - Arg::with_name("genesis_time") - .long("genesis_time") - .short("t") - .value_name("GENESIS_TIME") - .help("Time for validator deposits.") - .required(true), - ) - .arg( - Arg::with_name("output_dir") - .long("output_dir") + Arg::with_name("output_file") + .long("output_file") .short("d") .value_name("GENESIS_TIME") .help("Output directory for generated YAML.") - .default_value("validators"), + .default_value("keypairs.raw_keypairs"), ), ) .get_matches(); @@ -88,7 +80,7 @@ fn main() { Builder::from_env(Env::default().default_filter_or(log_level)).init(); } - let spec = match matches.value_of("spec") { + let _spec = match matches.value_of("spec") { Some("foundation") => ChainSpec::foundation(), Some("few_validators") => ChainSpec::few_validators(), _ => unreachable!(), // Has a default value, should always exist. @@ -98,7 +90,7 @@ fn main() { run_test(matches); } - if let Some(matches) = matches.subcommand_matches("prepare") { - prepare(matches, &spec); + if let Some(matches) = matches.subcommand_matches("gen_keys") { + gen_keys(matches); } } diff --git a/beacon_node/beacon_chain/test_harness/src/gen_keys.rs b/beacon_node/beacon_chain/test_harness/src/gen_keys.rs new file mode 100644 index 0000000000..f2f81b393a --- /dev/null +++ b/beacon_node/beacon_chain/test_harness/src/gen_keys.rs @@ -0,0 +1,20 @@ +use clap::{value_t, ArgMatches}; +use log::debug; +use std::path::Path; +use types::test_utils::{generate_deterministic_keypairs, KeypairsFile}; + +pub fn gen_keys(matches: &ArgMatches) { + let validator_count = value_t!(matches.value_of("validator_count"), usize) + .expect("Validator count is required argument"); + let output_file = matches + .value_of("output_file") + .expect("Output file has a default value."); + + let keypairs = generate_deterministic_keypairs(validator_count); + + debug!("Writing keypairs to file..."); + + let keypairs_path = Path::new(output_file); + + keypairs.to_raw_file(&keypairs_path, &keypairs).unwrap(); +} diff --git a/beacon_node/beacon_chain/test_harness/src/prepare.rs b/beacon_node/beacon_chain/test_harness/src/prepare.rs deleted file mode 100644 index 36a99317fd..0000000000 --- a/beacon_node/beacon_chain/test_harness/src/prepare.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::beacon_chain_harness::generate_deterministic_keypairs; -use bls::get_withdrawal_credentials; -use clap::{value_t, ArgMatches}; -use log::debug; -use serde_yaml; -use std::path::Path; -use std::{fs, fs::File}; -use types::*; - -const KEYPAIRS_FILE: &str = "keypairs.yaml"; -const VALIDATORS_FILE: &str = "validators.yaml"; - -pub fn prepare(matches: &ArgMatches, spec: &ChainSpec) { - let validator_count = value_t!(matches.value_of("validator_count"), usize) - .expect("Validator count is required argument"); - let output_dir = matches - .value_of("output_dir") - .expect("Output dir has a default value."); - - debug!("Created keypairs and validators, writing to file..."); - - fs::create_dir_all(Path::new(output_dir)).unwrap(); - - // Ensure that keypairs is dropped before writing validators, this provides a big memory saving - // for large validator_counts. - let validators: Vec = { - debug!("Creating {} keypairs...", validator_count); - let keypairs = generate_deterministic_keypairs(validator_count); - debug!("Writing {} keypairs to file...", validator_count); - write_keypairs(output_dir, &keypairs); - debug!("Creating {} validators...", validator_count); - keypairs - .iter() - .map(|keypair| generate_validator(&keypair, spec)) - .collect() - }; - - debug!("Writing {} validators to file...", validator_count); - write_validators(output_dir, &validators); -} - -fn generate_validator(keypair: &Keypair, spec: &ChainSpec) -> Validator { - let withdrawal_credentials = Hash256::from_slice(&get_withdrawal_credentials( - &keypair.pk, - spec.bls_withdrawal_prefix_byte, - )); - - Validator { - pubkey: keypair.pk.clone(), - withdrawal_credentials, - activation_epoch: spec.far_future_epoch, - exit_epoch: spec.far_future_epoch, - withdrawable_epoch: spec.far_future_epoch, - initiated_exit: false, - slashed: false, - } -} - -fn write_keypairs(output_dir: &str, keypairs: &[Keypair]) { - let keypairs_path = Path::new(output_dir).join(KEYPAIRS_FILE); - let keypairs_file = File::create(keypairs_path).unwrap(); - serde_yaml::to_writer(keypairs_file, &keypairs).unwrap(); -} - -fn write_validators(output_dir: &str, validators: &[Validator]) { - let validators_path = Path::new(output_dir).join(VALIDATORS_FILE); - let validators_file = File::create(validators_path).unwrap(); - serde_yaml::to_writer(validators_file, &validators).unwrap(); -} diff --git a/eth2/fork_choice/tests/tests.rs b/eth2/fork_choice/tests/tests.rs index 5bf3b7e57d..364e8796cd 100644 --- a/eth2/fork_choice/tests/tests.rs +++ b/eth2/fork_choice/tests/tests.rs @@ -224,7 +224,7 @@ fn setup_inital_state( let spec = ChainSpec::foundation(); - let state_builder = TestingBeaconStateBuilder::new(no_validators, &spec); + let state_builder = TestingBeaconStateBuilder::new(no_validators, None, &spec); let (state, _keypairs) = state_builder.build(); let state_root = state.canonical_root(); diff --git a/eth2/state_processing/benches/bench_block_processing.rs b/eth2/state_processing/benches/bench_block_processing.rs index 9d9a5647b7..aa595b7ac7 100644 --- a/eth2/state_processing/benches/bench_block_processing.rs +++ b/eth2/state_processing/benches/bench_block_processing.rs @@ -9,14 +9,19 @@ use state_processing::{ verify_block_signature, }, }; +use std::path::Path; use types::test_utils::{TestingBeaconBlockBuilder, TestingBeaconStateBuilder}; use types::*; /// Run the benchmarking suite on a foundation spec with 16,384 validators. -pub fn bench_block_processing_n_validators(c: &mut Criterion, validator_count: usize) { +pub fn bench_block_processing_n_validators( + c: &mut Criterion, + validator_count: usize, + keypair_file: Option<&Path>, +) { let spec = ChainSpec::foundation(); - let (mut state, keypairs) = build_state(validator_count, &spec); + let (mut state, keypairs) = build_state(validator_count, keypair_file, &spec); let block = build_block(&mut state, &keypairs, &spec); assert_eq!( @@ -79,8 +84,12 @@ pub fn bench_block_processing_n_validators(c: &mut Criterion, validator_count: u ); } -fn build_state(validator_count: usize, spec: &ChainSpec) -> (BeaconState, Vec) { - let mut builder = TestingBeaconStateBuilder::new(validator_count, &spec); +fn build_state( + validator_count: usize, + keypair_file: Option<&Path>, + spec: &ChainSpec, +) -> (BeaconState, Vec) { + let mut builder = TestingBeaconStateBuilder::new(validator_count, keypair_file, &spec); // Set the state to be just before an epoch transition. let target_slot = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch); diff --git a/eth2/state_processing/benches/bench_epoch_processing.rs b/eth2/state_processing/benches/bench_epoch_processing.rs index f7ae136761..5f07d11008 100644 --- a/eth2/state_processing/benches/bench_epoch_processing.rs +++ b/eth2/state_processing/benches/bench_epoch_processing.rs @@ -10,6 +10,7 @@ use state_processing::{ update_latest_slashed_balances, }, }; +use std::path::Path; use types::test_utils::TestingBeaconStateBuilder; use types::{validator_registry::get_active_validator_indices, *}; @@ -17,10 +18,14 @@ pub const BENCHING_SAMPLE_SIZE: usize = 10; pub const SMALL_BENCHING_SAMPLE_SIZE: usize = 10; /// Run the benchmarking suite on a foundation spec with 16,384 validators. -pub fn bench_epoch_processing_n_validators(c: &mut Criterion, validator_count: usize) { +pub fn bench_epoch_processing_n_validators( + c: &mut Criterion, + validator_count: usize, + keypair_file: Option<&Path>, +) { let spec = ChainSpec::foundation(); - let mut builder = TestingBeaconStateBuilder::new(validator_count, &spec); + let mut builder = TestingBeaconStateBuilder::new(validator_count, keypair_file, &spec); // Set the state to be just before an epoch transition. let target_slot = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch); diff --git a/eth2/state_processing/benches/benches.rs b/eth2/state_processing/benches/benches.rs index 6e54a25f52..721049eeb0 100644 --- a/eth2/state_processing/benches/benches.rs +++ b/eth2/state_processing/benches/benches.rs @@ -1,5 +1,9 @@ +use criterion::Benchmark; use criterion::Criterion; use criterion::{criterion_group, criterion_main}; +use std::path::Path; +use types::test_utils::TestingBeaconStateBuilder; +use types::*; mod bench_block_processing; mod bench_epoch_processing; @@ -7,9 +11,47 @@ mod bench_epoch_processing; pub const VALIDATOR_COUNT: usize = 300_032; pub fn state_processing(c: &mut Criterion) { - bench_block_processing::bench_block_processing_n_validators(c, VALIDATOR_COUNT); - bench_epoch_processing::bench_epoch_processing_n_validators(c, VALIDATOR_COUNT); + bench_block_processing::bench_block_processing_n_validators(c, VALIDATOR_COUNT, None); + bench_epoch_processing::bench_epoch_processing_n_validators(c, VALIDATOR_COUNT, None); } -criterion_group!(benches, state_processing,); +pub fn key_loading(c: &mut Criterion) { + let validator_count = 1000; + + c.bench( + &format!("{}_validators", validator_count), + Benchmark::new("generated", move |b| { + b.iter_batched( + || (), + |_| TestingBeaconStateBuilder::new(validator_count, None, &ChainSpec::foundation()), + criterion::BatchSize::SmallInput, + ) + }) + .sample_size(10), + ); + + // Note: path needs to be relative to where cargo is executed from. + let keypair_file = + Path::new("../../beacon_node/beacon_chain/test_harness/keypairs.raw_keypairs"); + c.bench( + &format!("{}_validators", validator_count), + Benchmark::new("from_file", move |b| { + b.iter_batched( + || (), + |_| { + TestingBeaconStateBuilder::new( + validator_count, + Some(&keypair_file), + &ChainSpec::foundation(), + ) + }, + criterion::BatchSize::SmallInput, + ) + }) + .sample_size(10), + ); +} + +// criterion_group!(benches, state_processing, key_loading); +criterion_group!(benches, key_loading); criterion_main!(benches); diff --git a/eth2/state_processing/src/per_epoch_processing/tests.rs b/eth2/state_processing/src/per_epoch_processing/tests.rs index 18c888e78c..df014e1d64 100644 --- a/eth2/state_processing/src/per_epoch_processing/tests.rs +++ b/eth2/state_processing/src/per_epoch_processing/tests.rs @@ -10,7 +10,7 @@ fn runs_without_error() { let spec = ChainSpec::few_validators(); - let mut builder = TestingBeaconStateBuilder::new(8, &spec); + let mut builder = TestingBeaconStateBuilder::new(8, None, &spec); let target_slot = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch); builder.teleport_to_slot(target_slot, &spec); diff --git a/eth2/types/src/beacon_state/tests.rs b/eth2/types/src/beacon_state/tests.rs index fc55520bb7..da5cdf0fb5 100644 --- a/eth2/types/src/beacon_state/tests.rs +++ b/eth2/types/src/beacon_state/tests.rs @@ -13,7 +13,7 @@ pub fn get_attestation_participants_consistency() { let mut rng = XorShiftRng::from_seed([42; 16]); let spec = ChainSpec::few_validators(); - let builder = TestingBeaconStateBuilder::new(8, &spec); + let builder = TestingBeaconStateBuilder::new(8, None, &spec); let (mut state, _keypairs) = builder.build(); state diff --git a/eth2/types/src/test_utils/generate_deterministic_keypairs.rs b/eth2/types/src/test_utils/generate_deterministic_keypairs.rs new file mode 100644 index 0000000000..f2ce8709e8 --- /dev/null +++ b/eth2/types/src/test_utils/generate_deterministic_keypairs.rs @@ -0,0 +1,30 @@ +use crate::*; +use int_to_bytes::int_to_bytes48; +use log::debug; +use rayon::prelude::*; + +/// Generates `validator_count` keypairs where the secret key is the index of the +/// validator. +/// +/// For example, the first validator has a secret key of `int_to_bytes48(1)`, the second has +/// `int_to_bytes48(2)` and so on. (We skip `0` as it generates a weird looking public key and is +/// probably invalid). +pub fn generate_deterministic_keypairs(validator_count: usize) -> Vec { + debug!( + "Generating {} deterministic validator keypairs...", + validator_count + ); + + let keypairs: Vec = (0..validator_count) + .collect::>() + .par_iter() + .map(|&i| { + let secret = int_to_bytes48(i as u64 + 1); + let sk = SecretKey::from_bytes(&secret).unwrap(); + let pk = PublicKey::from_secret_key(&sk); + Keypair { sk, pk } + }) + .collect(); + + keypairs +} diff --git a/eth2/types/src/test_utils/keypairs_file.rs b/eth2/types/src/test_utils/keypairs_file.rs new file mode 100644 index 0000000000..5828af9a99 --- /dev/null +++ b/eth2/types/src/test_utils/keypairs_file.rs @@ -0,0 +1,113 @@ +use crate::*; +use std::fs::File; +use std::io::{Error, ErrorKind, Read, Write}; +use std::path::Path; + +pub const PUBLIC_KEY_BYTES_LEN: usize = 48; +pub const SECRET_KEY_BYTES_LEN: usize = 48; + +pub const BATCH_SIZE: usize = 1_000; // ~15MB + +pub const KEYPAIR_BYTES_LEN: usize = PUBLIC_KEY_BYTES_LEN + SECRET_KEY_BYTES_LEN; +pub const BATCH_BYTE_LEN: usize = KEYPAIR_BYTES_LEN * BATCH_SIZE; + +pub trait KeypairsFile { + fn to_raw_file(&self, path: &Path, keypairs: &[Keypair]) -> Result<(), Error>; + fn from_raw_file(path: &Path, count: usize) -> Result, Error>; +} + +impl KeypairsFile for Vec { + fn to_raw_file(&self, path: &Path, keypairs: &[Keypair]) -> Result<(), Error> { + let mut keypairs_file = File::create(path)?; + + for keypair_batch in keypairs.chunks(BATCH_SIZE) { + let mut buf = Vec::with_capacity(BATCH_BYTE_LEN); + + for keypair in keypair_batch { + buf.append(&mut keypair.sk.as_raw().as_bytes()); + buf.append(&mut keypair.pk.as_raw().as_bytes()); + } + + keypairs_file.write_all(&buf)?; + } + + Ok(()) + } + + fn from_raw_file(path: &Path, count: usize) -> Result, Error> { + let mut keypairs_file = File::open(path)?; + + let mut keypairs = Vec::with_capacity(count); + + let indices: Vec = (0..count).collect(); + + for batch in indices.chunks(BATCH_SIZE) { + let mut buf = vec![0; batch.len() * KEYPAIR_BYTES_LEN]; + keypairs_file.read_exact(&mut buf)?; + + for (i, _) in batch.iter().enumerate() { + let sk_start = i * KEYPAIR_BYTES_LEN; + let sk_end = sk_start + SECRET_KEY_BYTES_LEN; + let sk = SecretKey::from_bytes(&buf[sk_start..sk_end]) + .map_err(|_| Error::new(ErrorKind::Other, "Invalid SecretKey bytes"))?; + + let pk_start = sk_end; + let pk_end = pk_start + PUBLIC_KEY_BYTES_LEN; + let pk = PublicKey::from_bytes(&buf[pk_start..pk_end]) + .map_err(|_| Error::new(ErrorKind::Other, "Invalid PublicKey bytes"))?; + + keypairs.push(Keypair { sk, pk }); + } + } + + Ok(keypairs) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::{distributions::Alphanumeric, thread_rng, Rng}; + use rayon::prelude::*; + use std::fs::remove_file; + + fn random_keypairs(n: usize) -> Vec { + (0..n).into_par_iter().map(|_| Keypair::random()).collect() + } + + fn random_tmp_file() -> String { + let mut rng = thread_rng(); + + rng.sample_iter(&Alphanumeric).take(7).collect() + } + + #[test] + #[ignore] + fn read_write_consistency_small_batch() { + let num_keypairs = 10; + let keypairs = random_keypairs(num_keypairs); + + let keypairs_path = Path::new("/tmp").join(random_tmp_file()); + keypairs.to_raw_file(&keypairs_path, &keypairs).unwrap(); + + let decoded = Vec::from_raw_file(&keypairs_path, num_keypairs).unwrap(); + remove_file(keypairs_path).unwrap(); + + assert_eq!(keypairs, decoded); + } + + #[test] + #[ignore] + fn read_write_consistency_big_batch() { + let num_keypairs = BATCH_SIZE + 1; + let keypairs = random_keypairs(num_keypairs); + + let keypairs_path = Path::new("/tmp").join(random_tmp_file()); + keypairs.to_raw_file(&keypairs_path, &keypairs).unwrap(); + + let decoded = Vec::from_raw_file(&keypairs_path, num_keypairs).unwrap(); + remove_file(keypairs_path).unwrap(); + + assert_eq!(keypairs, decoded); + } +} diff --git a/eth2/types/src/test_utils/mod.rs b/eth2/types/src/test_utils/mod.rs index 01d9668411..d34dbb89ca 100644 --- a/eth2/types/src/test_utils/mod.rs +++ b/eth2/types/src/test_utils/mod.rs @@ -1,3 +1,5 @@ +mod generate_deterministic_keypairs; +mod keypairs_file; mod test_random; mod testing_attestation_builder; mod testing_beacon_block_builder; @@ -6,6 +8,8 @@ mod testing_deposit_builder; mod testing_transfer_builder; mod testing_voluntary_exit_builder; +pub use generate_deterministic_keypairs::generate_deterministic_keypairs; +pub use keypairs_file::KeypairsFile; pub use rand::{prng::XorShiftRng, SeedableRng}; pub use test_random::TestRandom; pub use testing_attestation_builder::TestingAttestationBuilder; diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index e3f3313deb..3f9e7fd107 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -1,8 +1,9 @@ +use super::{generate_deterministic_keypairs, KeypairsFile}; use crate::beacon_state::BeaconStateBuilder; use crate::*; use bls::get_withdrawal_credentials; -use int_to_bytes::int_to_bytes48; use rayon::prelude::*; +use std::path::Path; pub struct TestingBeaconStateBuilder { state: BeaconState, @@ -10,17 +11,11 @@ pub struct TestingBeaconStateBuilder { } impl TestingBeaconStateBuilder { - pub fn new(validator_count: usize, spec: &ChainSpec) -> Self { - let keypairs: Vec = (0..validator_count) - .collect::>() - .par_iter() - .map(|&i| { - let secret = int_to_bytes48(i as u64 + 1); - let sk = SecretKey::from_bytes(&secret).unwrap(); - let pk = PublicKey::from_secret_key(&sk); - Keypair { sk, pk } - }) - .collect(); + pub fn new(validator_count: usize, keypairs_path: Option<&Path>, spec: &ChainSpec) -> Self { + let keypairs = match keypairs_path { + None => generate_deterministic_keypairs(validator_count), + Some(path) => Vec::from_raw_file(path, validator_count).unwrap(), + }; let validators = keypairs .par_iter() diff --git a/eth2/utils/bls/src/public_key.rs b/eth2/utils/bls/src/public_key.rs index ffe710d2da..ecdfce3ebb 100644 --- a/eth2/utils/bls/src/public_key.rs +++ b/eth2/utils/bls/src/public_key.rs @@ -1,6 +1,6 @@ use super::serde_vistors::HexVisitor; use super::SecretKey; -use bls_aggregates::PublicKey as RawPublicKey; +use bls_aggregates::{DecodeError as BlsDecodeError, PublicKey as RawPublicKey}; use hex::encode as hex_encode; use serde::de::{Deserialize, Deserializer}; use serde::ser::{Serialize, Serializer}; @@ -22,7 +22,14 @@ impl PublicKey { PublicKey(RawPublicKey::from_secret_key(secret_key.as_raw())) } - /// Returns the underlying signature. + /// Instantiate a PublicKey from existing bytes. + /// + /// Note: this is _not_ SSZ decoding. + pub fn from_bytes(bytes: &[u8]) -> Result { + Ok(Self(RawPublicKey::from_bytes(bytes)?)) + } + + /// Returns the underlying public key. pub fn as_raw(&self) -> &RawPublicKey { &self.0 } From cce88c9923136e51433eb894458581abf64e4303 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 12 Mar 2019 14:39:16 +1100 Subject: [PATCH 061/144] Improve DX for loading validator keys from file --- .../beacon_chain/test_harness/src/bin.rs | 8 +- eth2/fork_choice/tests/tests.rs | 3 +- .../benches/bench_block_processing.rs | 18 ++--- .../benches/bench_epoch_processing.rs | 10 +-- eth2/state_processing/benches/benches.rs | 17 +++-- .../src/per_epoch_processing/tests.rs | 2 +- eth2/types/Cargo.toml | 1 + eth2/types/src/beacon_state/tests.rs | 2 +- eth2/types/src/test_utils/keypairs_file.rs | 32 +++++--- eth2/types/src/test_utils/mod.rs | 2 +- .../testing_beacon_state_builder.rs | 75 +++++++++++++++++-- 11 files changed, 119 insertions(+), 51 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/src/bin.rs b/beacon_node/beacon_chain/test_harness/src/bin.rs index d5e43f67a9..df9ccd2229 100644 --- a/beacon_node/beacon_chain/test_harness/src/bin.rs +++ b/beacon_node/beacon_chain/test_harness/src/bin.rs @@ -2,6 +2,8 @@ use clap::{App, Arg, SubCommand}; use env_logger::{Builder, Env}; use gen_keys::gen_keys; use run_test::run_test; +use std::fs; +use types::test_utils::keypairs_path; use types::ChainSpec; mod beacon_chain_harness; @@ -13,6 +15,10 @@ mod validator_harness; use validator_harness::ValidatorHarness; fn main() { + let validator_file_path = keypairs_path(); + + fs::create_dir(validator_file_path.parent().unwrap()).unwrap(); + let matches = App::new("Lighthouse Test Harness Runner") .version("0.0.1") .author("Sigma Prime ") @@ -71,7 +77,7 @@ fn main() { .short("d") .value_name("GENESIS_TIME") .help("Output directory for generated YAML.") - .default_value("keypairs.raw_keypairs"), + .default_value(validator_file_path.to_str().unwrap()), ), ) .get_matches(); diff --git a/eth2/fork_choice/tests/tests.rs b/eth2/fork_choice/tests/tests.rs index 364e8796cd..7228bca10c 100644 --- a/eth2/fork_choice/tests/tests.rs +++ b/eth2/fork_choice/tests/tests.rs @@ -224,7 +224,8 @@ fn setup_inital_state( let spec = ChainSpec::foundation(); - let state_builder = TestingBeaconStateBuilder::new(no_validators, None, &spec); + let state_builder = + TestingBeaconStateBuilder::from_deterministic_keypairs(no_validators, &spec); let (state, _keypairs) = state_builder.build(); let state_root = state.canonical_root(); diff --git a/eth2/state_processing/benches/bench_block_processing.rs b/eth2/state_processing/benches/bench_block_processing.rs index aa595b7ac7..1028d4a204 100644 --- a/eth2/state_processing/benches/bench_block_processing.rs +++ b/eth2/state_processing/benches/bench_block_processing.rs @@ -9,19 +9,14 @@ use state_processing::{ verify_block_signature, }, }; -use std::path::Path; use types::test_utils::{TestingBeaconBlockBuilder, TestingBeaconStateBuilder}; use types::*; /// Run the benchmarking suite on a foundation spec with 16,384 validators. -pub fn bench_block_processing_n_validators( - c: &mut Criterion, - validator_count: usize, - keypair_file: Option<&Path>, -) { +pub fn bench_block_processing_n_validators(c: &mut Criterion, validator_count: usize) { let spec = ChainSpec::foundation(); - let (mut state, keypairs) = build_state(validator_count, keypair_file, &spec); + let (mut state, keypairs) = build_state(validator_count, &spec); let block = build_block(&mut state, &keypairs, &spec); assert_eq!( @@ -84,12 +79,9 @@ pub fn bench_block_processing_n_validators( ); } -fn build_state( - validator_count: usize, - keypair_file: Option<&Path>, - spec: &ChainSpec, -) -> (BeaconState, Vec) { - let mut builder = TestingBeaconStateBuilder::new(validator_count, keypair_file, &spec); +fn build_state(validator_count: usize, spec: &ChainSpec) -> (BeaconState, Vec) { + let mut builder = + TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, &spec); // Set the state to be just before an epoch transition. let target_slot = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch); diff --git a/eth2/state_processing/benches/bench_epoch_processing.rs b/eth2/state_processing/benches/bench_epoch_processing.rs index 5f07d11008..e4981b2008 100644 --- a/eth2/state_processing/benches/bench_epoch_processing.rs +++ b/eth2/state_processing/benches/bench_epoch_processing.rs @@ -10,7 +10,6 @@ use state_processing::{ update_latest_slashed_balances, }, }; -use std::path::Path; use types::test_utils::TestingBeaconStateBuilder; use types::{validator_registry::get_active_validator_indices, *}; @@ -18,14 +17,11 @@ pub const BENCHING_SAMPLE_SIZE: usize = 10; pub const SMALL_BENCHING_SAMPLE_SIZE: usize = 10; /// Run the benchmarking suite on a foundation spec with 16,384 validators. -pub fn bench_epoch_processing_n_validators( - c: &mut Criterion, - validator_count: usize, - keypair_file: Option<&Path>, -) { +pub fn bench_epoch_processing_n_validators(c: &mut Criterion, validator_count: usize) { let spec = ChainSpec::foundation(); - let mut builder = TestingBeaconStateBuilder::new(validator_count, keypair_file, &spec); + let mut builder = + TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, &spec); // Set the state to be just before an epoch transition. let target_slot = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch); diff --git a/eth2/state_processing/benches/benches.rs b/eth2/state_processing/benches/benches.rs index 721049eeb0..9b16f732a3 100644 --- a/eth2/state_processing/benches/benches.rs +++ b/eth2/state_processing/benches/benches.rs @@ -1,7 +1,6 @@ use criterion::Benchmark; use criterion::Criterion; use criterion::{criterion_group, criterion_main}; -use std::path::Path; use types::test_utils::TestingBeaconStateBuilder; use types::*; @@ -11,8 +10,8 @@ mod bench_epoch_processing; pub const VALIDATOR_COUNT: usize = 300_032; pub fn state_processing(c: &mut Criterion) { - bench_block_processing::bench_block_processing_n_validators(c, VALIDATOR_COUNT, None); - bench_epoch_processing::bench_epoch_processing_n_validators(c, VALIDATOR_COUNT, None); + bench_block_processing::bench_block_processing_n_validators(c, VALIDATOR_COUNT); + bench_epoch_processing::bench_epoch_processing_n_validators(c, VALIDATOR_COUNT); } pub fn key_loading(c: &mut Criterion) { @@ -23,7 +22,12 @@ pub fn key_loading(c: &mut Criterion) { Benchmark::new("generated", move |b| { b.iter_batched( || (), - |_| TestingBeaconStateBuilder::new(validator_count, None, &ChainSpec::foundation()), + |_| { + TestingBeaconStateBuilder::from_deterministic_keypairs( + validator_count, + &ChainSpec::foundation(), + ) + }, criterion::BatchSize::SmallInput, ) }) @@ -31,17 +35,14 @@ pub fn key_loading(c: &mut Criterion) { ); // Note: path needs to be relative to where cargo is executed from. - let keypair_file = - Path::new("../../beacon_node/beacon_chain/test_harness/keypairs.raw_keypairs"); c.bench( &format!("{}_validators", validator_count), Benchmark::new("from_file", move |b| { b.iter_batched( || (), |_| { - TestingBeaconStateBuilder::new( + TestingBeaconStateBuilder::from_default_keypairs_file_if_exists( validator_count, - Some(&keypair_file), &ChainSpec::foundation(), ) }, diff --git a/eth2/state_processing/src/per_epoch_processing/tests.rs b/eth2/state_processing/src/per_epoch_processing/tests.rs index df014e1d64..69450edcd1 100644 --- a/eth2/state_processing/src/per_epoch_processing/tests.rs +++ b/eth2/state_processing/src/per_epoch_processing/tests.rs @@ -10,7 +10,7 @@ fn runs_without_error() { let spec = ChainSpec::few_validators(); - let mut builder = TestingBeaconStateBuilder::new(8, None, &spec); + let mut builder = TestingBeaconStateBuilder::from_deterministic_keypairs(8, &spec); let target_slot = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch); builder.teleport_to_slot(target_slot, &spec); diff --git a/eth2/types/Cargo.toml b/eth2/types/Cargo.toml index e2930040d7..27aef19d64 100644 --- a/eth2/types/Cargo.toml +++ b/eth2/types/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] bls = { path = "../utils/bls" } boolean-bitfield = { path = "../utils/boolean-bitfield" } +dirs = "1.0" ethereum-types = "0.5" hashing = { path = "../utils/hashing" } honey-badger-split = { path = "../utils/honey-badger-split" } diff --git a/eth2/types/src/beacon_state/tests.rs b/eth2/types/src/beacon_state/tests.rs index da5cdf0fb5..61f3c03b09 100644 --- a/eth2/types/src/beacon_state/tests.rs +++ b/eth2/types/src/beacon_state/tests.rs @@ -13,7 +13,7 @@ pub fn get_attestation_participants_consistency() { let mut rng = XorShiftRng::from_seed([42; 16]); let spec = ChainSpec::few_validators(); - let builder = TestingBeaconStateBuilder::new(8, None, &spec); + let builder = TestingBeaconStateBuilder::from_deterministic_keypairs(8, &spec); let (mut state, _keypairs) = builder.build(); state diff --git a/eth2/types/src/test_utils/keypairs_file.rs b/eth2/types/src/test_utils/keypairs_file.rs index 5828af9a99..b0ac8424f4 100644 --- a/eth2/types/src/test_utils/keypairs_file.rs +++ b/eth2/types/src/test_utils/keypairs_file.rs @@ -1,4 +1,5 @@ use crate::*; +use rayon::prelude::*; use std::fs::File; use std::io::{Error, ErrorKind, Read, Write}; use std::path::Path; @@ -45,19 +46,27 @@ impl KeypairsFile for Vec { let mut buf = vec![0; batch.len() * KEYPAIR_BYTES_LEN]; keypairs_file.read_exact(&mut buf)?; - for (i, _) in batch.iter().enumerate() { - let sk_start = i * KEYPAIR_BYTES_LEN; - let sk_end = sk_start + SECRET_KEY_BYTES_LEN; - let sk = SecretKey::from_bytes(&buf[sk_start..sk_end]) - .map_err(|_| Error::new(ErrorKind::Other, "Invalid SecretKey bytes"))?; + let mut keypair_batch = batch + .par_iter() + .enumerate() + .map(|(i, _)| { + let sk_start = i * KEYPAIR_BYTES_LEN; + let sk_end = sk_start + SECRET_KEY_BYTES_LEN; + let sk = SecretKey::from_bytes(&buf[sk_start..sk_end]) + .map_err(|_| Error::new(ErrorKind::Other, "Invalid SecretKey bytes")) + .unwrap(); - let pk_start = sk_end; - let pk_end = pk_start + PUBLIC_KEY_BYTES_LEN; - let pk = PublicKey::from_bytes(&buf[pk_start..pk_end]) - .map_err(|_| Error::new(ErrorKind::Other, "Invalid PublicKey bytes"))?; + let pk_start = sk_end; + let pk_end = pk_start + PUBLIC_KEY_BYTES_LEN; + let pk = PublicKey::from_bytes(&buf[pk_start..pk_end]) + .map_err(|_| Error::new(ErrorKind::Other, "Invalid PublicKey bytes")) + .unwrap(); - keypairs.push(Keypair { sk, pk }); - } + Keypair { sk, pk } + }) + .collect(); + + keypairs.append(&mut keypair_batch); } Ok(keypairs) @@ -68,7 +77,6 @@ impl KeypairsFile for Vec { mod tests { use super::*; use rand::{distributions::Alphanumeric, thread_rng, Rng}; - use rayon::prelude::*; use std::fs::remove_file; fn random_keypairs(n: usize) -> Vec { diff --git a/eth2/types/src/test_utils/mod.rs b/eth2/types/src/test_utils/mod.rs index d34dbb89ca..26d340e7d6 100644 --- a/eth2/types/src/test_utils/mod.rs +++ b/eth2/types/src/test_utils/mod.rs @@ -14,7 +14,7 @@ pub use rand::{prng::XorShiftRng, SeedableRng}; pub use test_random::TestRandom; pub use testing_attestation_builder::TestingAttestationBuilder; pub use testing_beacon_block_builder::TestingBeaconBlockBuilder; -pub use testing_beacon_state_builder::TestingBeaconStateBuilder; +pub use testing_beacon_state_builder::{keypairs_path, TestingBeaconStateBuilder}; pub use testing_deposit_builder::TestingDepositBuilder; pub use testing_transfer_builder::TestingTransferBuilder; pub use testing_voluntary_exit_builder::TestingVoluntaryExitBuilder; diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index 3f9e7fd107..53481f0620 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -2,8 +2,22 @@ use super::{generate_deterministic_keypairs, KeypairsFile}; use crate::beacon_state::BeaconStateBuilder; use crate::*; use bls::get_withdrawal_credentials; +use dirs; use rayon::prelude::*; -use std::path::Path; +use std::path::{Path, PathBuf}; + +pub const KEYPAIRS_FILE: &str = "keypairs.raw_keypairs"; + +/// Returns the directory where the generated keypairs should be stored. +/// +/// It is either `$HOME/.lighthouse/keypairs.raw_keypairs` or, if `$HOME` is not available, +/// `./keypairs.raw_keypairs`. +pub fn keypairs_path() -> PathBuf { + let dir = dirs::home_dir() + .and_then(|home| Some(home.join(".lighthouse"))) + .unwrap_or_else(|| PathBuf::from("")); + dir.join(KEYPAIRS_FILE) +} pub struct TestingBeaconStateBuilder { state: BeaconState, @@ -11,11 +25,52 @@ pub struct TestingBeaconStateBuilder { } impl TestingBeaconStateBuilder { - pub fn new(validator_count: usize, keypairs_path: Option<&Path>, spec: &ChainSpec) -> Self { - let keypairs = match keypairs_path { - None => generate_deterministic_keypairs(validator_count), - Some(path) => Vec::from_raw_file(path, validator_count).unwrap(), - }; + /// Attempts to load validators from a file in the `CARGO_MANIFEST_DIR`. If the file is + /// unavailable, it generates the keys at runtime. + /// + /// If the `CARGO_MANIFEST_DIR` environment variable is not set, the local directory is used. + /// + /// See the `Self::from_keypairs_file` method for more info. + /// + /// # Panics + /// + /// If the file does not contain enough keypairs or is invalid. + pub fn from_default_keypairs_file_if_exists(validator_count: usize, spec: &ChainSpec) -> Self { + let dir = dirs::home_dir() + .and_then(|home| Some(home.join(".lighthouse"))) + .unwrap_or_else(|| PathBuf::from("")); + let file = dir.join(KEYPAIRS_FILE); + + if file.exists() { + TestingBeaconStateBuilder::from_keypairs_file(validator_count, &file, spec) + } else { + TestingBeaconStateBuilder::from_deterministic_keypairs(validator_count, spec) + } + } + + /// Loads the initial validator keypairs from a file on disk. + /// + /// Loading keypairs from file is ~10x faster than generating them. Use the `gen_keys` command + /// on the `test_harness` binary to generate the keys. In the `test_harness` dir, run `cargo + /// run -- gen_keys -h` for help. + /// + /// # Panics + /// + /// If the file does not exist, is invalid or does not contain enough keypairs. + pub fn from_keypairs_file(validator_count: usize, path: &Path, spec: &ChainSpec) -> Self { + let keypairs = Vec::from_raw_file(path, validator_count).unwrap(); + TestingBeaconStateBuilder::from_keypairs(keypairs, spec) + } + + /// Generates the validator keypairs deterministically. + pub fn from_deterministic_keypairs(validator_count: usize, spec: &ChainSpec) -> Self { + let keypairs = generate_deterministic_keypairs(validator_count); + TestingBeaconStateBuilder::from_keypairs(keypairs, spec) + } + + /// Creates the builder from an existing set of keypairs. + pub fn from_keypairs(keypairs: Vec, spec: &ChainSpec) -> Self { + let validator_count = keypairs.len(); let validators = keypairs .par_iter() @@ -61,10 +116,15 @@ impl TestingBeaconStateBuilder { } } + /// Consume the builder and return the `BeaconState` and the keypairs for each validator. pub fn build(self) -> (BeaconState, Vec) { (self.state, self.keypairs) } + /// Ensures that the state returned from `Self::build(..)` has all caches pre-built. + /// + /// Note: this performs the build when called. Ensure that no changes are made that would + /// invalidate this cache. pub fn build_caches(&mut self, spec: &ChainSpec) -> Result<(), BeaconStateError> { let state = &mut self.state; @@ -147,6 +207,9 @@ impl TestingBeaconStateBuilder { } } +/// Maps a committee to a `PendingAttestation`. +/// +/// The committee will be signed by all validators in the committee. fn committee_to_pending_attestation( state: &BeaconState, committee: &[usize], From efd56ebe375c73dd658e7c86c50a5431e22ad77f Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 12 Mar 2019 14:42:31 +1100 Subject: [PATCH 062/144] Ignore file-exists error. --- beacon_node/beacon_chain/test_harness/src/bin.rs | 2 +- eth2/types/src/test_utils/testing_beacon_state_builder.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/src/bin.rs b/beacon_node/beacon_chain/test_harness/src/bin.rs index df9ccd2229..3afc921dec 100644 --- a/beacon_node/beacon_chain/test_harness/src/bin.rs +++ b/beacon_node/beacon_chain/test_harness/src/bin.rs @@ -17,7 +17,7 @@ use validator_harness::ValidatorHarness; fn main() { let validator_file_path = keypairs_path(); - fs::create_dir(validator_file_path.parent().unwrap()).unwrap(); + let _ = fs::create_dir(validator_file_path.parent().unwrap()); let matches = App::new("Lighthouse Test Harness Runner") .version("0.0.1") diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index 53481f0620..63d6f58778 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -25,10 +25,10 @@ pub struct TestingBeaconStateBuilder { } impl TestingBeaconStateBuilder { - /// Attempts to load validators from a file in the `CARGO_MANIFEST_DIR`. If the file is - /// unavailable, it generates the keys at runtime. + /// Attempts to load validators from a file in `$HOME/.lighthouse/keypairs.raw_keypairs`. If + /// the file is unavailable, it generates the keys at runtime. /// - /// If the `CARGO_MANIFEST_DIR` environment variable is not set, the local directory is used. + /// If the `$HOME` environment variable is not set, the local directory is used. /// /// See the `Self::from_keypairs_file` method for more info. /// From c92f867cd851e5d6686ada6950b6c4bcd0853f29 Mon Sep 17 00:00:00 2001 From: Kirk Baird Date: Tue, 12 Mar 2019 16:01:09 +1100 Subject: [PATCH 063/144] Upgrade to signature scheme 0.6.0 --- eth2/types/src/test_utils/keypairs_file.rs | 2 +- eth2/utils/bls/Cargo.toml | 2 +- eth2/utils/bls/src/aggregate_signature.rs | 8 +------ eth2/utils/bls/src/public_key.rs | 28 +++++++++++----------- eth2/utils/bls/src/signature.rs | 9 ++++--- 5 files changed, 23 insertions(+), 26 deletions(-) diff --git a/eth2/types/src/test_utils/keypairs_file.rs b/eth2/types/src/test_utils/keypairs_file.rs index b0ac8424f4..5804b96967 100644 --- a/eth2/types/src/test_utils/keypairs_file.rs +++ b/eth2/types/src/test_utils/keypairs_file.rs @@ -58,7 +58,7 @@ impl KeypairsFile for Vec { let pk_start = sk_end; let pk_end = pk_start + PUBLIC_KEY_BYTES_LEN; - let pk = PublicKey::from_bytes(&buf[pk_start..pk_end]) + let pk = PublicKey::from_uncompressed_bytes(&buf[pk_start..pk_end]) .map_err(|_| Error::new(ErrorKind::Other, "Invalid PublicKey bytes")) .unwrap(); diff --git a/eth2/utils/bls/Cargo.toml b/eth2/utils/bls/Cargo.toml index 5ac38595ae..468ed80508 100644 --- a/eth2/utils/bls/Cargo.toml +++ b/eth2/utils/bls/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Paul Hauner "] edition = "2018" [dependencies] -bls-aggregates = { git = "https://github.com/sigp/signature-schemes", tag = "0.5.2" } +bls-aggregates = { git = "https://github.com/sigp/signature-schemes", tag = "0.6.0" } hashing = { path = "../hashing" } hex = "0.3" serde = "1.0" diff --git a/eth2/utils/bls/src/aggregate_signature.rs b/eth2/utils/bls/src/aggregate_signature.rs index 2d8776353f..fa3628a89d 100644 --- a/eth2/utils/bls/src/aggregate_signature.rs +++ b/eth2/utils/bls/src/aggregate_signature.rs @@ -48,15 +48,9 @@ impl AggregateSignature { domain: u64, aggregate_public_keys: &[&AggregatePublicKey], ) -> bool { - // TODO: the API for `RawAggregatePublicKey` shoudn't need to take an owned - // `AggregatePublicKey`. There is an issue to fix this, but in the meantime we need to - // clone. - // - // https://github.com/sigp/signature-schemes/issues/10 - let aggregate_public_keys: Vec = aggregate_public_keys + let aggregate_public_keys: Vec<&RawAggregatePublicKey> = aggregate_public_keys .iter() .map(|pk| pk.as_raw()) - .cloned() .collect(); // Messages are concatenated into one long message. diff --git a/eth2/utils/bls/src/public_key.rs b/eth2/utils/bls/src/public_key.rs index ecdfce3ebb..eaf2c9d3fa 100644 --- a/eth2/utils/bls/src/public_key.rs +++ b/eth2/utils/bls/src/public_key.rs @@ -1,6 +1,6 @@ use super::serde_vistors::HexVisitor; use super::SecretKey; -use bls_aggregates::{DecodeError as BlsDecodeError, PublicKey as RawPublicKey}; +use bls_aggregates::PublicKey as RawPublicKey; use hex::encode as hex_encode; use serde::de::{Deserialize, Deserializer}; use serde::ser::{Serialize, Serializer}; @@ -22,18 +22,22 @@ impl PublicKey { PublicKey(RawPublicKey::from_secret_key(secret_key.as_raw())) } - /// Instantiate a PublicKey from existing bytes. - /// - /// Note: this is _not_ SSZ decoding. - pub fn from_bytes(bytes: &[u8]) -> Result { - Ok(Self(RawPublicKey::from_bytes(bytes)?)) - } - - /// Returns the underlying public key. + /// Returns the underlying signature. pub fn as_raw(&self) -> &RawPublicKey { &self.0 } + /// Returns the PublicKey as (x, y) bytes + pub fn as_uncompressed_bytes(&mut self) -> Vec { + RawPublicKey::as_uncompressed_bytes(&mut self.0) + } + + /// Converts (x, y) bytes to PublicKey + pub fn from_uncompressed_bytes(bytes: &[u8]) -> Result { + let pubkey = RawPublicKey::from_uncompressed_bytes(&bytes).map_err(|_| DecodeError::Invalid)?; + Ok(PublicKey(pubkey)) + } + /// 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. @@ -100,11 +104,7 @@ impl PartialEq for PublicKey { impl Hash for PublicKey { fn hash(&self, state: &mut H) { - // Note: this is not necessarily the consensus-ready hash. Instead, it is designed to be - // optimally fast for internal usage. - // - // To hash for consensus purposes, use the SSZ-encoded bytes. - self.0.as_bytes().hash(state) + ssz_encode(self).hash(state) } } diff --git a/eth2/utils/bls/src/signature.rs b/eth2/utils/bls/src/signature.rs index 86c54cba70..760b0018ab 100644 --- a/eth2/utils/bls/src/signature.rs +++ b/eth2/utils/bls/src/signature.rs @@ -54,9 +54,12 @@ impl Signature { /// Returns a new empty signature. pub fn empty_signature() -> Self { + // Empty Signature is currently being represented as BLS::Signature.point_at_infinity() + // However it should be represented as vec![0; 96] but this + // would require all signatures to be represented in byte form as opposed to Signature let mut empty: Vec = vec![0; 96]; - // TODO: Modify the way flags are used (b_flag should not be used for empty_signature in the future) - empty[0] += u8::pow(2, 6); + // Sets C_flag and B_flag to 1 and all else to 0 + empty[0] += u8::pow(2, 6) + u8::pow(2, 7); Signature(RawSignature::from_bytes(&empty).unwrap()) } } @@ -129,7 +132,7 @@ mod tests { assert_eq!(sig_as_bytes.len(), 96); for (i, one_byte) in sig_as_bytes.iter().enumerate() { if i == 0 { - assert_eq!(*one_byte, u8::pow(2, 6)); + assert_eq!(*one_byte, u8::pow(2, 6) + u8::pow(2, 7)); } else { assert_eq!(*one_byte, 0); } From 1b252c3f82db7b595710081a580367290cb6628b Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 12 Mar 2019 17:15:45 +1100 Subject: [PATCH 064/144] Implement new uncompressed bytes for PublicKey --- eth2/types/src/test_utils/keypairs_file.rs | 4 ++-- eth2/utils/bls/src/public_key.rs | 21 +++++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/eth2/types/src/test_utils/keypairs_file.rs b/eth2/types/src/test_utils/keypairs_file.rs index 5804b96967..ebc50f5284 100644 --- a/eth2/types/src/test_utils/keypairs_file.rs +++ b/eth2/types/src/test_utils/keypairs_file.rs @@ -4,7 +4,7 @@ use std::fs::File; use std::io::{Error, ErrorKind, Read, Write}; use std::path::Path; -pub const PUBLIC_KEY_BYTES_LEN: usize = 48; +pub const PUBLIC_KEY_BYTES_LEN: usize = 96; pub const SECRET_KEY_BYTES_LEN: usize = 48; pub const BATCH_SIZE: usize = 1_000; // ~15MB @@ -26,7 +26,7 @@ impl KeypairsFile for Vec { for keypair in keypair_batch { buf.append(&mut keypair.sk.as_raw().as_bytes()); - buf.append(&mut keypair.pk.as_raw().as_bytes()); + buf.append(&mut keypair.pk.clone().as_uncompressed_bytes()); } keypairs_file.write_all(&buf)?; diff --git a/eth2/utils/bls/src/public_key.rs b/eth2/utils/bls/src/public_key.rs index eaf2c9d3fa..777ccceaab 100644 --- a/eth2/utils/bls/src/public_key.rs +++ b/eth2/utils/bls/src/public_key.rs @@ -27,14 +27,21 @@ impl PublicKey { &self.0 } + /// Converts compressed bytes to PublicKey + pub fn from_bytes(bytes: &[u8]) -> Result { + let pubkey = RawPublicKey::from_bytes(&bytes).map_err(|_| DecodeError::Invalid)?; + Ok(PublicKey(pubkey)) + } + /// Returns the PublicKey as (x, y) bytes - pub fn as_uncompressed_bytes(&mut self) -> Vec { - RawPublicKey::as_uncompressed_bytes(&mut self.0) + pub fn as_uncompressed_bytes(&self) -> Vec { + RawPublicKey::as_uncompressed_bytes(&mut self.0.clone()) } /// Converts (x, y) bytes to PublicKey pub fn from_uncompressed_bytes(bytes: &[u8]) -> Result { - let pubkey = RawPublicKey::from_uncompressed_bytes(&bytes).map_err(|_| DecodeError::Invalid)?; + let pubkey = + RawPublicKey::from_uncompressed_bytes(&bytes).map_err(|_| DecodeError::Invalid)?; Ok(PublicKey(pubkey)) } @@ -103,8 +110,14 @@ impl PartialEq for PublicKey { } impl Hash for PublicKey { + /// 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(&self, state: &mut H) { - ssz_encode(self).hash(state) + self.as_uncompressed_bytes().hash(state) } } From fbfa233d36c45af1dbd85192931427095aed2d64 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 12 Mar 2019 17:16:12 +1100 Subject: [PATCH 065/144] Add debug messages to TestingBeaconStateBuilder --- .../benches/bench_block_processing.rs | 13 +++++ eth2/state_processing/benches/benches.rs | 50 ++++--------------- .../testing_beacon_state_builder.rs | 17 +++++-- 3 files changed, 35 insertions(+), 45 deletions(-) diff --git a/eth2/state_processing/benches/bench_block_processing.rs b/eth2/state_processing/benches/bench_block_processing.rs index 1028d4a204..031942473c 100644 --- a/eth2/state_processing/benches/bench_block_processing.rs +++ b/eth2/state_processing/benches/bench_block_processing.rs @@ -1,5 +1,6 @@ use criterion::Criterion; use criterion::{black_box, Benchmark}; +use log::debug; use ssz::TreeHash; use state_processing::{ per_block_processing, @@ -107,6 +108,10 @@ fn build_block(state: &mut BeaconState, keypairs: &[Keypair], spec: &ChainSpec) let mut validators_iter = (0..keypairs.len() as u64).into_iter(); // Insert the maximum possible number of `ProposerSlashing` objects. + debug!( + "Inserting {} proposer slashings...", + spec.max_proposer_slashings + ); for _ in 0..spec.max_proposer_slashings { let validator_index = validators_iter.next().expect("Insufficient validators."); @@ -119,6 +124,10 @@ fn build_block(state: &mut BeaconState, keypairs: &[Keypair], spec: &ChainSpec) } // Insert the maximum possible number of `AttesterSlashing` objects + debug!( + "Inserting {} attester slashings...", + spec.max_attester_slashings + ); for _ in 0..spec.max_attester_slashings { let mut attesters: Vec = vec![]; let mut secret_keys: Vec<&SecretKey> = vec![]; @@ -134,17 +143,20 @@ fn build_block(state: &mut BeaconState, keypairs: &[Keypair], spec: &ChainSpec) } // Insert the maximum possible number of `Attestation` objects. + debug!("Inserting {} attestations...", spec.max_attestations); let all_secret_keys: Vec<&SecretKey> = keypairs.iter().map(|keypair| &keypair.sk).collect(); builder .fill_with_attestations(state, &all_secret_keys, spec) .unwrap(); // Insert the maximum possible number of `Deposit` objects. + debug!("Inserting {} deposits...", spec.max_deposits); for i in 0..spec.max_deposits { builder.insert_deposit(32_000_000_000, state.deposit_index + i, state, spec); } // Insert the maximum possible number of `Exit` objects. + debug!("Inserting {} exits...", spec.max_voluntary_exits); for _ in 0..spec.max_voluntary_exits { let validator_index = validators_iter.next().expect("Insufficient validators."); @@ -157,6 +169,7 @@ fn build_block(state: &mut BeaconState, keypairs: &[Keypair], spec: &ChainSpec) } // Insert the maximum possible number of `Transfer` objects. + debug!("Inserting {} transfers...", spec.max_transfers); for _ in 0..spec.max_transfers { let validator_index = validators_iter.next().expect("Insufficient validators."); diff --git a/eth2/state_processing/benches/benches.rs b/eth2/state_processing/benches/benches.rs index 9b16f732a3..ad8c4f714f 100644 --- a/eth2/state_processing/benches/benches.rs +++ b/eth2/state_processing/benches/benches.rs @@ -1,6 +1,7 @@ use criterion::Benchmark; use criterion::Criterion; use criterion::{criterion_group, criterion_main}; +use env_logger::{Builder, Env}; use types::test_utils::TestingBeaconStateBuilder; use types::*; @@ -9,50 +10,17 @@ mod bench_epoch_processing; pub const VALIDATOR_COUNT: usize = 300_032; +// `LOG_LEVEL == "debug"` gives logs, but they're very noisy and slow down benching. +pub const LOG_LEVEL: &str = ""; + pub fn state_processing(c: &mut Criterion) { + if LOG_LEVEL != "" { + Builder::from_env(Env::default().default_filter_or(LOG_LEVEL)).init(); + } + bench_block_processing::bench_block_processing_n_validators(c, VALIDATOR_COUNT); bench_epoch_processing::bench_epoch_processing_n_validators(c, VALIDATOR_COUNT); } -pub fn key_loading(c: &mut Criterion) { - let validator_count = 1000; - - c.bench( - &format!("{}_validators", validator_count), - Benchmark::new("generated", move |b| { - b.iter_batched( - || (), - |_| { - TestingBeaconStateBuilder::from_deterministic_keypairs( - validator_count, - &ChainSpec::foundation(), - ) - }, - criterion::BatchSize::SmallInput, - ) - }) - .sample_size(10), - ); - - // Note: path needs to be relative to where cargo is executed from. - c.bench( - &format!("{}_validators", validator_count), - Benchmark::new("from_file", move |b| { - b.iter_batched( - || (), - |_| { - TestingBeaconStateBuilder::from_default_keypairs_file_if_exists( - validator_count, - &ChainSpec::foundation(), - ) - }, - criterion::BatchSize::SmallInput, - ) - }) - .sample_size(10), - ); -} - -// criterion_group!(benches, state_processing, key_loading); -criterion_group!(benches, key_loading); +criterion_group!(benches, state_processing); criterion_main!(benches); diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index 63d6f58778..afefa40633 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -3,6 +3,7 @@ use crate::beacon_state::BeaconStateBuilder; use crate::*; use bls::get_withdrawal_credentials; use dirs; +use log::debug; use rayon::prelude::*; use std::path::{Path, PathBuf}; @@ -58,12 +59,14 @@ impl TestingBeaconStateBuilder { /// /// If the file does not exist, is invalid or does not contain enough keypairs. pub fn from_keypairs_file(validator_count: usize, path: &Path, spec: &ChainSpec) -> Self { + debug!("Loading {} keypairs from file...", validator_count); let keypairs = Vec::from_raw_file(path, validator_count).unwrap(); TestingBeaconStateBuilder::from_keypairs(keypairs, spec) } /// Generates the validator keypairs deterministically. pub fn from_deterministic_keypairs(validator_count: usize, spec: &ChainSpec) -> Self { + debug!("Generating {} deterministic keypairs...", validator_count); let keypairs = generate_deterministic_keypairs(validator_count); TestingBeaconStateBuilder::from_keypairs(keypairs, spec) } @@ -72,6 +75,10 @@ impl TestingBeaconStateBuilder { pub fn from_keypairs(keypairs: Vec, spec: &ChainSpec) -> Self { let validator_count = keypairs.len(); + debug!( + "Building {} Validator objects from keypairs...", + validator_count + ); let validators = keypairs .par_iter() .map(|keypair| { @@ -103,6 +110,7 @@ impl TestingBeaconStateBuilder { let balances = vec![32_000_000_000; validator_count]; + debug!("Importing {} existing validators...", validator_count); state_builder.import_existing_validators( validators, balances, @@ -110,10 +118,11 @@ impl TestingBeaconStateBuilder { spec, ); - Self { - state: state_builder.build(spec).unwrap(), - keypairs, - } + let state = state_builder.build(spec).unwrap(); + + debug!("BeaconState built."); + + Self { state, keypairs } } /// Consume the builder and return the `BeaconState` and the keypairs for each validator. From 9057b436f339ab55d11858bba23998cbde919c97 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 12 Mar 2019 17:19:35 +1100 Subject: [PATCH 066/144] Run rustfmt --- eth2/utils/bls/src/aggregate_signature.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/eth2/utils/bls/src/aggregate_signature.rs b/eth2/utils/bls/src/aggregate_signature.rs index fa3628a89d..af0879ec79 100644 --- a/eth2/utils/bls/src/aggregate_signature.rs +++ b/eth2/utils/bls/src/aggregate_signature.rs @@ -48,10 +48,8 @@ impl AggregateSignature { domain: u64, aggregate_public_keys: &[&AggregatePublicKey], ) -> bool { - let aggregate_public_keys: Vec<&RawAggregatePublicKey> = aggregate_public_keys - .iter() - .map(|pk| pk.as_raw()) - .collect(); + let aggregate_public_keys: Vec<&RawAggregatePublicKey> = + aggregate_public_keys.iter().map(|pk| pk.as_raw()).collect(); // Messages are concatenated into one long message. let mut msg: Vec = vec![]; From dc221f322063ea7739b0c82b8c399beb14e667b2 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 12 Mar 2019 17:30:00 +1100 Subject: [PATCH 067/144] Make attester/proposer slashing builders test-only They didn't do anything useful or safe for production. --- beacon_node/beacon_chain/test_harness/src/test_case.rs | 8 +++----- eth2/types/src/attester_slashing.rs | 4 ---- eth2/types/src/proposer_slashing.rs | 4 ---- eth2/types/src/test_utils/mod.rs | 4 ++++ .../testing_attester_slashing_builder.rs} | 4 ++-- .../src/test_utils/testing_beacon_block_builder.rs | 10 ++++------ .../testing_proposer_slashing_builder.rs} | 4 ++-- 7 files changed, 15 insertions(+), 23 deletions(-) rename eth2/types/src/{attester_slashing/builder.rs => test_utils/testing_attester_slashing_builder.rs} (97%) rename eth2/types/src/{proposer_slashing/builder.rs => test_utils/testing_proposer_slashing_builder.rs} (95%) diff --git a/beacon_node/beacon_chain/test_harness/src/test_case.rs b/beacon_node/beacon_chain/test_harness/src/test_case.rs index a18b4688b4..b6b1ea5cca 100644 --- a/beacon_node/beacon_chain/test_harness/src/test_case.rs +++ b/beacon_node/beacon_chain/test_harness/src/test_case.rs @@ -9,9 +9,7 @@ use ssz::SignedRoot; use std::path::Path; use types::*; -use types::{ - attester_slashing::AttesterSlashingBuilder, proposer_slashing::ProposerSlashingBuilder, -}; +use types::test_utils::{TestingAttesterSlashingBuilder, TestingProposerSlashingBuilder}; use yaml_rust::Yaml; mod config; @@ -331,7 +329,7 @@ fn build_double_vote_attester_slashing( .expect("Unable to sign AttesterSlashing") }; - AttesterSlashingBuilder::double_vote(validator_indices, signer) + TestingAttesterSlashingBuilder::double_vote(validator_indices, signer) } /// Builds an `ProposerSlashing` for some `validator_index`. @@ -344,5 +342,5 @@ fn build_proposer_slashing(harness: &BeaconChainHarness, validator_index: u64) - .expect("Unable to sign AttesterSlashing") }; - ProposerSlashingBuilder::double_vote(validator_index, signer, &harness.spec) + TestingProposerSlashingBuilder::double_vote(validator_index, signer, &harness.spec) } diff --git a/eth2/types/src/attester_slashing.rs b/eth2/types/src/attester_slashing.rs index 1cb6719606..7a0752b6ac 100644 --- a/eth2/types/src/attester_slashing.rs +++ b/eth2/types/src/attester_slashing.rs @@ -4,10 +4,6 @@ use serde_derive::Serialize; use ssz_derive::{Decode, Encode, TreeHash}; use test_random_derive::TestRandom; -mod builder; - -pub use builder::AttesterSlashingBuilder; - /// Two conflicting attestations. /// /// Spec v0.4.0 diff --git a/eth2/types/src/proposer_slashing.rs b/eth2/types/src/proposer_slashing.rs index f86e7f3a88..394c55a017 100644 --- a/eth2/types/src/proposer_slashing.rs +++ b/eth2/types/src/proposer_slashing.rs @@ -5,10 +5,6 @@ use serde_derive::Serialize; use ssz_derive::{Decode, Encode, TreeHash}; use test_random_derive::TestRandom; -mod builder; - -pub use builder::ProposerSlashingBuilder; - /// Two conflicting proposals from the same proposer (validator). /// /// Spec v0.4.0 diff --git a/eth2/types/src/test_utils/mod.rs b/eth2/types/src/test_utils/mod.rs index 26d340e7d6..6fdbe53ad7 100644 --- a/eth2/types/src/test_utils/mod.rs +++ b/eth2/types/src/test_utils/mod.rs @@ -2,9 +2,11 @@ mod generate_deterministic_keypairs; mod keypairs_file; mod test_random; mod testing_attestation_builder; +mod testing_attester_slashing_builder; mod testing_beacon_block_builder; mod testing_beacon_state_builder; mod testing_deposit_builder; +mod testing_proposer_slashing_builder; mod testing_transfer_builder; mod testing_voluntary_exit_builder; @@ -13,8 +15,10 @@ pub use keypairs_file::KeypairsFile; pub use rand::{prng::XorShiftRng, SeedableRng}; pub use test_random::TestRandom; pub use testing_attestation_builder::TestingAttestationBuilder; +pub use testing_attester_slashing_builder::TestingAttesterSlashingBuilder; pub use testing_beacon_block_builder::TestingBeaconBlockBuilder; pub use testing_beacon_state_builder::{keypairs_path, TestingBeaconStateBuilder}; pub use testing_deposit_builder::TestingDepositBuilder; +pub use testing_proposer_slashing_builder::TestingProposerSlashingBuilder; pub use testing_transfer_builder::TestingTransferBuilder; pub use testing_voluntary_exit_builder::TestingVoluntaryExitBuilder; diff --git a/eth2/types/src/attester_slashing/builder.rs b/eth2/types/src/test_utils/testing_attester_slashing_builder.rs similarity index 97% rename from eth2/types/src/attester_slashing/builder.rs rename to eth2/types/src/test_utils/testing_attester_slashing_builder.rs index 8edf4ed65c..d9da3db2d1 100644 --- a/eth2/types/src/attester_slashing/builder.rs +++ b/eth2/types/src/test_utils/testing_attester_slashing_builder.rs @@ -2,9 +2,9 @@ use crate::*; use ssz::TreeHash; /// Builds an `AttesterSlashing`. -pub struct AttesterSlashingBuilder(); +pub struct TestingAttesterSlashingBuilder(); -impl AttesterSlashingBuilder { +impl TestingAttesterSlashingBuilder { /// Builds an `AttesterSlashing` that is a double vote. /// /// The `signer` function is used to sign the double-vote and accepts: diff --git a/eth2/types/src/test_utils/testing_beacon_block_builder.rs b/eth2/types/src/test_utils/testing_beacon_block_builder.rs index bbdc5046b8..3ebd24b0a9 100644 --- a/eth2/types/src/test_utils/testing_beacon_block_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_block_builder.rs @@ -1,9 +1,7 @@ use crate::{ - attester_slashing::AttesterSlashingBuilder, - proposer_slashing::ProposerSlashingBuilder, test_utils::{ - TestingAttestationBuilder, TestingDepositBuilder, TestingTransferBuilder, - TestingVoluntaryExitBuilder, + TestingAttestationBuilder, TestingAttesterSlashingBuilder, TestingDepositBuilder, + TestingProposerSlashingBuilder, TestingTransferBuilder, TestingVoluntaryExitBuilder, }, *, }; @@ -232,7 +230,7 @@ fn build_proposer_slashing( Signature::new(message, domain, secret_key) }; - ProposerSlashingBuilder::double_vote(validator_index, signer, spec) + TestingProposerSlashingBuilder::double_vote(validator_index, signer, spec) } /// Builds an `AttesterSlashing` for some `validator_indices`. @@ -253,5 +251,5 @@ fn build_double_vote_attester_slashing( Signature::new(message, domain, secret_keys[key_index]) }; - AttesterSlashingBuilder::double_vote(validator_indices, signer) + TestingAttesterSlashingBuilder::double_vote(validator_indices, signer) } diff --git a/eth2/types/src/proposer_slashing/builder.rs b/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs similarity index 95% rename from eth2/types/src/proposer_slashing/builder.rs rename to eth2/types/src/test_utils/testing_proposer_slashing_builder.rs index 472a76ec15..43ff3d0b76 100644 --- a/eth2/types/src/proposer_slashing/builder.rs +++ b/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs @@ -2,9 +2,9 @@ use crate::*; use ssz::SignedRoot; /// Builds a `ProposerSlashing`. -pub struct ProposerSlashingBuilder(); +pub struct TestingProposerSlashingBuilder(); -impl ProposerSlashingBuilder { +impl TestingProposerSlashingBuilder { /// Builds a `ProposerSlashing` that is a double vote. /// /// The `signer` function is used to sign the double-vote and accepts: From f949919b9bf7aedf79d095399fd2559c7954453e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 12 Mar 2019 18:02:53 +1100 Subject: [PATCH 068/144] Add comments to epoch_processing --- .../beacon_chain/test_harness/src/gen_keys.rs | 1 + .../beacon_chain/test_harness/src/run_test.rs | 1 + .../src/per_epoch_processing.rs | 67 +++++++++++++++---- .../src/per_epoch_processing/attester_sets.rs | 35 ++++++++++ 4 files changed, 90 insertions(+), 14 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/src/gen_keys.rs b/beacon_node/beacon_chain/test_harness/src/gen_keys.rs index f2f81b393a..abd512423e 100644 --- a/beacon_node/beacon_chain/test_harness/src/gen_keys.rs +++ b/beacon_node/beacon_chain/test_harness/src/gen_keys.rs @@ -3,6 +3,7 @@ use log::debug; use std::path::Path; use types::test_utils::{generate_deterministic_keypairs, KeypairsFile}; +/// Creates a file containing BLS keypairs. pub fn gen_keys(matches: &ArgMatches) { let validator_count = value_t!(matches.value_of("validator_count"), usize) .expect("Validator count is required argument"); diff --git a/beacon_node/beacon_chain/test_harness/src/run_test.rs b/beacon_node/beacon_chain/test_harness/src/run_test.rs index 51a993bd7c..d4e2e1cf20 100644 --- a/beacon_node/beacon_chain/test_harness/src/run_test.rs +++ b/beacon_node/beacon_chain/test_harness/src/run_test.rs @@ -4,6 +4,7 @@ use std::path::Path; use std::{fs::File, io::prelude::*}; use yaml_rust::YamlLoader; +/// Runs a YAML-specified test case. pub fn run_test(matches: &ArgMatches) { if let Some(yaml_file) = matches.value_of("yaml") { let docs = { diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index bb064ac34f..4abbe012cd 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -3,7 +3,6 @@ use errors::EpochProcessingError as Error; use fnv::FnvHashMap; use fnv::FnvHashSet; use integer_sqrt::IntegerSquareRoot; -use log::debug; use rayon::prelude::*; use ssz::TreeHash; use std::collections::HashMap; @@ -17,14 +16,20 @@ pub mod inclusion_distance; pub mod tests; pub mod winning_root; +/// Maps a shard to a winning root. +/// +/// It is generated during crosslink processing and later used to reward/penalize validators. +pub type WinningRootHashSet = HashMap; + +/// Performs per-epoch processing on some BeaconState. +/// +/// Mutates the given `BeaconState`, returning early if an error is encountered. If an error is +/// returned, a state might be "half-processed" and therefore in an invalid state. +/// +/// Spec v0.4.0 pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { let previous_epoch = state.previous_epoch(spec); - debug!( - "Starting per-epoch processing on epoch {}...", - state.current_epoch(spec) - ); - // Ensure all of the caches are built. state.build_epoch_cache(RelativeEpoch::Previous, spec)?; state.build_epoch_cache(RelativeEpoch::Current, spec)?; @@ -79,11 +84,12 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result // Rotate the epoch caches to suit the epoch transition. state.advance_caches(); - debug!("Epoch transition complete."); - Ok(()) } +/// Returns a list of active validator indices for the state's current epoch. +/// +/// Spec v0.4.0 pub fn calculate_active_validator_indices(state: &BeaconState, spec: &ChainSpec) -> Vec { get_active_validator_indices( &state.validator_registry, @@ -91,6 +97,14 @@ pub fn calculate_active_validator_indices(state: &BeaconState, spec: &ChainSpec) ) } +/// Calculates various sets of attesters, including: +/// +/// - current epoch attesters +/// - current epoch boundary attesters +/// - previous epoch attesters +/// - etc. +/// +/// Spec v0.4.0 pub fn calculate_attester_sets( state: &BeaconState, spec: &ChainSpec, @@ -113,6 +127,13 @@ pub fn process_eth1_data(state: &mut BeaconState, spec: &ChainSpec) { } } +/// Update the following fields on the `BeaconState`: +/// +/// - `justification_bitfield`. +/// - `finalized_epoch` +/// - `justified_epoch` +/// - `previous_justified_epoch` +/// /// Spec v0.4.0 pub fn process_justification( state: &mut BeaconState, @@ -190,8 +211,13 @@ pub fn process_justification( state.justified_epoch = new_justified_epoch; } -pub type WinningRootHashSet = HashMap; - +/// Updates the following fields on the `BeaconState`: +/// +/// - `latest_crosslinks` +/// +/// Also returns a `WinningRootHashSet` for later use during epoch processing. +/// +/// Spec v0.4.0 pub fn process_crosslinks( state: &mut BeaconState, spec: &ChainSpec, @@ -250,6 +276,10 @@ pub fn process_crosslinks( Ok(winning_root_for_shards) } +/// Updates the following fields on the BeaconState: +/// +/// - `validator_balances` +/// /// Spec v0.4.0 pub fn process_rewards_and_penalities( state: &mut BeaconState, @@ -488,7 +518,9 @@ pub fn process_rewards_and_penalities( Ok(()) } -// Spec v0.4.0 +/// Peforms a validator registry update, if required. +/// +/// Spec v0.4.0 pub fn process_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { let current_epoch = state.current_epoch(spec); let next_epoch = state.next_epoch(spec); @@ -535,7 +567,10 @@ pub fn process_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> Ok(()) } -// Spec v0.4.0 +/// Updates the state's `latest_active_index_roots` field with a tree hash the active validator +/// indices for the next epoch. +/// +/// Spec v0.4.0 pub fn update_active_tree_index_roots( state: &mut BeaconState, spec: &ChainSpec, @@ -555,7 +590,9 @@ pub fn update_active_tree_index_roots( Ok(()) } -// Spec v0.4.0 +/// Advances the state's `latest_slashed_balances` field. +/// +/// Spec v0.4.0 pub fn update_latest_slashed_balances(state: &mut BeaconState, spec: &ChainSpec) { let current_epoch = state.current_epoch(spec); let next_epoch = state.next_epoch(spec); @@ -564,7 +601,9 @@ pub fn update_latest_slashed_balances(state: &mut BeaconState, spec: &ChainSpec) state.latest_slashed_balances[current_epoch.as_usize() % spec.latest_slashed_exit_length]; } -// Spec v0.4.0 +/// Removes all pending attestations from the previous epoch. +/// +/// Spec v0.4.0 pub fn clean_attestations(state: &mut BeaconState, spec: &ChainSpec) { let current_epoch = state.current_epoch(spec); diff --git a/eth2/state_processing/src/per_epoch_processing/attester_sets.rs b/eth2/state_processing/src/per_epoch_processing/attester_sets.rs index d82774ac2b..03f49c1d39 100644 --- a/eth2/state_processing/src/per_epoch_processing/attester_sets.rs +++ b/eth2/state_processing/src/per_epoch_processing/attester_sets.rs @@ -1,13 +1,17 @@ use fnv::FnvHashSet; use types::*; +/// A set of validator indices, along with the total balance of all those attesters. #[derive(Default)] pub struct Attesters { + /// A set of validator indices. pub indices: FnvHashSet, + /// The total balance of all validators in `self.indices`. pub balance: u64, } impl Attesters { + /// Add the given indices to the set, incrementing the sets balance by the provided balance. fn add(&mut self, additional_indices: &[usize], additional_balance: u64) { self.indices.reserve(additional_indices.len()); for i in additional_indices { @@ -17,15 +21,35 @@ impl Attesters { } } +/// A collection of `Attester` objects, representing set of attesters that are rewarded/penalized +/// during an epoch transition. pub struct AttesterSets { + /// All validators who attested during the state's current epoch. pub current_epoch: Attesters, + /// All validators who attested that the beacon block root of the first slot of the state's + /// current epoch is the same as the one stored in this state. + /// + /// In short validators who agreed with the state about the first slot of the current epoch. pub current_epoch_boundary: Attesters, + /// All validators who attested during the state's previous epoch. pub previous_epoch: Attesters, + /// All validators who attested that the beacon block root of the first slot of the state's + /// previous epoch is the same as the one stored in this state. + /// + /// In short, validators who agreed with the state about the first slot of the previous epoch. pub previous_epoch_boundary: Attesters, + /// All validators who attested that the beacon block root at the pending attestation's slot is + /// the same as the one stored in this state. + /// + /// In short, validators who agreed with the state about the current beacon block root when + /// they attested. pub previous_epoch_head: Attesters, } impl AttesterSets { + /// Loop through all attestations in the state and instantiate a complete `AttesterSets` struct. + /// + /// Spec v0.4.0 pub fn new(state: &BeaconState, spec: &ChainSpec) -> Result { let mut current_epoch = Attesters::default(); let mut current_epoch_boundary = Attesters::default(); @@ -67,10 +91,17 @@ impl AttesterSets { } } +/// Returns `true` if some `PendingAttestation` is from the supplied `epoch`. +/// +/// Spec v0.4.0 fn is_from_epoch(a: &PendingAttestation, epoch: Epoch, spec: &ChainSpec) -> bool { a.data.slot.epoch(spec.slots_per_epoch) == epoch } +/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for +/// the first slot of the given epoch. +/// +/// Spec v0.4.0 fn has_common_epoch_boundary_root( a: &PendingAttestation, state: &BeaconState, @@ -85,6 +116,10 @@ fn has_common_epoch_boundary_root( Ok(a.data.epoch_boundary_root == state_boundary_root) } +/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for +/// the current slot of the `PendingAttestation`. +/// +/// Spec v0.4.0 fn has_common_beacon_block_root( a: &PendingAttestation, state: &BeaconState, From 2be0373f01814a5ce17c4e5aeebd2a9a45cc15d9 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Tue, 12 Mar 2019 18:26:41 +1100 Subject: [PATCH 069/144] Add comments to new functions/structs. --- eth2/types/src/beacon_block.rs | 6 +++ eth2/types/src/beacon_state.rs | 49 +++---------------- eth2/types/src/test_utils/keypairs_file.rs | 7 +++ .../test_utils/testing_attestation_builder.rs | 9 ++++ .../testing_attester_slashing_builder.rs | 2 + .../testing_beacon_block_builder.rs | 19 ++++++- .../testing_beacon_state_builder.rs | 3 ++ .../src/test_utils/testing_deposit_builder.rs | 11 +++++ .../testing_proposer_slashing_builder.rs | 2 + .../test_utils/testing_transfer_builder.rs | 8 +++ .../testing_voluntary_exit_builder.rs | 8 +++ 11 files changed, 80 insertions(+), 44 deletions(-) diff --git a/eth2/types/src/beacon_block.rs b/eth2/types/src/beacon_block.rs index 9e1b3f7aef..b67c866a4d 100644 --- a/eth2/types/src/beacon_block.rs +++ b/eth2/types/src/beacon_block.rs @@ -23,6 +23,8 @@ pub struct BeaconBlock { impl BeaconBlock { /// Produce the first block of the Beacon Chain. + /// + /// Spec v0.4.0 pub fn genesis(state_root: Hash256, spec: &ChainSpec) -> BeaconBlock { BeaconBlock { slot: spec.genesis_slot, @@ -46,11 +48,15 @@ impl BeaconBlock { } /// Returns the `hash_tree_root` of the block. + /// + /// Spec v0.4.0 pub fn canonical_root(&self) -> Hash256 { Hash256::from_slice(&self.hash_tree_root()[..]) } /// Returns an unsigned proposal for block. + /// + /// Spec v0.4.0 pub fn proposal(&self, spec: &ChainSpec) -> Proposal { Proposal { slot: self.slot, diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index f69746dae5..b4faa6a49c 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -6,7 +6,6 @@ use honey_badger_split::SplitExt; use int_to_bytes::int_to_bytes32; use log::{debug, error, trace}; use rand::RngCore; -use rayon::prelude::*; use serde_derive::Serialize; use ssz::{hash, Decodable, DecodeError, Encodable, SignedRoot, SszStream, TreeHash}; use std::collections::HashMap; @@ -113,6 +112,11 @@ pub struct BeaconState { impl BeaconState { /// Produce the first state of the Beacon Chain. + /// + /// This does not fully build a genesis beacon state, it omits processing of initial validator + /// deposits. To obtain a full genesis beacon state, use the `BeaconStateBuilder`. + /// + /// Spec v0.4.0 pub fn genesis(genesis_time: u64, latest_eth1_data: Eth1Data, spec: &ChainSpec) -> BeaconState { let initial_crosslink = Crosslink { epoch: spec.genesis_epoch, @@ -185,44 +189,9 @@ impl BeaconState { } } - /// Produce the first state of the Beacon Chain. - pub fn process_initial_deposits( - &mut self, - initial_validator_deposits: Vec, - spec: &ChainSpec, - ) -> Result<(), Error> { - debug!("Processing genesis deposits..."); - - let deposit_data = initial_validator_deposits - .par_iter() - .map(|deposit| &deposit.deposit_data) - .collect(); - - self.process_deposits(deposit_data, spec); - - trace!("Processed genesis deposits."); - - for validator_index in 0..self.validator_registry.len() { - if self.get_effective_balance(validator_index, spec) >= spec.max_deposit_amount { - self.activate_validator(validator_index, true, spec); - } - } - - self.deposit_index = initial_validator_deposits.len() as u64; - - let genesis_active_index_root = hash_tree_root(get_active_validator_indices( - &self.validator_registry, - spec.genesis_epoch, - )); - self.latest_active_index_roots = - vec![genesis_active_index_root; spec.latest_active_index_roots_length]; - - self.current_shuffling_seed = self.generate_seed(spec.genesis_epoch, spec)?; - - Ok(()) - } - /// Returns the `hash_tree_root` of the state. + /// + /// Spec v0.4.0 pub fn canonical_root(&self) -> Hash256 { Hash256::from_slice(&self.hash_tree_root()[..]) } @@ -1129,10 +1098,6 @@ impl BeaconState { } } -fn hash_tree_root(input: Vec) -> Hash256 { - Hash256::from_slice(&input.hash_tree_root()[..]) -} - impl Encodable for BeaconState { fn ssz_append(&self, s: &mut SszStream) { s.append(&self.slot); diff --git a/eth2/types/src/test_utils/keypairs_file.rs b/eth2/types/src/test_utils/keypairs_file.rs index ebc50f5284..a1ea4d928b 100644 --- a/eth2/types/src/test_utils/keypairs_file.rs +++ b/eth2/types/src/test_utils/keypairs_file.rs @@ -12,12 +12,17 @@ pub const BATCH_SIZE: usize = 1_000; // ~15MB pub const KEYPAIR_BYTES_LEN: usize = PUBLIC_KEY_BYTES_LEN + SECRET_KEY_BYTES_LEN; pub const BATCH_BYTE_LEN: usize = KEYPAIR_BYTES_LEN * BATCH_SIZE; +/// Defines a trait that allows reading/writing a vec of `Keypair` from/to a file. pub trait KeypairsFile { + /// Write to file, without guaranteeing interoperability with other clients. fn to_raw_file(&self, path: &Path, keypairs: &[Keypair]) -> Result<(), Error>; + /// Read from file, without guaranteeing interoperability with other clients. fn from_raw_file(path: &Path, count: usize) -> Result, Error>; } impl KeypairsFile for Vec { + /// Write the keypairs to file, using the fastest possible method without guaranteeing + /// interoperability with other clients. fn to_raw_file(&self, path: &Path, keypairs: &[Keypair]) -> Result<(), Error> { let mut keypairs_file = File::create(path)?; @@ -35,6 +40,8 @@ impl KeypairsFile for Vec { Ok(()) } + /// Read the keypairs from file, using the fastest possible method without guaranteeing + /// interoperability with other clients. fn from_raw_file(path: &Path, count: usize) -> Result, Error> { let mut keypairs_file = File::open(path)?; diff --git a/eth2/types/src/test_utils/testing_attestation_builder.rs b/eth2/types/src/test_utils/testing_attestation_builder.rs index f52edadfef..8c86d756de 100644 --- a/eth2/types/src/test_utils/testing_attestation_builder.rs +++ b/eth2/types/src/test_utils/testing_attestation_builder.rs @@ -1,12 +1,16 @@ use crate::*; use ssz::TreeHash; +/// Builds an attestation to be used for testing purposes. +/// +/// This struct should **never be used for production purposes.** pub struct TestingAttestationBuilder { committee: Vec, attestation: Attestation, } impl TestingAttestationBuilder { + /// Create a new attestation builder. pub fn new( state: &BeaconState, committee: &[usize], @@ -70,6 +74,10 @@ impl TestingAttestationBuilder { } } + /// Signs the attestation with a subset (or all) committee members. + /// + /// `secret_keys` must be supplied in the same order as `signing_validators`. I.e., the first + /// keypair must be that of the first signing validator. pub fn sign( &mut self, signing_validators: &[usize], @@ -111,6 +119,7 @@ impl TestingAttestationBuilder { } } + /// Consume the builder and return the attestation. pub fn build(self) -> Attestation { self.attestation } diff --git a/eth2/types/src/test_utils/testing_attester_slashing_builder.rs b/eth2/types/src/test_utils/testing_attester_slashing_builder.rs index d9da3db2d1..232de87ec9 100644 --- a/eth2/types/src/test_utils/testing_attester_slashing_builder.rs +++ b/eth2/types/src/test_utils/testing_attester_slashing_builder.rs @@ -2,6 +2,8 @@ use crate::*; use ssz::TreeHash; /// Builds an `AttesterSlashing`. +/// +/// This struct should **never be used for production purposes.** pub struct TestingAttesterSlashingBuilder(); impl TestingAttesterSlashingBuilder { diff --git a/eth2/types/src/test_utils/testing_beacon_block_builder.rs b/eth2/types/src/test_utils/testing_beacon_block_builder.rs index 3ebd24b0a9..97e395e1f5 100644 --- a/eth2/types/src/test_utils/testing_beacon_block_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_block_builder.rs @@ -8,22 +8,29 @@ use crate::{ use rayon::prelude::*; use ssz::{SignedRoot, TreeHash}; +/// Builds a beacon block to be used for testing purposes. +/// +/// This struct should **never be used for production purposes.** pub struct TestingBeaconBlockBuilder { block: BeaconBlock, } impl TestingBeaconBlockBuilder { + /// Create a new builder from genesis. pub fn new(spec: &ChainSpec) -> Self { Self { block: BeaconBlock::genesis(spec.zero_hash, spec), } } + /// Set the slot of the block. pub fn set_slot(&mut self, slot: Slot) { self.block.slot = slot; } /// Signs the block. + /// + /// Modifying the block after signing may invalidate the signature. pub fn sign(&mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) { let proposal = self.block.proposal(spec); let message = proposal.signed_root(); @@ -33,6 +40,8 @@ impl TestingBeaconBlockBuilder { } /// Sets the randao to be a signature across the blocks epoch. + /// + /// Modifying the block's slot after signing may invalidate the signature. pub fn set_randao_reveal(&mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) { let epoch = self.block.slot.epoch(spec.slots_per_epoch); let message = epoch.hash_tree_root(); @@ -65,9 +74,15 @@ impl TestingBeaconBlockBuilder { self.block.body.attester_slashings.push(attester_slashing); } - /// Fills the block with as many attestations as possible. + /// Fills the block with `MAX_ATTESTATIONS` attestations. /// - /// Note: this will not perform well when `jepoch_committees_count % slots_per_epoch != 0` + /// It will first go and get each committee that is able to include an attestation in this + /// block. If there are enough committees, it will produce an attestation for each. If there + /// are _not_ enough committees, it will start splitting the committees in half until it + /// achieves the target. It will then produce separate attestations for each split committee. + /// + /// Note: the signed messages of the split committees will be identical -- it would be possible + /// to aggregate these split attestations. pub fn fill_with_attestations( &mut self, state: &BeaconState, diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index afefa40633..b2cf28c8a3 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -20,6 +20,9 @@ pub fn keypairs_path() -> PathBuf { dir.join(KEYPAIRS_FILE) } +/// Builds a beacon state to be used for testing purposes. +/// +/// This struct should **never be used for production purposes.** pub struct TestingBeaconStateBuilder { state: BeaconState, keypairs: Vec, diff --git a/eth2/types/src/test_utils/testing_deposit_builder.rs b/eth2/types/src/test_utils/testing_deposit_builder.rs index 56e81cad0a..7293114688 100644 --- a/eth2/types/src/test_utils/testing_deposit_builder.rs +++ b/eth2/types/src/test_utils/testing_deposit_builder.rs @@ -1,11 +1,15 @@ use crate::*; use bls::get_withdrawal_credentials; +/// Builds an deposit to be used for testing purposes. +/// +/// This struct should **never be used for production purposes.** pub struct TestingDepositBuilder { deposit: Deposit, } impl TestingDepositBuilder { + /// Instantiates a new builder. pub fn new(amount: u64) -> Self { let keypair = Keypair::random(); @@ -26,10 +30,16 @@ impl TestingDepositBuilder { Self { deposit } } + /// Set the `deposit.index` value. pub fn set_index(&mut self, index: u64) { self.deposit.index = index; } + /// Signs the deposit, also setting the following values: + /// + /// - `pubkey` to the signing pubkey. + /// - `withdrawal_credentials` to the signing pubkey. + /// - `proof_of_possesssion` pub fn sign(&mut self, keypair: &Keypair, state: &BeaconState, spec: &ChainSpec) { let withdrawal_credentials = Hash256::from_slice( &get_withdrawal_credentials(&keypair.pk, spec.bls_withdrawal_prefix_byte)[..], @@ -47,6 +57,7 @@ impl TestingDepositBuilder { DepositInput::create_proof_of_possession(&keypair, &withdrawal_credentials, domain); } + /// Builds the deposit, consuming the builder. pub fn build(self) -> Deposit { self.deposit } diff --git a/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs b/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs index 43ff3d0b76..7f16b679f7 100644 --- a/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs +++ b/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs @@ -2,6 +2,8 @@ use crate::*; use ssz::SignedRoot; /// Builds a `ProposerSlashing`. +/// +/// This struct should **never be used for production purposes.** pub struct TestingProposerSlashingBuilder(); impl TestingProposerSlashingBuilder { diff --git a/eth2/types/src/test_utils/testing_transfer_builder.rs b/eth2/types/src/test_utils/testing_transfer_builder.rs index c343e8fd25..c4256ebea2 100644 --- a/eth2/types/src/test_utils/testing_transfer_builder.rs +++ b/eth2/types/src/test_utils/testing_transfer_builder.rs @@ -1,11 +1,15 @@ use crate::*; use ssz::SignedRoot; +/// Builds a transfer to be used for testing purposes. +/// +/// This struct should **never be used for production purposes.** pub struct TestingTransferBuilder { transfer: Transfer, } impl TestingTransferBuilder { + /// Instantiates a new builder. pub fn new(from: u64, to: u64, amount: u64, slot: Slot) -> Self { let keypair = Keypair::random(); @@ -22,6 +26,9 @@ impl TestingTransferBuilder { Self { transfer } } + /// Signs the transfer. + /// + /// The keypair must match that of the `from` validator index. pub fn sign(&mut self, keypair: Keypair, fork: &Fork, spec: &ChainSpec) { self.transfer.pubkey = keypair.pk; let message = self.transfer.signed_root(); @@ -31,6 +38,7 @@ impl TestingTransferBuilder { self.transfer.signature = Signature::new(&message, domain, &keypair.sk); } + /// Builds the transfer, consuming the builder. pub fn build(self) -> Transfer { self.transfer } diff --git a/eth2/types/src/test_utils/testing_voluntary_exit_builder.rs b/eth2/types/src/test_utils/testing_voluntary_exit_builder.rs index 92ef4484ea..fe5c8325ad 100644 --- a/eth2/types/src/test_utils/testing_voluntary_exit_builder.rs +++ b/eth2/types/src/test_utils/testing_voluntary_exit_builder.rs @@ -1,11 +1,15 @@ use crate::*; use ssz::SignedRoot; +/// Builds an exit to be used for testing purposes. +/// +/// This struct should **never be used for production purposes.** pub struct TestingVoluntaryExitBuilder { exit: VoluntaryExit, } impl TestingVoluntaryExitBuilder { + /// Instantiates a new builder. pub fn new(epoch: Epoch, validator_index: u64) -> Self { let exit = VoluntaryExit { epoch, @@ -16,6 +20,9 @@ impl TestingVoluntaryExitBuilder { Self { exit } } + /// Signs the exit. + /// + /// The signing secret key must match that of the exiting validator. pub fn sign(&mut self, secret_key: &SecretKey, fork: &Fork, spec: &ChainSpec) { let message = self.exit.signed_root(); let domain = spec.get_domain(self.exit.epoch, Domain::Exit, fork); @@ -23,6 +30,7 @@ impl TestingVoluntaryExitBuilder { self.exit.signature = Signature::new(&message, domain, secret_key); } + /// Builds the exit, consuming the builder. pub fn build(self) -> VoluntaryExit { self.exit } From 2b7aa269c301e964673328fad5b55497cc79988c Mon Sep 17 00:00:00 2001 From: pawanjay176 Date: Wed, 13 Mar 2019 00:22:15 +0530 Subject: [PATCH 070/144] Add OptimizedLMDGhost fork choice rule and tests --- eth2/fork_choice/src/lib.rs | 4 + eth2/fork_choice/src/optimized_lmd_ghost.rs | 452 ++++++++++++++++++++ eth2/fork_choice/tests/tests.rs | 22 +- 3 files changed, 475 insertions(+), 3 deletions(-) create mode 100644 eth2/fork_choice/src/optimized_lmd_ghost.rs diff --git a/eth2/fork_choice/src/lib.rs b/eth2/fork_choice/src/lib.rs index 6062c19b15..a947473f38 100644 --- a/eth2/fork_choice/src/lib.rs +++ b/eth2/fork_choice/src/lib.rs @@ -23,6 +23,7 @@ extern crate types; pub mod bitwise_lmd_ghost; pub mod longest_chain; pub mod slow_lmd_ghost; +pub mod optimized_lmd_ghost; use db::stores::BeaconBlockAtSlotError; use db::DBError; @@ -31,6 +32,7 @@ use types::{BeaconBlock, ChainSpec, Hash256}; pub use bitwise_lmd_ghost::BitwiseLMDGhost; pub use longest_chain::LongestChain; pub use slow_lmd_ghost::SlowLMDGhost; +pub use optimized_lmd_ghost::OptimizedLMDGhost; /// Defines the interface for Fork Choices. Each Fork choice will define their own data structures /// which can be built in block processing through the `add_block` and `add_attestation` functions. @@ -101,4 +103,6 @@ pub enum ForkChoiceAlgorithm { SlowLMDGhost, /// An optimised version of bitwise LMD-GHOST by Vitalik. BitwiseLMDGhost, + /// An optimised implementation of LMD ghost. + OptimizedLMDGhost } diff --git a/eth2/fork_choice/src/optimized_lmd_ghost.rs b/eth2/fork_choice/src/optimized_lmd_ghost.rs new file mode 100644 index 0000000000..093120bb5e --- /dev/null +++ b/eth2/fork_choice/src/optimized_lmd_ghost.rs @@ -0,0 +1,452 @@ +//! The optimised bitwise LMD-GHOST fork choice rule. +extern crate bit_vec; + +use crate::{ForkChoice, ForkChoiceError}; +use db::{ + stores::{BeaconBlockStore, BeaconStateStore}, + ClientDB, +}; +use log::{debug, trace}; +use std::collections::HashMap; +use std::sync::Arc; +use types::{ + readers::BeaconBlockReader, validator_registry::get_active_validator_indices, BeaconBlock, + ChainSpec, Hash256, Slot, SlotHeight, +}; + +//TODO: Pruning - Children +//TODO: Handle Syncing + +// NOTE: This uses u32 to represent difference between block heights. Thus this is only +// applicable for block height differences in the range of a u32. +// This can potentially be parallelized in some parts. + +/// Compute the base-2 logarithm of an integer, floored (rounded down) +#[inline] +fn log2_int(x: u64) -> u32 { + if x == 0 { + return 0; + } + 63 - x.leading_zeros() +} + +fn power_of_2_below(x: u64) -> u64 { + 2u64.pow(log2_int(x)) +} + +/// Stores the necessary data structures to run the optimised lmd ghost algorithm. +pub struct OptimizedLMDGhost { + /// A cache of known ancestors at given heights for a specific block. + //TODO: Consider FnvHashMap + cache: HashMap, Hash256>, + /// Log lookup table for blocks to their ancestors. + //TODO: Verify we only want/need a size 16 log lookup + ancestors: Vec>, + /// Stores the children for any given parent. + children: HashMap>, + /// The latest attestation targets as a map of validator index to block hash. + //TODO: Could this be a fixed size vec + latest_attestation_targets: HashMap, + /// Block storage access. + block_store: Arc>, + /// State storage access. + state_store: Arc>, + max_known_height: SlotHeight, +} + +impl OptimizedLMDGhost +where + T: ClientDB + Sized, +{ + pub fn new( + block_store: Arc>, + state_store: Arc>, + ) -> Self { + OptimizedLMDGhost { + cache: HashMap::new(), + ancestors: vec![HashMap::new(); 16], + latest_attestation_targets: HashMap::new(), + children: HashMap::new(), + max_known_height: SlotHeight::new(0), + block_store, + state_store, + } + } + + /// Finds the latest votes weighted by validator balance. Returns a hashmap of block_hash to + /// weighted votes. + pub fn get_latest_votes( + &self, + state_root: &Hash256, + block_slot: Slot, + spec: &ChainSpec, + ) -> Result, ForkChoiceError> { + // get latest votes + // Note: Votes are weighted by min(balance, MAX_DEPOSIT_AMOUNT) // + // FORK_CHOICE_BALANCE_INCREMENT + // build a hashmap of block_hash to weighted votes + let mut latest_votes: HashMap = HashMap::new(); + // gets the current weighted votes + let current_state = self + .state_store + .get_deserialized(&state_root)? + .ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?; + + let active_validator_indices = get_active_validator_indices( + ¤t_state.validator_registry[..], + block_slot.epoch(spec.slots_per_epoch), + ); + + for index in active_validator_indices { + let balance = std::cmp::min( + current_state.validator_balances[index], + spec.max_deposit_amount, + ) / spec.fork_choice_balance_increment; + if balance > 0 { + if let Some(target) = self.latest_attestation_targets.get(&(index as u64)) { + *latest_votes.entry(*target).or_insert_with(|| 0) += balance; + } + } + } + trace!("Latest votes: {:?}", latest_votes); + Ok(latest_votes) + } + + /// Gets the ancestor at a given height `at_height` of a block specified by `block_hash`. + fn get_ancestor( + &mut self, + block_hash: Hash256, + target_height: SlotHeight, + spec: &ChainSpec, + ) -> Option { + // return None if we can't get the block from the db. + let block_height = { + let block_slot = self + .block_store + .get_deserialized(&block_hash) + .ok()? + .expect("Should have returned already if None") + .slot; + + block_slot.height(spec.genesis_slot) + }; + + // verify we haven't exceeded the block height + if target_height >= block_height { + if target_height > block_height { + return None; + } else { + return Some(block_hash); + } + } + // check if the result is stored in our cache + let cache_key = CacheKey::new(&block_hash, target_height.as_u64()); + if let Some(ancestor) = self.cache.get(&cache_key) { + return Some(*ancestor); + } + + // not in the cache recursively search for ancestors using a log-lookup + if let Some(ancestor) = { + let ancestor_lookup = self.ancestors + [log2_int((block_height - target_height - 1u64).as_u64()) as usize] + .get(&block_hash) + //TODO: Panic if we can't lookup and fork choice fails + .expect("All blocks should be added to the ancestor log lookup table"); + self.get_ancestor(*ancestor_lookup, target_height, &spec) + } { + // add the result to the cache + self.cache.insert(cache_key, ancestor); + return Some(ancestor); + } + + None + } + + // looks for an obvious block winner given the latest votes for a specific height + fn get_clear_winner( + &mut self, + latest_votes: &HashMap, + block_height: SlotHeight, + spec: &ChainSpec, + ) -> Option { + // map of vote counts for every hash at this height + let mut current_votes: HashMap = HashMap::new(); + let mut total_vote_count = 0; + + trace!("Clear winner at block height: {}", block_height); + // loop through the latest votes and count all votes + // these have already been weighted by balance + for (hash, votes) in latest_votes.iter() { + if let Some(ancestor) = self.get_ancestor(*hash, block_height, spec) { + let current_vote_value = current_votes.get(&ancestor).unwrap_or_else(|| &0); + current_votes.insert(ancestor, current_vote_value + *votes); + total_vote_count += votes; + } + } + // Check if there is a clear block winner at this height. If so return it. + for (hash, votes) in current_votes.iter() { + if *votes > total_vote_count / 2 { + // we have a clear winner, return it + return Some(*hash); + } + } + // didn't find a clear winner + None + } + + // Finds the best child (one with highest votes) + fn choose_best_child(&self, votes: &HashMap) -> Option { + if votes.is_empty() { + return None; + } + let mut best_child: Hash256 = Hash256::from(0); + let mut max_votes: u64 = 0; + for (&candidate, &votes) in votes.iter() { + // Choose the smaller hash to break ties deterministically + if votes == max_votes && candidate < best_child { + best_child = candidate; + } + if votes > max_votes { + max_votes = votes; + best_child = candidate; + } + } + Some(best_child) + } +} + +impl ForkChoice for OptimizedLMDGhost { + fn add_block( + &mut self, + block: &BeaconBlock, + block_hash: &Hash256, + spec: &ChainSpec, + ) -> Result<(), ForkChoiceError> { + // get the height of the parent + let parent_height = self + .block_store + .get_deserialized(&block.parent_root)? + .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(block.parent_root))? + .slot() + .height(spec.genesis_slot); + + let parent_hash = &block.parent_root; + + // add the new block to the children of parent + (*self + .children + .entry(block.parent_root) + .or_insert_with(|| vec![])) + .push(block_hash.clone()); + + // build the ancestor data structure + for index in 0..16 { + if parent_height % (1 << index) == 0 { + self.ancestors[index].insert(*block_hash, *parent_hash); + } else { + // TODO: This is unsafe. Will panic if parent_hash doesn't exist. Using it for debugging + let parent_ancestor = self.ancestors[index][parent_hash]; + self.ancestors[index].insert(*block_hash, parent_ancestor); + } + } + // update the max height + self.max_known_height = std::cmp::max(self.max_known_height, parent_height + 1); + Ok(()) + } + + fn add_attestation( + &mut self, + validator_index: u64, + target_block_root: &Hash256, + spec: &ChainSpec, + ) -> Result<(), ForkChoiceError> { + // simply add the attestation to the latest_attestation_target if the block_height is + // larger + trace!( + "Adding attestation of validator: {:?} for block: {}", + validator_index, + target_block_root + ); + let attestation_target = self + .latest_attestation_targets + .entry(validator_index) + .or_insert_with(|| *target_block_root); + // if we already have a value + if attestation_target != target_block_root { + trace!("Old attestation found: {:?}", attestation_target); + // get the height of the target block + let block_height = self + .block_store + .get_deserialized(&target_block_root)? + .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*target_block_root))? + .slot() + .height(spec.genesis_slot); + + // get the height of the past target block + let past_block_height = self + .block_store + .get_deserialized(&attestation_target)? + .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*attestation_target))? + .slot() + .height(spec.genesis_slot); + // update the attestation only if the new target is higher + if past_block_height < block_height { + trace!("Updating old attestation"); + *attestation_target = *target_block_root; + } + } + Ok(()) + } + + /// Perform lmd_ghost on the current chain to find the head. + fn find_head( + &mut self, + justified_block_start: &Hash256, + spec: &ChainSpec, + ) -> Result { + debug!( + "Starting optimised fork choice at block: {}", + justified_block_start + ); + let block = self + .block_store + .get_deserialized(&justified_block_start)? + .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))?; + + let block_slot = block.slot(); + let state_root = block.state_root(); + let mut block_height = block_slot.height(spec.genesis_slot); + + let mut current_head = *justified_block_start; + + let mut latest_votes = self.get_latest_votes(&state_root, block_slot, spec)?; + + // remove any votes that don't relate to our current head. + latest_votes + .retain(|hash, _| self.get_ancestor(*hash, block_height, spec) == Some(current_head)); + + // begin searching for the head + loop { + debug!( + "Iteration for block: {} with vote length: {}", + current_head, + latest_votes.len() + ); + // if there are no children, we are done, return the current_head + let children = match self.children.get(¤t_head) { + Some(children) => children.clone(), + None => { + debug!("Head found: {}", current_head); + return Ok(current_head); + } + }; + + // logarithmic lookup blocks to see if there are obvious winners, if so, + // progress to the next iteration. + let mut step = + power_of_2_below(self.max_known_height.saturating_sub(block_height).as_u64()) / 2; + while step > 0 { + trace!("Current Step: {}", step); + if let Some(clear_winner) = self.get_clear_winner( + &latest_votes, + block_height - (block_height % step) + step, + spec, + ) { + current_head = clear_winner; + break; + } + step /= 2; + } + if step > 0 { + trace!("Found clear winner: {}", current_head); + } + // if our skip lookup failed and we only have one child, progress to that child + else if children.len() == 1 { + current_head = children[0]; + trace!( + "Lookup failed, only one child, proceeding to child: {}", + current_head + ); + } + // we need to find the best child path to progress down. + else { + trace!("Searching for best child"); + let mut child_votes = HashMap::new(); + for (voted_hash, vote) in latest_votes.iter() { + // if the latest votes correspond to a child + if let Some(child) = self.get_ancestor(*voted_hash, block_height + 1, spec) { + // add up the votes for each child + *child_votes.entry(child).or_insert_with(|| 0) += vote; + } + } + // given the votes on the children, find the best child + current_head = self + .choose_best_child(&child_votes) + .ok_or(ForkChoiceError::CannotFindBestChild)?; + trace!("Best child found: {}", current_head); + } + + // didn't find head yet, proceed to next iteration + // update block height + block_height = self + .block_store + .get_deserialized(¤t_head)? + .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(current_head))? + .slot() + .height(spec.genesis_slot); + // prune the latest votes for votes that are not part of current chosen chain + // more specifically, only keep votes that have head as an ancestor + for hash in latest_votes.keys() { + trace!( + "Ancestor for vote: {} at height: {} is: {:?}", + hash, + block_height, + self.get_ancestor(*hash, block_height, spec) + ); + } + latest_votes.retain(|hash, _| { + self.get_ancestor(*hash, block_height, spec) == Some(current_head) + }); + } + } +} + +/// Type for storing blocks in a memory cache. Key is comprised of block-hash plus the height. +#[derive(PartialEq, Eq, Hash)] +pub struct CacheKey { + block_hash: Hash256, + block_height: T, +} + +impl CacheKey { + pub fn new(block_hash: &Hash256, block_height: T) -> Self { + CacheKey { + block_hash: *block_hash, + block_height, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + pub fn test_power_of_2_below() { + assert_eq!(power_of_2_below(4), 4); + assert_eq!(power_of_2_below(5), 4); + assert_eq!(power_of_2_below(7), 4); + assert_eq!(power_of_2_below(24), 16); + assert_eq!(power_of_2_below(32), 32); + assert_eq!(power_of_2_below(33), 32); + assert_eq!(power_of_2_below(63), 32); + } + + #[test] + pub fn test_power_of_2_below_large() { + let pow: u64 = 1 << 24; + for x in (pow - 20)..(pow + 20) { + assert!(power_of_2_below(x) <= x, "{}", x); + } + } +} diff --git a/eth2/fork_choice/tests/tests.rs b/eth2/fork_choice/tests/tests.rs index 7228bca10c..1c8d92e0a6 100644 --- a/eth2/fork_choice/tests/tests.rs +++ b/eth2/fork_choice/tests/tests.rs @@ -3,7 +3,7 @@ extern crate beacon_chain; extern crate bls; extern crate db; -//extern crate env_logger; // for debugging +// extern crate env_logger; // for debugging extern crate fork_choice; extern crate hex; extern crate log; @@ -15,8 +15,8 @@ pub use beacon_chain::BeaconChain; use bls::Signature; use db::stores::{BeaconBlockStore, BeaconStateStore}; use db::MemoryDB; -//use env_logger::{Builder, Env}; -use fork_choice::{BitwiseLMDGhost, ForkChoice, ForkChoiceAlgorithm, LongestChain, SlowLMDGhost}; +// use env_logger::{Builder, Env}; +use fork_choice::{BitwiseLMDGhost, OptimizedLMDGhost, ForkChoice, ForkChoiceAlgorithm, LongestChain, SlowLMDGhost}; use ssz::ssz_encode; use std::collections::HashMap; use std::sync::Arc; @@ -27,6 +27,18 @@ use yaml_rust::yaml; // Note: We Assume the block Id's are hex-encoded. +#[test] +fn test_optimized_lmd_ghost() { + // set up logging + // Builder::from_env(Env::default().default_filter_or("trace")).init(); + + test_yaml_vectors( + ForkChoiceAlgorithm::OptimizedLMDGhost, + "tests/lmd_ghost_test_vectors.yaml", + 100, + ); +} + #[test] fn test_bitwise_lmd_ghost() { // set up logging @@ -212,6 +224,10 @@ fn setup_inital_state( // the fork choice instantiation let fork_choice: Box = match fork_choice_algo { + ForkChoiceAlgorithm::OptimizedLMDGhost => Box::new(OptimizedLMDGhost::new( + block_store.clone(), + state_store.clone(), + )), ForkChoiceAlgorithm::BitwiseLMDGhost => Box::new(BitwiseLMDGhost::new( block_store.clone(), state_store.clone(), From 6f5593ef2bf2de66078e365186d4098acb91a085 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 13 Mar 2019 10:24:02 +1100 Subject: [PATCH 071/144] Optimize TestingAttesterSlashingBuilder --- .../test_utils/testing_attester_slashing_builder.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/eth2/types/src/test_utils/testing_attester_slashing_builder.rs b/eth2/types/src/test_utils/testing_attester_slashing_builder.rs index 232de87ec9..92c7fe814a 100644 --- a/eth2/types/src/test_utils/testing_attester_slashing_builder.rs +++ b/eth2/types/src/test_utils/testing_attester_slashing_builder.rs @@ -67,13 +67,15 @@ impl TestingAttesterSlashingBuilder { }; let add_signatures = |attestation: &mut SlashableAttestation| { + // All validators sign with a `false` custody bit. + let attestation_data_and_custody_bit = AttestationDataAndCustodyBit { + data: attestation.data.clone(), + custody_bit: false, + }; + let message = attestation_data_and_custody_bit.hash_tree_root(); + for (i, validator_index) in validator_indices.iter().enumerate() { attestation.custody_bitfield.set(i, false); - let attestation_data_and_custody_bit = AttestationDataAndCustodyBit { - data: attestation.data.clone(), - custody_bit: attestation.custody_bitfield.get(i).unwrap(), - }; - let message = attestation_data_and_custody_bit.hash_tree_root(); let signature = signer(*validator_index, &message[..], epoch, Domain::Attestation); attestation.aggregate_signature.add(&signature); } From 2d2ba6576b5b916449fb17f1e29d49764f690ac2 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 13 Mar 2019 11:24:46 +1100 Subject: [PATCH 072/144] Remove old, superseded benches --- .../beacon_chain/test_harness/Cargo.toml | 5 -- .../test_harness/benches/state_transition.rs | 69 ------------------- 2 files changed, 74 deletions(-) delete mode 100644 beacon_node/beacon_chain/test_harness/benches/state_transition.rs diff --git a/beacon_node/beacon_chain/test_harness/Cargo.toml b/beacon_node/beacon_chain/test_harness/Cargo.toml index 448934eb3f..50d1547328 100644 --- a/beacon_node/beacon_chain/test_harness/Cargo.toml +++ b/beacon_node/beacon_chain/test_harness/Cargo.toml @@ -12,12 +12,7 @@ path = "src/bin.rs" name = "test_harness" path = "src/lib.rs" -[[bench]] -name = "state_transition" -harness = false - [dev-dependencies] -criterion = "0.2" state_processing = { path = "../../../eth2/state_processing" } [dependencies] diff --git a/beacon_node/beacon_chain/test_harness/benches/state_transition.rs b/beacon_node/beacon_chain/test_harness/benches/state_transition.rs deleted file mode 100644 index 7d1c446534..0000000000 --- a/beacon_node/beacon_chain/test_harness/benches/state_transition.rs +++ /dev/null @@ -1,69 +0,0 @@ -use criterion::Criterion; -use criterion::{black_box, criterion_group, criterion_main, Benchmark}; -// use env_logger::{Builder, Env}; -use state_processing::SlotProcessable; -use test_harness::BeaconChainHarness; -use types::{ChainSpec, Hash256}; - -fn mid_epoch_state_transition(c: &mut Criterion) { - // Builder::from_env(Env::default().default_filter_or("debug")).init(); - - let validator_count = 1000; - let mut rig = BeaconChainHarness::new(ChainSpec::foundation(), validator_count); - - let epoch_depth = (rig.spec.slots_per_epoch * 2) + (rig.spec.slots_per_epoch / 2); - - for _ in 0..epoch_depth { - rig.advance_chain_with_block(); - } - - let state = rig.beacon_chain.state.read().clone(); - - assert!((state.slot + 1) % rig.spec.slots_per_epoch != 0); - - c.bench_function("mid-epoch state transition 10k validators", move |b| { - let state = state.clone(); - b.iter(|| { - let mut state = state.clone(); - black_box(state.per_slot_processing(Hash256::zero(), &rig.spec)) - }) - }); -} - -fn epoch_boundary_state_transition(c: &mut Criterion) { - // Builder::from_env(Env::default().default_filter_or("debug")).init(); - - let validator_count = 10000; - let mut rig = BeaconChainHarness::new(ChainSpec::foundation(), validator_count); - - let epoch_depth = rig.spec.slots_per_epoch * 2; - - for _ in 0..(epoch_depth - 1) { - rig.advance_chain_with_block(); - } - - let state = rig.beacon_chain.state.read().clone(); - - assert_eq!((state.slot + 1) % rig.spec.slots_per_epoch, 0); - - c.bench( - "routines", - Benchmark::new("routine_1", move |b| { - let state = state.clone(); - b.iter(|| { - let mut state = state.clone(); - black_box(black_box( - state.per_slot_processing(Hash256::zero(), &rig.spec), - )) - }) - }) - .sample_size(5), // sample size is low because function is sloooow. - ); -} - -criterion_group!( - benches, - mid_epoch_state_transition, - epoch_boundary_state_transition -); -criterion_main!(benches); From 6c4e457c8adf69266fe9088d3ed470302d204705 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 13 Mar 2019 11:25:17 +1100 Subject: [PATCH 073/144] Fix test_harness tests They were broken by changes to TestingBeaconStateBuilder and where the keypairs file is stored. --- .../test_harness/src/beacon_chain_harness.rs | 90 ++----------------- .../beacon_chain/test_harness/src/run_test.rs | 7 +- .../test_harness/src/test_case.rs | 4 +- .../beacon_chain/test_harness/tests/chain.rs | 4 +- 4 files changed, 10 insertions(+), 95 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index c442c05dbc..28723a2032 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -1,7 +1,6 @@ use super::ValidatorHarness; use beacon_chain::{BeaconChain, BlockProcessingOutcome}; pub use beacon_chain::{BeaconChainError, CheckPoint}; -use bls::get_withdrawal_credentials; use db::{ stores::{BeaconBlockStore, BeaconStateStore}, MemoryDB, @@ -12,11 +11,10 @@ use rayon::prelude::*; use slot_clock::TestingSlotClock; use ssz::TreeHash; use std::collections::HashSet; -use std::fs::File; use std::iter::FromIterator; use std::path::Path; use std::sync::Arc; -use types::{beacon_state::BeaconStateBuilder, test_utils::generate_deterministic_keypairs, *}; +use types::{test_utils::TestingBeaconStateBuilder, *}; mod generate_deposits; @@ -42,95 +40,17 @@ impl BeaconChainHarness { /// /// - A keypair, `BlockProducer` and `Attester` for each validator. /// - A new BeaconChain struct where the given validators are in the genesis. - pub fn new( - spec: ChainSpec, - validator_count: usize, - validators_dir: Option<&Path>, - skip_deposit_verification: bool, - ) -> Self { + pub fn new(spec: ChainSpec, validator_count: usize) -> Self { let db = Arc::new(MemoryDB::open()); let block_store = Arc::new(BeaconBlockStore::new(db.clone())); let state_store = Arc::new(BeaconStateStore::new(db.clone())); - let genesis_time = 1_549_935_547; // 12th Feb 2018 (arbitrary value in the past). let slot_clock = TestingSlotClock::new(spec.genesis_slot.as_u64()); let fork_choice = BitwiseLMDGhost::new(block_store.clone(), state_store.clone()); - let latest_eth1_data = Eth1Data { - deposit_root: Hash256::zero(), - block_hash: Hash256::zero(), - }; - let mut state_builder = BeaconStateBuilder::new(genesis_time, latest_eth1_data, &spec); + let state_builder = + TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, &spec); + let (genesis_state, keypairs) = state_builder.build(); - // If a `validators_dir` is specified, load the keypairs a YAML file. - // - // Otherwise, generate them deterministically where the first validator has a secret key of - // `1`, etc. - let keypairs = if let Some(path) = validators_dir { - debug!("Loading validator keypairs from file..."); - let keypairs_file = File::open(path.join("keypairs.yaml")).unwrap(); - let mut keypairs: Vec = serde_yaml::from_reader(&keypairs_file).unwrap(); - keypairs.truncate(validator_count); - keypairs - } else { - debug!("Generating validator keypairs..."); - generate_deterministic_keypairs(validator_count) - }; - - // Skipping deposit verification means directly generating `Validator` records, instead - // of generating `Deposit` objects, verifying them and converting them into `Validator` - // records. - // - // It is much faster to skip deposit verification, however it does not test the initial - // validator induction part of beacon chain genesis. - if skip_deposit_verification { - let validators = keypairs - .iter() - .map(|keypair| { - let withdrawal_credentials = Hash256::from_slice(&get_withdrawal_credentials( - &keypair.pk, - spec.bls_withdrawal_prefix_byte, - )); - - Validator { - pubkey: keypair.pk.clone(), - withdrawal_credentials, - activation_epoch: spec.far_future_epoch, - exit_epoch: spec.far_future_epoch, - withdrawable_epoch: spec.far_future_epoch, - initiated_exit: false, - slashed: false, - } - }) - .collect(); - - let balances = vec![32_000_000_000; validator_count]; - - state_builder.import_existing_validators( - validators, - balances, - validator_count as u64, - &spec, - ); - } else { - debug!("Generating initial validator deposits..."); - let deposits = generate_deposits_from_keypairs( - &keypairs, - genesis_time, - spec.get_domain( - spec.genesis_epoch, - Domain::Deposit, - &Fork { - previous_version: spec.genesis_fork_version, - current_version: spec.genesis_fork_version, - epoch: spec.genesis_epoch, - }, - ), - &spec, - ); - state_builder.process_initial_deposits(&deposits, &spec); - }; - - let genesis_state = state_builder.build(&spec).unwrap(); let state_root = Hash256::from_slice(&genesis_state.hash_tree_root()); let genesis_block = BeaconBlock::genesis(state_root, &spec); diff --git a/beacon_node/beacon_chain/test_harness/src/run_test.rs b/beacon_node/beacon_chain/test_harness/src/run_test.rs index d4e2e1cf20..4caa299d6b 100644 --- a/beacon_node/beacon_chain/test_harness/src/run_test.rs +++ b/beacon_node/beacon_chain/test_harness/src/run_test.rs @@ -1,6 +1,5 @@ use crate::test_case::TestCase; use clap::ArgMatches; -use std::path::Path; use std::{fs::File, io::prelude::*}; use yaml_rust::YamlLoader; @@ -17,10 +16,6 @@ pub fn run_test(matches: &ArgMatches) { }; for doc in &docs { - let validators_dir = matches - .value_of("validators_dir") - .and_then(|dir_str| Some(Path::new(dir_str))); - // For each `test_cases` YAML in the document, build a `TestCase`, execute it and // assert that the execution result matches the test_case description. // @@ -35,7 +30,7 @@ pub fn run_test(matches: &ArgMatches) { // panics with a message. for test_case in doc["test_cases"].as_vec().unwrap() { let test_case = TestCase::from_yaml(test_case); - test_case.assert_result_valid(test_case.execute(validators_dir)) + test_case.assert_result_valid(test_case.execute()) } } } diff --git a/beacon_node/beacon_chain/test_harness/src/test_case.rs b/beacon_node/beacon_chain/test_harness/src/test_case.rs index b6b1ea5cca..cee78f6c40 100644 --- a/beacon_node/beacon_chain/test_harness/src/test_case.rs +++ b/beacon_node/beacon_chain/test_harness/src/test_case.rs @@ -69,7 +69,7 @@ impl TestCase { /// Executes the test case, returning an `ExecutionResult`. #[allow(clippy::cyclomatic_complexity)] - pub fn execute(&self, validators_dir: Option<&Path>) -> ExecutionResult { + pub fn execute(&self) -> ExecutionResult { let spec = self.spec(); let validator_count = self.config.deposits_for_chain_start; let slots = self.config.num_slots; @@ -79,7 +79,7 @@ impl TestCase { validator_count ); - let mut harness = BeaconChainHarness::new(spec, validator_count, validators_dir, true); + let mut harness = BeaconChainHarness::new(spec, validator_count); info!("Starting simulation across {} slots...", slots); diff --git a/beacon_node/beacon_chain/test_harness/tests/chain.rs b/beacon_node/beacon_chain/test_harness/tests/chain.rs index e5a52a314a..e72c3a5aa5 100644 --- a/beacon_node/beacon_chain/test_harness/tests/chain.rs +++ b/beacon_node/beacon_chain/test_harness/tests/chain.rs @@ -10,7 +10,7 @@ fn it_can_build_on_genesis_block() { let spec = ChainSpec::few_validators(); let validator_count = 8; - let mut harness = BeaconChainHarness::new(spec, validator_count as usize, None, true); + let mut harness = BeaconChainHarness::new(spec, validator_count as usize); harness.advance_chain_with_block(); } @@ -25,7 +25,7 @@ fn it_can_produce_past_first_epoch_boundary() { debug!("Starting harness build..."); - let mut harness = BeaconChainHarness::new(spec, validator_count, None, true); + let mut harness = BeaconChainHarness::new(spec, validator_count); debug!("Harness built, tests starting.."); From 6101036c8ef5f9d126b588b428679f16a1b7da89 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Tue, 5 Mar 2019 17:28:51 +1100 Subject: [PATCH 074/144] Re-apply removal of duplicated ssz_tests Also, re-apply: "use cfg(test) for test macros" --- eth2/types/src/attestation.rs | 25 +------- eth2/types/src/attestation_data.rs | 25 +------- .../src/attestation_data_and_custody_bit.rs | 27 +------- eth2/types/src/attester_slashing.rs | 25 +------- eth2/types/src/beacon_block.rs | 25 +------- eth2/types/src/beacon_block_body.rs | 25 +------- eth2/types/src/beacon_state/tests.rs | 24 +------ eth2/types/src/crosslink.rs | 25 +------- eth2/types/src/deposit.rs | 25 +------- eth2/types/src/deposit_data.rs | 25 +------- eth2/types/src/deposit_input.rs | 25 +------- eth2/types/src/eth1_data.rs | 25 +------- eth2/types/src/eth1_data_vote.rs | 25 +------- eth2/types/src/fork.rs | 25 +------- eth2/types/src/lib.rs | 1 + eth2/types/src/pending_attestation.rs | 25 +------- eth2/types/src/proposal.rs | 26 +------- eth2/types/src/proposer_slashing.rs | 25 +------- eth2/types/src/shard_reassignment_record.rs | 25 +------- eth2/types/src/slashable_attestation.rs | 24 +------ eth2/types/src/slot_epoch.rs | 4 -- eth2/types/src/slot_epoch_macros.rs | 38 ++--------- eth2/types/src/slot_height.rs | 3 - eth2/types/src/test_utils/mod.rs | 63 ++++++++++++++++--- eth2/types/src/transfer.rs | 25 +------- eth2/types/src/validator.rs | 24 +------ eth2/types/src/voluntary_exit.rs | 25 +------- 27 files changed, 83 insertions(+), 576 deletions(-) diff --git a/eth2/types/src/attestation.rs b/eth2/types/src/attestation.rs index 67bff3d209..dcc4c1fda8 100644 --- a/eth2/types/src/attestation.rs +++ b/eth2/types/src/attestation.rs @@ -20,29 +20,6 @@ pub struct Attestation { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, TreeHash}; - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = Attestation::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = Attestation::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + ssz_tests!(Attestation); } diff --git a/eth2/types/src/attestation_data.rs b/eth2/types/src/attestation_data.rs index 5899ab52d4..6e3cb3891e 100644 --- a/eth2/types/src/attestation_data.rs +++ b/eth2/types/src/attestation_data.rs @@ -38,29 +38,6 @@ impl Eq for AttestationData {} #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, TreeHash}; - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = AttestationData::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = AttestationData::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + ssz_tests!(AttestationData); } diff --git a/eth2/types/src/attestation_data_and_custody_bit.rs b/eth2/types/src/attestation_data_and_custody_bit.rs index e9cf4bb672..020b07d28e 100644 --- a/eth2/types/src/attestation_data_and_custody_bit.rs +++ b/eth2/types/src/attestation_data_and_custody_bit.rs @@ -25,31 +25,6 @@ impl TestRandom for AttestationDataAndCustodyBit { #[cfg(test)] mod test { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, TreeHash}; - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - - let original = AttestationDataAndCustodyBit::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = AttestationDataAndCustodyBit::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + ssz_tests!(AttestationDataAndCustodyBit); } diff --git a/eth2/types/src/attester_slashing.rs b/eth2/types/src/attester_slashing.rs index 406e09f29f..f437d41f24 100644 --- a/eth2/types/src/attester_slashing.rs +++ b/eth2/types/src/attester_slashing.rs @@ -16,29 +16,6 @@ pub struct AttesterSlashing { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, TreeHash}; - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = AttesterSlashing::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = AttesterSlashing::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + ssz_tests!(AttesterSlashing); } diff --git a/eth2/types/src/beacon_block.rs b/eth2/types/src/beacon_block.rs index 0274539baf..615d9f9288 100644 --- a/eth2/types/src/beacon_block.rs +++ b/eth2/types/src/beacon_block.rs @@ -70,29 +70,6 @@ impl BeaconBlock { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, TreeHash}; - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = BeaconBlock::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = BeaconBlock::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + ssz_tests!(BeaconBlock); } diff --git a/eth2/types/src/beacon_block_body.rs b/eth2/types/src/beacon_block_body.rs index 2a43f289ce..70ce24dbe8 100644 --- a/eth2/types/src/beacon_block_body.rs +++ b/eth2/types/src/beacon_block_body.rs @@ -21,29 +21,6 @@ pub struct BeaconBlockBody { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, TreeHash}; - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = BeaconBlockBody::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = BeaconBlockBody::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + ssz_tests!(BeaconBlockBody); } diff --git a/eth2/types/src/beacon_state/tests.rs b/eth2/types/src/beacon_state/tests.rs index 78f2f573ef..1e1a555fd6 100644 --- a/eth2/types/src/beacon_state/tests.rs +++ b/eth2/types/src/beacon_state/tests.rs @@ -4,7 +4,6 @@ use super::*; use crate::test_utils::TestingBeaconStateBuilder; use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; use crate::{BeaconState, ChainSpec}; -use ssz::{ssz_encode, Decodable}; /// Tests that `get_attestation_participants` is consistent with the result of /// get_crosslink_committees_at_slot` with a full bitfield. @@ -51,25 +50,4 @@ pub fn get_attestation_participants_consistency() { } } -#[test] -pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = BeaconState::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); -} - -#[test] -pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = BeaconState::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 -} +ssz_tests!(BeaconState); diff --git a/eth2/types/src/crosslink.rs b/eth2/types/src/crosslink.rs index ed31a80d5d..5db5e20a60 100644 --- a/eth2/types/src/crosslink.rs +++ b/eth2/types/src/crosslink.rs @@ -19,29 +19,6 @@ pub struct Crosslink { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, TreeHash}; - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = Crosslink::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = Crosslink::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + ssz_tests!(Crosslink); } diff --git a/eth2/types/src/deposit.rs b/eth2/types/src/deposit.rs index 0b0d0cc644..14eb19ad69 100644 --- a/eth2/types/src/deposit.rs +++ b/eth2/types/src/deposit.rs @@ -18,29 +18,6 @@ pub struct Deposit { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, TreeHash}; - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = Deposit::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = Deposit::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + ssz_tests!(Deposit); } diff --git a/eth2/types/src/deposit_data.rs b/eth2/types/src/deposit_data.rs index b2fe99fda6..9d6c1bda73 100644 --- a/eth2/types/src/deposit_data.rs +++ b/eth2/types/src/deposit_data.rs @@ -18,29 +18,6 @@ pub struct DepositData { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, TreeHash}; - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = DepositData::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = DepositData::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + ssz_tests!(DepositData); } diff --git a/eth2/types/src/deposit_input.rs b/eth2/types/src/deposit_input.rs index 966c9fad06..9a90319017 100644 --- a/eth2/types/src/deposit_input.rs +++ b/eth2/types/src/deposit_input.rs @@ -66,29 +66,6 @@ impl DepositInput { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, TreeHash}; - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = DepositInput::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = DepositInput::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + ssz_tests!(DepositInput); } diff --git a/eth2/types/src/eth1_data.rs b/eth2/types/src/eth1_data.rs index 88f91e3a92..c4b2b1894f 100644 --- a/eth2/types/src/eth1_data.rs +++ b/eth2/types/src/eth1_data.rs @@ -17,29 +17,6 @@ pub struct Eth1Data { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, TreeHash}; - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = Eth1Data::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = Eth1Data::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + ssz_tests!(Eth1Data); } diff --git a/eth2/types/src/eth1_data_vote.rs b/eth2/types/src/eth1_data_vote.rs index bd8266ce37..4788833bd3 100644 --- a/eth2/types/src/eth1_data_vote.rs +++ b/eth2/types/src/eth1_data_vote.rs @@ -17,29 +17,6 @@ pub struct Eth1DataVote { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, TreeHash}; - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = Eth1DataVote::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = Eth1DataVote::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + ssz_tests!(Eth1DataVote); } diff --git a/eth2/types/src/fork.rs b/eth2/types/src/fork.rs index 9cf6ae3960..f3b62f5a8d 100644 --- a/eth2/types/src/fork.rs +++ b/eth2/types/src/fork.rs @@ -29,29 +29,6 @@ impl Fork { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, TreeHash}; - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = Fork::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = Fork::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + ssz_tests!(Fork); } diff --git a/eth2/types/src/lib.rs b/eth2/types/src/lib.rs index 76fcb43edd..e7be732ebe 100644 --- a/eth2/types/src/lib.rs +++ b/eth2/types/src/lib.rs @@ -1,3 +1,4 @@ +#[macro_use] pub mod test_utils; pub mod attestation; diff --git a/eth2/types/src/pending_attestation.rs b/eth2/types/src/pending_attestation.rs index c1293546e2..68dd1c3451 100644 --- a/eth2/types/src/pending_attestation.rs +++ b/eth2/types/src/pending_attestation.rs @@ -19,29 +19,6 @@ pub struct PendingAttestation { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, TreeHash}; - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = PendingAttestation::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = PendingAttestation::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + ssz_tests!(PendingAttestation); } diff --git a/eth2/types/src/proposal.rs b/eth2/types/src/proposal.rs index dda544a19c..59d6370e1d 100644 --- a/eth2/types/src/proposal.rs +++ b/eth2/types/src/proposal.rs @@ -23,30 +23,7 @@ pub struct Proposal { mod tests { use super::*; use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, SignedRoot, TreeHash}; - - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = Proposal::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = Proposal::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + use ssz::{SignedRoot, TreeHash}; #[derive(TreeHash)] struct SignedProposal { @@ -75,4 +52,5 @@ mod tests { assert_eq!(original.signed_root(), other.hash_tree_root()); } + ssz_tests!(Proposal); } diff --git a/eth2/types/src/proposer_slashing.rs b/eth2/types/src/proposer_slashing.rs index ddd951ae14..26c3d67a77 100644 --- a/eth2/types/src/proposer_slashing.rs +++ b/eth2/types/src/proposer_slashing.rs @@ -18,29 +18,6 @@ pub struct ProposerSlashing { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, TreeHash}; - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = ProposerSlashing::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = ProposerSlashing::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + ssz_tests!(ProposerSlashing); } diff --git a/eth2/types/src/shard_reassignment_record.rs b/eth2/types/src/shard_reassignment_record.rs index d8595b69d7..9f1705f163 100644 --- a/eth2/types/src/shard_reassignment_record.rs +++ b/eth2/types/src/shard_reassignment_record.rs @@ -14,29 +14,6 @@ pub struct ShardReassignmentRecord { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, TreeHash}; - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = ShardReassignmentRecord::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = ShardReassignmentRecord::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + ssz_tests!(ShardReassignmentRecord); } diff --git a/eth2/types/src/slashable_attestation.rs b/eth2/types/src/slashable_attestation.rs index 2c4bde8db4..56c9dfc2f6 100644 --- a/eth2/types/src/slashable_attestation.rs +++ b/eth2/types/src/slashable_attestation.rs @@ -46,7 +46,6 @@ mod tests { use crate::chain_spec::ChainSpec; use crate::slot_epoch::{Epoch, Slot}; use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, TreeHash}; #[test] pub fn test_is_double_vote_true() { @@ -120,28 +119,7 @@ mod tests { ); } - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = SlashableAttestation::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = SlashableAttestation::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + ssz_tests!(SlashableAttestation); fn create_slashable_attestation( slot_factor: u64, diff --git a/eth2/types/src/slot_epoch.rs b/eth2/types/src/slot_epoch.rs index 2af7f5196c..c40b6badfc 100644 --- a/eth2/types/src/slot_epoch.rs +++ b/eth2/types/src/slot_epoch.rs @@ -103,8 +103,6 @@ impl<'a> Iterator for SlotIter<'a> { #[cfg(test)] mod slot_tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::ssz_encode; all_tests!(Slot); } @@ -112,8 +110,6 @@ mod slot_tests { #[cfg(test)] mod epoch_tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::ssz_encode; all_tests!(Epoch); diff --git a/eth2/types/src/slot_epoch_macros.rs b/eth2/types/src/slot_epoch_macros.rs index 4b2332bafc..300ad3f6f4 100644 --- a/eth2/types/src/slot_epoch_macros.rs +++ b/eth2/types/src/slot_epoch_macros.rs @@ -248,7 +248,7 @@ macro_rules! impl_common { } // test macros -#[allow(unused_macros)] +#[cfg(test)] macro_rules! new_tests { ($type: ident) => { #[test] @@ -260,7 +260,7 @@ macro_rules! new_tests { }; } -#[allow(unused_macros)] +#[cfg(test)] macro_rules! from_into_tests { ($type: ident, $other: ident) => { #[test] @@ -286,7 +286,7 @@ macro_rules! from_into_tests { }; } -#[allow(unused_macros)] +#[cfg(test)] macro_rules! math_between_tests { ($type: ident, $other: ident) => { #[test] @@ -434,7 +434,7 @@ macro_rules! math_between_tests { }; } -#[allow(unused_macros)] +#[cfg(test)] macro_rules! math_tests { ($type: ident) => { #[test] @@ -528,35 +528,7 @@ macro_rules! math_tests { }; } -#[allow(unused_macros)] -macro_rules! ssz_tests { - ($type: ident) => { - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = $type::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = $type::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = $type::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } - }; -} - -#[allow(unused_macros)] +#[cfg(test)] macro_rules! all_tests { ($type: ident) => { new_tests!($type); diff --git a/eth2/types/src/slot_height.rs b/eth2/types/src/slot_height.rs index 1739227a40..4a783d4a06 100644 --- a/eth2/types/src/slot_height.rs +++ b/eth2/types/src/slot_height.rs @@ -33,11 +33,8 @@ impl SlotHeight { } #[cfg(test)] - mod slot_height_tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::ssz_encode; all_tests!(SlotHeight); } diff --git a/eth2/types/src/test_utils/mod.rs b/eth2/types/src/test_utils/mod.rs index 6fdbe53ad7..4b435805ca 100644 --- a/eth2/types/src/test_utils/mod.rs +++ b/eth2/types/src/test_utils/mod.rs @@ -13,12 +13,57 @@ mod testing_voluntary_exit_builder; pub use generate_deterministic_keypairs::generate_deterministic_keypairs; pub use keypairs_file::KeypairsFile; pub use rand::{prng::XorShiftRng, SeedableRng}; -pub use test_random::TestRandom; -pub use testing_attestation_builder::TestingAttestationBuilder; -pub use testing_attester_slashing_builder::TestingAttesterSlashingBuilder; -pub use testing_beacon_block_builder::TestingBeaconBlockBuilder; -pub use testing_beacon_state_builder::{keypairs_path, TestingBeaconStateBuilder}; -pub use testing_deposit_builder::TestingDepositBuilder; -pub use testing_proposer_slashing_builder::TestingProposerSlashingBuilder; -pub use testing_transfer_builder::TestingTransferBuilder; -pub use testing_voluntary_exit_builder::TestingVoluntaryExitBuilder; + +pub mod address; +pub mod aggregate_signature; +pub mod bitfield; +pub mod hash256; +#[macro_use] +mod macros; +pub mod public_key; +pub mod secret_key; +pub mod signature; + +pub trait TestRandom +where + T: RngCore, +{ + fn random_for_test(rng: &mut T) -> Self; +} + +impl TestRandom for bool { + fn random_for_test(rng: &mut T) -> Self { + (rng.next_u32() % 2) == 1 + } +} + +impl TestRandom for u64 { + fn random_for_test(rng: &mut T) -> Self { + rng.next_u64() + } +} + +impl TestRandom for u32 { + fn random_for_test(rng: &mut T) -> Self { + rng.next_u32() + } +} + +impl TestRandom for usize { + fn random_for_test(rng: &mut T) -> Self { + rng.next_u32() as usize + } +} + +impl TestRandom for Vec +where + U: TestRandom, +{ + fn random_for_test(rng: &mut T) -> Self { + vec![ + ::random_for_test(rng), + ::random_for_test(rng), + ::random_for_test(rng), + ] + } +} diff --git a/eth2/types/src/transfer.rs b/eth2/types/src/transfer.rs index b3c283fa2d..af3b18ef42 100644 --- a/eth2/types/src/transfer.rs +++ b/eth2/types/src/transfer.rs @@ -24,29 +24,6 @@ pub struct Transfer { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, TreeHash}; - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = Transfer::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = Transfer::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + ssz_tests!(Transfer); } diff --git a/eth2/types/src/validator.rs b/eth2/types/src/validator.rs index 8901dcd81f..6d1936bfd3 100644 --- a/eth2/types/src/validator.rs +++ b/eth2/types/src/validator.rs @@ -54,18 +54,6 @@ impl Default for Validator { mod tests { use super::*; use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, TreeHash}; - - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = Validator::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } #[test] fn test_validator_can_be_active() { @@ -90,15 +78,5 @@ mod tests { } } - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = Validator::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + ssz_tests!(Validator); } diff --git a/eth2/types/src/voluntary_exit.rs b/eth2/types/src/voluntary_exit.rs index 36b5597f07..38630a0575 100644 --- a/eth2/types/src/voluntary_exit.rs +++ b/eth2/types/src/voluntary_exit.rs @@ -19,29 +19,6 @@ pub struct VoluntaryExit { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{ssz_encode, Decodable, TreeHash}; - #[test] - pub fn test_ssz_round_trip() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = VoluntaryExit::random_for_test(&mut rng); - - let bytes = ssz_encode(&original); - let (decoded, _) = <_>::ssz_decode(&bytes, 0).unwrap(); - - assert_eq!(original, decoded); - } - - #[test] - pub fn test_hash_tree_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = VoluntaryExit::random_for_test(&mut rng); - - let result = original.hash_tree_root(); - - assert_eq!(result.len(), 32); - // TODO: Add further tests - // https://github.com/sigp/lighthouse/issues/170 - } + ssz_tests!(VoluntaryExit); } From f68f52e206abaedb1819fbd54e3d4cb542924c59 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 13 Mar 2019 12:22:15 +1100 Subject: [PATCH 075/144] Fix issues with previous cherry pick --- eth2/types/src/test_utils/mod.rs | 65 ++++++-------------------------- 1 file changed, 11 insertions(+), 54 deletions(-) diff --git a/eth2/types/src/test_utils/mod.rs b/eth2/types/src/test_utils/mod.rs index 4b435805ca..9d04d1ca7c 100644 --- a/eth2/types/src/test_utils/mod.rs +++ b/eth2/types/src/test_utils/mod.rs @@ -1,3 +1,5 @@ +#[macro_use] +mod macros; mod generate_deterministic_keypairs; mod keypairs_file; mod test_random; @@ -13,57 +15,12 @@ mod testing_voluntary_exit_builder; pub use generate_deterministic_keypairs::generate_deterministic_keypairs; pub use keypairs_file::KeypairsFile; pub use rand::{prng::XorShiftRng, SeedableRng}; - -pub mod address; -pub mod aggregate_signature; -pub mod bitfield; -pub mod hash256; -#[macro_use] -mod macros; -pub mod public_key; -pub mod secret_key; -pub mod signature; - -pub trait TestRandom -where - T: RngCore, -{ - fn random_for_test(rng: &mut T) -> Self; -} - -impl TestRandom for bool { - fn random_for_test(rng: &mut T) -> Self { - (rng.next_u32() % 2) == 1 - } -} - -impl TestRandom for u64 { - fn random_for_test(rng: &mut T) -> Self { - rng.next_u64() - } -} - -impl TestRandom for u32 { - fn random_for_test(rng: &mut T) -> Self { - rng.next_u32() - } -} - -impl TestRandom for usize { - fn random_for_test(rng: &mut T) -> Self { - rng.next_u32() as usize - } -} - -impl TestRandom for Vec -where - U: TestRandom, -{ - fn random_for_test(rng: &mut T) -> Self { - vec![ - ::random_for_test(rng), - ::random_for_test(rng), - ::random_for_test(rng), - ] - } -} +pub use test_random::TestRandom; +pub use testing_attestation_builder::TestingAttestationBuilder; +pub use testing_attester_slashing_builder::TestingAttesterSlashingBuilder; +pub use testing_beacon_block_builder::TestingBeaconBlockBuilder; +pub use testing_beacon_state_builder::{keypairs_path, TestingBeaconStateBuilder}; +pub use testing_deposit_builder::TestingDepositBuilder; +pub use testing_proposer_slashing_builder::TestingProposerSlashingBuilder; +pub use testing_transfer_builder::TestingTransferBuilder; +pub use testing_voluntary_exit_builder::TestingVoluntaryExitBuilder; From bfa2e71b468e34eedcf847076aa29f3a53038bfd Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 13 Mar 2019 14:41:43 +1100 Subject: [PATCH 076/144] Move `PublicKey` to store uncompressed bytes. This is an optimisation that allows for faster hashing of a public key, however it adds a penalty to SSZ encoding because we need to go decompressed -> PublicKey -> compressed. The spec presently uses compressed bytes to store public keys, however I'm hoping it will change. --- eth2/utils/bls/src/aggregate_public_key.rs | 2 +- eth2/utils/bls/src/public_key.rs | 56 ++++++++++++++++------ eth2/utils/bls/src/signature.rs | 4 +- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/eth2/utils/bls/src/aggregate_public_key.rs b/eth2/utils/bls/src/aggregate_public_key.rs index 2174a43cb0..47c95d6c9c 100644 --- a/eth2/utils/bls/src/aggregate_public_key.rs +++ b/eth2/utils/bls/src/aggregate_public_key.rs @@ -14,7 +14,7 @@ impl AggregatePublicKey { } pub fn add(&mut self, public_key: &PublicKey) { - self.0.add(public_key.as_raw()) + self.0.add(&public_key.as_raw()) } /// Returns the underlying signature. diff --git a/eth2/utils/bls/src/public_key.rs b/eth2/utils/bls/src/public_key.rs index c85760bbf0..3e2bff19e2 100644 --- a/eth2/utils/bls/src/public_key.rs +++ b/eth2/utils/bls/src/public_key.rs @@ -10,39 +10,58 @@ use ssz::{ use std::default; use std::hash::{Hash, Hasher}; -/// A single BLS signature. +/// A single BLS public key. +/// +/// This struct stores an uncompressed public key as a byte vec. The reason we store bytes instead +/// of the `RawPublicKey` struct is because it allows for building a hashmap of `PublicKey` much +/// faster. +/// +/// Storing as uncompressed bytes costs ~0.02% more time when adding a `PublicKey` to an +/// `AggregateKey`, however it saves ~0.5ms each time you need to add a pubkey to a hashmap. /// /// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ /// serialization). #[derive(Debug, Clone, Eq)] -pub struct PublicKey(RawPublicKey); +pub struct PublicKey { + bytes: Vec, +} impl PublicKey { pub fn from_secret_key(secret_key: &SecretKey) -> Self { - PublicKey(RawPublicKey::from_secret_key(secret_key.as_raw())) + let mut raw_key = RawPublicKey::from_secret_key(secret_key.as_raw()); + let uncompressed_bytes = raw_key.as_uncompressed_bytes(); + Self { + bytes: uncompressed_bytes, + } } /// Returns the underlying signature. - pub fn as_raw(&self) -> &RawPublicKey { - &self.0 + pub fn as_raw(&self) -> RawPublicKey { + RawPublicKey::from_uncompressed_bytes(&self.bytes).expect("PublicKey in invalid state") } /// Converts compressed bytes to PublicKey pub fn from_bytes(bytes: &[u8]) -> Result { - let pubkey = RawPublicKey::from_bytes(&bytes).map_err(|_| DecodeError::Invalid)?; - Ok(PublicKey(pubkey)) + let mut pubkey = RawPublicKey::from_bytes(&bytes).map_err(|_| DecodeError::Invalid)?; + Ok(Self { + bytes: pubkey.as_uncompressed_bytes(), + }) } /// Returns the PublicKey as (x, y) bytes pub fn as_uncompressed_bytes(&self) -> Vec { - RawPublicKey::as_uncompressed_bytes(&mut self.0.clone()) + self.bytes.clone() } /// Converts (x, y) bytes to PublicKey pub fn from_uncompressed_bytes(bytes: &[u8]) -> Result { - let pubkey = + // Do a conversion to check the bytes are valid. + let _pubkey = RawPublicKey::from_uncompressed_bytes(&bytes).map_err(|_| DecodeError::Invalid)?; - Ok(PublicKey(pubkey)) + + Ok(Self { + bytes: bytes.to_vec(), + }) } /// Returns the last 6 bytes of the SSZ encoding of the public key, as a hex string. @@ -64,15 +83,22 @@ impl default::Default for PublicKey { impl Encodable for PublicKey { fn ssz_append(&self, s: &mut SszStream) { - s.append_vec(&self.0.as_bytes()); + s.append_vec(&self.as_raw().as_bytes()); } } impl Decodable for PublicKey { fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { let (sig_bytes, i) = decode_ssz_list(bytes, i)?; - let raw_sig = RawPublicKey::from_bytes(&sig_bytes).map_err(|_| DecodeError::TooShort)?; - Ok((PublicKey(raw_sig), i)) + let mut raw_sig = + RawPublicKey::from_bytes(&sig_bytes).map_err(|_| DecodeError::TooShort)?; + + Ok(( + Self { + bytes: raw_sig.as_uncompressed_bytes(), + }, + i, + )) } } @@ -99,7 +125,7 @@ impl<'de> Deserialize<'de> for PublicKey { impl TreeHash for PublicKey { fn hash_tree_root(&self) -> Vec { - hash(&self.0.as_bytes()) + hash(&self.as_raw().as_bytes()) } } @@ -117,7 +143,7 @@ impl Hash for PublicKey { /// /// Use `ssz::Encode` to obtain the bytes required for consensus hashing. fn hash(&self, state: &mut H) { - self.as_uncompressed_bytes().hash(state) + self.bytes.hash(state) } } diff --git a/eth2/utils/bls/src/signature.rs b/eth2/utils/bls/src/signature.rs index 47598bc66d..47e1dad2eb 100644 --- a/eth2/utils/bls/src/signature.rs +++ b/eth2/utils/bls/src/signature.rs @@ -33,7 +33,7 @@ impl Signature { /// Verify the Signature against a PublicKey. pub fn verify(&self, msg: &[u8], domain: u64, pk: &PublicKey) -> bool { - self.0.verify(msg, domain, pk.as_raw()) + self.0.verify(msg, domain, &pk.as_raw()) } /// Verify the Signature against a PublicKey, where the message has already been hashed. @@ -44,7 +44,7 @@ impl Signature { pk: &PublicKey, ) -> bool { self.0 - .verify_hashed(x_real_hashed, x_imaginary_hashed, pk.as_raw()) + .verify_hashed(x_real_hashed, x_imaginary_hashed, &pk.as_raw()) } /// Returns the underlying signature. From 6cd3c4bd1a6531009a16a2a1894ff7c70636d5d8 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 13 Mar 2019 16:40:28 +1100 Subject: [PATCH 077/144] Add a cache for public keys to BeaconState This allows for a fast lookup of "is this public key already in the validator registry". --- eth2/state_processing/benches/benches.rs | 5 +- .../src/per_block_processing.rs | 9 ++-- .../src/per_block_processing/errors.rs | 5 +- .../per_block_processing/verify_deposit.rs | 7 +-- eth2/types/src/beacon_state.rs | 46 +++++++++++++++++++ eth2/types/src/beacon_state/pubkey_cache.rs | 45 ++++++++++++++++++ eth2/types/src/lib.rs | 3 +- .../testing_beacon_state_builder.rs | 2 + 8 files changed, 109 insertions(+), 13 deletions(-) create mode 100644 eth2/types/src/beacon_state/pubkey_cache.rs diff --git a/eth2/state_processing/benches/benches.rs b/eth2/state_processing/benches/benches.rs index ad8c4f714f..4de97a298e 100644 --- a/eth2/state_processing/benches/benches.rs +++ b/eth2/state_processing/benches/benches.rs @@ -1,14 +1,11 @@ -use criterion::Benchmark; use criterion::Criterion; use criterion::{criterion_group, criterion_main}; use env_logger::{Builder, Env}; -use types::test_utils::TestingBeaconStateBuilder; -use types::*; mod bench_block_processing; mod bench_epoch_processing; -pub const VALIDATOR_COUNT: usize = 300_032; +pub const VALIDATOR_COUNT: usize = 16_384; // `LOG_LEVEL == "debug"` gives logs, but they're very noisy and slow down benching. pub const LOG_LEVEL: &str = ""; diff --git a/eth2/state_processing/src/per_block_processing.rs b/eth2/state_processing/src/per_block_processing.rs index 7b5aafa7f9..13a47836b5 100644 --- a/eth2/state_processing/src/per_block_processing.rs +++ b/eth2/state_processing/src/per_block_processing.rs @@ -373,19 +373,20 @@ pub fn process_deposits( .map_err(|e| e.into_with_index(i)) })?; - let public_key_to_index_hashmap = build_public_key_hashmap(&state); - // Check `state.deposit_index` and update the state in series. for (i, deposit) in deposits.iter().enumerate() { verify_deposit_index(state, deposit).map_err(|e| e.into_with_index(i))?; + // Ensure the state's pubkey cache is fully up-to-date, it will be used to check to see if the + // depositing validator already exists in the registry. + state.update_pubkey_cache()?; + // Get an `Option` where `u64` is the validator index if this deposit public key // already exists in the beacon_state. // // This function also verifies the withdrawal credentials. let validator_index = - get_existing_validator_index(state, deposit, &public_key_to_index_hashmap) - .map_err(|e| e.into_with_index(i))?; + get_existing_validator_index(state, deposit).map_err(|e| e.into_with_index(i))?; let deposit_data = &deposit.deposit_data; let deposit_input = &deposit.deposit_data.deposit_input; diff --git a/eth2/state_processing/src/per_block_processing/errors.rs b/eth2/state_processing/src/per_block_processing/errors.rs index a3e3ebad1a..8366a6584f 100644 --- a/eth2/state_processing/src/per_block_processing/errors.rs +++ b/eth2/state_processing/src/per_block_processing/errors.rs @@ -294,6 +294,8 @@ impl_into_with_index_without_beacon_error!( pub enum DepositValidationError { /// Validation completed successfully and the object is invalid. Invalid(DepositInvalid), + /// Encountered a `BeaconStateError` whilst attempting to determine validity. + BeaconStateError(BeaconStateError), } /// Describes why an object is invalid. @@ -313,7 +315,8 @@ pub enum DepositInvalid { BadMerkleProof, } -impl_into_with_index_without_beacon_error!(DepositValidationError, DepositInvalid); +impl_from_beacon_state_error!(DepositValidationError); +impl_into_with_index_with_beacon_error!(DepositValidationError, DepositInvalid); /* * `Exit` Validation diff --git a/eth2/state_processing/src/per_block_processing/verify_deposit.rs b/eth2/state_processing/src/per_block_processing/verify_deposit.rs index 1aabbb973f..aad38f616c 100644 --- a/eth2/state_processing/src/per_block_processing/verify_deposit.rs +++ b/eth2/state_processing/src/per_block_processing/verify_deposit.rs @@ -72,11 +72,12 @@ pub fn build_public_key_hashmap(state: &BeaconState) -> PublicKeyValidatorIndexH pub fn get_existing_validator_index( state: &BeaconState, deposit: &Deposit, - pubkey_map: &HashMap, ) -> Result, Error> { let deposit_input = &deposit.deposit_data.deposit_input; - let validator_index = pubkey_map.get(&deposit_input.pubkey).and_then(|i| Some(*i)); + let validator_index = state + .get_validator_index(&deposit_input.pubkey)? + .and_then(|i| Some(i)); match validator_index { None => Ok(None), @@ -86,7 +87,7 @@ pub fn get_existing_validator_index( == state.validator_registry[index as usize].withdrawal_credentials, Invalid::BadWithdrawalCredentials ); - Ok(Some(index)) + Ok(Some(index as u64)) } } } diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 878d13b860..fc410fe641 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -5,6 +5,7 @@ use helpers::*; use honey_badger_split::SplitExt; use int_to_bytes::int_to_bytes32; use log::{debug, error, trace}; +use pubkey_cache::PubkeyCache; use rand::RngCore; use serde_derive::Serialize; use ssz::{hash, Decodable, DecodeError, Encodable, SignedRoot, SszStream, TreeHash}; @@ -16,6 +17,7 @@ pub use builder::BeaconStateBuilder; mod builder; mod epoch_cache; pub mod helpers; +mod pubkey_cache; mod tests; pub type Committee = Vec; @@ -52,6 +54,11 @@ pub enum Error { InsufficientAttestations, InsufficientCommittees, EpochCacheUninitialized(RelativeEpoch), + PubkeyCacheInconsistent, + PubkeyCacheIncomplete { + cache_len: usize, + registry_len: usize, + }, } macro_rules! safe_add_assign { @@ -108,6 +115,7 @@ pub struct BeaconState { // Caching (not in the spec) pub cache_index_offset: usize, pub caches: Vec, + pub pubkey_cache: PubkeyCache, } impl BeaconState { @@ -186,6 +194,7 @@ impl BeaconState { */ cache_index_offset: 0, caches: vec![EpochCache::empty(); CACHED_EPOCHS], + pubkey_cache: PubkeyCache::empty(), } } @@ -293,6 +302,41 @@ impl BeaconState { } } + /// Updates the pubkey cache, if required. + /// + /// Adds all `pubkeys` from the `validator_registry` which are not already in the cache. Will + /// never re-add a pubkey. + pub fn update_pubkey_cache(&mut self) -> Result<(), Error> { + for (i, validator) in self + .validator_registry + .iter() + .enumerate() + .skip(self.pubkey_cache.len()) + { + let success = self.pubkey_cache.insert(validator.pubkey.clone(), i); + if !success { + return Err(Error::PubkeyCacheInconsistent); + } + } + + Ok(()) + } + + /// If a validator pubkey exists in the validator registry, returns `Some(i)`, otherwise + /// returns `None`. + /// + /// Requires a fully up-to-date `pubkey_cache`, returns an error if this is not the case. + pub fn get_validator_index(&self, pubkey: &PublicKey) -> Result, Error> { + if self.pubkey_cache.len() == self.validator_registry.len() { + Ok(self.pubkey_cache.get(pubkey)) + } else { + Err(Error::PubkeyCacheIncomplete { + cache_len: self.pubkey_cache.len(), + registry_len: self.validator_registry.len(), + }) + } + } + /// The epoch corresponding to `self.slot`. /// /// Spec v0.4.0 @@ -1188,6 +1232,7 @@ impl Decodable for BeaconState { deposit_index, cache_index_offset: 0, caches: vec![EpochCache::empty(); CACHED_EPOCHS], + pubkey_cache: PubkeyCache::empty(), }, i, )) @@ -1258,6 +1303,7 @@ impl TestRandom for BeaconState { deposit_index: <_>::random_for_test(rng), cache_index_offset: 0, caches: vec![EpochCache::empty(); CACHED_EPOCHS], + pubkey_cache: PubkeyCache::empty(), } } } diff --git a/eth2/types/src/beacon_state/pubkey_cache.rs b/eth2/types/src/beacon_state/pubkey_cache.rs new file mode 100644 index 0000000000..c051475792 --- /dev/null +++ b/eth2/types/src/beacon_state/pubkey_cache.rs @@ -0,0 +1,45 @@ +use crate::*; +use serde_derive::Serialize; +use std::collections::HashMap; + +type ValidatorIndex = usize; + +#[derive(Debug, PartialEq, Clone, Default, Serialize)] +pub struct PubkeyCache { + map: HashMap, +} + +impl PubkeyCache { + /// Instantiates a new, empty cache. + pub fn empty() -> Self { + Self { + map: HashMap::new(), + } + } + + /// Returns the number of validator indices already in the map. + pub fn len(&self) -> ValidatorIndex { + self.map.len() + } + + /// Inserts a validator index into the map. + /// + /// The added index must equal the number of validators already added to the map. This ensures + /// that an index is never skipped. + pub fn insert(&mut self, pubkey: PublicKey, index: ValidatorIndex) -> bool { + if index == self.map.len() { + self.map.insert(pubkey, index); + true + } else { + false + } + } + + /// Inserts a validator index into the map. + /// + /// The added index must equal the number of validators already added to the map. This ensures + /// that an index is never skipped. + pub fn get(&self, pubkey: &PublicKey) -> Option { + self.map.get(pubkey).cloned() + } +} diff --git a/eth2/types/src/lib.rs b/eth2/types/src/lib.rs index 76fcb43edd..3da6a497fe 100644 --- a/eth2/types/src/lib.rs +++ b/eth2/types/src/lib.rs @@ -1,4 +1,4 @@ -pub mod test_utils; +//! Ethereum 2.0 types pub mod attestation; pub mod attestation_data; @@ -22,6 +22,7 @@ pub mod proposer_slashing; pub mod readers; pub mod shard_reassignment_record; pub mod slashable_attestation; +pub mod test_utils; pub mod transfer; pub mod voluntary_exit; #[macro_use] diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index b2cf28c8a3..b9f3c63e06 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -144,6 +144,8 @@ impl TestingBeaconStateBuilder { state.build_epoch_cache(RelativeEpoch::Current, &spec)?; state.build_epoch_cache(RelativeEpoch::Next, &spec)?; + state.update_pubkey_cache()?; + Ok(()) } From 587be831b57528eb016b1c66c0e3eb8679c74f04 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 13 Mar 2019 16:49:32 +1100 Subject: [PATCH 078/144] Add method for dropping pubkey cache. Add bench. --- .../benches/bench_block_processing.rs | 17 +++++++++++++++++ eth2/types/src/beacon_state.rs | 5 +++++ 2 files changed, 22 insertions(+) diff --git a/eth2/state_processing/benches/bench_block_processing.rs b/eth2/state_processing/benches/bench_block_processing.rs index 031942473c..128b1051b2 100644 --- a/eth2/state_processing/benches/bench_block_processing.rs +++ b/eth2/state_processing/benches/bench_block_processing.rs @@ -426,6 +426,23 @@ fn bench_block_processing( .sample_size(10), ); + let mut state = initial_state.clone(); + state.drop_pubkey_cache(); + c.bench( + &format!("{}/block_processing", desc), + Benchmark::new("build_pubkey_cache", move |b| { + b.iter_batched( + || state.clone(), + |mut state| { + state.update_pubkey_cache().unwrap(); + state + }, + criterion::BatchSize::SmallInput, + ) + }) + .sample_size(10), + ); + let block = initial_block.clone(); c.bench( &format!("{}/block_processing", desc), diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index fc410fe641..a1dd8983c9 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -322,6 +322,11 @@ impl BeaconState { Ok(()) } + /// Completely drops the `pubkey_cache`, replacing it with a new, empty cache. + pub fn drop_pubkey_cache(&mut self) { + self.pubkey_cache = PubkeyCache::empty() + } + /// If a validator pubkey exists in the validator registry, returns `Some(i)`, otherwise /// returns `None`. /// From b2fb2afb2813c4288a462c4a84bf6bf9ea7f7528 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Wed, 13 Mar 2019 16:51:37 +1100 Subject: [PATCH 079/144] Revert "Move `PublicKey` to store uncomp. bytes." This reverts commit bfa2e71b468e34eedcf847076aa29f3a53038bfd. --- eth2/utils/bls/src/aggregate_public_key.rs | 2 +- eth2/utils/bls/src/public_key.rs | 56 ++++++---------------- eth2/utils/bls/src/signature.rs | 4 +- 3 files changed, 18 insertions(+), 44 deletions(-) diff --git a/eth2/utils/bls/src/aggregate_public_key.rs b/eth2/utils/bls/src/aggregate_public_key.rs index 47c95d6c9c..2174a43cb0 100644 --- a/eth2/utils/bls/src/aggregate_public_key.rs +++ b/eth2/utils/bls/src/aggregate_public_key.rs @@ -14,7 +14,7 @@ impl AggregatePublicKey { } pub fn add(&mut self, public_key: &PublicKey) { - self.0.add(&public_key.as_raw()) + self.0.add(public_key.as_raw()) } /// Returns the underlying signature. diff --git a/eth2/utils/bls/src/public_key.rs b/eth2/utils/bls/src/public_key.rs index 3e2bff19e2..c85760bbf0 100644 --- a/eth2/utils/bls/src/public_key.rs +++ b/eth2/utils/bls/src/public_key.rs @@ -10,58 +10,39 @@ use ssz::{ use std::default; use std::hash::{Hash, Hasher}; -/// A single BLS public key. -/// -/// This struct stores an uncompressed public key as a byte vec. The reason we store bytes instead -/// of the `RawPublicKey` struct is because it allows for building a hashmap of `PublicKey` much -/// faster. -/// -/// Storing as uncompressed bytes costs ~0.02% more time when adding a `PublicKey` to an -/// `AggregateKey`, however it saves ~0.5ms each time you need to add a pubkey to a hashmap. +/// 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 PublicKey { - bytes: Vec, -} +pub struct PublicKey(RawPublicKey); impl PublicKey { pub fn from_secret_key(secret_key: &SecretKey) -> Self { - let mut raw_key = RawPublicKey::from_secret_key(secret_key.as_raw()); - let uncompressed_bytes = raw_key.as_uncompressed_bytes(); - Self { - bytes: uncompressed_bytes, - } + PublicKey(RawPublicKey::from_secret_key(secret_key.as_raw())) } /// Returns the underlying signature. - pub fn as_raw(&self) -> RawPublicKey { - RawPublicKey::from_uncompressed_bytes(&self.bytes).expect("PublicKey in invalid state") + pub fn as_raw(&self) -> &RawPublicKey { + &self.0 } /// Converts compressed bytes to PublicKey pub fn from_bytes(bytes: &[u8]) -> Result { - let mut pubkey = RawPublicKey::from_bytes(&bytes).map_err(|_| DecodeError::Invalid)?; - Ok(Self { - bytes: pubkey.as_uncompressed_bytes(), - }) + let pubkey = RawPublicKey::from_bytes(&bytes).map_err(|_| DecodeError::Invalid)?; + Ok(PublicKey(pubkey)) } /// Returns the PublicKey as (x, y) bytes pub fn as_uncompressed_bytes(&self) -> Vec { - self.bytes.clone() + RawPublicKey::as_uncompressed_bytes(&mut self.0.clone()) } /// Converts (x, y) bytes to PublicKey pub fn from_uncompressed_bytes(bytes: &[u8]) -> Result { - // Do a conversion to check the bytes are valid. - let _pubkey = + let pubkey = RawPublicKey::from_uncompressed_bytes(&bytes).map_err(|_| DecodeError::Invalid)?; - - Ok(Self { - bytes: bytes.to_vec(), - }) + Ok(PublicKey(pubkey)) } /// Returns the last 6 bytes of the SSZ encoding of the public key, as a hex string. @@ -83,22 +64,15 @@ impl default::Default for PublicKey { impl Encodable for PublicKey { fn ssz_append(&self, s: &mut SszStream) { - s.append_vec(&self.as_raw().as_bytes()); + s.append_vec(&self.0.as_bytes()); } } impl Decodable for PublicKey { fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { let (sig_bytes, i) = decode_ssz_list(bytes, i)?; - let mut raw_sig = - RawPublicKey::from_bytes(&sig_bytes).map_err(|_| DecodeError::TooShort)?; - - Ok(( - Self { - bytes: raw_sig.as_uncompressed_bytes(), - }, - i, - )) + let raw_sig = RawPublicKey::from_bytes(&sig_bytes).map_err(|_| DecodeError::TooShort)?; + Ok((PublicKey(raw_sig), i)) } } @@ -125,7 +99,7 @@ impl<'de> Deserialize<'de> for PublicKey { impl TreeHash for PublicKey { fn hash_tree_root(&self) -> Vec { - hash(&self.as_raw().as_bytes()) + hash(&self.0.as_bytes()) } } @@ -143,7 +117,7 @@ impl Hash for PublicKey { /// /// Use `ssz::Encode` to obtain the bytes required for consensus hashing. fn hash(&self, state: &mut H) { - self.bytes.hash(state) + self.as_uncompressed_bytes().hash(state) } } diff --git a/eth2/utils/bls/src/signature.rs b/eth2/utils/bls/src/signature.rs index 47e1dad2eb..47598bc66d 100644 --- a/eth2/utils/bls/src/signature.rs +++ b/eth2/utils/bls/src/signature.rs @@ -33,7 +33,7 @@ impl Signature { /// Verify the Signature against a PublicKey. pub fn verify(&self, msg: &[u8], domain: u64, pk: &PublicKey) -> bool { - self.0.verify(msg, domain, &pk.as_raw()) + self.0.verify(msg, domain, pk.as_raw()) } /// Verify the Signature against a PublicKey, where the message has already been hashed. @@ -44,7 +44,7 @@ impl Signature { pk: &PublicKey, ) -> bool { self.0 - .verify_hashed(x_real_hashed, x_imaginary_hashed, &pk.as_raw()) + .verify_hashed(x_real_hashed, x_imaginary_hashed, pk.as_raw()) } /// Returns the underlying signature. From 181aeb3d71d612fab940d7475dc59977b008a4c9 Mon Sep 17 00:00:00 2001 From: pawanjay176 Date: Wed, 13 Mar 2019 14:31:40 +0530 Subject: [PATCH 080/144] Refactored to use max_by --- eth2/fork_choice/src/optimized_lmd_ghost.rs | 25 +++++++++++---------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/eth2/fork_choice/src/optimized_lmd_ghost.rs b/eth2/fork_choice/src/optimized_lmd_ghost.rs index 093120bb5e..e0074d4dec 100644 --- a/eth2/fork_choice/src/optimized_lmd_ghost.rs +++ b/eth2/fork_choice/src/optimized_lmd_ghost.rs @@ -8,6 +8,7 @@ use db::{ }; use log::{debug, trace}; use std::collections::HashMap; +use std::cmp::Ordering; use std::sync::Arc; use types::{ readers::BeaconBlockReader, validator_registry::get_active_validator_indices, BeaconBlock, @@ -199,19 +200,19 @@ where if votes.is_empty() { return None; } - let mut best_child: Hash256 = Hash256::from(0); - let mut max_votes: u64 = 0; - for (&candidate, &votes) in votes.iter() { - // Choose the smaller hash to break ties deterministically - if votes == max_votes && candidate < best_child { - best_child = candidate; + + // Iterate through hashmap to get child with maximum votes + let best_child = votes.iter().max_by(|(child1,v1), (child2, v2)| { + let mut result = v1.cmp(v2); + // If votes are equal, choose smaller hash to break ties deterministically + if result == Ordering::Equal { + // Reverse so that max_by chooses smaller hash + result = child1.cmp(child2).reverse(); } - if votes > max_votes { - max_votes = votes; - best_child = candidate; - } - } - Some(best_child) + result + }); + + Some(*best_child.unwrap().0) } } From 4c45b90df5af507c668329092ffc1b39e6b3d0fb Mon Sep 17 00:00:00 2001 From: pawanjay176 Date: Wed, 13 Mar 2019 14:34:00 +0530 Subject: [PATCH 081/144] Formatted changes using rustfmt --- eth2/fork_choice/src/lib.rs | 6 +++--- eth2/fork_choice/src/optimized_lmd_ghost.rs | 10 +++++----- eth2/fork_choice/tests/tests.rs | 4 +++- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/eth2/fork_choice/src/lib.rs b/eth2/fork_choice/src/lib.rs index a947473f38..0d6969e89f 100644 --- a/eth2/fork_choice/src/lib.rs +++ b/eth2/fork_choice/src/lib.rs @@ -22,8 +22,8 @@ extern crate types; pub mod bitwise_lmd_ghost; pub mod longest_chain; -pub mod slow_lmd_ghost; pub mod optimized_lmd_ghost; +pub mod slow_lmd_ghost; use db::stores::BeaconBlockAtSlotError; use db::DBError; @@ -31,8 +31,8 @@ use types::{BeaconBlock, ChainSpec, Hash256}; pub use bitwise_lmd_ghost::BitwiseLMDGhost; pub use longest_chain::LongestChain; -pub use slow_lmd_ghost::SlowLMDGhost; pub use optimized_lmd_ghost::OptimizedLMDGhost; +pub use slow_lmd_ghost::SlowLMDGhost; /// Defines the interface for Fork Choices. Each Fork choice will define their own data structures /// which can be built in block processing through the `add_block` and `add_attestation` functions. @@ -104,5 +104,5 @@ pub enum ForkChoiceAlgorithm { /// An optimised version of bitwise LMD-GHOST by Vitalik. BitwiseLMDGhost, /// An optimised implementation of LMD ghost. - OptimizedLMDGhost + OptimizedLMDGhost, } diff --git a/eth2/fork_choice/src/optimized_lmd_ghost.rs b/eth2/fork_choice/src/optimized_lmd_ghost.rs index e0074d4dec..636ccdabc5 100644 --- a/eth2/fork_choice/src/optimized_lmd_ghost.rs +++ b/eth2/fork_choice/src/optimized_lmd_ghost.rs @@ -7,8 +7,8 @@ use db::{ ClientDB, }; use log::{debug, trace}; -use std::collections::HashMap; use std::cmp::Ordering; +use std::collections::HashMap; use std::sync::Arc; use types::{ readers::BeaconBlockReader, validator_registry::get_active_validator_indices, BeaconBlock, @@ -202,12 +202,12 @@ where } // Iterate through hashmap to get child with maximum votes - let best_child = votes.iter().max_by(|(child1,v1), (child2, v2)| { - let mut result = v1.cmp(v2); + let best_child = votes.iter().max_by(|(child1, v1), (child2, v2)| { + let mut result = v1.cmp(v2); // If votes are equal, choose smaller hash to break ties deterministically if result == Ordering::Equal { - // Reverse so that max_by chooses smaller hash - result = child1.cmp(child2).reverse(); + // Reverse so that max_by chooses smaller hash + result = child1.cmp(child2).reverse(); } result }); diff --git a/eth2/fork_choice/tests/tests.rs b/eth2/fork_choice/tests/tests.rs index 1c8d92e0a6..5fb963ea5e 100644 --- a/eth2/fork_choice/tests/tests.rs +++ b/eth2/fork_choice/tests/tests.rs @@ -16,7 +16,9 @@ use bls::Signature; use db::stores::{BeaconBlockStore, BeaconStateStore}; use db::MemoryDB; // use env_logger::{Builder, Env}; -use fork_choice::{BitwiseLMDGhost, OptimizedLMDGhost, ForkChoice, ForkChoiceAlgorithm, LongestChain, SlowLMDGhost}; +use fork_choice::{ + BitwiseLMDGhost, ForkChoice, ForkChoiceAlgorithm, LongestChain, OptimizedLMDGhost, SlowLMDGhost, +}; use ssz::ssz_encode; use std::collections::HashMap; use std::sync::Arc; From 804da3c3ff0197381abb62b72f41555770bd672f Mon Sep 17 00:00:00 2001 From: pawanjay176 Date: Thu, 14 Mar 2019 00:58:15 +0530 Subject: [PATCH 082/144] Adds additional tests for lmd ghost fork choice rules --- .../tests/bitwise_lmd_ghost_test_vectors.yaml | 66 +++++++++++++++++++ .../tests/lmd_ghost_test_vectors.yaml | 16 +++++ 2 files changed, 82 insertions(+) diff --git a/eth2/fork_choice/tests/bitwise_lmd_ghost_test_vectors.yaml b/eth2/fork_choice/tests/bitwise_lmd_ghost_test_vectors.yaml index 3233137ab1..931d8decf2 100644 --- a/eth2/fork_choice/tests/bitwise_lmd_ghost_test_vectors.yaml +++ b/eth2/fork_choice/tests/bitwise_lmd_ghost_test_vectors.yaml @@ -63,3 +63,69 @@ test_cases: - b7: 2 heads: - id: 'b4' +- blocks: + - id: 'b0' + parent: 'b0' + - id: 'b1' + parent: 'b0' + - id: 'b2' + parent: 'b0' + - id: 'b3' + parent: 'b0' + - id: 'b4' + parent: 'b1' + - id: 'b5' + parent: 'b1' + - id: 'b6' + parent: 'b2' + - id: 'b7' + parent: 'b2' + - id: 'b8' + parent: 'b3' + - id: 'b9' + parent: 'b3' + weights: + - b1: 2 + - b2: 1 + - b3: 1 + - b4: 7 + - b5: 5 + - b6: 2 + - b7: 4 + - b8: 4 + - b9: 2 + heads: + - id: 'b4' +- blocks: + - id: 'b0' + parent: 'b0' + - id: 'b1' + parent: 'b0' + - id: 'b2' + parent: 'b0' + - id: 'b3' + parent: 'b0' + - id: 'b4' + parent: 'b1' + - id: 'b5' + parent: 'b1' + - id: 'b6' + parent: 'b2' + - id: 'b7' + parent: 'b2' + - id: 'b8' + parent: 'b3' + - id: 'b9' + parent: 'b3' + weights: + - b1: 1 + - b2: 1 + - b3: 1 + - b4: 7 + - b5: 5 + - b6: 2 + - b7: 4 + - b8: 4 + - b9: 2 + heads: + - id: 'b7' \ No newline at end of file diff --git a/eth2/fork_choice/tests/lmd_ghost_test_vectors.yaml b/eth2/fork_choice/tests/lmd_ghost_test_vectors.yaml index 4676d82016..dab998bebb 100644 --- a/eth2/fork_choice/tests/lmd_ghost_test_vectors.yaml +++ b/eth2/fork_choice/tests/lmd_ghost_test_vectors.yaml @@ -35,3 +35,19 @@ test_cases: - b3: 3 heads: - id: 'b1' +# equal weights children. Should choose lower hash b2 +- blocks: + - id: 'b0' + parent: 'b0' + - id: 'b1' + parent: 'b0' + - id: 'b2' + parent: 'b0' + - id: 'b3' + parent: 'b0' + weights: + - b1: 5 + - b2: 6 + - b3: 6 + heads: + - id: 'b2' From 6f919e6f7dbb6879afcd2af51409184887748d49 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 14 Mar 2019 11:53:50 +1100 Subject: [PATCH 083/144] Add first iteration on faster rewards processing. --- .../benches/bench_epoch_processing.rs | 19 +- eth2/state_processing/benches/benches.rs | 2 +- .../src/per_epoch_processing.rs | 151 ++++++-------- .../src/per_epoch_processing/attesters.rs | 195 ++++++++++++++++++ 4 files changed, 265 insertions(+), 102 deletions(-) create mode 100644 eth2/state_processing/src/per_epoch_processing/attesters.rs diff --git a/eth2/state_processing/benches/bench_epoch_processing.rs b/eth2/state_processing/benches/bench_epoch_processing.rs index e4981b2008..93c6c7ebd7 100644 --- a/eth2/state_processing/benches/bench_epoch_processing.rs +++ b/eth2/state_processing/benches/bench_epoch_processing.rs @@ -150,13 +150,15 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let state_clone = state.clone(); let spec_clone = spec.clone(); + let active_validator_indices = calculate_active_validator_indices(&state, &spec); c.bench( &format!("{}/epoch_processing", desc), Benchmark::new("calculate_attester_sets", move |b| { b.iter_batched( || state_clone.clone(), |mut state| { - calculate_attester_sets(&mut state, &spec_clone).unwrap(); + calculate_attester_sets(&mut state, &active_validator_indices, &spec_clone) + .unwrap(); state }, criterion::BatchSize::SmallInput, @@ -168,8 +170,8 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let state_clone = state.clone(); let spec_clone = spec.clone(); let previous_epoch = state.previous_epoch(&spec); - let attesters = calculate_attester_sets(&state, &spec).unwrap(); let active_validator_indices = calculate_active_validator_indices(&state, &spec); + let attesters = calculate_attester_sets(&state, &active_validator_indices, &spec).unwrap(); let current_total_balance = state.get_total_balance(&active_validator_indices[..], &spec); let previous_total_balance = state.get_total_balance( &get_active_validator_indices(&state.validator_registry, previous_epoch)[..], @@ -185,8 +187,8 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp &mut state, current_total_balance, previous_total_balance, - attesters.previous_epoch_boundary.balance, - attesters.current_epoch_boundary.balance, + attesters.balances.previous_epoch_boundary, + attesters.balances.current_epoch_boundary, &spec_clone, ); state @@ -214,8 +216,8 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let mut state_clone = state.clone(); let spec_clone = spec.clone(); let previous_epoch = state.previous_epoch(&spec); - let attesters = calculate_attester_sets(&state, &spec).unwrap(); let active_validator_indices = calculate_active_validator_indices(&state, &spec); + let attesters = calculate_attester_sets(&state, &active_validator_indices, &spec).unwrap(); let previous_total_balance = state.get_total_balance( &get_active_validator_indices(&state.validator_registry, previous_epoch)[..], &spec, @@ -229,7 +231,6 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp |mut state| { process_rewards_and_penalities( &mut state, - &active_validator_indices, &attesters, previous_total_balance, &winning_root_for_shards, @@ -264,8 +265,8 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let mut state_clone = state.clone(); let spec_clone = spec.clone(); let previous_epoch = state.previous_epoch(&spec); - let attesters = calculate_attester_sets(&state, &spec).unwrap(); let active_validator_indices = calculate_active_validator_indices(&state, &spec); + let attesters = calculate_attester_sets(&state, &active_validator_indices, &spec).unwrap(); let current_total_balance = state.get_total_balance(&active_validator_indices[..], spec); let previous_total_balance = state.get_total_balance( &get_active_validator_indices(&state.validator_registry, previous_epoch)[..], @@ -279,8 +280,8 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp &mut state_clone, current_total_balance, previous_total_balance, - attesters.previous_epoch_boundary.balance, - attesters.current_epoch_boundary.balance, + attesters.balances.previous_epoch_boundary, + attesters.balances.current_epoch_boundary, spec, ); assert!( diff --git a/eth2/state_processing/benches/benches.rs b/eth2/state_processing/benches/benches.rs index ad8c4f714f..c619e1ef75 100644 --- a/eth2/state_processing/benches/benches.rs +++ b/eth2/state_processing/benches/benches.rs @@ -18,8 +18,8 @@ pub fn state_processing(c: &mut Criterion) { Builder::from_env(Env::default().default_filter_or(LOG_LEVEL)).init(); } - bench_block_processing::bench_block_processing_n_validators(c, VALIDATOR_COUNT); bench_epoch_processing::bench_epoch_processing_n_validators(c, VALIDATOR_COUNT); + bench_block_processing::bench_block_processing_n_validators(c, VALIDATOR_COUNT); } criterion_group!(benches, state_processing); diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index 4abbe012cd..4fe53dd6b3 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -1,6 +1,5 @@ -use attester_sets::AttesterSets; +use attesters::Attesters; use errors::EpochProcessingError as Error; -use fnv::FnvHashMap; use fnv::FnvHashSet; use integer_sqrt::IntegerSquareRoot; use rayon::prelude::*; @@ -11,6 +10,7 @@ use types::{validator_registry::get_active_validator_indices, *}; use winning_root::{winning_root, WinningRoot}; pub mod attester_sets; +pub mod attesters; pub mod errors; pub mod inclusion_distance; pub mod tests; @@ -35,8 +35,6 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result state.build_epoch_cache(RelativeEpoch::Current, spec)?; state.build_epoch_cache(RelativeEpoch::Next, spec)?; - let attesters = calculate_attester_sets(&state, spec)?; - let active_validator_indices = calculate_active_validator_indices(&state, spec); let current_total_balance = state.get_total_balance(&active_validator_indices[..], spec); @@ -46,14 +44,16 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result spec, ); + let attesters = calculate_attester_sets(&state, &active_validator_indices, spec)?; + process_eth1_data(state, spec); process_justification( state, current_total_balance, previous_total_balance, - attesters.previous_epoch_boundary.balance, - attesters.current_epoch_boundary.balance, + attesters.balances.previous_epoch_boundary, + attesters.balances.current_epoch_boundary, spec, ); @@ -63,7 +63,6 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result // Rewards and Penalities process_rewards_and_penalities( state, - &active_validator_indices, &attesters, previous_total_balance, &winning_root_for_shards, @@ -107,9 +106,13 @@ pub fn calculate_active_validator_indices(state: &BeaconState, spec: &ChainSpec) /// Spec v0.4.0 pub fn calculate_attester_sets( state: &BeaconState, + active_validator_indices: &[usize], spec: &ChainSpec, -) -> Result { - AttesterSets::new(&state, spec) +) -> Result { + let mut attesters = Attesters::empty(state.validator_registry.len()); + attesters.process_active_validator_indices(&active_validator_indices); + attesters.process_attestations(&state, &state.latest_attestations, spec)?; + Ok(attesters) } /// Spec v0.4.0 @@ -283,22 +286,20 @@ pub fn process_crosslinks( /// Spec v0.4.0 pub fn process_rewards_and_penalities( state: &mut BeaconState, - active_validator_indices: &[usize], - attesters: &AttesterSets, + attesters: &Attesters, previous_total_balance: u64, winning_root_for_shards: &WinningRootHashSet, spec: &ChainSpec, ) -> Result<(), Error> { let next_epoch = state.next_epoch(spec); - let active_validator_indices: FnvHashSet = - FnvHashSet::from_iter(active_validator_indices.iter().cloned()); - + /* let previous_epoch_attestations: Vec<&PendingAttestation> = state .latest_attestations .par_iter() .filter(|a| a.data.slot.epoch(spec.slots_per_epoch) == state.previous_epoch(spec)) .collect(); + */ let base_reward_quotient = previous_total_balance.integer_sqrt() / spec.base_reward_quotient; @@ -309,6 +310,7 @@ pub fn process_rewards_and_penalities( return Err(Error::PreviousTotalBalanceIsZero); } + /* // Map is ValidatorIndex -> ProposerIndex let mut inclusion_slots: FnvHashMap = FnvHashMap::default(); for a in &previous_epoch_attestations { @@ -330,79 +332,55 @@ pub fn process_rewards_and_penalities( ); } } + */ // Justification and finalization let epochs_since_finality = next_epoch - state.finalized_epoch; - if epochs_since_finality <= 4 { - state.validator_balances = state - .validator_balances - .par_iter() - .enumerate() - .map(|(index, &balance)| { - let mut balance = balance; + state.validator_balances = state + .validator_balances + .par_iter() + .enumerate() + .map(|(index, &balance)| { + let mut balance = balance; + let status = &attesters.statuses[index]; + + if epochs_since_finality <= 4 { let base_reward = state.base_reward(index, base_reward_quotient, spec); // Expected FFG source - if attesters.previous_epoch.indices.contains(&index) { + if status.is_previous_epoch { safe_add_assign!( balance, - base_reward * attesters.previous_epoch.balance / previous_total_balance + base_reward * attesters.balances.previous_epoch / previous_total_balance ); - } else if active_validator_indices.contains(&index) { + } else if status.is_active { safe_sub_assign!(balance, base_reward); } // Expected FFG target - if attesters.previous_epoch_boundary.indices.contains(&index) { + if status.is_previous_epoch_boundary { safe_add_assign!( balance, - base_reward * attesters.previous_epoch_boundary.balance + base_reward * attesters.balances.previous_epoch_boundary / previous_total_balance ); - } else if active_validator_indices.contains(&index) { + } else if status.is_active { safe_sub_assign!(balance, base_reward); } // Expected beacon chain head - if attesters.previous_epoch_head.indices.contains(&index) { + if status.is_previous_epoch_head { safe_add_assign!( balance, - base_reward * attesters.previous_epoch_head.balance + base_reward * attesters.balances.previous_epoch_head / previous_total_balance ); - } else if active_validator_indices.contains(&index) { + } else if status.is_active { safe_sub_assign!(balance, base_reward); }; - - if attesters.previous_epoch.indices.contains(&index) { - let base_reward = state.base_reward(index, base_reward_quotient, spec); - - let (inclusion_distance, _) = inclusion_slots - .get(&index) - .expect("Inconsistent inclusion_slots."); - - if *inclusion_distance > 0 { - safe_add_assign!( - balance, - base_reward * spec.min_attestation_inclusion_delay - / inclusion_distance.as_u64() - ) - } - } - - balance - }) - .collect(); - } else { - state.validator_balances = state - .validator_balances - .par_iter() - .enumerate() - .map(|(index, &balance)| { - let mut balance = balance; - + } else { let inactivity_penalty = state.inactivity_penalty( index, epochs_since_finality, @@ -410,14 +388,14 @@ pub fn process_rewards_and_penalities( spec, ); - if active_validator_indices.contains(&index) { - if !attesters.previous_epoch.indices.contains(&index) { + if status.is_active { + if !status.is_previous_epoch { safe_sub_assign!(balance, inactivity_penalty); } - if !attesters.previous_epoch_boundary.indices.contains(&index) { + if !status.is_previous_epoch_boundary { safe_sub_assign!(balance, inactivity_penalty); } - if !attesters.previous_epoch_head.indices.contains(&index) { + if !status.is_previous_epoch_head { safe_sub_assign!(balance, inactivity_penalty); } @@ -426,42 +404,31 @@ pub fn process_rewards_and_penalities( safe_sub_assign!(balance, 2 * inactivity_penalty + base_reward); } } + } - if attesters.previous_epoch.indices.contains(&index) { - let base_reward = state.base_reward(index, base_reward_quotient, spec); - - let (inclusion_distance, _) = inclusion_slots - .get(&index) - .expect("Inconsistent inclusion_slots."); - - if *inclusion_distance > 0 { - safe_add_assign!( - balance, - base_reward * spec.min_attestation_inclusion_delay - / inclusion_distance.as_u64() - ) - } - } - - balance - }) - .collect(); - } + balance + }) + .collect(); // Attestation inclusion - // - for &index in &attesters.previous_epoch.indices { - let (_, proposer_index) = inclusion_slots - .get(&index) - .ok_or_else(|| Error::InclusionSlotsInconsistent(index))?; + for (index, _validator) in state.validator_registry.iter().enumerate() { + let status = &attesters.statuses[index]; - let base_reward = state.base_reward(*proposer_index, base_reward_quotient, spec); + if status.is_previous_epoch { + let proposer_index = status.inclusion_info.proposer_index; + let inclusion_distance = status.inclusion_info.distance; - safe_add_assign!( - state.validator_balances[*proposer_index], - base_reward / spec.attestation_inclusion_reward_quotient - ); + let base_reward = state.base_reward(proposer_index, base_reward_quotient, spec); + + if inclusion_distance > 0 && inclusion_distance < Slot::max_value() { + safe_add_assign!( + state.validator_balances[proposer_index], + base_reward * spec.min_attestation_inclusion_delay + / inclusion_distance.as_u64() + ) + } + } } //Crosslinks diff --git a/eth2/state_processing/src/per_epoch_processing/attesters.rs b/eth2/state_processing/src/per_epoch_processing/attesters.rs new file mode 100644 index 0000000000..662ddceed4 --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/attesters.rs @@ -0,0 +1,195 @@ +use types::*; + +macro_rules! set_self_if_other_is_true { + ($self_: ident, $other: ident, $var: ident) => { + $self_.$var = $other.$var & !$self_.$var; + }; +} + +#[derive(Clone)] +pub struct InclusionInfo { + pub slot: Slot, + pub distance: Slot, + pub proposer_index: usize, +} + +impl Default for InclusionInfo { + fn default() -> Self { + Self { + slot: Slot::max_value(), + distance: Slot::max_value(), + proposer_index: 0, + } + } +} + +impl InclusionInfo { + pub fn update(&mut self, other: &Self) { + if other.slot < self.slot { + self.slot = other.slot; + self.distance = other.distance; + self.proposer_index = other.proposer_index; + } + } +} + +#[derive(Default, Clone)] +pub struct AttesterStatus { + pub is_active: bool, + + pub is_current_epoch: bool, + pub is_current_epoch_boundary: bool, + pub is_previous_epoch: bool, + pub is_previous_epoch_boundary: bool, + pub is_previous_epoch_head: bool, + + pub inclusion_info: InclusionInfo, +} + +impl AttesterStatus { + pub fn update(&mut self, other: &Self) { + // Update all the bool fields, only updating `self` if `other` is true (never setting + // `self` to false). + set_self_if_other_is_true!(self, other, is_active); + set_self_if_other_is_true!(self, other, is_current_epoch); + set_self_if_other_is_true!(self, other, is_current_epoch_boundary); + set_self_if_other_is_true!(self, other, is_previous_epoch); + set_self_if_other_is_true!(self, other, is_previous_epoch_boundary); + set_self_if_other_is_true!(self, other, is_previous_epoch_head); + + self.inclusion_info.update(&other.inclusion_info); + } +} + +#[derive(Default, Clone)] +pub struct TotalBalances { + pub current_epoch: u64, + pub current_epoch_boundary: u64, + pub previous_epoch: u64, + pub previous_epoch_boundary: u64, + pub previous_epoch_head: u64, +} + +pub struct Attesters { + pub statuses: Vec, + pub balances: TotalBalances, +} + +impl Attesters { + pub fn empty(num_validators: usize) -> Self { + Self { + statuses: vec![AttesterStatus::default(); num_validators], + balances: TotalBalances::default(), + } + } + + pub fn process_active_validator_indices(&mut self, active_validator_indices: &[usize]) { + let status = AttesterStatus { + is_active: true, + ..AttesterStatus::default() + }; + + for &i in active_validator_indices { + self.statuses[i].update(&status); + } + } + + pub fn process_attestations( + &mut self, + state: &BeaconState, + attestations: &[PendingAttestation], + spec: &ChainSpec, + ) -> Result<(), BeaconStateError> { + for a in attestations { + let attesting_indices = + state.get_attestation_participants(&a.data, &a.aggregation_bitfield, spec)?; + let attesting_balance = state.get_total_balance(&attesting_indices, spec); + + let mut status = AttesterStatus::default(); + + // Profile this attestation, updating the total balances and generating an + // `AttesterStatus` object that applies to all participants in the attestation. + if is_from_epoch(a, state.current_epoch(spec), spec) { + self.balances.current_epoch += attesting_balance; + status.is_current_epoch = true; + + if has_common_epoch_boundary_root(a, state, state.current_epoch(spec), spec)? { + self.balances.current_epoch_boundary += attesting_balance; + status.is_current_epoch_boundary = true; + } + } else if is_from_epoch(a, state.previous_epoch(spec), spec) { + self.balances.previous_epoch += attesting_balance; + status.is_previous_epoch = true; + + // The inclusion slot and distance are only required for previous epoch attesters. + status.inclusion_info = InclusionInfo { + slot: a.inclusion_slot, + distance: inclusion_distance(a), + proposer_index: state.get_beacon_proposer_index(a.inclusion_slot, spec)?, + }; + + if has_common_epoch_boundary_root(a, state, state.previous_epoch(spec), spec)? { + self.balances.previous_epoch_boundary += attesting_balance; + status.is_previous_epoch_boundary = true; + } + + if has_common_beacon_block_root(a, state, spec)? { + self.balances.previous_epoch_head += attesting_balance; + status.is_previous_epoch_head = true; + } + } + + // Loop through the participating validator indices and update the status vec. + for validator_index in attesting_indices { + self.statuses[validator_index].update(&status); + } + } + + Ok(()) + } +} + +fn inclusion_distance(a: &PendingAttestation) -> Slot { + a.inclusion_slot - a.data.slot +} + +/// Returns `true` if some `PendingAttestation` is from the supplied `epoch`. +/// +/// Spec v0.4.0 +fn is_from_epoch(a: &PendingAttestation, epoch: Epoch, spec: &ChainSpec) -> bool { + a.data.slot.epoch(spec.slots_per_epoch) == epoch +} + +/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for +/// the first slot of the given epoch. +/// +/// Spec v0.4.0 +fn has_common_epoch_boundary_root( + a: &PendingAttestation, + state: &BeaconState, + epoch: Epoch, + spec: &ChainSpec, +) -> Result { + let slot = epoch.start_slot(spec.slots_per_epoch); + let state_boundary_root = *state + .get_block_root(slot, spec) + .ok_or_else(|| BeaconStateError::InsufficientBlockRoots)?; + + Ok(a.data.epoch_boundary_root == state_boundary_root) +} + +/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for +/// the current slot of the `PendingAttestation`. +/// +/// Spec v0.4.0 +fn has_common_beacon_block_root( + a: &PendingAttestation, + state: &BeaconState, + spec: &ChainSpec, +) -> Result { + let state_block_root = *state + .get_block_root(a.data.slot, spec) + .ok_or_else(|| BeaconStateError::InsufficientBlockRoots)?; + + Ok(a.data.beacon_block_root == state_block_root) +} From a319144835f89ccac00756879e158711a260d1b7 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 14 Mar 2019 12:17:43 +1100 Subject: [PATCH 084/144] Improve crosslink reward processing --- .../benches/bench_epoch_processing.rs | 6 +- .../src/per_epoch_processing.rs | 105 +++--------------- .../src/per_epoch_processing/attesters.rs | 41 +++++++ 3 files changed, 60 insertions(+), 92 deletions(-) diff --git a/eth2/state_processing/benches/bench_epoch_processing.rs b/eth2/state_processing/benches/bench_epoch_processing.rs index 93c6c7ebd7..ab4f61c009 100644 --- a/eth2/state_processing/benches/bench_epoch_processing.rs +++ b/eth2/state_processing/benches/bench_epoch_processing.rs @@ -227,11 +227,11 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp &format!("{}/epoch_processing", desc), Benchmark::new("process_rewards_and_penalties", move |b| { b.iter_batched( - || state_clone.clone(), - |mut state| { + || (state_clone.clone(), attesters.clone()), + |(mut state, mut attesters)| { process_rewards_and_penalities( &mut state, - &attesters, + &mut attesters, previous_total_balance, &winning_root_for_shards, &spec_clone, diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index 4fe53dd6b3..2377d7ded1 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -1,11 +1,9 @@ use attesters::Attesters; use errors::EpochProcessingError as Error; -use fnv::FnvHashSet; use integer_sqrt::IntegerSquareRoot; use rayon::prelude::*; use ssz::TreeHash; use std::collections::HashMap; -use std::iter::FromIterator; use types::{validator_registry::get_active_validator_indices, *}; use winning_root::{winning_root, WinningRoot}; @@ -44,7 +42,7 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result spec, ); - let attesters = calculate_attester_sets(&state, &active_validator_indices, spec)?; + let mut attesters = calculate_attester_sets(&state, &active_validator_indices, spec)?; process_eth1_data(state, spec); @@ -63,7 +61,7 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result // Rewards and Penalities process_rewards_and_penalities( state, - &attesters, + &mut attesters, previous_total_balance, &winning_root_for_shards, spec, @@ -286,21 +284,13 @@ pub fn process_crosslinks( /// Spec v0.4.0 pub fn process_rewards_and_penalities( state: &mut BeaconState, - attesters: &Attesters, + attesters: &mut Attesters, previous_total_balance: u64, winning_root_for_shards: &WinningRootHashSet, spec: &ChainSpec, ) -> Result<(), Error> { let next_epoch = state.next_epoch(spec); - /* - let previous_epoch_attestations: Vec<&PendingAttestation> = state - .latest_attestations - .par_iter() - .filter(|a| a.data.slot.epoch(spec.slots_per_epoch) == state.previous_epoch(spec)) - .collect(); - */ - let base_reward_quotient = previous_total_balance.integer_sqrt() / spec.base_reward_quotient; if base_reward_quotient == 0 { @@ -310,29 +300,7 @@ pub fn process_rewards_and_penalities( return Err(Error::PreviousTotalBalanceIsZero); } - /* - // Map is ValidatorIndex -> ProposerIndex - let mut inclusion_slots: FnvHashMap = FnvHashMap::default(); - for a in &previous_epoch_attestations { - let participants = - state.get_attestation_participants(&a.data, &a.aggregation_bitfield, spec)?; - let inclusion_distance = (a.inclusion_slot - a.data.slot).as_u64(); - for participant in participants { - if let Some((existing_distance, _)) = inclusion_slots.get(&participant) { - if *existing_distance <= inclusion_distance { - continue; - } - } - let proposer_index = state - .get_beacon_proposer_index(a.data.slot, spec) - .map_err(|_| Error::UnableToDetermineProducer)?; - inclusion_slots.insert( - participant, - (Slot::from(inclusion_distance), proposer_index), - ); - } - } - */ + attesters.process_winning_roots(state, winning_root_for_shards, spec)?; // Justification and finalization @@ -345,10 +313,9 @@ pub fn process_rewards_and_penalities( .map(|(index, &balance)| { let mut balance = balance; let status = &attesters.statuses[index]; + let base_reward = state.base_reward(index, base_reward_quotient, spec); if epochs_since_finality <= 4 { - let base_reward = state.base_reward(index, base_reward_quotient, spec); - // Expected FFG source if status.is_previous_epoch { safe_add_assign!( @@ -406,6 +373,17 @@ pub fn process_rewards_and_penalities( } } + // Crosslinks + + if let Some(ref info) = status.winning_root_info { + safe_add_assign!( + balance, + base_reward * info.total_attesting_balance / info.total_committee_balance + ); + } else { + safe_sub_assign!(balance, base_reward); + } + balance }) .collect(); @@ -431,57 +409,6 @@ pub fn process_rewards_and_penalities( } } - //Crosslinks - - for slot in state.previous_epoch(spec).slot_iter(spec.slots_per_epoch) { - // Clone removes the borrow which becomes an issue when mutating `state.balances`. - let crosslink_committees_at_slot = - state.get_crosslink_committees_at_slot(slot, spec)?.clone(); - - for (crosslink_committee, shard) in crosslink_committees_at_slot { - let shard = shard as u64; - - // Note: I'm a little uncertain of the logic here -- I am waiting for spec v0.5.0 to - // clear it up. - // - // What happens here is: - // - // - If there was some crosslink root elected by the super-majority of this committee, - // then we reward all who voted for that root and penalize all that did not. - // - However, if there _was not_ some super-majority-voted crosslink root, then penalize - // all the validators. - // - // I'm not quite sure that the second case (no super-majority crosslink) is correct. - if let Some(winning_root) = winning_root_for_shards.get(&shard) { - // Hash set de-dedups and (hopefully) offers a speed improvement from faster - // lookups. - let attesting_validator_indices: FnvHashSet = - FnvHashSet::from_iter(winning_root.attesting_validator_indices.iter().cloned()); - - for &index in &crosslink_committee { - let base_reward = state.base_reward(index, base_reward_quotient, spec); - - let total_balance = state.get_total_balance(&crosslink_committee, spec); - - if attesting_validator_indices.contains(&index) { - safe_add_assign!( - state.validator_balances[index], - base_reward * winning_root.total_attesting_balance / total_balance - ); - } else { - safe_sub_assign!(state.validator_balances[index], base_reward); - } - } - } else { - for &index in &crosslink_committee { - let base_reward = state.base_reward(index, base_reward_quotient, spec); - - safe_sub_assign!(state.validator_balances[index], base_reward); - } - } - } - } - Ok(()) } diff --git a/eth2/state_processing/src/per_epoch_processing/attesters.rs b/eth2/state_processing/src/per_epoch_processing/attesters.rs index 662ddceed4..ef26d338d6 100644 --- a/eth2/state_processing/src/per_epoch_processing/attesters.rs +++ b/eth2/state_processing/src/per_epoch_processing/attesters.rs @@ -1,3 +1,4 @@ +use super::WinningRootHashSet; use types::*; macro_rules! set_self_if_other_is_true { @@ -6,6 +7,12 @@ macro_rules! set_self_if_other_is_true { }; } +#[derive(Default, Clone)] +pub struct WinningRootInfo { + pub total_committee_balance: u64, + pub total_attesting_balance: u64, +} + #[derive(Clone)] pub struct InclusionInfo { pub slot: Slot, @@ -44,6 +51,7 @@ pub struct AttesterStatus { pub is_previous_epoch_head: bool, pub inclusion_info: InclusionInfo, + pub winning_root_info: Option, } impl AttesterStatus { @@ -70,6 +78,7 @@ pub struct TotalBalances { pub previous_epoch_head: u64, } +#[derive(Clone)] pub struct Attesters { pub statuses: Vec, pub balances: TotalBalances, @@ -147,6 +156,38 @@ impl Attesters { Ok(()) } + + pub fn process_winning_roots( + &mut self, + state: &BeaconState, + winning_roots: &WinningRootHashSet, + spec: &ChainSpec, + ) -> Result<(), BeaconStateError> { + // Loop through each slot in the previous epoch. + for slot in state.previous_epoch(spec).slot_iter(spec.slots_per_epoch) { + let crosslink_committees_at_slot = + state.get_crosslink_committees_at_slot(slot, spec)?; + + // Loop through each committee in the slot. + for (crosslink_committee, shard) in crosslink_committees_at_slot { + // If there was some winning crosslink root for the committee's shard. + if let Some(winning_root) = winning_roots.get(&shard) { + let total_committee_balance = + state.get_total_balance(&crosslink_committee, spec); + for &validator_index in &winning_root.attesting_validator_indices { + // Take note of the balance information for the winning root, it will be + // used later to calculate rewards for that validator. + self.statuses[validator_index].winning_root_info = Some(WinningRootInfo { + total_committee_balance, + total_attesting_balance: winning_root.total_attesting_balance, + }) + } + } + } + } + + Ok(()) + } } fn inclusion_distance(a: &PendingAttestation) -> Slot { From 95599ddc66649511f5e75deddd03035d17c451f8 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 14 Mar 2019 12:49:48 +1100 Subject: [PATCH 085/144] Update Attesters struct - Renames variables - Moves total balance calculation into struct --- .../benches/bench_epoch_processing.rs | 123 ++---------------- .../src/per_epoch_processing.rs | 67 ++++------ .../src/per_epoch_processing/attesters.rs | 90 +++++++------ 3 files changed, 88 insertions(+), 192 deletions(-) diff --git a/eth2/state_processing/benches/bench_epoch_processing.rs b/eth2/state_processing/benches/bench_epoch_processing.rs index ab4f61c009..d95f1c8194 100644 --- a/eth2/state_processing/benches/bench_epoch_processing.rs +++ b/eth2/state_processing/benches/bench_epoch_processing.rs @@ -4,14 +4,13 @@ use ssz::TreeHash; use state_processing::{ per_epoch_processing, per_epoch_processing::{ - calculate_active_validator_indices, calculate_attester_sets, clean_attestations, - process_crosslinks, process_eth1_data, process_justification, - process_rewards_and_penalities, process_validator_registry, update_active_tree_index_roots, - update_latest_slashed_balances, + calculate_attester_sets, clean_attestations, process_crosslinks, process_eth1_data, + process_justification, process_rewards_and_penalities, process_validator_registry, + update_active_tree_index_roots, update_latest_slashed_balances, }, }; use types::test_utils::TestingBeaconStateBuilder; -use types::{validator_registry::get_active_validator_indices, *}; +use types::*; pub const BENCHING_SAMPLE_SIZE: usize = 10; pub const SMALL_BENCHING_SAMPLE_SIZE: usize = 10; @@ -73,64 +72,6 @@ pub fn bench_epoch_processing_n_validators(c: &mut Criterion, validator_count: u /// /// `desc` will be added to the title of each bench. fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSpec, desc: &str) { - let state_clone = state.clone(); - let spec_clone = spec.clone(); - c.bench( - &format!("{}/epoch_processing", desc), - Benchmark::new("calculate_active_validator_indices", move |b| { - b.iter_batched( - || state_clone.clone(), - |mut state| { - calculate_active_validator_indices(&mut state, &spec_clone); - state - }, - criterion::BatchSize::SmallInput, - ) - }) - .sample_size(BENCHING_SAMPLE_SIZE), - ); - - let state_clone = state.clone(); - let spec_clone = spec.clone(); - let active_validator_indices = calculate_active_validator_indices(&state, &spec); - c.bench( - &format!("{}/epoch_processing", desc), - Benchmark::new("calculate_current_total_balance", move |b| { - b.iter_batched( - || state_clone.clone(), - |state| { - state.get_total_balance(&active_validator_indices[..], &spec_clone); - state - }, - criterion::BatchSize::SmallInput, - ) - }) - .sample_size(BENCHING_SAMPLE_SIZE), - ); - - let state_clone = state.clone(); - let spec_clone = spec.clone(); - c.bench( - &format!("{}/epoch_processing", desc), - Benchmark::new("calculate_previous_total_balance", move |b| { - b.iter_batched( - || state_clone.clone(), - |state| { - state.get_total_balance( - &get_active_validator_indices( - &state.validator_registry, - state.previous_epoch(&spec_clone), - )[..], - &spec_clone, - ); - state - }, - criterion::BatchSize::SmallInput, - ) - }) - .sample_size(BENCHING_SAMPLE_SIZE), - ); - let state_clone = state.clone(); let spec_clone = spec.clone(); c.bench( @@ -150,15 +91,13 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let state_clone = state.clone(); let spec_clone = spec.clone(); - let active_validator_indices = calculate_active_validator_indices(&state, &spec); c.bench( &format!("{}/epoch_processing", desc), Benchmark::new("calculate_attester_sets", move |b| { b.iter_batched( || state_clone.clone(), |mut state| { - calculate_attester_sets(&mut state, &active_validator_indices, &spec_clone) - .unwrap(); + calculate_attester_sets(&mut state, &spec_clone).unwrap(); state }, criterion::BatchSize::SmallInput, @@ -169,14 +108,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let state_clone = state.clone(); let spec_clone = spec.clone(); - let previous_epoch = state.previous_epoch(&spec); - let active_validator_indices = calculate_active_validator_indices(&state, &spec); - let attesters = calculate_attester_sets(&state, &active_validator_indices, &spec).unwrap(); - let current_total_balance = state.get_total_balance(&active_validator_indices[..], &spec); - let previous_total_balance = state.get_total_balance( - &get_active_validator_indices(&state.validator_registry, previous_epoch)[..], - &spec, - ); + let attesters = calculate_attester_sets(&state, &spec).unwrap(); c.bench( &format!("{}/epoch_processing", desc), Benchmark::new("process_justification", move |b| { @@ -185,10 +117,10 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp |mut state| { process_justification( &mut state, - current_total_balance, - previous_total_balance, - attesters.balances.previous_epoch_boundary, - attesters.balances.current_epoch_boundary, + attesters.balances.current_epoch_total, + attesters.balances.previous_epoch_total, + attesters.balances.previous_epoch_boundary_attesters, + attesters.balances.current_epoch_boundary_attesters, &spec_clone, ); state @@ -215,13 +147,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let mut state_clone = state.clone(); let spec_clone = spec.clone(); - let previous_epoch = state.previous_epoch(&spec); - let active_validator_indices = calculate_active_validator_indices(&state, &spec); - let attesters = calculate_attester_sets(&state, &active_validator_indices, &spec).unwrap(); - let previous_total_balance = state.get_total_balance( - &get_active_validator_indices(&state.validator_registry, previous_epoch)[..], - &spec, - ); + let attesters = calculate_attester_sets(&state, &spec).unwrap(); let winning_root_for_shards = process_crosslinks(&mut state_clone, &spec).unwrap(); c.bench( &format!("{}/epoch_processing", desc), @@ -232,7 +158,6 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp process_rewards_and_penalities( &mut state, &mut attesters, - previous_total_balance, &winning_root_for_shards, &spec_clone, ) @@ -262,32 +187,8 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp .sample_size(BENCHING_SAMPLE_SIZE), ); - let mut state_clone = state.clone(); + let state_clone = state.clone(); let spec_clone = spec.clone(); - let previous_epoch = state.previous_epoch(&spec); - let active_validator_indices = calculate_active_validator_indices(&state, &spec); - let attesters = calculate_attester_sets(&state, &active_validator_indices, &spec).unwrap(); - let current_total_balance = state.get_total_balance(&active_validator_indices[..], spec); - let previous_total_balance = state.get_total_balance( - &get_active_validator_indices(&state.validator_registry, previous_epoch)[..], - &spec, - ); - assert_eq!( - state_clone.finalized_epoch, state_clone.validator_registry_update_epoch, - "The last registry update should be at the last finalized epoch." - ); - process_justification( - &mut state_clone, - current_total_balance, - previous_total_balance, - attesters.balances.previous_epoch_boundary, - attesters.balances.current_epoch_boundary, - spec, - ); - assert!( - state_clone.finalized_epoch > state_clone.validator_registry_update_epoch, - "The state should have been finalized." - ); c.bench( &format!("{}/epoch_processing", desc), Benchmark::new("process_validator_registry", move |b| { diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index 2377d7ded1..03135df66b 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -26,32 +26,21 @@ pub type WinningRootHashSet = HashMap; /// /// Spec v0.4.0 pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { - let previous_epoch = state.previous_epoch(spec); - // Ensure all of the caches are built. state.build_epoch_cache(RelativeEpoch::Previous, spec)?; state.build_epoch_cache(RelativeEpoch::Current, spec)?; state.build_epoch_cache(RelativeEpoch::Next, spec)?; - let active_validator_indices = calculate_active_validator_indices(&state, spec); - - let current_total_balance = state.get_total_balance(&active_validator_indices[..], spec); - - let previous_total_balance = state.get_total_balance( - &get_active_validator_indices(&state.validator_registry, previous_epoch)[..], - spec, - ); - - let mut attesters = calculate_attester_sets(&state, &active_validator_indices, spec)?; + let mut attesters = calculate_attester_sets(&state, spec)?; process_eth1_data(state, spec); process_justification( state, - current_total_balance, - previous_total_balance, - attesters.balances.previous_epoch_boundary, - attesters.balances.current_epoch_boundary, + attesters.balances.current_epoch_total, + attesters.balances.previous_epoch_total, + attesters.balances.previous_epoch_boundary_attesters, + attesters.balances.current_epoch_boundary_attesters, spec, ); @@ -59,13 +48,7 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result let winning_root_for_shards = process_crosslinks(state, spec)?; // Rewards and Penalities - process_rewards_and_penalities( - state, - &mut attesters, - previous_total_balance, - &winning_root_for_shards, - spec, - )?; + process_rewards_and_penalities(state, &mut attesters, &winning_root_for_shards, spec)?; // Ejections state.process_ejections(spec); @@ -104,12 +87,12 @@ pub fn calculate_active_validator_indices(state: &BeaconState, spec: &ChainSpec) /// Spec v0.4.0 pub fn calculate_attester_sets( state: &BeaconState, - active_validator_indices: &[usize], spec: &ChainSpec, ) -> Result { - let mut attesters = Attesters::empty(state.validator_registry.len()); - attesters.process_active_validator_indices(&active_validator_indices); + let mut attesters = Attesters::new(state, spec); + attesters.process_attestations(&state, &state.latest_attestations, spec)?; + Ok(attesters) } @@ -285,12 +268,13 @@ pub fn process_crosslinks( pub fn process_rewards_and_penalities( state: &mut BeaconState, attesters: &mut Attesters, - previous_total_balance: u64, winning_root_for_shards: &WinningRootHashSet, spec: &ChainSpec, ) -> Result<(), Error> { let next_epoch = state.next_epoch(spec); + let previous_total_balance = attesters.balances.previous_epoch_total; + let base_reward_quotient = previous_total_balance.integer_sqrt() / spec.base_reward_quotient; if base_reward_quotient == 0 { @@ -317,34 +301,35 @@ pub fn process_rewards_and_penalities( if epochs_since_finality <= 4 { // Expected FFG source - if status.is_previous_epoch { + if status.is_previous_epoch_attester { safe_add_assign!( balance, - base_reward * attesters.balances.previous_epoch / previous_total_balance + base_reward * attesters.balances.previous_epoch_attesters + / previous_total_balance ); - } else if status.is_active { + } else if status.is_active_in_previous_epoch { safe_sub_assign!(balance, base_reward); } // Expected FFG target - if status.is_previous_epoch_boundary { + if status.is_previous_epoch_boundary_attester { safe_add_assign!( balance, - base_reward * attesters.balances.previous_epoch_boundary + base_reward * attesters.balances.previous_epoch_boundary_attesters / previous_total_balance ); - } else if status.is_active { + } else if status.is_active_in_previous_epoch { safe_sub_assign!(balance, base_reward); } // Expected beacon chain head - if status.is_previous_epoch_head { + if status.is_previous_epoch_head_attester { safe_add_assign!( balance, - base_reward * attesters.balances.previous_epoch_head + base_reward * attesters.balances.previous_epoch_head_attesters / previous_total_balance ); - } else if status.is_active { + } else if status.is_active_in_previous_epoch { safe_sub_assign!(balance, base_reward); }; } else { @@ -355,14 +340,14 @@ pub fn process_rewards_and_penalities( spec, ); - if status.is_active { - if !status.is_previous_epoch { + if status.is_active_in_previous_epoch { + if !status.is_previous_epoch_attester { safe_sub_assign!(balance, inactivity_penalty); } - if !status.is_previous_epoch_boundary { + if !status.is_previous_epoch_boundary_attester { safe_sub_assign!(balance, inactivity_penalty); } - if !status.is_previous_epoch_head { + if !status.is_previous_epoch_head_attester { safe_sub_assign!(balance, inactivity_penalty); } @@ -393,7 +378,7 @@ pub fn process_rewards_and_penalities( for (index, _validator) in state.validator_registry.iter().enumerate() { let status = &attesters.statuses[index]; - if status.is_previous_epoch { + if status.is_previous_epoch_attester { let proposer_index = status.inclusion_info.proposer_index; let inclusion_distance = status.inclusion_info.distance; diff --git a/eth2/state_processing/src/per_epoch_processing/attesters.rs b/eth2/state_processing/src/per_epoch_processing/attesters.rs index ef26d338d6..1ffbdf6525 100644 --- a/eth2/state_processing/src/per_epoch_processing/attesters.rs +++ b/eth2/state_processing/src/per_epoch_processing/attesters.rs @@ -42,28 +42,31 @@ impl InclusionInfo { #[derive(Default, Clone)] pub struct AttesterStatus { - pub is_active: bool, + pub is_active_in_current_epoch: bool, + pub is_active_in_previous_epoch: bool, - pub is_current_epoch: bool, - pub is_current_epoch_boundary: bool, - pub is_previous_epoch: bool, - pub is_previous_epoch_boundary: bool, - pub is_previous_epoch_head: bool, + pub is_current_epoch_attester: bool, + pub is_current_epoch_boundary_attester: bool, + pub is_previous_epoch_attester: bool, + pub is_previous_epoch_boundary_attester: bool, + pub is_previous_epoch_head_attester: bool, pub inclusion_info: InclusionInfo, pub winning_root_info: Option, } impl AttesterStatus { + /// Note: does not update the winning root info. pub fn update(&mut self, other: &Self) { // Update all the bool fields, only updating `self` if `other` is true (never setting // `self` to false). - set_self_if_other_is_true!(self, other, is_active); - set_self_if_other_is_true!(self, other, is_current_epoch); - set_self_if_other_is_true!(self, other, is_current_epoch_boundary); - set_self_if_other_is_true!(self, other, is_previous_epoch); - set_self_if_other_is_true!(self, other, is_previous_epoch_boundary); - set_self_if_other_is_true!(self, other, is_previous_epoch_head); + set_self_if_other_is_true!(self, other, is_active_in_current_epoch); + set_self_if_other_is_true!(self, other, is_active_in_previous_epoch); + set_self_if_other_is_true!(self, other, is_current_epoch_attester); + set_self_if_other_is_true!(self, other, is_current_epoch_boundary_attester); + set_self_if_other_is_true!(self, other, is_previous_epoch_attester); + set_self_if_other_is_true!(self, other, is_previous_epoch_boundary_attester); + set_self_if_other_is_true!(self, other, is_previous_epoch_head_attester); self.inclusion_info.update(&other.inclusion_info); } @@ -71,11 +74,13 @@ impl AttesterStatus { #[derive(Default, Clone)] pub struct TotalBalances { - pub current_epoch: u64, - pub current_epoch_boundary: u64, - pub previous_epoch: u64, - pub previous_epoch_boundary: u64, - pub previous_epoch_head: u64, + pub current_epoch_total: u64, + pub previous_epoch_total: u64, + pub current_epoch_attesters: u64, + pub current_epoch_boundary_attesters: u64, + pub previous_epoch_attesters: u64, + pub previous_epoch_boundary_attesters: u64, + pub previous_epoch_head_attesters: u64, } #[derive(Clone)] @@ -85,22 +90,27 @@ pub struct Attesters { } impl Attesters { - pub fn empty(num_validators: usize) -> Self { - Self { - statuses: vec![AttesterStatus::default(); num_validators], - balances: TotalBalances::default(), - } - } + pub fn new(state: &BeaconState, spec: &ChainSpec) -> Self { + let mut statuses = Vec::with_capacity(state.validator_registry.len()); + let mut balances = TotalBalances::default(); - pub fn process_active_validator_indices(&mut self, active_validator_indices: &[usize]) { - let status = AttesterStatus { - is_active: true, - ..AttesterStatus::default() - }; + for (i, validator) in state.validator_registry.iter().enumerate() { + let mut status = AttesterStatus::default(); - for &i in active_validator_indices { - self.statuses[i].update(&status); + if validator.is_active_at(state.current_epoch(spec)) { + status.is_active_in_current_epoch = true; + balances.current_epoch_total += state.get_effective_balance(i, spec); + } + + if validator.is_active_at(state.previous_epoch(spec)) { + status.is_active_in_previous_epoch = true; + balances.previous_epoch_total += state.get_effective_balance(i, spec); + } + + statuses.push(status); } + + Self { statuses, balances } } pub fn process_attestations( @@ -119,16 +129,16 @@ impl Attesters { // Profile this attestation, updating the total balances and generating an // `AttesterStatus` object that applies to all participants in the attestation. if is_from_epoch(a, state.current_epoch(spec), spec) { - self.balances.current_epoch += attesting_balance; - status.is_current_epoch = true; + self.balances.current_epoch_attesters += attesting_balance; + status.is_current_epoch_attester = true; if has_common_epoch_boundary_root(a, state, state.current_epoch(spec), spec)? { - self.balances.current_epoch_boundary += attesting_balance; - status.is_current_epoch_boundary = true; + self.balances.current_epoch_boundary_attesters += attesting_balance; + status.is_current_epoch_boundary_attester = true; } } else if is_from_epoch(a, state.previous_epoch(spec), spec) { - self.balances.previous_epoch += attesting_balance; - status.is_previous_epoch = true; + self.balances.previous_epoch_attesters += attesting_balance; + status.is_previous_epoch_attester = true; // The inclusion slot and distance are only required for previous epoch attesters. status.inclusion_info = InclusionInfo { @@ -138,13 +148,13 @@ impl Attesters { }; if has_common_epoch_boundary_root(a, state, state.previous_epoch(spec), spec)? { - self.balances.previous_epoch_boundary += attesting_balance; - status.is_previous_epoch_boundary = true; + self.balances.previous_epoch_boundary_attesters += attesting_balance; + status.is_previous_epoch_boundary_attester = true; } if has_common_beacon_block_root(a, state, spec)? { - self.balances.previous_epoch_head += attesting_balance; - status.is_previous_epoch_head = true; + self.balances.previous_epoch_head_attesters += attesting_balance; + status.is_previous_epoch_head_attester = true; } } From 10aee6214c4658322353de9b92df4e8194503c76 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 14 Mar 2019 14:59:00 +1100 Subject: [PATCH 086/144] Tidy per_epoch_processing Mainly renaming variables and files for readability. --- .../benches/bench_epoch_processing.rs | 19 ++---- .../src/per_epoch_processing.rs | 66 ++++++++----------- .../{attesters.rs => validator_statuses.rs} | 37 ++++++----- 3 files changed, 57 insertions(+), 65 deletions(-) rename eth2/state_processing/src/per_epoch_processing/{attesters.rs => validator_statuses.rs} (89%) diff --git a/eth2/state_processing/benches/bench_epoch_processing.rs b/eth2/state_processing/benches/bench_epoch_processing.rs index d95f1c8194..49b4f4371c 100644 --- a/eth2/state_processing/benches/bench_epoch_processing.rs +++ b/eth2/state_processing/benches/bench_epoch_processing.rs @@ -4,7 +4,7 @@ use ssz::TreeHash; use state_processing::{ per_epoch_processing, per_epoch_processing::{ - calculate_attester_sets, clean_attestations, process_crosslinks, process_eth1_data, + clean_attestations, initialize_validator_statuses, process_crosslinks, process_eth1_data, process_justification, process_rewards_and_penalities, process_validator_registry, update_active_tree_index_roots, update_latest_slashed_balances, }, @@ -93,11 +93,11 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let spec_clone = spec.clone(); c.bench( &format!("{}/epoch_processing", desc), - Benchmark::new("calculate_attester_sets", move |b| { + Benchmark::new("initialize_validator_statuses", move |b| { b.iter_batched( || state_clone.clone(), |mut state| { - calculate_attester_sets(&mut state, &spec_clone).unwrap(); + initialize_validator_statuses(&mut state, &spec_clone).unwrap(); state }, criterion::BatchSize::SmallInput, @@ -108,21 +108,14 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let state_clone = state.clone(); let spec_clone = spec.clone(); - let attesters = calculate_attester_sets(&state, &spec).unwrap(); + let attesters = initialize_validator_statuses(&state, &spec).unwrap(); c.bench( &format!("{}/epoch_processing", desc), Benchmark::new("process_justification", move |b| { b.iter_batched( || state_clone.clone(), |mut state| { - process_justification( - &mut state, - attesters.balances.current_epoch_total, - attesters.balances.previous_epoch_total, - attesters.balances.previous_epoch_boundary_attesters, - attesters.balances.current_epoch_boundary_attesters, - &spec_clone, - ); + process_justification(&mut state, &attesters.total_balances, &spec_clone); state }, criterion::BatchSize::SmallInput, @@ -147,7 +140,7 @@ fn bench_epoch_processing(c: &mut Criterion, state: &BeaconState, spec: &ChainSp let mut state_clone = state.clone(); let spec_clone = spec.clone(); - let attesters = calculate_attester_sets(&state, &spec).unwrap(); + let attesters = initialize_validator_statuses(&state, &spec).unwrap(); let winning_root_for_shards = process_crosslinks(&mut state_clone, &spec).unwrap(); c.bench( &format!("{}/epoch_processing", desc), diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index 03135df66b..044d32eaed 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -1,17 +1,17 @@ -use attesters::Attesters; use errors::EpochProcessingError as Error; use integer_sqrt::IntegerSquareRoot; use rayon::prelude::*; use ssz::TreeHash; use std::collections::HashMap; use types::{validator_registry::get_active_validator_indices, *}; +use validator_statuses::{TotalBalances, ValidatorStatuses}; use winning_root::{winning_root, WinningRoot}; pub mod attester_sets; -pub mod attesters; pub mod errors; pub mod inclusion_distance; pub mod tests; +pub mod validator_statuses; pub mod winning_root; /// Maps a shard to a winning root. @@ -31,24 +31,17 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result state.build_epoch_cache(RelativeEpoch::Current, spec)?; state.build_epoch_cache(RelativeEpoch::Next, spec)?; - let mut attesters = calculate_attester_sets(&state, spec)?; + let mut statuses = initialize_validator_statuses(&state, spec)?; process_eth1_data(state, spec); - process_justification( - state, - attesters.balances.current_epoch_total, - attesters.balances.previous_epoch_total, - attesters.balances.previous_epoch_boundary_attesters, - attesters.balances.current_epoch_boundary_attesters, - spec, - ); + process_justification(state, &statuses.total_balances, spec); // Crosslinks let winning_root_for_shards = process_crosslinks(state, spec)?; // Rewards and Penalities - process_rewards_and_penalities(state, &mut attesters, &winning_root_for_shards, spec)?; + process_rewards_and_penalities(state, &mut statuses, &winning_root_for_shards, spec)?; // Ejections state.process_ejections(spec); @@ -85,15 +78,15 @@ pub fn calculate_active_validator_indices(state: &BeaconState, spec: &ChainSpec) /// - etc. /// /// Spec v0.4.0 -pub fn calculate_attester_sets( +pub fn initialize_validator_statuses( state: &BeaconState, spec: &ChainSpec, -) -> Result { - let mut attesters = Attesters::new(state, spec); +) -> Result { + let mut statuses = ValidatorStatuses::new(state, spec); - attesters.process_attestations(&state, &state.latest_attestations, spec)?; + statuses.process_attestations(&state, &state.latest_attestations, spec)?; - Ok(attesters) + Ok(statuses) } /// Spec v0.4.0 @@ -121,10 +114,7 @@ pub fn process_eth1_data(state: &mut BeaconState, spec: &ChainSpec) { /// Spec v0.4.0 pub fn process_justification( state: &mut BeaconState, - current_total_balance: u64, - previous_total_balance: u64, - previous_epoch_boundary_attesting_balance: u64, - current_epoch_boundary_attesting_balance: u64, + total_balances: &TotalBalances, spec: &ChainSpec, ) { let previous_epoch = state.previous_epoch(spec); @@ -137,7 +127,8 @@ pub fn process_justification( // // - Set the 2nd bit of the bitfield. // - Set the previous epoch to be justified. - if (3 * previous_epoch_boundary_attesting_balance) >= (2 * previous_total_balance) { + if (3 * total_balances.previous_epoch_boundary_attesters) >= (2 * total_balances.previous_epoch) + { state.justification_bitfield |= 2; new_justified_epoch = previous_epoch; } @@ -145,7 +136,7 @@ pub fn process_justification( // // - Set the 1st bit of the bitfield. // - Set the current epoch to be justified. - if (3 * current_epoch_boundary_attesting_balance) >= (2 * current_total_balance) { + if (3 * total_balances.current_epoch_boundary_attesters) >= (2 * total_balances.current_epoch) { state.justification_bitfield |= 1; new_justified_epoch = current_epoch; } @@ -267,25 +258,26 @@ pub fn process_crosslinks( /// Spec v0.4.0 pub fn process_rewards_and_penalities( state: &mut BeaconState, - attesters: &mut Attesters, + statuses: &mut ValidatorStatuses, winning_root_for_shards: &WinningRootHashSet, spec: &ChainSpec, ) -> Result<(), Error> { let next_epoch = state.next_epoch(spec); - let previous_total_balance = attesters.balances.previous_epoch_total; + statuses.process_winning_roots(state, winning_root_for_shards, spec)?; - let base_reward_quotient = previous_total_balance.integer_sqrt() / spec.base_reward_quotient; + let total_balances = &statuses.total_balances; + + let base_reward_quotient = + total_balances.previous_epoch.integer_sqrt() / spec.base_reward_quotient; if base_reward_quotient == 0 { return Err(Error::BaseRewardQuotientIsZero); } - if previous_total_balance == 0 { + if total_balances.previous_epoch == 0 { return Err(Error::PreviousTotalBalanceIsZero); } - attesters.process_winning_roots(state, winning_root_for_shards, spec)?; - // Justification and finalization let epochs_since_finality = next_epoch - state.finalized_epoch; @@ -296,7 +288,7 @@ pub fn process_rewards_and_penalities( .enumerate() .map(|(index, &balance)| { let mut balance = balance; - let status = &attesters.statuses[index]; + let status = &statuses.get(index); let base_reward = state.base_reward(index, base_reward_quotient, spec); if epochs_since_finality <= 4 { @@ -304,8 +296,8 @@ pub fn process_rewards_and_penalities( if status.is_previous_epoch_attester { safe_add_assign!( balance, - base_reward * attesters.balances.previous_epoch_attesters - / previous_total_balance + base_reward * total_balances.previous_epoch_attesters + / total_balances.previous_epoch ); } else if status.is_active_in_previous_epoch { safe_sub_assign!(balance, base_reward); @@ -315,8 +307,8 @@ pub fn process_rewards_and_penalities( if status.is_previous_epoch_boundary_attester { safe_add_assign!( balance, - base_reward * attesters.balances.previous_epoch_boundary_attesters - / previous_total_balance + base_reward * total_balances.previous_epoch_boundary_attesters + / total_balances.previous_epoch ); } else if status.is_active_in_previous_epoch { safe_sub_assign!(balance, base_reward); @@ -326,8 +318,8 @@ pub fn process_rewards_and_penalities( if status.is_previous_epoch_head_attester { safe_add_assign!( balance, - base_reward * attesters.balances.previous_epoch_head_attesters - / previous_total_balance + base_reward * total_balances.previous_epoch_head_attesters + / total_balances.previous_epoch ); } else if status.is_active_in_previous_epoch { safe_sub_assign!(balance, base_reward); @@ -376,7 +368,7 @@ pub fn process_rewards_and_penalities( // Attestation inclusion for (index, _validator) in state.validator_registry.iter().enumerate() { - let status = &attesters.statuses[index]; + let status = &statuses.get(index); if status.is_previous_epoch_attester { let proposer_index = status.inclusion_info.proposer_index; diff --git a/eth2/state_processing/src/per_epoch_processing/attesters.rs b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs similarity index 89% rename from eth2/state_processing/src/per_epoch_processing/attesters.rs rename to eth2/state_processing/src/per_epoch_processing/validator_statuses.rs index 1ffbdf6525..70eeaf82a9 100644 --- a/eth2/state_processing/src/per_epoch_processing/attesters.rs +++ b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs @@ -74,8 +74,8 @@ impl AttesterStatus { #[derive(Default, Clone)] pub struct TotalBalances { - pub current_epoch_total: u64, - pub previous_epoch_total: u64, + pub current_epoch: u64, + pub previous_epoch: u64, pub current_epoch_attesters: u64, pub current_epoch_boundary_attesters: u64, pub previous_epoch_attesters: u64, @@ -84,33 +84,40 @@ pub struct TotalBalances { } #[derive(Clone)] -pub struct Attesters { - pub statuses: Vec, - pub balances: TotalBalances, +pub struct ValidatorStatuses { + statuses: Vec, + pub total_balances: TotalBalances, } -impl Attesters { +impl ValidatorStatuses { pub fn new(state: &BeaconState, spec: &ChainSpec) -> Self { let mut statuses = Vec::with_capacity(state.validator_registry.len()); - let mut balances = TotalBalances::default(); + let mut total_balances = TotalBalances::default(); for (i, validator) in state.validator_registry.iter().enumerate() { let mut status = AttesterStatus::default(); if validator.is_active_at(state.current_epoch(spec)) { status.is_active_in_current_epoch = true; - balances.current_epoch_total += state.get_effective_balance(i, spec); + total_balances.current_epoch += state.get_effective_balance(i, spec); } if validator.is_active_at(state.previous_epoch(spec)) { status.is_active_in_previous_epoch = true; - balances.previous_epoch_total += state.get_effective_balance(i, spec); + total_balances.previous_epoch += state.get_effective_balance(i, spec); } statuses.push(status); } - Self { statuses, balances } + Self { + statuses, + total_balances, + } + } + + pub fn get(&self, i: usize) -> &AttesterStatus { + &self.statuses[i] } pub fn process_attestations( @@ -129,15 +136,15 @@ impl Attesters { // Profile this attestation, updating the total balances and generating an // `AttesterStatus` object that applies to all participants in the attestation. if is_from_epoch(a, state.current_epoch(spec), spec) { - self.balances.current_epoch_attesters += attesting_balance; + self.total_balances.current_epoch_attesters += attesting_balance; status.is_current_epoch_attester = true; if has_common_epoch_boundary_root(a, state, state.current_epoch(spec), spec)? { - self.balances.current_epoch_boundary_attesters += attesting_balance; + self.total_balances.current_epoch_boundary_attesters += attesting_balance; status.is_current_epoch_boundary_attester = true; } } else if is_from_epoch(a, state.previous_epoch(spec), spec) { - self.balances.previous_epoch_attesters += attesting_balance; + self.total_balances.previous_epoch_attesters += attesting_balance; status.is_previous_epoch_attester = true; // The inclusion slot and distance are only required for previous epoch attesters. @@ -148,12 +155,12 @@ impl Attesters { }; if has_common_epoch_boundary_root(a, state, state.previous_epoch(spec), spec)? { - self.balances.previous_epoch_boundary_attesters += attesting_balance; + self.total_balances.previous_epoch_boundary_attesters += attesting_balance; status.is_previous_epoch_boundary_attester = true; } if has_common_beacon_block_root(a, state, spec)? { - self.balances.previous_epoch_head_attesters += attesting_balance; + self.total_balances.previous_epoch_head_attesters += attesting_balance; status.is_previous_epoch_head_attester = true; } } From f4959fc03c283a0c55193908e48b85e694f37974 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 14 Mar 2019 15:10:20 +1100 Subject: [PATCH 087/144] Add TestingBeaconStateBuilder fn for cloned kps Allows for faster test setups. Implemented method for fork choice tests. --- eth2/fork_choice/tests/tests.rs | 6 +++--- .../test_utils/testing_beacon_state_builder.rs | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/eth2/fork_choice/tests/tests.rs b/eth2/fork_choice/tests/tests.rs index 5fb963ea5e..cd5ff360f8 100644 --- a/eth2/fork_choice/tests/tests.rs +++ b/eth2/fork_choice/tests/tests.rs @@ -24,7 +24,7 @@ use std::collections::HashMap; use std::sync::Arc; use std::{fs::File, io::prelude::*, path::PathBuf}; use types::test_utils::TestingBeaconStateBuilder; -use types::{BeaconBlock, BeaconBlockBody, ChainSpec, Eth1Data, Hash256, Slot}; +use types::{BeaconBlock, BeaconBlockBody, ChainSpec, Eth1Data, Hash256, Keypair, Slot}; use yaml_rust::yaml; // Note: We Assume the block Id's are hex-encoded. @@ -218,7 +218,7 @@ fn load_test_cases_from_yaml(file_path: &str) -> Vec { // initialise a single validator and state. All blocks will reference this state root. fn setup_inital_state( fork_choice_algo: &ForkChoiceAlgorithm, - no_validators: usize, + num_validators: usize, ) -> (Box, Arc>, Hash256) { let db = Arc::new(MemoryDB::open()); let block_store = Arc::new(BeaconBlockStore::new(db.clone())); @@ -243,7 +243,7 @@ fn setup_inital_state( let spec = ChainSpec::foundation(); let state_builder = - TestingBeaconStateBuilder::from_deterministic_keypairs(no_validators, &spec); + TestingBeaconStateBuilder::from_single_keypair(num_validators, &Keypair::random(), &spec); let (state, _keypairs) = state_builder.build(); let state_root = state.canonical_root(); diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index b2cf28c8a3..c116cd1b71 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -74,6 +74,22 @@ impl TestingBeaconStateBuilder { TestingBeaconStateBuilder::from_keypairs(keypairs, spec) } + /// Uses the given keypair for all validators. + pub fn from_single_keypair( + validator_count: usize, + keypair: &Keypair, + spec: &ChainSpec, + ) -> Self { + debug!("Generating {} cloned keypairs...", validator_count); + + let mut keypairs = Vec::with_capacity(validator_count); + for _ in 0..validator_count { + keypairs.push(keypair.clone()) + } + + TestingBeaconStateBuilder::from_keypairs(keypairs, spec) + } + /// Creates the builder from an existing set of keypairs. pub fn from_keypairs(keypairs: Vec, spec: &ChainSpec) -> Self { let validator_count = keypairs.len(); From 12214e7eed21e2eb80914369371f9939a4ce6b57 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 14 Mar 2019 15:11:03 +1100 Subject: [PATCH 088/144] Ignore long running DB test --- beacon_node/db/src/stores/beacon_block_store.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/beacon_node/db/src/stores/beacon_block_store.rs b/beacon_node/db/src/stores/beacon_block_store.rs index bd5149cfdd..92d296c373 100644 --- a/beacon_node/db/src/stores/beacon_block_store.rs +++ b/beacon_node/db/src/stores/beacon_block_store.rs @@ -198,6 +198,7 @@ mod tests { } #[test] + #[ignore] fn test_block_at_slot() { let db = Arc::new(MemoryDB::open()); let bs = Arc::new(BeaconBlockStore::new(db.clone())); From 086e9574d2bce180ae8a60ef0f2845d029f80d06 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Thu, 14 Mar 2019 15:22:45 +1100 Subject: [PATCH 089/144] Correct all fork choice rules for children with no votes. --- eth2/fork_choice/src/bitwise_lmd_ghost.rs | 22 ++++++++++++++----- eth2/fork_choice/src/optimized_lmd_ghost.rs | 22 ++++++++++++++----- eth2/fork_choice/src/protolambda_lmd_ghost.rs | 1 - eth2/fork_choice/src/slow_lmd_ghost.rs | 7 ++++++ .../tests/bitwise_lmd_ghost_test_vectors.yaml | 15 ++++++++++++- .../tests/lmd_ghost_test_vectors.yaml | 12 ++++++++++ 6 files changed, 67 insertions(+), 12 deletions(-) delete mode 100644 eth2/fork_choice/src/protolambda_lmd_ghost.rs diff --git a/eth2/fork_choice/src/bitwise_lmd_ghost.rs b/eth2/fork_choice/src/bitwise_lmd_ghost.rs index fd1c3dea45..d7b10015be 100644 --- a/eth2/fork_choice/src/bitwise_lmd_ghost.rs +++ b/eth2/fork_choice/src/bitwise_lmd_ghost.rs @@ -409,11 +409,23 @@ impl ForkChoice for BitwiseLMDGhost { *child_votes.entry(child).or_insert_with(|| 0) += vote; } } - // given the votes on the children, find the best child - current_head = self - .choose_best_child(&child_votes) - .ok_or(ForkChoiceError::CannotFindBestChild)?; - trace!("Best child found: {}", current_head); + // check if we have votes of children, if not select the smallest hash child + if child_votes.is_empty() { + current_head = *children + .iter() + .min_by(|child1, child2| child1.cmp(child2)) + .expect("Must be children here"); + trace!( + "Children have no votes - smallest hash chosen: {}", + current_head + ); + } else { + // given the votes on the children, find the best child + current_head = self + .choose_best_child(&child_votes) + .ok_or(ForkChoiceError::CannotFindBestChild)?; + trace!("Best child found: {}", current_head); + } } // didn't find head yet, proceed to next iteration diff --git a/eth2/fork_choice/src/optimized_lmd_ghost.rs b/eth2/fork_choice/src/optimized_lmd_ghost.rs index 636ccdabc5..30c84e9e14 100644 --- a/eth2/fork_choice/src/optimized_lmd_ghost.rs +++ b/eth2/fork_choice/src/optimized_lmd_ghost.rs @@ -380,11 +380,23 @@ impl ForkChoice for OptimizedLMDGhost { *child_votes.entry(child).or_insert_with(|| 0) += vote; } } - // given the votes on the children, find the best child - current_head = self - .choose_best_child(&child_votes) - .ok_or(ForkChoiceError::CannotFindBestChild)?; - trace!("Best child found: {}", current_head); + // check if we have votes of children, if not select the smallest hash child + if child_votes.is_empty() { + current_head = *children + .iter() + .min_by(|child1, child2| child1.cmp(child2)) + .expect("Must be children here"); + trace!( + "Children have no votes - smallest hash chosen: {}", + current_head + ); + } else { + // given the votes on the children, find the best child + current_head = self + .choose_best_child(&child_votes) + .ok_or(ForkChoiceError::CannotFindBestChild)?; + trace!("Best child found: {}", current_head); + } } // didn't find head yet, proceed to next iteration diff --git a/eth2/fork_choice/src/protolambda_lmd_ghost.rs b/eth2/fork_choice/src/protolambda_lmd_ghost.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/eth2/fork_choice/src/protolambda_lmd_ghost.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/eth2/fork_choice/src/slow_lmd_ghost.rs b/eth2/fork_choice/src/slow_lmd_ghost.rs index ab4cd2adaf..abf13f21b0 100644 --- a/eth2/fork_choice/src/slow_lmd_ghost.rs +++ b/eth2/fork_choice/src/slow_lmd_ghost.rs @@ -210,6 +210,7 @@ impl ForkChoice for SlowLMDGhost { trace!("Children found: {:?}", children); let mut head_vote_count = 0; + head_hash = children[0]; for child_hash in children { let vote_count = self.get_vote_count(&latest_votes, &child_hash)?; trace!("Vote count for child: {} is: {}", child_hash, vote_count); @@ -218,6 +219,12 @@ impl ForkChoice for SlowLMDGhost { head_hash = *child_hash; head_vote_count = vote_count; } + // resolve ties - choose smaller hash + else if vote_count == head_vote_count { + if *child_hash < head_hash { + head_hash = *child_hash; + } + } } } Ok(head_hash) diff --git a/eth2/fork_choice/tests/bitwise_lmd_ghost_test_vectors.yaml b/eth2/fork_choice/tests/bitwise_lmd_ghost_test_vectors.yaml index 931d8decf2..61b0b05c40 100644 --- a/eth2/fork_choice/tests/bitwise_lmd_ghost_test_vectors.yaml +++ b/eth2/fork_choice/tests/bitwise_lmd_ghost_test_vectors.yaml @@ -128,4 +128,17 @@ test_cases: - b8: 4 - b9: 2 heads: - - id: 'b7' \ No newline at end of file + - id: 'b7' +- blocks: + - id: 'b0' + parent: 'b0' + - id: 'b1' + parent: 'b0' + - id: 'b2' + parent: 'b0' + weights: + - b1: 0 + - b2: 0 + heads: + - id: 'b1' + diff --git a/eth2/fork_choice/tests/lmd_ghost_test_vectors.yaml b/eth2/fork_choice/tests/lmd_ghost_test_vectors.yaml index dab998bebb..e7847de11a 100644 --- a/eth2/fork_choice/tests/lmd_ghost_test_vectors.yaml +++ b/eth2/fork_choice/tests/lmd_ghost_test_vectors.yaml @@ -51,3 +51,15 @@ test_cases: - b3: 6 heads: - id: 'b2' +- blocks: + - id: 'b0' + parent: 'b0' + - id: 'b1' + parent: 'b0' + - id: 'b2' + parent: 'b0' + weights: + - b1: 0 + - b2: 0 + heads: + - id: 'b1' From 1c1c15a122e1c589649a06cb27b227b4d5321230 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 14 Mar 2019 16:00:22 +1100 Subject: [PATCH 090/144] Tidy per epoch processing - Add comments to ValidatorStatuses - Add some checks to guard against a bad statuses list - Remove unused attester_sets.rs file. --- .../src/per_epoch_processing.rs | 16 ++- .../src/per_epoch_processing/attester_sets.rs | 133 ------------------ .../src/per_epoch_processing/errors.rs | 1 + .../validator_statuses.rs | 80 ++++++++++- 4 files changed, 87 insertions(+), 143 deletions(-) delete mode 100644 eth2/state_processing/src/per_epoch_processing/attester_sets.rs diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index 044d32eaed..8c4b8e88ba 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -7,7 +7,6 @@ use types::{validator_registry::get_active_validator_indices, *}; use validator_statuses::{TotalBalances, ValidatorStatuses}; use winning_root::{winning_root, WinningRoot}; -pub mod attester_sets; pub mod errors; pub mod inclusion_distance; pub mod tests; @@ -271,12 +270,18 @@ pub fn process_rewards_and_penalities( let base_reward_quotient = total_balances.previous_epoch.integer_sqrt() / spec.base_reward_quotient; + // Guard against a divide-by-zero during the validator balance update. if base_reward_quotient == 0 { return Err(Error::BaseRewardQuotientIsZero); } + // Guard against a divide-by-zero during the validator balance update. if total_balances.previous_epoch == 0 { return Err(Error::PreviousTotalBalanceIsZero); } + // Guard against an out-of-bounds during the validator balance update. + if statuses.statuses.len() != state.validator_balances.len() { + return Err(Error::ValidatorStatusesInconsistent); + } // Justification and finalization @@ -288,7 +293,7 @@ pub fn process_rewards_and_penalities( .enumerate() .map(|(index, &balance)| { let mut balance = balance; - let status = &statuses.get(index); + let status = &statuses.statuses[index]; let base_reward = state.base_reward(index, base_reward_quotient, spec); if epochs_since_finality <= 4 { @@ -367,8 +372,13 @@ pub fn process_rewards_and_penalities( // Attestation inclusion + // Guard against an out-of-bounds during the attester inclusion balance update. + if statuses.statuses.len() != state.validator_registry.len() { + return Err(Error::ValidatorStatusesInconsistent); + } + for (index, _validator) in state.validator_registry.iter().enumerate() { - let status = &statuses.get(index); + let status = &statuses.statuses[index]; if status.is_previous_epoch_attester { let proposer_index = status.inclusion_info.proposer_index; diff --git a/eth2/state_processing/src/per_epoch_processing/attester_sets.rs b/eth2/state_processing/src/per_epoch_processing/attester_sets.rs deleted file mode 100644 index 03f49c1d39..0000000000 --- a/eth2/state_processing/src/per_epoch_processing/attester_sets.rs +++ /dev/null @@ -1,133 +0,0 @@ -use fnv::FnvHashSet; -use types::*; - -/// A set of validator indices, along with the total balance of all those attesters. -#[derive(Default)] -pub struct Attesters { - /// A set of validator indices. - pub indices: FnvHashSet, - /// The total balance of all validators in `self.indices`. - pub balance: u64, -} - -impl Attesters { - /// Add the given indices to the set, incrementing the sets balance by the provided balance. - fn add(&mut self, additional_indices: &[usize], additional_balance: u64) { - self.indices.reserve(additional_indices.len()); - for i in additional_indices { - self.indices.insert(*i); - } - self.balance = self.balance.saturating_add(additional_balance); - } -} - -/// A collection of `Attester` objects, representing set of attesters that are rewarded/penalized -/// during an epoch transition. -pub struct AttesterSets { - /// All validators who attested during the state's current epoch. - pub current_epoch: Attesters, - /// All validators who attested that the beacon block root of the first slot of the state's - /// current epoch is the same as the one stored in this state. - /// - /// In short validators who agreed with the state about the first slot of the current epoch. - pub current_epoch_boundary: Attesters, - /// All validators who attested during the state's previous epoch. - pub previous_epoch: Attesters, - /// All validators who attested that the beacon block root of the first slot of the state's - /// previous epoch is the same as the one stored in this state. - /// - /// In short, validators who agreed with the state about the first slot of the previous epoch. - pub previous_epoch_boundary: Attesters, - /// All validators who attested that the beacon block root at the pending attestation's slot is - /// the same as the one stored in this state. - /// - /// In short, validators who agreed with the state about the current beacon block root when - /// they attested. - pub previous_epoch_head: Attesters, -} - -impl AttesterSets { - /// Loop through all attestations in the state and instantiate a complete `AttesterSets` struct. - /// - /// Spec v0.4.0 - pub fn new(state: &BeaconState, spec: &ChainSpec) -> Result { - let mut current_epoch = Attesters::default(); - let mut current_epoch_boundary = Attesters::default(); - let mut previous_epoch = Attesters::default(); - let mut previous_epoch_boundary = Attesters::default(); - let mut previous_epoch_head = Attesters::default(); - - for a in &state.latest_attestations { - let attesting_indices = - state.get_attestation_participants(&a.data, &a.aggregation_bitfield, spec)?; - let attesting_balance = state.get_total_balance(&attesting_indices, spec); - - if is_from_epoch(a, state.current_epoch(spec), spec) { - current_epoch.add(&attesting_indices, attesting_balance); - - if has_common_epoch_boundary_root(a, state, state.current_epoch(spec), spec)? { - current_epoch_boundary.add(&attesting_indices, attesting_balance); - } - } else if is_from_epoch(a, state.previous_epoch(spec), spec) { - previous_epoch.add(&attesting_indices, attesting_balance); - - if has_common_epoch_boundary_root(a, state, state.previous_epoch(spec), spec)? { - previous_epoch_boundary.add(&attesting_indices, attesting_balance); - } - - if has_common_beacon_block_root(a, state, spec)? { - previous_epoch_head.add(&attesting_indices, attesting_balance); - } - } - } - - Ok(Self { - current_epoch, - current_epoch_boundary, - previous_epoch, - previous_epoch_boundary, - previous_epoch_head, - }) - } -} - -/// Returns `true` if some `PendingAttestation` is from the supplied `epoch`. -/// -/// Spec v0.4.0 -fn is_from_epoch(a: &PendingAttestation, epoch: Epoch, spec: &ChainSpec) -> bool { - a.data.slot.epoch(spec.slots_per_epoch) == epoch -} - -/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for -/// the first slot of the given epoch. -/// -/// Spec v0.4.0 -fn has_common_epoch_boundary_root( - a: &PendingAttestation, - state: &BeaconState, - epoch: Epoch, - spec: &ChainSpec, -) -> Result { - let slot = epoch.start_slot(spec.slots_per_epoch); - let state_boundary_root = *state - .get_block_root(slot, spec) - .ok_or_else(|| BeaconStateError::InsufficientBlockRoots)?; - - Ok(a.data.epoch_boundary_root == state_boundary_root) -} - -/// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for -/// the current slot of the `PendingAttestation`. -/// -/// Spec v0.4.0 -fn has_common_beacon_block_root( - a: &PendingAttestation, - state: &BeaconState, - spec: &ChainSpec, -) -> Result { - let state_block_root = *state - .get_block_root(a.data.slot, spec) - .ok_or_else(|| BeaconStateError::InsufficientBlockRoots)?; - - Ok(a.data.beacon_block_root == state_block_root) -} diff --git a/eth2/state_processing/src/per_epoch_processing/errors.rs b/eth2/state_processing/src/per_epoch_processing/errors.rs index c60e00cae7..94fc0cca5b 100644 --- a/eth2/state_processing/src/per_epoch_processing/errors.rs +++ b/eth2/state_processing/src/per_epoch_processing/errors.rs @@ -8,6 +8,7 @@ pub enum EpochProcessingError { NoRandaoSeed, PreviousTotalBalanceIsZero, InclusionDistanceZero, + ValidatorStatusesInconsistent, /// Unable to get the inclusion distance for a validator that should have an inclusion /// distance. This indicates an internal inconsistency. /// diff --git a/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs index 70eeaf82a9..f76900f3b4 100644 --- a/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs +++ b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs @@ -1,26 +1,40 @@ use super::WinningRootHashSet; use types::*; +/// Sets the boolean `var` on `self` to be true if it is true on `other`. Otherwise leaves `self` +/// as is. macro_rules! set_self_if_other_is_true { ($self_: ident, $other: ident, $var: ident) => { - $self_.$var = $other.$var & !$self_.$var; + if $other.$var { + $self_.$var = true; + } }; } +/// The information required to reward some validator for their participation in a "winning" +/// crosslink root. #[derive(Default, Clone)] pub struct WinningRootInfo { + /// The total balance of the crosslink committee. pub total_committee_balance: u64, + /// The total balance of the crosslink committee that attested for the "winning" root. pub total_attesting_balance: u64, } +/// The information required to reward a block producer for including an attestation in a block. #[derive(Clone)] pub struct InclusionInfo { + /// The earliest slot a validator had an attestation included in the previous epoch. pub slot: Slot, + /// The distance between the attestation slot and the slot that attestation was included in a + /// block. pub distance: Slot, + /// The index of the proposer at the slot where the attestation was included. pub proposer_index: usize, } impl Default for InclusionInfo { + /// Defaults to `slot` and `distance` at their maximum values and `proposer_index` at zero. fn default() -> Self { Self { slot: Slot::max_value(), @@ -31,6 +45,8 @@ impl Default for InclusionInfo { } impl InclusionInfo { + /// Tests if some `other` `InclusionInfo` has a lower inclusion slot than `self`. If so, + /// replaces `self` with `other`. pub fn update(&mut self, other: &Self) { if other.slot < self.slot { self.slot = other.slot; @@ -40,23 +56,43 @@ impl InclusionInfo { } } +/// Information required to reward some validator during the current and previous epoch. #[derive(Default, Clone)] pub struct AttesterStatus { + /// True if the validator was active in the state's _current_ epoch. pub is_active_in_current_epoch: bool, + /// True if the validator was active in the state's _previous_ epoch. pub is_active_in_previous_epoch: bool, + /// True if the validator had an attestation included in the _current_ epoch. pub is_current_epoch_attester: bool, + /// True if the validator's beacon block root attestation for the first slot of the _current_ + /// epoch matches the block root known to the state. pub is_current_epoch_boundary_attester: bool, + /// True if the validator had an attestation included in the _previous_ epoch. pub is_previous_epoch_attester: bool, + /// True if the validator's beacon block root attestation for the first slot of the _previous_ + /// epoch matches the block root known to the state. pub is_previous_epoch_boundary_attester: bool, + /// True if the validator's beacon block root attestation in the _previous_ epoch at the + /// attestation's slot (`attestation_data.slot`) matches the block root known to the state. pub is_previous_epoch_head_attester: bool, + /// Information used to reward the block producer of this validators earliest-included + /// attestation. pub inclusion_info: InclusionInfo, + /// Information used to reward/penalize the validator if they voted in the super-majority for + /// some shard block. pub winning_root_info: Option, } impl AttesterStatus { - /// Note: does not update the winning root info. + /// Accepts some `other` `AttesterStatus` and updates `self` if required. + /// + /// Will never set one of the `bool` fields to `false`, it will only set it to `true` if other + /// contains a `true` field. + /// + /// Note: does not update the winning root info, this is done manually. pub fn update(&mut self, other: &Self) { // Update all the bool fields, only updating `self` if `other` is true (never setting // `self` to false). @@ -72,24 +108,46 @@ impl AttesterStatus { } } +/// The total effective balances for different sets of validators during the previous and current +/// epochs. #[derive(Default, Clone)] pub struct TotalBalances { + /// The total effective balance of all active validators during the _current_ epoch. pub current_epoch: u64, + /// The total effective balance of all active validators during the _previous_ epoch. pub previous_epoch: u64, + /// The total effective balance of all validators who attested during the _current_ epoch. pub current_epoch_attesters: u64, + /// The total effective balance of all validators who attested during the _current_ epoch and + /// agreed with the state about the beacon block at the first slot of the _current_ epoch. pub current_epoch_boundary_attesters: u64, + /// The total effective balance of all validators who attested during the _previous_ epoch. pub previous_epoch_attesters: u64, + /// The total effective balance of all validators who attested during the _previous_ epoch and + /// agreed with the state about the beacon block at the first slot of the _previous_ epoch. pub previous_epoch_boundary_attesters: u64, + /// The total effective balance of all validators who attested during the _previous_ epoch and + /// agreed with the state about the beacon block at the time of attestation. pub previous_epoch_head_attesters: u64, } +/// Summarised information about validator participation in the _previous and _current_ epochs of +/// some `BeaconState`. #[derive(Clone)] pub struct ValidatorStatuses { - statuses: Vec, + /// Information about each individual validator from the state's validator registy. + pub statuses: Vec, + /// Summed balances for various sets of validators. pub total_balances: TotalBalances, } impl ValidatorStatuses { + /// Initializes a new instance, determining: + /// + /// - Active validators + /// - Total balances for the current and previous epochs. + /// + /// Spec v0.4.0 pub fn new(state: &BeaconState, spec: &ChainSpec) -> Self { let mut statuses = Vec::with_capacity(state.validator_registry.len()); let mut total_balances = TotalBalances::default(); @@ -116,10 +174,10 @@ impl ValidatorStatuses { } } - pub fn get(&self, i: usize) -> &AttesterStatus { - &self.statuses[i] - } - + /// Process some attestations from the given `state` updating the `statuses` and + /// `total_balances` fields. + /// + /// Spec v0.4.0 pub fn process_attestations( &mut self, state: &BeaconState, @@ -174,6 +232,10 @@ impl ValidatorStatuses { Ok(()) } + /// Update the `statuses` for each validator based upon whether or not they attested to the + /// "winning" shard block root for the previous epoch. + /// + /// Spec v0.4.0 pub fn process_winning_roots( &mut self, state: &BeaconState, @@ -207,6 +269,10 @@ impl ValidatorStatuses { } } +/// Returns the distance between when the attestation was created and when it was included in a +/// block. +/// +/// Spec v0.4.0 fn inclusion_distance(a: &PendingAttestation) -> Slot { a.inclusion_slot - a.data.slot } From fc0cdb8226f66fa5056b9677252d5c66bb4303b5 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 14 Mar 2019 18:10:36 +1300 Subject: [PATCH 091/144] Start building non-worst case benches --- .../benches/bench_block_processing.rs | 231 +++++++++++------- .../testing_beacon_block_builder.rs | 15 +- 2 files changed, 152 insertions(+), 94 deletions(-) diff --git a/eth2/state_processing/benches/bench_block_processing.rs b/eth2/state_processing/benches/bench_block_processing.rs index 128b1051b2..a315717b20 100644 --- a/eth2/state_processing/benches/bench_block_processing.rs +++ b/eth2/state_processing/benches/bench_block_processing.rs @@ -17,6 +17,8 @@ use types::*; pub fn bench_block_processing_n_validators(c: &mut Criterion, validator_count: usize) { let spec = ChainSpec::foundation(); + let bench_builder = BlockBenchingBuilder::new(validator_count, &spec); + let (mut state, keypairs) = build_state(validator_count, &spec); let block = build_block(&mut state, &keypairs, &spec); @@ -94,105 +96,160 @@ fn build_state(validator_count: usize, spec: &ChainSpec) -> (BeaconState, Vec BeaconBlock { - let mut builder = TestingBeaconBlockBuilder::new(spec); +pub struct BlockBenchingBuilder { + pub state_builder: TestingBeaconStateBuilder, + pub block_builder: TestingBeaconBlockBuilder, - builder.set_slot(state.slot); + pub num_validators: usize, + pub num_proposer_slashings: usize, + pub num_attester_slashings: usize, + pub num_indices_per_slashable_vote: usize, + pub num_attestations: usize, + pub num_deposits: usize, + pub num_exits: usize, + pub num_transfers: usize, +} - let proposer_index = state.get_beacon_proposer_index(state.slot, spec).unwrap(); - let keypair = &keypairs[proposer_index]; +impl BlockBenchingBuilder { + pub fn new(num_validators: usize, spec: &ChainSpec) -> Self { + let mut state_builder = + TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(num_validators, &spec); + let mut block_builder = TestingBeaconBlockBuilder::new(spec); - builder.set_randao_reveal(&keypair.sk, &state.fork, spec); - - // Used as a stream of validator indices for use in slashings, exits, etc. - let mut validators_iter = (0..keypairs.len() as u64).into_iter(); - - // Insert the maximum possible number of `ProposerSlashing` objects. - debug!( - "Inserting {} proposer slashings...", - spec.max_proposer_slashings - ); - for _ in 0..spec.max_proposer_slashings { - let validator_index = validators_iter.next().expect("Insufficient validators."); - - builder.insert_proposer_slashing( - validator_index, - &keypairs[validator_index as usize].sk, - &state.fork, - spec, - ); + Self { + state_builder, + block_builder, + num_validators: 0, + num_proposer_slashings: 0, + num_attester_slashings: 0, + num_indices_per_slashable_vote: spec.max_indices_per_slashable_vote as usize, + num_attestations: 0, + num_deposits: 0, + num_exits: 0, + num_transfers: 0 + } } - // Insert the maximum possible number of `AttesterSlashing` objects - debug!( - "Inserting {} attester slashings...", - spec.max_attester_slashings - ); - for _ in 0..spec.max_attester_slashings { - let mut attesters: Vec = vec![]; - let mut secret_keys: Vec<&SecretKey> = vec![]; + pub fn maximize_block_operations(&mut self, spec: &ChainSpec) { + self.num_proposer_slashings = spec.max_proposer_slashings as usize; + self.num_attester_slashings = spec.max_attester_slashings as usize; + self.num_indices_per_slashable_vote = spec.max_indices_per_slashable_vote as usize; + self.num_attestations = spec.max_attestations as usize; + self.num_deposits = spec.max_deposits as usize; + self.num_exits = spec.max_voluntary_exits as usize; + self.num_transfers = spec.max_transfers as usize; + } - for _ in 0..spec.max_indices_per_slashable_vote { + pub fn set_slot(&mut self, slot: Slot, spec: &ChainSpec) { + self.state_builder.teleport_to_slot(slot, &spec); + } + + pub fn build_caches(&mut self, spec: &ChainSpec) { + // Builds all caches; benches will not contain shuffling/committee building times. + self.state_builder.build_caches(&spec).unwrap(); + } + + pub fn build(self, spec: &ChainSpec) -> (BeaconBlock, BeaconState) { + let (mut state, keypairs) = self.state_builder.build(); + let builder = &mut self.block_builder; + + builder.set_slot(state.slot); + + let proposer_index = state.get_beacon_proposer_index(state.slot, spec).unwrap(); + let keypair = &keypairs[proposer_index]; + + builder.set_randao_reveal(&keypair.sk, &state.fork, spec); + + // Used as a stream of validator indices for use in slashings, exits, etc. + let mut validators_iter = (0..keypairs.len() as u64).into_iter(); + + // Insert `ProposerSlashing` objects. + debug!( + "Inserting {} proposer slashings...", + self.num_proposer_slashings + ); + for _ in 0..self.num_proposer_slashings { let validator_index = validators_iter.next().expect("Insufficient validators."); - attesters.push(validator_index); - secret_keys.push(&keypairs[validator_index as usize].sk); + builder.insert_proposer_slashing( + validator_index, + &keypairs[validator_index as usize].sk, + &state.fork, + spec, + ); } - builder.insert_attester_slashing(&attesters, &secret_keys, &state.fork, spec); - } - - // Insert the maximum possible number of `Attestation` objects. - debug!("Inserting {} attestations...", spec.max_attestations); - let all_secret_keys: Vec<&SecretKey> = keypairs.iter().map(|keypair| &keypair.sk).collect(); - builder - .fill_with_attestations(state, &all_secret_keys, spec) - .unwrap(); - - // Insert the maximum possible number of `Deposit` objects. - debug!("Inserting {} deposits...", spec.max_deposits); - for i in 0..spec.max_deposits { - builder.insert_deposit(32_000_000_000, state.deposit_index + i, state, spec); - } - - // Insert the maximum possible number of `Exit` objects. - debug!("Inserting {} exits...", spec.max_voluntary_exits); - for _ in 0..spec.max_voluntary_exits { - let validator_index = validators_iter.next().expect("Insufficient validators."); - - builder.insert_exit( - state, - validator_index, - &keypairs[validator_index as usize].sk, - spec, + // Insert `AttesterSlashing` objects + debug!( + "Inserting {} attester slashings...", + self.num_attester_slashings ); + for _ in 0..self.num_attester_slashings { + let mut attesters: Vec = vec![]; + let mut secret_keys: Vec<&SecretKey> = vec![]; + + for _ in 0..self.num_indices_per_slashable_vote { + let validator_index = validators_iter.next().expect("Insufficient validators."); + + attesters.push(validator_index); + secret_keys.push(&keypairs[validator_index as usize].sk); + } + + builder.insert_attester_slashing(&attesters, &secret_keys, &state.fork, spec); + } + + // Insert `Attestation` objects. + debug!("Inserting {} attestations...", self.num_attestations); + let all_secret_keys: Vec<&SecretKey> = keypairs.iter().map(|keypair| &keypair.sk).collect(); + builder + .insert_attestations(&state, &all_secret_keys, self.num_attestations as usize, spec) + .unwrap(); + + // Insert `Deposit` objects. + debug!("Inserting {} deposits...", self.num_deposits); + for i in 0..self.num_deposits { + builder.insert_deposit(32_000_000_000, state.deposit_index + (i as u64), &state, spec); + } + + // Insert the maximum possible number of `Exit` objects. + debug!("Inserting {} exits...", self.num_exits); + for _ in 0..self.num_exits { + let validator_index = validators_iter.next().expect("Insufficient validators."); + + builder.insert_exit( + &state, + validator_index, + &keypairs[validator_index as usize].sk, + spec, + ); + } + + // Insert the maximum possible number of `Transfer` objects. + debug!("Inserting {} transfers...", self.num_transfers); + for _ in 0..self.num_transfers { + let validator_index = validators_iter.next().expect("Insufficient validators."); + + // Manually set the validator to be withdrawn. + state.validator_registry[validator_index as usize].withdrawable_epoch = + state.previous_epoch(spec); + + builder.insert_transfer( + &state, + validator_index, + validator_index, + 1, + keypairs[validator_index as usize].clone(), + spec, + ); + } + + let mut block = builder.build(&keypair.sk, &state.fork, spec); + + // Set the eth1 data to be different from the state. + block.eth1_data.block_hash = Hash256::from_slice(&vec![42; 32]); + + (block, state) } - - // Insert the maximum possible number of `Transfer` objects. - debug!("Inserting {} transfers...", spec.max_transfers); - for _ in 0..spec.max_transfers { - let validator_index = validators_iter.next().expect("Insufficient validators."); - - // Manually set the validator to be withdrawn. - state.validator_registry[validator_index as usize].withdrawable_epoch = - state.previous_epoch(spec); - - builder.insert_transfer( - state, - validator_index, - validator_index, - 1, - keypairs[validator_index as usize].clone(), - spec, - ); - } - - let mut block = builder.build(&keypair.sk, &state.fork, spec); - - // Set the eth1 data to be different from the state. - block.eth1_data.block_hash = Hash256::from_slice(&vec![42; 32]); - - block } /// Run the detailed benchmarking suite on the given `BeaconState`. diff --git a/eth2/types/src/test_utils/testing_beacon_block_builder.rs b/eth2/types/src/test_utils/testing_beacon_block_builder.rs index 97e395e1f5..ecb42e27b8 100644 --- a/eth2/types/src/test_utils/testing_beacon_block_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_block_builder.rs @@ -74,19 +74,20 @@ impl TestingBeaconBlockBuilder { self.block.body.attester_slashings.push(attester_slashing); } - /// Fills the block with `MAX_ATTESTATIONS` attestations. + /// Fills the block with `num_attestations` attestations. /// /// It will first go and get each committee that is able to include an attestation in this - /// block. If there are enough committees, it will produce an attestation for each. If there - /// are _not_ enough committees, it will start splitting the committees in half until it + /// block. If there _are_ enough committees, it will produce an attestation for each. If there + /// _are not_ enough committees, it will start splitting the committees in half until it /// achieves the target. It will then produce separate attestations for each split committee. /// /// Note: the signed messages of the split committees will be identical -- it would be possible /// to aggregate these split attestations. - pub fn fill_with_attestations( + pub fn insert_attestations( &mut self, state: &BeaconState, secret_keys: &[&SecretKey], + num_attestations: usize, spec: &ChainSpec, ) -> Result<(), BeaconStateError> { let mut slot = self.block.slot - spec.min_attestation_inclusion_delay; @@ -110,7 +111,7 @@ impl TestingBeaconBlockBuilder { } for (committee, shard) in state.get_crosslink_committees_at_slot(slot, spec)? { - if attestations_added >= spec.max_attestations { + if attestations_added >= num_attestations { break; } @@ -125,12 +126,12 @@ impl TestingBeaconBlockBuilder { // Loop through all the committees, splitting each one in half until we have // `MAX_ATTESTATIONS` committees. loop { - if committees.len() >= spec.max_attestations as usize { + if committees.len() >= num_attestations as usize { break; } for index in 0..committees.len() { - if committees.len() >= spec.max_attestations as usize { + if committees.len() >= num_attestations as usize { break; } From 2535f47f132006480b0f5dc85d2a92f6e6ddd066 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 14 Mar 2019 16:44:56 +1100 Subject: [PATCH 092/144] Add builder for benches Also adds a "sane" case for block processing --- .../benches/bench_block_processing.rs | 243 +----------------- eth2/state_processing/benches/benches.rs | 94 ++++++- .../benches/block_benching_builder.rs | 175 +++++++++++++ .../testing_beacon_block_builder.rs | 2 +- 4 files changed, 266 insertions(+), 248 deletions(-) create mode 100644 eth2/state_processing/benches/block_benching_builder.rs diff --git a/eth2/state_processing/benches/bench_block_processing.rs b/eth2/state_processing/benches/bench_block_processing.rs index a315717b20..2ee08c96a0 100644 --- a/eth2/state_processing/benches/bench_block_processing.rs +++ b/eth2/state_processing/benches/bench_block_processing.rs @@ -1,6 +1,5 @@ use criterion::Criterion; use criterion::{black_box, Benchmark}; -use log::debug; use ssz::TreeHash; use state_processing::{ per_block_processing, @@ -10,252 +9,12 @@ use state_processing::{ verify_block_signature, }, }; -use types::test_utils::{TestingBeaconBlockBuilder, TestingBeaconStateBuilder}; use types::*; -/// Run the benchmarking suite on a foundation spec with 16,384 validators. -pub fn bench_block_processing_n_validators(c: &mut Criterion, validator_count: usize) { - let spec = ChainSpec::foundation(); - - let bench_builder = BlockBenchingBuilder::new(validator_count, &spec); - - let (mut state, keypairs) = build_state(validator_count, &spec); - let block = build_block(&mut state, &keypairs, &spec); - - assert_eq!( - block.body.proposer_slashings.len(), - spec.max_proposer_slashings as usize, - "The block should have the maximum possible proposer slashings" - ); - - assert_eq!( - block.body.attester_slashings.len(), - spec.max_attester_slashings as usize, - "The block should have the maximum possible attester slashings" - ); - - for attester_slashing in &block.body.attester_slashings { - let len_1 = attester_slashing - .slashable_attestation_1 - .validator_indices - .len(); - let len_2 = attester_slashing - .slashable_attestation_1 - .validator_indices - .len(); - assert!( - (len_1 == len_2) && (len_2 == spec.max_indices_per_slashable_vote as usize), - "Each attester slashing should have the maximum possible validator indices" - ); - } - - assert_eq!( - block.body.attestations.len(), - spec.max_attestations as usize, - "The block should have the maximum possible attestations." - ); - - assert_eq!( - block.body.deposits.len(), - spec.max_deposits as usize, - "The block should have the maximum possible deposits." - ); - - assert_eq!( - block.body.voluntary_exits.len(), - spec.max_voluntary_exits as usize, - "The block should have the maximum possible voluntary exits." - ); - - assert_eq!( - block.body.transfers.len(), - spec.max_transfers as usize, - "The block should have the maximum possible transfers." - ); - - bench_block_processing( - c, - &block, - &state, - &spec, - &format!("{}_validators", validator_count), - ); -} - -fn build_state(validator_count: usize, spec: &ChainSpec) -> (BeaconState, Vec) { - let mut builder = - TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, &spec); - - // Set the state to be just before an epoch transition. - let target_slot = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch); - builder.teleport_to_slot(target_slot, &spec); - - // Builds all caches; benches will not contain shuffling/committee building times. - builder.build_caches(&spec).unwrap(); - - builder.build() -} - -pub struct BlockBenchingBuilder { - pub state_builder: TestingBeaconStateBuilder, - pub block_builder: TestingBeaconBlockBuilder, - - pub num_validators: usize, - pub num_proposer_slashings: usize, - pub num_attester_slashings: usize, - pub num_indices_per_slashable_vote: usize, - pub num_attestations: usize, - pub num_deposits: usize, - pub num_exits: usize, - pub num_transfers: usize, -} - -impl BlockBenchingBuilder { - pub fn new(num_validators: usize, spec: &ChainSpec) -> Self { - let mut state_builder = - TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(num_validators, &spec); - let mut block_builder = TestingBeaconBlockBuilder::new(spec); - - Self { - state_builder, - block_builder, - num_validators: 0, - num_proposer_slashings: 0, - num_attester_slashings: 0, - num_indices_per_slashable_vote: spec.max_indices_per_slashable_vote as usize, - num_attestations: 0, - num_deposits: 0, - num_exits: 0, - num_transfers: 0 - } - } - - pub fn maximize_block_operations(&mut self, spec: &ChainSpec) { - self.num_proposer_slashings = spec.max_proposer_slashings as usize; - self.num_attester_slashings = spec.max_attester_slashings as usize; - self.num_indices_per_slashable_vote = spec.max_indices_per_slashable_vote as usize; - self.num_attestations = spec.max_attestations as usize; - self.num_deposits = spec.max_deposits as usize; - self.num_exits = spec.max_voluntary_exits as usize; - self.num_transfers = spec.max_transfers as usize; - } - - pub fn set_slot(&mut self, slot: Slot, spec: &ChainSpec) { - self.state_builder.teleport_to_slot(slot, &spec); - } - - pub fn build_caches(&mut self, spec: &ChainSpec) { - // Builds all caches; benches will not contain shuffling/committee building times. - self.state_builder.build_caches(&spec).unwrap(); - } - - pub fn build(self, spec: &ChainSpec) -> (BeaconBlock, BeaconState) { - let (mut state, keypairs) = self.state_builder.build(); - let builder = &mut self.block_builder; - - builder.set_slot(state.slot); - - let proposer_index = state.get_beacon_proposer_index(state.slot, spec).unwrap(); - let keypair = &keypairs[proposer_index]; - - builder.set_randao_reveal(&keypair.sk, &state.fork, spec); - - // Used as a stream of validator indices for use in slashings, exits, etc. - let mut validators_iter = (0..keypairs.len() as u64).into_iter(); - - // Insert `ProposerSlashing` objects. - debug!( - "Inserting {} proposer slashings...", - self.num_proposer_slashings - ); - for _ in 0..self.num_proposer_slashings { - let validator_index = validators_iter.next().expect("Insufficient validators."); - - builder.insert_proposer_slashing( - validator_index, - &keypairs[validator_index as usize].sk, - &state.fork, - spec, - ); - } - - // Insert `AttesterSlashing` objects - debug!( - "Inserting {} attester slashings...", - self.num_attester_slashings - ); - for _ in 0..self.num_attester_slashings { - let mut attesters: Vec = vec![]; - let mut secret_keys: Vec<&SecretKey> = vec![]; - - for _ in 0..self.num_indices_per_slashable_vote { - let validator_index = validators_iter.next().expect("Insufficient validators."); - - attesters.push(validator_index); - secret_keys.push(&keypairs[validator_index as usize].sk); - } - - builder.insert_attester_slashing(&attesters, &secret_keys, &state.fork, spec); - } - - // Insert `Attestation` objects. - debug!("Inserting {} attestations...", self.num_attestations); - let all_secret_keys: Vec<&SecretKey> = keypairs.iter().map(|keypair| &keypair.sk).collect(); - builder - .insert_attestations(&state, &all_secret_keys, self.num_attestations as usize, spec) - .unwrap(); - - // Insert `Deposit` objects. - debug!("Inserting {} deposits...", self.num_deposits); - for i in 0..self.num_deposits { - builder.insert_deposit(32_000_000_000, state.deposit_index + (i as u64), &state, spec); - } - - // Insert the maximum possible number of `Exit` objects. - debug!("Inserting {} exits...", self.num_exits); - for _ in 0..self.num_exits { - let validator_index = validators_iter.next().expect("Insufficient validators."); - - builder.insert_exit( - &state, - validator_index, - &keypairs[validator_index as usize].sk, - spec, - ); - } - - // Insert the maximum possible number of `Transfer` objects. - debug!("Inserting {} transfers...", self.num_transfers); - for _ in 0..self.num_transfers { - let validator_index = validators_iter.next().expect("Insufficient validators."); - - // Manually set the validator to be withdrawn. - state.validator_registry[validator_index as usize].withdrawable_epoch = - state.previous_epoch(spec); - - builder.insert_transfer( - &state, - validator_index, - validator_index, - 1, - keypairs[validator_index as usize].clone(), - spec, - ); - } - - let mut block = builder.build(&keypair.sk, &state.fork, spec); - - // Set the eth1 data to be different from the state. - block.eth1_data.block_hash = Hash256::from_slice(&vec![42; 32]); - - (block, state) - } -} - /// Run the detailed benchmarking suite on the given `BeaconState`. /// /// `desc` will be added to the title of each bench. -fn bench_block_processing( +pub fn bench_block_processing( c: &mut Criterion, initial_block: &BeaconBlock, initial_state: &BeaconState, diff --git a/eth2/state_processing/benches/benches.rs b/eth2/state_processing/benches/benches.rs index af384b00a2..685858c78c 100644 --- a/eth2/state_processing/benches/benches.rs +++ b/eth2/state_processing/benches/benches.rs @@ -1,23 +1,107 @@ +use block_benching_builder::BlockBenchingBuilder; use criterion::Criterion; use criterion::{criterion_group, criterion_main}; use env_logger::{Builder, Env}; +use log::info; +use types::*; mod bench_block_processing; mod bench_epoch_processing; +mod block_benching_builder; pub const VALIDATOR_COUNT: usize = 16_384; // `LOG_LEVEL == "debug"` gives logs, but they're very noisy and slow down benching. -pub const LOG_LEVEL: &str = ""; +pub const LOG_LEVEL: &str = "info"; -pub fn state_processing(c: &mut Criterion) { +/// Build a worst-case block and benchmark processing it. +pub fn block_processing_worst_case(c: &mut Criterion) { if LOG_LEVEL != "" { Builder::from_env(Env::default().default_filter_or(LOG_LEVEL)).init(); } + info!( + "Building worst case block bench with {} validators", + VALIDATOR_COUNT + ); - bench_epoch_processing::bench_epoch_processing_n_validators(c, VALIDATOR_COUNT); - bench_block_processing::bench_block_processing_n_validators(c, VALIDATOR_COUNT); + // Use the specifications from the Eth2.0 spec. + let spec = ChainSpec::foundation(); + + // Create a builder for configuring the block and state for benching. + let mut bench_builder = BlockBenchingBuilder::new(VALIDATOR_COUNT, &spec); + + // Set the number of included operations to be maximum (e.g., `MAX_ATTESTATIONS`, etc.) + bench_builder.maximize_block_operations(&spec); + + // Set the state and block to be in the last slot of the 4th epoch. + let last_slot_of_epoch = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch); + bench_builder.set_slot(last_slot_of_epoch, &spec); + + // Build all the state caches so the build times aren't included in the benches. + bench_builder.build_caches(&spec); + + // Generate the block and state. + let (block, state) = bench_builder.build(&spec); + + // Run the benches. + bench_block_processing::bench_block_processing( + c, + &block, + &state, + &spec, + &format!("{}_validators/worst_case", VALIDATOR_COUNT), + ); } -criterion_group!(benches, state_processing); +/// Build a reasonable-case block and benchmark processing it. +pub fn block_processing_reasonable_case(c: &mut Criterion) { + info!( + "Building reasonable case block bench with {} validators", + VALIDATOR_COUNT + ); + + // Use the specifications from the Eth2.0 spec. + let spec = ChainSpec::foundation(); + + // Create a builder for configuring the block and state for benching. + let mut bench_builder = BlockBenchingBuilder::new(VALIDATOR_COUNT, &spec); + + // Set the number of included operations to what we might expect normally. + bench_builder.num_proposer_slashings = 0; + bench_builder.num_attester_slashings = 0; + bench_builder.num_attestations = (spec.shard_count / spec.slots_per_epoch) as usize; + bench_builder.num_deposits = 2; + bench_builder.num_exits = 2; + bench_builder.num_transfers = 2; + + // Set the state and block to be in the last slot of the 4th epoch. + let last_slot_of_epoch = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch); + bench_builder.set_slot(last_slot_of_epoch, &spec); + + // Build all the state caches so the build times aren't included in the benches. + bench_builder.build_caches(&spec); + + // Generate the block and state. + let (block, state) = bench_builder.build(&spec); + + // Run the benches. + bench_block_processing::bench_block_processing( + c, + &block, + &state, + &spec, + &format!("{}_validators/reasonable_case", VALIDATOR_COUNT), + ); +} + +pub fn state_processing(c: &mut Criterion) { + bench_epoch_processing::bench_epoch_processing_n_validators(c, VALIDATOR_COUNT); +} + +criterion_group!( + benches, + block_processing_reasonable_case, + block_processing_worst_case, + state_processing +); criterion_main!(benches); diff --git a/eth2/state_processing/benches/block_benching_builder.rs b/eth2/state_processing/benches/block_benching_builder.rs new file mode 100644 index 0000000000..b993851d7b --- /dev/null +++ b/eth2/state_processing/benches/block_benching_builder.rs @@ -0,0 +1,175 @@ +use log::info; +use types::test_utils::{TestingBeaconBlockBuilder, TestingBeaconStateBuilder}; +use types::*; + +pub struct BlockBenchingBuilder { + pub state_builder: TestingBeaconStateBuilder, + pub block_builder: TestingBeaconBlockBuilder, + + pub num_validators: usize, + pub num_proposer_slashings: usize, + pub num_attester_slashings: usize, + pub num_indices_per_slashable_vote: usize, + pub num_attestations: usize, + pub num_deposits: usize, + pub num_exits: usize, + pub num_transfers: usize, +} + +impl BlockBenchingBuilder { + pub fn new(num_validators: usize, spec: &ChainSpec) -> Self { + let state_builder = + TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(num_validators, &spec); + let block_builder = TestingBeaconBlockBuilder::new(spec); + + Self { + state_builder, + block_builder, + num_validators: 0, + num_proposer_slashings: 0, + num_attester_slashings: 0, + num_indices_per_slashable_vote: spec.max_indices_per_slashable_vote as usize, + num_attestations: 0, + num_deposits: 0, + num_exits: 0, + num_transfers: 0, + } + } + + pub fn maximize_block_operations(&mut self, spec: &ChainSpec) { + self.num_proposer_slashings = spec.max_proposer_slashings as usize; + self.num_attester_slashings = spec.max_attester_slashings as usize; + self.num_indices_per_slashable_vote = spec.max_indices_per_slashable_vote as usize; + self.num_attestations = spec.max_attestations as usize; + self.num_deposits = spec.max_deposits as usize; + self.num_exits = spec.max_voluntary_exits as usize; + self.num_transfers = spec.max_transfers as usize; + } + + pub fn set_slot(&mut self, slot: Slot, spec: &ChainSpec) { + self.state_builder.teleport_to_slot(slot, &spec); + } + + pub fn build_caches(&mut self, spec: &ChainSpec) { + // Builds all caches; benches will not contain shuffling/committee building times. + self.state_builder.build_caches(&spec).unwrap(); + } + + pub fn build(mut self, spec: &ChainSpec) -> (BeaconBlock, BeaconState) { + let (mut state, keypairs) = self.state_builder.build(); + let builder = &mut self.block_builder; + + builder.set_slot(state.slot); + + let proposer_index = state.get_beacon_proposer_index(state.slot, spec).unwrap(); + let keypair = &keypairs[proposer_index]; + + builder.set_randao_reveal(&keypair.sk, &state.fork, spec); + + // Used as a stream of validator indices for use in slashings, exits, etc. + let mut validators_iter = (0..keypairs.len() as u64).into_iter(); + + // Insert `ProposerSlashing` objects. + for _ in 0..self.num_proposer_slashings { + let validator_index = validators_iter.next().expect("Insufficient validators."); + + builder.insert_proposer_slashing( + validator_index, + &keypairs[validator_index as usize].sk, + &state.fork, + spec, + ); + } + info!( + "Inserted {} proposer slashings.", + builder.block.body.proposer_slashings.len() + ); + + // Insert `AttesterSlashing` objects + for _ in 0..self.num_attester_slashings { + let mut attesters: Vec = vec![]; + let mut secret_keys: Vec<&SecretKey> = vec![]; + + for _ in 0..self.num_indices_per_slashable_vote { + let validator_index = validators_iter.next().expect("Insufficient validators."); + + attesters.push(validator_index); + secret_keys.push(&keypairs[validator_index as usize].sk); + } + + builder.insert_attester_slashing(&attesters, &secret_keys, &state.fork, spec); + } + info!( + "Inserted {} attester slashings.", + builder.block.body.attester_slashings.len() + ); + + // Insert `Attestation` objects. + let all_secret_keys: Vec<&SecretKey> = keypairs.iter().map(|keypair| &keypair.sk).collect(); + builder + .insert_attestations( + &state, + &all_secret_keys, + self.num_attestations as usize, + spec, + ) + .unwrap(); + info!( + "Inserted {} attestations.", + builder.block.body.attestations.len() + ); + + // Insert `Deposit` objects. + for i in 0..self.num_deposits { + builder.insert_deposit( + 32_000_000_000, + state.deposit_index + (i as u64), + &state, + spec, + ); + } + info!("Inserted {} deposits.", builder.block.body.deposits.len()); + + // Insert the maximum possible number of `Exit` objects. + for _ in 0..self.num_exits { + let validator_index = validators_iter.next().expect("Insufficient validators."); + + builder.insert_exit( + &state, + validator_index, + &keypairs[validator_index as usize].sk, + spec, + ); + } + info!( + "Inserted {} exits.", + builder.block.body.voluntary_exits.len() + ); + + // Insert the maximum possible number of `Transfer` objects. + for _ in 0..self.num_transfers { + let validator_index = validators_iter.next().expect("Insufficient validators."); + + // Manually set the validator to be withdrawn. + state.validator_registry[validator_index as usize].withdrawable_epoch = + state.previous_epoch(spec); + + builder.insert_transfer( + &state, + validator_index, + validator_index, + 1, + keypairs[validator_index as usize].clone(), + spec, + ); + } + info!("Inserted {} transfers.", builder.block.body.transfers.len()); + + let mut block = self.block_builder.build(&keypair.sk, &state.fork, spec); + + // Set the eth1 data to be different from the state. + block.eth1_data.block_hash = Hash256::from_slice(&vec![42; 32]); + + (block, state) + } +} diff --git a/eth2/types/src/test_utils/testing_beacon_block_builder.rs b/eth2/types/src/test_utils/testing_beacon_block_builder.rs index ecb42e27b8..58633b5cee 100644 --- a/eth2/types/src/test_utils/testing_beacon_block_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_block_builder.rs @@ -12,7 +12,7 @@ use ssz::{SignedRoot, TreeHash}; /// /// This struct should **never be used for production purposes.** pub struct TestingBeaconBlockBuilder { - block: BeaconBlock, + pub block: BeaconBlock, } impl TestingBeaconBlockBuilder { From 4fd8551e8b6a541720bb93cce85dcca302fa021b Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 14 Mar 2019 17:49:39 +1100 Subject: [PATCH 093/144] Update loglevel, comments in benches --- eth2/state_processing/benches/benches.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/eth2/state_processing/benches/benches.rs b/eth2/state_processing/benches/benches.rs index 685858c78c..0cf797147f 100644 --- a/eth2/state_processing/benches/benches.rs +++ b/eth2/state_processing/benches/benches.rs @@ -11,7 +11,7 @@ mod block_benching_builder; pub const VALIDATOR_COUNT: usize = 16_384; -// `LOG_LEVEL == "debug"` gives logs, but they're very noisy and slow down benching. +// `LOG_LEVEL == "info"` gives handy messages. pub const LOG_LEVEL: &str = "info"; /// Build a worst-case block and benchmark processing it. @@ -40,10 +40,8 @@ pub fn block_processing_worst_case(c: &mut Criterion) { // Build all the state caches so the build times aren't included in the benches. bench_builder.build_caches(&spec); - // Generate the block and state. + // Generate the block and state then run benches. let (block, state) = bench_builder.build(&spec); - - // Run the benches. bench_block_processing::bench_block_processing( c, &block, @@ -81,10 +79,8 @@ pub fn block_processing_reasonable_case(c: &mut Criterion) { // Build all the state caches so the build times aren't included in the benches. bench_builder.build_caches(&spec); - // Generate the block and state. + // Generate the block and state then run benches. let (block, state) = bench_builder.build(&spec); - - // Run the benches. bench_block_processing::bench_block_processing( c, &block, From 3dfdfc95ac84aac141ee24ddc89c8f35b53c87e7 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 14 Mar 2019 17:53:13 +1100 Subject: [PATCH 094/144] Fix test_utils macro definition It needed to be defined before it was used in an module. --- eth2/types/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth2/types/src/lib.rs b/eth2/types/src/lib.rs index 4d13fd16c0..7b1d848377 100644 --- a/eth2/types/src/lib.rs +++ b/eth2/types/src/lib.rs @@ -2,6 +2,7 @@ #[macro_use] pub mod test_utils; + pub mod attestation; pub mod attestation_data; pub mod attestation_data_and_custody_bit; @@ -24,7 +25,6 @@ pub mod proposer_slashing; pub mod readers; pub mod shard_reassignment_record; pub mod slashable_attestation; -pub mod test_utils; pub mod transfer; pub mod voluntary_exit; #[macro_use] From 2bfc8ed4dadc86848a6a98c5427131d2db6dee9f Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 14 Mar 2019 18:08:09 +1100 Subject: [PATCH 095/144] Fix failing doc test --- beacon_node/beacon_chain/test_harness/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/test_harness/src/lib.rs b/beacon_node/beacon_chain/test_harness/src/lib.rs index f58c1b598d..0703fd4a5a 100644 --- a/beacon_node/beacon_chain/test_harness/src/lib.rs +++ b/beacon_node/beacon_chain/test_harness/src/lib.rs @@ -15,7 +15,7 @@ //! let validator_count = 8; //! let spec = ChainSpec::few_validators(); //! -//! let mut harness = BeaconChainHarness::new(spec, validator_count, None, true); +//! let mut harness = BeaconChainHarness::new(spec, validator_count); //! //! harness.advance_chain_with_block(); //! From 8cc89b98206f0cf3abe9707e2dd2641303e9bb8b Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 14 Mar 2019 18:08:21 +1100 Subject: [PATCH 096/144] Fix clippy warnings --- .../test_harness/src/beacon_chain_harness.rs | 5 -- .../beacon_chain_harness/generate_deposits.rs | 46 ------------------- .../test_harness/src/test_case.rs | 1 - 3 files changed, 52 deletions(-) delete mode 100644 beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index 28723a2032..d74464ad4c 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -12,14 +12,9 @@ use slot_clock::TestingSlotClock; use ssz::TreeHash; use std::collections::HashSet; use std::iter::FromIterator; -use std::path::Path; use std::sync::Arc; use types::{test_utils::TestingBeaconStateBuilder, *}; -mod generate_deposits; - -pub use generate_deposits::generate_deposits_from_keypairs; - /// The beacon chain harness simulates a single beacon node with `validator_count` validators connected /// to it. Each validator is provided a borrow to the beacon chain, where it may read /// information and submit blocks/attestations for processing. diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs deleted file mode 100644 index bba3aec1cb..0000000000 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness/generate_deposits.rs +++ /dev/null @@ -1,46 +0,0 @@ -use bls::get_withdrawal_credentials; -use log::debug; -use rayon::prelude::*; -use types::*; - -/// Generates a `Deposit` for each keypairs -pub fn generate_deposits_from_keypairs( - keypairs: &[Keypair], - genesis_time: u64, - domain: u64, - spec: &ChainSpec, -) -> Vec { - debug!( - "Generating {} validator deposits from random keypairs...", - keypairs.len() - ); - - let initial_validator_deposits = keypairs - .par_iter() - .map(|keypair| { - let withdrawal_credentials = Hash256::from_slice( - &get_withdrawal_credentials(&keypair.pk, spec.bls_withdrawal_prefix_byte)[..], - ); - Deposit { - branch: vec![], // branch verification is not specified. - index: 0, // index verification is not specified. - deposit_data: DepositData { - amount: 32_000_000_000, // 32 ETH (in Gwei) - timestamp: genesis_time - 1, - deposit_input: DepositInput { - pubkey: keypair.pk.clone(), - // Validator can withdraw using their main keypair. - withdrawal_credentials: withdrawal_credentials.clone(), - proof_of_possession: DepositInput::create_proof_of_possession( - &keypair, - &withdrawal_credentials, - domain, - ), - }, - }, - } - }) - .collect(); - - initial_validator_deposits -} diff --git a/beacon_node/beacon_chain/test_harness/src/test_case.rs b/beacon_node/beacon_chain/test_harness/src/test_case.rs index cee78f6c40..0a62069724 100644 --- a/beacon_node/beacon_chain/test_harness/src/test_case.rs +++ b/beacon_node/beacon_chain/test_harness/src/test_case.rs @@ -6,7 +6,6 @@ use beacon_chain::CheckPoint; use bls::get_withdrawal_credentials; use log::{info, warn}; use ssz::SignedRoot; -use std::path::Path; use types::*; use types::test_utils::{TestingAttesterSlashingBuilder, TestingProposerSlashingBuilder}; From 65e3b388a0c7ffe0e578d9bcc6746170c5227b49 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 14 Mar 2019 18:17:32 +1100 Subject: [PATCH 097/144] Update signature-scheme to v0.6.1 --- eth2/utils/bls/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth2/utils/bls/Cargo.toml b/eth2/utils/bls/Cargo.toml index 468ed80508..2466605b0c 100644 --- a/eth2/utils/bls/Cargo.toml +++ b/eth2/utils/bls/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Paul Hauner "] edition = "2018" [dependencies] -bls-aggregates = { git = "https://github.com/sigp/signature-schemes", tag = "0.6.0" } +bls-aggregates = { git = "https://github.com/sigp/signature-schemes", tag = "0.6.1" } hashing = { path = "../hashing" } hex = "0.3" serde = "1.0" From 96d96ba9ba55c927a1e3228aa8af89b9aac6b097 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 14 Mar 2019 20:54:30 +1100 Subject: [PATCH 098/144] Remove assertion in benches --- .../state_processing/benches/bench_epoch_processing.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/eth2/state_processing/benches/bench_epoch_processing.rs b/eth2/state_processing/benches/bench_epoch_processing.rs index 49b4f4371c..cc77012962 100644 --- a/eth2/state_processing/benches/bench_epoch_processing.rs +++ b/eth2/state_processing/benches/bench_epoch_processing.rs @@ -48,16 +48,6 @@ pub fn bench_epoch_processing_n_validators(c: &mut Criterion, validator_count: u "The state should have an attestation for each committee." ); - // Assert that each attestation in the state has full participation. - let committee_size = validator_count / committees_per_epoch as usize; - for a in &state.latest_attestations { - assert_eq!( - a.aggregation_bitfield.num_set_bits(), - committee_size, - "Each attestation in the state should have full participation" - ); - } - // Assert that we will run the first arm of process_rewards_and_penalities let epochs_since_finality = state.next_epoch(&spec) - state.finalized_epoch; assert_eq!( From 81543971142c32c90cd9ecfc7085004aa22ec17b Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 14 Mar 2019 20:54:44 +1100 Subject: [PATCH 099/144] Set map initial cacacity in epoch cache builder This should help reduce reallocations --- eth2/types/src/beacon_state/epoch_cache.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eth2/types/src/beacon_state/epoch_cache.rs b/eth2/types/src/beacon_state/epoch_cache.rs index bbc991646e..e6bacd351f 100644 --- a/eth2/types/src/beacon_state/epoch_cache.rs +++ b/eth2/types/src/beacon_state/epoch_cache.rs @@ -33,12 +33,13 @@ impl EpochCache { ) -> Result { let mut epoch_committees: Vec = Vec::with_capacity(spec.slots_per_epoch as usize); - let mut attestation_duty_map: AttestationDutyMap = HashMap::new(); let mut shard_committee_index_map: ShardCommitteeIndexMap = HashMap::new(); let shuffling = state.get_shuffling_for_slot(epoch.start_slot(spec.slots_per_epoch), false, spec)?; + let mut attestation_duty_map: AttestationDutyMap = HashMap::with_capacity(shuffling.len()); + for (epoch_committeess_index, slot) in epoch.slot_iter(spec.slots_per_epoch).enumerate() { let slot_committees = state.calculate_crosslink_committees_at_slot( slot, From 902b80a579315499cd61925076d19e454483ce3d Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Thu, 14 Mar 2019 22:16:21 +1100 Subject: [PATCH 100/144] Optimise epoch building --- eth2/types/src/beacon_state.rs | 35 +++++-------- eth2/types/src/beacon_state/epoch_cache.rs | 60 ++++++++++------------ eth2/types/src/validator_registry.rs | 22 ++++---- 3 files changed, 51 insertions(+), 66 deletions(-) diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index a1dd8983c9..2463f4701c 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -537,8 +537,8 @@ impl BeaconState { let cache = self.cache(relative_epoch)?; let (committee_slot_index, committee_index) = cache - .shard_committee_index_map - .get(&attestation_data.shard) + .shard_committee_indices + .get(attestation_data.shard as usize) .ok_or_else(|| Error::ShardOutOfBounds)?; let (committee, shard) = &cache.committees[*committee_slot_index][*committee_index]; @@ -787,7 +787,6 @@ impl BeaconState { &self, slot: Slot, registry_change: bool, - spec: &ChainSpec, ) -> Result>, Error> { let (_committees_per_epoch, seed, shuffling_epoch, _shuffling_start_shard) = @@ -810,7 +809,6 @@ impl BeaconState { ) -> Result>, Error> { let active_validator_indices = get_active_validator_indices(&self.validator_registry, epoch); - if active_validator_indices.is_empty() { error!("get_shuffling: no validators."); return Err(Error::InsufficientValidators); @@ -912,22 +910,18 @@ impl BeaconState { } } - /// Return the list of ``(committee, shard)`` tuples for the ``slot``. + /// Return the ordered list of shards tuples for the `slot`. /// /// Note: There are two possible shufflings for crosslink committees for a /// `slot` in the next epoch: with and without a `registry_change` /// - /// Note: does not utilize the cache, `get_crosslink_committees_at_slot` is an equivalent - /// function which uses the cache. - /// /// Spec v0.4.0 - pub(crate) fn calculate_crosslink_committees_at_slot( + pub(crate) fn get_shards_for_slot( &self, slot: Slot, registry_change: bool, - shuffling: Vec>, spec: &ChainSpec, - ) -> Result, u64)>, Error> { + ) -> Result, Error> { let (committees_per_epoch, _seed, _shuffling_epoch, shuffling_start_shard) = self.get_committee_params_at_slot(slot, registry_change, spec)?; @@ -936,15 +930,12 @@ impl BeaconState { let slot_start_shard = (shuffling_start_shard + committees_per_slot * offset) % spec.shard_count; - let mut crosslinks_at_slot = vec![]; + let mut shards_at_slot = vec![]; for i in 0..committees_per_slot { - let tuple = ( - shuffling[(committees_per_slot * offset + i) as usize].clone(), - (slot_start_shard + i) % spec.shard_count, - ); - crosslinks_at_slot.push(tuple) + shards_at_slot.push((slot_start_shard + i) % spec.shard_count) } - Ok(crosslinks_at_slot) + + Ok(shards_at_slot) } /// Returns the `slot`, `shard` and `committee_index` for which a validator must produce an @@ -962,10 +953,10 @@ impl BeaconState { ) -> Result, Error> { let cache = self.cache(RelativeEpoch::Current)?; - Ok(cache - .attestation_duty_map - .get(&(validator_index as u64)) - .and_then(|tuple| Some(*tuple))) + Ok(*cache + .attestation_duties + .get(validator_index) + .ok_or_else(|| Error::UnknownValidator)?) } /// Process the slashings. diff --git a/eth2/types/src/beacon_state/epoch_cache.rs b/eth2/types/src/beacon_state/epoch_cache.rs index e6bacd351f..f9bc0d2e77 100644 --- a/eth2/types/src/beacon_state/epoch_cache.rs +++ b/eth2/types/src/beacon_state/epoch_cache.rs @@ -1,8 +1,6 @@ -use super::{AttestationDutyMap, BeaconState, CrosslinkCommittees, Error, ShardCommitteeIndexMap}; +use super::{AttestationDuty, BeaconState, CrosslinkCommittees, Error}; use crate::{ChainSpec, Epoch}; -use log::trace; use serde_derive::Serialize; -use std::collections::HashMap; #[derive(Debug, PartialEq, Clone, Serialize)] pub struct EpochCache { @@ -11,21 +9,23 @@ pub struct EpochCache { /// The crosslink committees for an epoch. pub committees: Vec, /// Maps validator index to a slot, shard and committee index for attestation. - pub attestation_duty_map: AttestationDutyMap, + pub attestation_duties: Vec>, /// Maps a shard to an index of `self.committees`. - pub shard_committee_index_map: ShardCommitteeIndexMap, + pub shard_committee_indices: Vec<(usize, usize)>, } impl EpochCache { + /// Return a new, completely empty cache. pub fn empty() -> EpochCache { EpochCache { initialized: false, committees: vec![], - attestation_duty_map: AttestationDutyMap::new(), - shard_committee_index_map: ShardCommitteeIndexMap::new(), + attestation_duties: vec![], + shard_committee_indices: vec![], } } + /// Return a new, fully initialized cache. pub fn initialized( state: &BeaconState, epoch: Epoch, @@ -33,42 +33,36 @@ impl EpochCache { ) -> Result { let mut epoch_committees: Vec = Vec::with_capacity(spec.slots_per_epoch as usize); - let mut shard_committee_index_map: ShardCommitteeIndexMap = HashMap::new(); - let shuffling = + let mut attestation_duties = vec![None; state.validator_registry.len()]; + + let mut shard_committee_indices = vec![(0, 0); spec.shard_count as usize]; + + let mut shuffling = state.get_shuffling_for_slot(epoch.start_slot(spec.slots_per_epoch), false, spec)?; - let mut attestation_duty_map: AttestationDutyMap = HashMap::with_capacity(shuffling.len()); + for (epoch_committees_index, slot) in epoch.slot_iter(spec.slots_per_epoch).enumerate() { + let mut slot_committees: Vec<(Vec, u64)> = vec![]; - for (epoch_committeess_index, slot) in epoch.slot_iter(spec.slots_per_epoch).enumerate() { - let slot_committees = state.calculate_crosslink_committees_at_slot( - slot, - false, - shuffling.clone(), - spec, - )?; + let shards = state.get_shards_for_slot(slot, false, spec)?; + for shard in shards { + let committee = shuffling.remove(0); + slot_committees.push((committee, shard)); + } for (slot_committees_index, (committee, shard)) in slot_committees.iter().enumerate() { - // Empty committees are not permitted. if committee.is_empty() { return Err(Error::InsufficientValidators); } - trace!( - "shard: {}, epoch_i: {}, slot_i: {}", - shard, - epoch_committeess_index, - slot_committees_index - ); - - shard_committee_index_map - .insert(*shard, (epoch_committeess_index, slot_committees_index)); + // Store the slot and committee index for this shard. + shard_committee_indices[*shard as usize] = + (epoch_committees_index, slot_committees_index); + // For each validator, store their attestation duties. for (committee_index, validator_index) in committee.iter().enumerate() { - attestation_duty_map.insert( - *validator_index as u64, - (slot, *shard, committee_index as u64), - ); + attestation_duties[*validator_index] = + Some((slot, *shard, committee_index as u64)) } } @@ -78,8 +72,8 @@ impl EpochCache { Ok(EpochCache { initialized: true, committees: epoch_committees, - attestation_duty_map, - shard_committee_index_map, + attestation_duties, + shard_committee_indices, }) } } diff --git a/eth2/types/src/validator_registry.rs b/eth2/types/src/validator_registry.rs index 7b55e78cb0..db35ae9936 100644 --- a/eth2/types/src/validator_registry.rs +++ b/eth2/types/src/validator_registry.rs @@ -7,17 +7,17 @@ use crate::Epoch; /// /// Spec v0.4.0 pub fn get_active_validator_indices(validators: &[Validator], epoch: Epoch) -> Vec { - validators - .iter() - .enumerate() - .filter_map(|(index, validator)| { - if validator.is_active_at(epoch) { - Some(index) - } else { - None - } - }) - .collect::>() + let mut active = Vec::with_capacity(validators.len()); + + for (index, validator) in validators.iter().enumerate() { + if validator.is_active_at(epoch) { + active.push(index) + } + } + + active.shrink_to_fit(); + + active } #[cfg(test)] From 236b97476ae110c608678f34de16a85d990186b6 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 13:31:30 +1100 Subject: [PATCH 101/144] Marge fixes to test_harness, add serdehex crate --- Cargo.toml | 1 + .../src/test_utils/epoch_map.rs | 4 +- eth2/state_processing/Cargo.toml | 3 + eth2/types/src/attestation.rs | 15 ++++- eth2/types/src/attestation_data.rs | 3 +- eth2/types/src/attester_slashing.rs | 4 +- eth2/types/src/beacon_block.rs | 15 ++++- eth2/types/src/beacon_block_body.rs | 6 +- eth2/types/src/beacon_state.rs | 28 ++++----- eth2/types/src/beacon_state/epoch_cache.rs | 14 +---- eth2/types/src/beacon_state/pubkey_cache.rs | 11 +--- eth2/types/src/chain_spec.rs | 32 ++++++---- eth2/types/src/crosslink.rs | 14 ++++- eth2/types/src/eth1_data.rs | 6 +- eth2/types/src/eth1_data_vote.rs | 6 +- eth2/types/src/fork.rs | 33 ++++++++--- eth2/types/src/pending_attestation.rs | 4 +- eth2/types/src/proposal.rs | 15 ++++- eth2/types/src/proposer_slashing.rs | 4 +- eth2/types/src/slashable_attestation.rs | 15 ++++- eth2/types/src/test_utils/test_random.rs | 14 +++++ eth2/types/src/transfer.rs | 15 ++++- eth2/types/src/voluntary_exit.rs | 15 ++++- eth2/utils/bls/Cargo.toml | 1 + eth2/utils/bls/src/aggregate_signature.rs | 16 ++++- eth2/utils/bls/src/public_key.rs | 13 ++-- eth2/utils/boolean-bitfield/Cargo.toml | 1 + eth2/utils/boolean-bitfield/src/lib.rs | 19 +++++- eth2/utils/serde_hex/Cargo.toml | 9 +++ eth2/utils/serde_hex/src/lib.rs | 59 +++++++++++++++++++ eth2/utils/ssz/src/impl_decode.rs | 27 +++++++++ eth2/utils/ssz/src/impl_encode.rs | 25 ++++++++ 32 files changed, 355 insertions(+), 92 deletions(-) create mode 100644 eth2/utils/serde_hex/Cargo.toml create mode 100644 eth2/utils/serde_hex/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index c5aae7f43f..d149030b66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [ "eth2/utils/honey-badger-split", "eth2/utils/merkle_proof", "eth2/utils/int_to_bytes", + "eth2/utils/serde_hex", "eth2/utils/slot_clock", "eth2/utils/ssz", "eth2/utils/ssz_derive", diff --git a/eth2/block_proposer/src/test_utils/epoch_map.rs b/eth2/block_proposer/src/test_utils/epoch_map.rs index 6658c75265..c06c376c68 100644 --- a/eth2/block_proposer/src/test_utils/epoch_map.rs +++ b/eth2/block_proposer/src/test_utils/epoch_map.rs @@ -28,8 +28,8 @@ impl DutiesReader for EpochMap { fn fork(&self) -> Result { Ok(Fork { - previous_version: 0, - current_version: 0, + previous_version: [0; 4], + current_version: [0; 4], epoch: Epoch::new(0), }) } diff --git a/eth2/state_processing/Cargo.toml b/eth2/state_processing/Cargo.toml index f6692b259e..4e37fce0c2 100644 --- a/eth2/state_processing/Cargo.toml +++ b/eth2/state_processing/Cargo.toml @@ -11,6 +11,9 @@ harness = false [dev-dependencies] criterion = "0.2" env_logger = "0.6.0" +serde = "1.0" +serde_derive = "1.0" +serde_yaml = "0.8" [dependencies] bls = { path = "../utils/bls" } diff --git a/eth2/types/src/attestation.rs b/eth2/types/src/attestation.rs index dcc4c1fda8..4b3c2e89cc 100644 --- a/eth2/types/src/attestation.rs +++ b/eth2/types/src/attestation.rs @@ -1,7 +1,7 @@ use super::{AggregateSignature, AttestationData, Bitfield}; use crate::test_utils::TestRandom; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use ssz::TreeHash; use ssz_derive::{Decode, Encode, SignedRoot, TreeHash}; use test_random_derive::TestRandom; @@ -9,7 +9,18 @@ use test_random_derive::TestRandom; /// Details an attestation that can be slashable. /// /// Spec v0.4.0 -#[derive(Debug, Clone, PartialEq, Serialize, Encode, Decode, TreeHash, TestRandom, SignedRoot)] +#[derive( + Debug, + Clone, + PartialEq, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + TestRandom, + SignedRoot, +)] pub struct Attestation { pub aggregation_bitfield: Bitfield, pub data: AttestationData, diff --git a/eth2/types/src/attestation_data.rs b/eth2/types/src/attestation_data.rs index 6e3cb3891e..791ba00d27 100644 --- a/eth2/types/src/attestation_data.rs +++ b/eth2/types/src/attestation_data.rs @@ -1,7 +1,7 @@ use crate::test_utils::TestRandom; use crate::{Crosslink, Epoch, Hash256, Slot}; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use ssz::TreeHash; use ssz_derive::{Decode, Encode, SignedRoot, TreeHash}; use test_random_derive::TestRandom; @@ -15,6 +15,7 @@ use test_random_derive::TestRandom; PartialEq, Default, Serialize, + Deserialize, Hash, Encode, Decode, diff --git a/eth2/types/src/attester_slashing.rs b/eth2/types/src/attester_slashing.rs index f437d41f24..195c0fdccd 100644 --- a/eth2/types/src/attester_slashing.rs +++ b/eth2/types/src/attester_slashing.rs @@ -1,13 +1,13 @@ use crate::{test_utils::TestRandom, SlashableAttestation}; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode, TreeHash}; use test_random_derive::TestRandom; /// Two conflicting attestations. /// /// Spec v0.4.0 -#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)] pub struct AttesterSlashing { pub slashable_attestation_1: SlashableAttestation, pub slashable_attestation_2: SlashableAttestation, diff --git a/eth2/types/src/beacon_block.rs b/eth2/types/src/beacon_block.rs index 615d9f9288..56f77c8d25 100644 --- a/eth2/types/src/beacon_block.rs +++ b/eth2/types/src/beacon_block.rs @@ -2,7 +2,7 @@ use crate::test_utils::TestRandom; use crate::{BeaconBlockBody, ChainSpec, Eth1Data, Hash256, Proposal, Slot}; use bls::Signature; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use ssz::{SignedRoot, TreeHash}; use ssz_derive::{Decode, Encode, SignedRoot, TreeHash}; use test_random_derive::TestRandom; @@ -10,7 +10,18 @@ use test_random_derive::TestRandom; /// A block of the `BeaconChain`. /// /// Spec v0.4.0 -#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom, SignedRoot)] +#[derive( + Debug, + PartialEq, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + TestRandom, + SignedRoot, +)] pub struct BeaconBlock { pub slot: Slot, pub parent_root: Hash256, diff --git a/eth2/types/src/beacon_block_body.rs b/eth2/types/src/beacon_block_body.rs index 70ce24dbe8..ce8020fec0 100644 --- a/eth2/types/src/beacon_block_body.rs +++ b/eth2/types/src/beacon_block_body.rs @@ -1,14 +1,16 @@ use super::{Attestation, AttesterSlashing, Deposit, ProposerSlashing, Transfer, VoluntaryExit}; use crate::test_utils::TestRandom; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode, TreeHash}; use test_random_derive::TestRandom; /// The body of a `BeaconChain` block, containing operations. /// /// Spec v0.4.0 -#[derive(Debug, PartialEq, Clone, Default, Serialize, Encode, Decode, TreeHash, TestRandom)] +#[derive( + Debug, PartialEq, Clone, Default, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom, +)] pub struct BeaconBlockBody { pub proposer_slashings: Vec, pub attester_slashings: Vec, diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 2463f4701c..2644b3e73d 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -7,7 +7,7 @@ use int_to_bytes::int_to_bytes32; use log::{debug, error, trace}; use pubkey_cache::PubkeyCache; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use ssz::{hash, Decodable, DecodeError, Encodable, SignedRoot, SszStream, TreeHash}; use std::collections::HashMap; use swap_or_not_shuffle::shuffle_list; @@ -72,7 +72,7 @@ macro_rules! safe_sub_assign { }; } -#[derive(Debug, PartialEq, Clone, Default, Serialize)] +#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)] pub struct BeaconState { // Misc pub slot: Slot, @@ -114,7 +114,9 @@ pub struct BeaconState { // Caching (not in the spec) pub cache_index_offset: usize, + #[serde(default)] pub caches: Vec, + #[serde(default)] pub pubkey_cache: PubkeyCache, } @@ -137,11 +139,7 @@ impl BeaconState { */ slot: spec.genesis_slot, genesis_time, - fork: Fork { - previous_version: spec.genesis_fork_version, - current_version: spec.genesis_fork_version, - epoch: spec.genesis_epoch, - }, + fork: Fork::genesis(spec), /* * Validator registry @@ -193,8 +191,8 @@ impl BeaconState { * Caching (not in spec) */ cache_index_offset: 0, - caches: vec![EpochCache::empty(); CACHED_EPOCHS], - pubkey_cache: PubkeyCache::empty(), + caches: vec![EpochCache::default(); CACHED_EPOCHS], + pubkey_cache: PubkeyCache::default(), } } @@ -276,7 +274,7 @@ impl BeaconState { /// Removes the specified cache and sets it to uninitialized. pub fn drop_cache(&mut self, relative_epoch: RelativeEpoch) { let previous_cache_index = self.cache_index(relative_epoch); - self.caches[previous_cache_index] = EpochCache::empty(); + self.caches[previous_cache_index] = EpochCache::default(); } /// Returns the index of `self.caches` for some `RelativeEpoch`. @@ -324,7 +322,7 @@ impl BeaconState { /// Completely drops the `pubkey_cache`, replacing it with a new, empty cache. pub fn drop_pubkey_cache(&mut self) { - self.pubkey_cache = PubkeyCache::empty() + self.pubkey_cache = PubkeyCache::default() } /// If a validator pubkey exists in the validator registry, returns `Some(i)`, otherwise @@ -1227,8 +1225,8 @@ impl Decodable for BeaconState { eth1_data_votes, deposit_index, cache_index_offset: 0, - caches: vec![EpochCache::empty(); CACHED_EPOCHS], - pubkey_cache: PubkeyCache::empty(), + caches: vec![EpochCache::default(); CACHED_EPOCHS], + pubkey_cache: PubkeyCache::default(), }, i, )) @@ -1298,8 +1296,8 @@ impl TestRandom for BeaconState { eth1_data_votes: <_>::random_for_test(rng), deposit_index: <_>::random_for_test(rng), cache_index_offset: 0, - caches: vec![EpochCache::empty(); CACHED_EPOCHS], - pubkey_cache: PubkeyCache::empty(), + caches: vec![EpochCache::default(); CACHED_EPOCHS], + pubkey_cache: PubkeyCache::default(), } } } diff --git a/eth2/types/src/beacon_state/epoch_cache.rs b/eth2/types/src/beacon_state/epoch_cache.rs index f9bc0d2e77..ddcca0a9a0 100644 --- a/eth2/types/src/beacon_state/epoch_cache.rs +++ b/eth2/types/src/beacon_state/epoch_cache.rs @@ -1,8 +1,8 @@ use super::{AttestationDuty, BeaconState, CrosslinkCommittees, Error}; use crate::{ChainSpec, Epoch}; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; -#[derive(Debug, PartialEq, Clone, Serialize)] +#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)] pub struct EpochCache { /// True if this cache has been initialized. pub initialized: bool, @@ -15,16 +15,6 @@ pub struct EpochCache { } impl EpochCache { - /// Return a new, completely empty cache. - pub fn empty() -> EpochCache { - EpochCache { - initialized: false, - committees: vec![], - attestation_duties: vec![], - shard_committee_indices: vec![], - } - } - /// Return a new, fully initialized cache. pub fn initialized( state: &BeaconState, diff --git a/eth2/types/src/beacon_state/pubkey_cache.rs b/eth2/types/src/beacon_state/pubkey_cache.rs index c051475792..340bdb3119 100644 --- a/eth2/types/src/beacon_state/pubkey_cache.rs +++ b/eth2/types/src/beacon_state/pubkey_cache.rs @@ -1,22 +1,15 @@ use crate::*; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use std::collections::HashMap; type ValidatorIndex = usize; -#[derive(Debug, PartialEq, Clone, Default, Serialize)] +#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)] pub struct PubkeyCache { map: HashMap, } impl PubkeyCache { - /// Instantiates a new, empty cache. - pub fn empty() -> Self { - Self { - map: HashMap::new(), - } - } - /// Returns the number of validator indices already in the map. pub fn len(&self) -> ValidatorIndex { self.map.len() diff --git a/eth2/types/src/chain_spec.rs b/eth2/types/src/chain_spec.rs index 789bb6c0cc..108516695d 100644 --- a/eth2/types/src/chain_spec.rs +++ b/eth2/types/src/chain_spec.rs @@ -1,5 +1,7 @@ use crate::{Address, Epoch, Fork, Hash256, Slot}; use bls::Signature; +use int_to_bytes::int_to_bytes4; +use serde_derive::Deserialize; const GWEI: u64 = 1_000_000_000; @@ -15,7 +17,8 @@ pub enum Domain { /// Holds all the "constants" for a BeaconChain. /// /// Spec v0.4.0 -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Debug, Clone, Deserialize)] +#[serde(default)] pub struct ChainSpec { /* * Misc @@ -45,7 +48,7 @@ pub struct ChainSpec { /* * Initial Values */ - pub genesis_fork_version: u64, + pub genesis_fork_version: u32, pub genesis_slot: Slot, pub genesis_epoch: Epoch, pub genesis_start_shard: u64, @@ -100,12 +103,12 @@ pub struct ChainSpec { * * Use `ChainSpec::get_domain(..)` to access these values. */ - domain_deposit: u64, - domain_attestation: u64, - domain_proposal: u64, - domain_exit: u64, - domain_randao: u64, - domain_transfer: u64, + domain_deposit: u32, + domain_attestation: u32, + domain_proposal: u32, + domain_exit: u32, + domain_randao: u32, + domain_transfer: u32, } impl ChainSpec { @@ -135,8 +138,11 @@ impl ChainSpec { Domain::Transfer => self.domain_transfer, }; - let fork_version = fork.get_fork_version(epoch); - fork_version * u64::pow(2, 32) + domain_constant + let mut fork_and_domain = [0; 8]; + fork_and_domain.copy_from_slice(&fork.get_fork_version(epoch)); + fork_and_domain.copy_from_slice(&int_to_bytes4(domain_constant)); + + u64::from_le_bytes(fork_and_domain) } /// Returns a `ChainSpec` compatible with the Ethereum Foundation specification. @@ -254,6 +260,12 @@ impl ChainSpec { } } +impl Default for ChainSpec { + fn default() -> Self { + Self::foundation() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/eth2/types/src/crosslink.rs b/eth2/types/src/crosslink.rs index 5db5e20a60..dfa0311eff 100644 --- a/eth2/types/src/crosslink.rs +++ b/eth2/types/src/crosslink.rs @@ -1,7 +1,7 @@ use crate::test_utils::TestRandom; use crate::{Epoch, Hash256}; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode, TreeHash}; use test_random_derive::TestRandom; @@ -9,7 +9,17 @@ use test_random_derive::TestRandom; /// /// Spec v0.4.0 #[derive( - Debug, Clone, PartialEq, Default, Serialize, Hash, Encode, Decode, TreeHash, TestRandom, + Debug, + Clone, + PartialEq, + Default, + Serialize, + Deserialize, + Hash, + Encode, + Decode, + TreeHash, + TestRandom, )] pub struct Crosslink { pub epoch: Epoch, diff --git a/eth2/types/src/eth1_data.rs b/eth2/types/src/eth1_data.rs index c4b2b1894f..0f1dbfec59 100644 --- a/eth2/types/src/eth1_data.rs +++ b/eth2/types/src/eth1_data.rs @@ -1,14 +1,16 @@ use super::Hash256; use crate::test_utils::TestRandom; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode, TreeHash}; use test_random_derive::TestRandom; /// Contains data obtained from the Eth1 chain. /// /// Spec v0.4.0 -#[derive(Debug, PartialEq, Clone, Default, Serialize, Encode, Decode, TreeHash, TestRandom)] +#[derive( + Debug, PartialEq, Clone, Default, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom, +)] pub struct Eth1Data { pub deposit_root: Hash256, pub block_hash: Hash256, diff --git a/eth2/types/src/eth1_data_vote.rs b/eth2/types/src/eth1_data_vote.rs index 4788833bd3..d709608d5f 100644 --- a/eth2/types/src/eth1_data_vote.rs +++ b/eth2/types/src/eth1_data_vote.rs @@ -1,14 +1,16 @@ use super::Eth1Data; use crate::test_utils::TestRandom; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode, TreeHash}; use test_random_derive::TestRandom; /// A summation of votes for some `Eth1Data`. /// /// Spec v0.4.0 -#[derive(Debug, PartialEq, Clone, Default, Serialize, Encode, Decode, TreeHash, TestRandom)] +#[derive( + Debug, PartialEq, Clone, Default, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom, +)] pub struct Eth1DataVote { pub eth1_data: Eth1Data, pub vote_count: u64, diff --git a/eth2/types/src/fork.rs b/eth2/types/src/fork.rs index f3b62f5a8d..f0e3d10467 100644 --- a/eth2/types/src/fork.rs +++ b/eth2/types/src/fork.rs @@ -1,24 +1,41 @@ -use crate::{test_utils::TestRandom, Epoch}; +use crate::{test_utils::TestRandom, ChainSpec, Epoch}; +use int_to_bytes::int_to_bytes4; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode, TreeHash}; use test_random_derive::TestRandom; /// Specifies a fork of the `BeaconChain`, to prevent replay attacks. /// -/// Spec v0.4.0 -#[derive(Debug, Clone, PartialEq, Default, Serialize, Encode, Decode, TreeHash, TestRandom)] +/// Spec v0.5.0 +#[derive( + Debug, Clone, PartialEq, Default, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom, +)] pub struct Fork { - pub previous_version: u64, - pub current_version: u64, + pub previous_version: [u8; 4], + pub current_version: [u8; 4], pub epoch: Epoch, } impl Fork { + /// Initialize the `Fork` from the genesis parameters in the `spec`. + /// + /// Spec v0.5.0 + pub fn genesis(spec: &ChainSpec) -> Self { + let mut current_version: [u8; 4] = [0; 4]; + current_version.copy_from_slice(&int_to_bytes4(spec.genesis_fork_version)); + + Self { + previous_version: current_version, + current_version, + epoch: spec.genesis_epoch, + } + } + /// Return the fork version of the given ``epoch``. /// - /// Spec v0.4.0 - pub fn get_fork_version(&self, epoch: Epoch) -> u64 { + /// Spec v0.5.0 + pub fn get_fork_version(&self, epoch: Epoch) -> [u8; 4] { if epoch < self.epoch { return self.previous_version; } diff --git a/eth2/types/src/pending_attestation.rs b/eth2/types/src/pending_attestation.rs index 68dd1c3451..70907c29df 100644 --- a/eth2/types/src/pending_attestation.rs +++ b/eth2/types/src/pending_attestation.rs @@ -1,14 +1,14 @@ use crate::test_utils::TestRandom; use crate::{AttestationData, Bitfield, Slot}; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode, TreeHash}; use test_random_derive::TestRandom; /// An attestation that has been included in the state but not yet fully processed. /// /// Spec v0.4.0 -#[derive(Debug, Clone, PartialEq, Serialize, Encode, Decode, TreeHash, TestRandom)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)] pub struct PendingAttestation { pub aggregation_bitfield: Bitfield, pub data: AttestationData, diff --git a/eth2/types/src/proposal.rs b/eth2/types/src/proposal.rs index 59d6370e1d..36fba5603e 100644 --- a/eth2/types/src/proposal.rs +++ b/eth2/types/src/proposal.rs @@ -2,7 +2,7 @@ use crate::test_utils::TestRandom; use crate::{Hash256, Slot}; use bls::Signature; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use ssz::TreeHash; use ssz_derive::{Decode, Encode, SignedRoot, TreeHash}; use test_random_derive::TestRandom; @@ -10,7 +10,18 @@ use test_random_derive::TestRandom; /// A proposal for some shard or beacon block. /// /// Spec v0.4.0 -#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom, SignedRoot)] +#[derive( + Debug, + PartialEq, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + TestRandom, + SignedRoot, +)] pub struct Proposal { pub slot: Slot, /// Shard number (spec.beacon_chain_shard_number for beacon chain) diff --git a/eth2/types/src/proposer_slashing.rs b/eth2/types/src/proposer_slashing.rs index 26c3d67a77..bc5b8665ef 100644 --- a/eth2/types/src/proposer_slashing.rs +++ b/eth2/types/src/proposer_slashing.rs @@ -1,14 +1,14 @@ use super::Proposal; use crate::test_utils::TestRandom; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode, TreeHash}; use test_random_derive::TestRandom; /// Two conflicting proposals from the same proposer (validator). /// /// Spec v0.4.0 -#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)] pub struct ProposerSlashing { pub proposer_index: u64, pub proposal_1: Proposal, diff --git a/eth2/types/src/slashable_attestation.rs b/eth2/types/src/slashable_attestation.rs index 56c9dfc2f6..bc9b2769a4 100644 --- a/eth2/types/src/slashable_attestation.rs +++ b/eth2/types/src/slashable_attestation.rs @@ -1,6 +1,6 @@ use crate::{test_utils::TestRandom, AggregateSignature, AttestationData, Bitfield, ChainSpec}; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use ssz::TreeHash; use ssz_derive::{Decode, Encode, SignedRoot, TreeHash}; use test_random_derive::TestRandom; @@ -10,7 +10,18 @@ use test_random_derive::TestRandom; /// To be included in an `AttesterSlashing`. /// /// Spec v0.4.0 -#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom, SignedRoot)] +#[derive( + Debug, + PartialEq, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + TestRandom, + SignedRoot, +)] pub struct SlashableAttestation { /// Lists validator registry indices, not committee indices. pub validator_indices: Vec, diff --git a/eth2/types/src/test_utils/test_random.rs b/eth2/types/src/test_utils/test_random.rs index 3b172463e4..cb7abe3a42 100644 --- a/eth2/types/src/test_utils/test_random.rs +++ b/eth2/types/src/test_utils/test_random.rs @@ -51,3 +51,17 @@ where ] } } + +macro_rules! impl_test_random_for_u8_array { + ($len: expr) => { + impl TestRandom for [u8; $len] { + fn random_for_test(rng: &mut T) -> Self { + let mut bytes = [0; $len]; + rng.fill_bytes(&mut bytes); + bytes + } + } + }; +} + +impl_test_random_for_u8_array!(4); diff --git a/eth2/types/src/transfer.rs b/eth2/types/src/transfer.rs index af3b18ef42..a46e24e241 100644 --- a/eth2/types/src/transfer.rs +++ b/eth2/types/src/transfer.rs @@ -2,7 +2,7 @@ use super::Slot; use crate::test_utils::TestRandom; use bls::{PublicKey, Signature}; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use ssz::TreeHash; use ssz_derive::{Decode, Encode, SignedRoot, TreeHash}; use test_random_derive::TestRandom; @@ -10,7 +10,18 @@ use test_random_derive::TestRandom; /// The data submitted to the deposit contract. /// /// Spec v0.4.0 -#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom, SignedRoot)] +#[derive( + Debug, + PartialEq, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + TestRandom, + SignedRoot, +)] pub struct Transfer { pub from: u64, pub to: u64, diff --git a/eth2/types/src/voluntary_exit.rs b/eth2/types/src/voluntary_exit.rs index 38630a0575..5fdfcdd82f 100644 --- a/eth2/types/src/voluntary_exit.rs +++ b/eth2/types/src/voluntary_exit.rs @@ -1,7 +1,7 @@ use crate::{test_utils::TestRandom, Epoch}; use bls::Signature; use rand::RngCore; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use ssz::TreeHash; use ssz_derive::{Decode, Encode, SignedRoot, TreeHash}; use test_random_derive::TestRandom; @@ -9,7 +9,18 @@ use test_random_derive::TestRandom; /// An exit voluntarily submitted a validator who wishes to withdraw. /// /// Spec v0.4.0 -#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom, SignedRoot)] +#[derive( + Debug, + PartialEq, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + TestRandom, + SignedRoot, +)] pub struct VoluntaryExit { pub epoch: Epoch, pub validator_index: u64, diff --git a/eth2/utils/bls/Cargo.toml b/eth2/utils/bls/Cargo.toml index 2466605b0c..4230a06eae 100644 --- a/eth2/utils/bls/Cargo.toml +++ b/eth2/utils/bls/Cargo.toml @@ -10,4 +10,5 @@ hashing = { path = "../hashing" } hex = "0.3" serde = "1.0" serde_derive = "1.0" +serde_hex = { path = "../serde_hex" } ssz = { path = "../ssz" } diff --git a/eth2/utils/bls/src/aggregate_signature.rs b/eth2/utils/bls/src/aggregate_signature.rs index 9c5ed03750..7b80d3bbfd 100644 --- a/eth2/utils/bls/src/aggregate_signature.rs +++ b/eth2/utils/bls/src/aggregate_signature.rs @@ -2,7 +2,9 @@ use super::{AggregatePublicKey, Signature}; use bls_aggregates::{ AggregatePublicKey as RawAggregatePublicKey, AggregateSignature as RawAggregateSignature, }; +use serde::de::{Deserialize, Deserializer}; use serde::ser::{Serialize, Serializer}; +use serde_hex::{encode as hex_encode, PrefixedHexVisitor}; use ssz::{ decode_ssz_list, hash, ssz_encode, Decodable, DecodeError, Encodable, SszStream, TreeHash, }; @@ -82,7 +84,19 @@ impl Serialize for AggregateSignature { where S: Serializer, { - serializer.serialize_bytes(&ssz_encode(self)) + serializer.serialize_str(&hex_encode(ssz_encode(self))) + } +} + +impl<'de> Deserialize<'de> for AggregateSignature { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let bytes = deserializer.deserialize_str(PrefixedHexVisitor)?; + let (obj, _) = <_>::ssz_decode(&bytes[..], 0) + .map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?; + Ok(obj) } } diff --git a/eth2/utils/bls/src/public_key.rs b/eth2/utils/bls/src/public_key.rs index c85760bbf0..5a348f530d 100644 --- a/eth2/utils/bls/src/public_key.rs +++ b/eth2/utils/bls/src/public_key.rs @@ -1,9 +1,8 @@ -use super::serde_vistors::HexVisitor; use super::SecretKey; use bls_aggregates::PublicKey as RawPublicKey; -use hex::encode as hex_encode; use serde::de::{Deserialize, Deserializer}; use serde::ser::{Serialize, Serializer}; +use serde_hex::{encode as hex_encode, PrefixedHexVisitor}; use ssz::{ decode_ssz_list, hash, ssz_encode, Decodable, DecodeError, Encodable, SszStream, TreeHash, }; @@ -81,7 +80,7 @@ impl Serialize for PublicKey { where S: Serializer, { - serializer.serialize_str(&hex_encode(ssz_encode(self))) + serializer.serialize_str(&hex_encode(self.as_raw().as_bytes())) } } @@ -90,10 +89,10 @@ impl<'de> Deserialize<'de> for PublicKey { where D: Deserializer<'de>, { - let bytes = deserializer.deserialize_str(HexVisitor)?; - let (pubkey, _) = <_>::ssz_decode(&bytes[..], 0) - .map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?; - Ok(pubkey) + let bytes = deserializer.deserialize_str(PrefixedHexVisitor)?; + let obj = PublicKey::from_bytes(&bytes[..]) + .map_err(|e| serde::de::Error::custom(format!("invalid pubkey ({:?})", e)))?; + Ok(obj) } } diff --git a/eth2/utils/boolean-bitfield/Cargo.toml b/eth2/utils/boolean-bitfield/Cargo.toml index d94b9f7b14..cf037c5d70 100644 --- a/eth2/utils/boolean-bitfield/Cargo.toml +++ b/eth2/utils/boolean-bitfield/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Paul Hauner "] edition = "2018" [dependencies] +serde_hex = { path = "../serde_hex" } ssz = { path = "../ssz" } bit-vec = "0.5.0" serde = "1.0" diff --git a/eth2/utils/boolean-bitfield/src/lib.rs b/eth2/utils/boolean-bitfield/src/lib.rs index a0fce1f0a4..443cd06dad 100644 --- a/eth2/utils/boolean-bitfield/src/lib.rs +++ b/eth2/utils/boolean-bitfield/src/lib.rs @@ -3,7 +3,10 @@ extern crate ssz; use bit_vec::BitVec; +use serde::de::{Deserialize, Deserializer}; use serde::ser::{Serialize, Serializer}; +use serde_hex::{encode, PrefixedHexVisitor}; +use ssz::Decodable; use std::cmp; use std::default; @@ -178,11 +181,25 @@ impl ssz::Decodable for BooleanBitfield { } impl Serialize for BooleanBitfield { + /// Serde serialization is compliant the Ethereum YAML test format. fn serialize(&self, serializer: S) -> Result where S: Serializer, { - serializer.serialize_bytes(&ssz::ssz_encode(self)) + serializer.serialize_str(&encode(&ssz::ssz_encode(self))) + } +} + +impl<'de> Deserialize<'de> for BooleanBitfield { + /// Serde serialization is compliant the Ethereum YAML test format. + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let bytes = deserializer.deserialize_str(PrefixedHexVisitor)?; + let (bitfield, _) = <_>::ssz_decode(&bytes[..], 0) + .map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?; + Ok(bitfield) } } diff --git a/eth2/utils/serde_hex/Cargo.toml b/eth2/utils/serde_hex/Cargo.toml new file mode 100644 index 0000000000..b28194dd6f --- /dev/null +++ b/eth2/utils/serde_hex/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "serde_hex" +version = "0.1.0" +authors = ["Paul Hauner "] +edition = "2018" + +[dependencies] +serde = "1.0" +hex = "0.3" diff --git a/eth2/utils/serde_hex/src/lib.rs b/eth2/utils/serde_hex/src/lib.rs new file mode 100644 index 0000000000..3be20d93f9 --- /dev/null +++ b/eth2/utils/serde_hex/src/lib.rs @@ -0,0 +1,59 @@ +use hex; +use hex::ToHex; +use serde::de::{self, Visitor}; +use std::fmt; + +pub fn encode>(data: T) -> String { + let mut hex = String::with_capacity(data.as_ref().len() * 2); + + // Writing to a string never errors, so we can unwrap here. + data.write_hex(&mut hex).unwrap(); + + let mut s = "0x".to_string(); + + s.push_str(hex.as_str()); + + s +} + +pub struct PrefixedHexVisitor; + +impl<'de> Visitor<'de> for PrefixedHexVisitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a hex string with 0x prefix") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + if value.starts_with("0x") { + Ok(hex::decode(&value[2..]) + .map_err(|e| de::Error::custom(format!("invalid hex ({:?})", e)))?) + } else { + Err(de::Error::custom("missing 0x prefix")) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn encoding() { + let bytes = vec![0, 255]; + let hex = encode(&bytes); + assert_eq!(hex.as_str(), "0x00ff"); + + let bytes = vec![]; + let hex = encode(&bytes); + assert_eq!(hex.as_str(), "0x"); + + let bytes = vec![1, 2, 3]; + let hex = encode(&bytes); + assert_eq!(hex.as_str(), "0x010203"); + } +} diff --git a/eth2/utils/ssz/src/impl_decode.rs b/eth2/utils/ssz/src/impl_decode.rs index b13cbeb5de..152e367606 100644 --- a/eth2/utils/ssz/src/impl_decode.rs +++ b/eth2/utils/ssz/src/impl_decode.rs @@ -24,11 +24,30 @@ macro_rules! impl_decodable_for_uint { }; } +macro_rules! impl_decodable_for_u8_array { + ($len: expr) => { + impl Decodable for [u8; $len] { + fn ssz_decode(bytes: &[u8], index: usize) -> Result<(Self, usize), DecodeError> { + if index + $len > bytes.len() { + Err(DecodeError::TooShort) + } else { + let mut array: [u8; $len] = [0; $len]; + array.copy_from_slice(&bytes[index..index + $len]); + + Ok((array, index + $len)) + } + } + } + }; +} + impl_decodable_for_uint!(u16, 16); impl_decodable_for_uint!(u32, 32); impl_decodable_for_uint!(u64, 64); impl_decodable_for_uint!(usize, 64); +impl_decodable_for_u8_array!(4); + impl Decodable for u8 { fn ssz_decode(bytes: &[u8], index: usize) -> Result<(Self, usize), DecodeError> { if index >= bytes.len() { @@ -246,4 +265,12 @@ mod tests { let result: Result<(bool, usize), DecodeError> = decode_ssz(&ssz, 0); assert_eq!(result, Err(DecodeError::Invalid)); } + + #[test] + fn test_decode_u8_array() { + let ssz = vec![0, 1, 2, 3]; + let (result, index): ([u8; 4], usize) = decode_ssz(&ssz, 0).unwrap(); + assert_eq!(index, 4); + assert_eq!(result, [0, 1, 2, 3]); + } } diff --git a/eth2/utils/ssz/src/impl_encode.rs b/eth2/utils/ssz/src/impl_encode.rs index bb1ec42d5d..b7d008ccfb 100644 --- a/eth2/utils/ssz/src/impl_encode.rs +++ b/eth2/utils/ssz/src/impl_encode.rs @@ -40,12 +40,25 @@ macro_rules! impl_encodable_for_uint { }; } +macro_rules! impl_encodable_for_u8_array { + ($len: expr) => { + impl Encodable for [u8; $len] { + fn ssz_append(&self, s: &mut SszStream) { + let bytes: Vec = self.iter().cloned().collect(); + s.append_encoded_raw(&bytes); + } + } + }; +} + impl_encodable_for_uint!(u8, 8); impl_encodable_for_uint!(u16, 16); impl_encodable_for_uint!(u32, 32); impl_encodable_for_uint!(u64, 64); impl_encodable_for_uint!(usize, 64); +impl_encodable_for_u8_array!(4); + impl Encodable for bool { fn ssz_append(&self, s: &mut SszStream) { let byte = if *self { 0b1000_0000 } else { 0b0000_0000 }; @@ -77,6 +90,7 @@ where #[cfg(test)] mod tests { use super::*; + use crate::ssz_encode; #[test] fn test_ssz_encode_h256() { @@ -226,4 +240,15 @@ mod tests { ssz.append(&x); assert_eq!(ssz.drain(), vec![0b1000_0000]); } + + #[test] + fn test_ssz_encode_u8_array() { + let x: [u8; 4] = [0, 1, 7, 8]; + let ssz = ssz_encode(&x); + assert_eq!(ssz, vec![0, 1, 7, 8]); + + let x: [u8; 4] = [255, 255, 255, 255]; + let ssz = ssz_encode(&x); + assert_eq!(ssz, vec![255, 255, 255, 255]); + } } From ef86948259c11ba2129982dd53e0619ed2cdedd1 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 13:32:23 +1100 Subject: [PATCH 102/144] Add bones of YAML state test parsing. Is incomplete, need to update all our types to v0.5.0 first --- eth2/state_processing/specs/example.yml | 347 ++++++++++++++++++++++++ eth2/state_processing/tests/tests.rs | 42 +++ 2 files changed, 389 insertions(+) create mode 100644 eth2/state_processing/specs/example.yml create mode 100644 eth2/state_processing/tests/tests.rs diff --git a/eth2/state_processing/specs/example.yml b/eth2/state_processing/specs/example.yml new file mode 100644 index 0000000000..95b749bc45 --- /dev/null +++ b/eth2/state_processing/specs/example.yml @@ -0,0 +1,347 @@ +title: Sanity tests +summary: Basic sanity checks from phase 0 spec pythonization. All tests are run with + `verify_signatures` as set to False. +test_suite: beacon_state +fork: tchaikovsky +version: v0.5.0 +test_cases: +- name: test_empty_block_transition + config: {SHARD_COUNT: 8, TARGET_COMMITTEE_SIZE: 4, MAX_BALANCE_CHURN_QUOTIENT: 32, + MAX_INDICES_PER_SLASHABLE_VOTE: 4096, MAX_EXIT_DEQUEUES_PER_EPOCH: 4, SHUFFLE_ROUND_COUNT: 90, + DEPOSIT_CONTRACT_TREE_DEPTH: 32, MIN_DEPOSIT_AMOUNT: 1000000000, MAX_DEPOSIT_AMOUNT: 32000000000, + FORK_CHOICE_BALANCE_INCREMENT: 1000000000, EJECTION_BALANCE: 16000000000, GENESIS_FORK_VERSION: 0, + GENESIS_SLOT: 4294967296, GENESIS_EPOCH: 536870912, GENESIS_START_SHARD: 0, BLS_WITHDRAWAL_PREFIX_BYTE: 0, + SECONDS_PER_SLOT: 6, MIN_ATTESTATION_INCLUSION_DELAY: 2, SLOTS_PER_EPOCH: 8, MIN_SEED_LOOKAHEAD: 1, + ACTIVATION_EXIT_DELAY: 4, EPOCHS_PER_ETH1_VOTING_PERIOD: 16, SLOTS_PER_HISTORICAL_ROOT: 64, + MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256, PERSISTENT_COMMITTEE_PERIOD: 2048, LATEST_RANDAO_MIXES_LENGTH: 64, + LATEST_ACTIVE_INDEX_ROOTS_LENGTH: 64, LATEST_SLASHED_EXIT_LENGTH: 64, BASE_REWARD_QUOTIENT: 32, + WHISTLEBLOWER_REWARD_QUOTIENT: 512, ATTESTATION_INCLUSION_REWARD_QUOTIENT: 8, + INACTIVITY_PENALTY_QUOTIENT: 16777216, MIN_PENALTY_QUOTIENT: 32, MAX_PROPOSER_SLASHINGS: 16, + MAX_ATTESTER_SLASHINGS: 1, MAX_ATTESTATIONS: 128, MAX_DEPOSITS: 16, MAX_VOLUNTARY_EXITS: 16, + MAX_TRANSFERS: 16, DOMAIN_BEACON_BLOCK: 0, DOMAIN_RANDAO: 1, DOMAIN_ATTESTATION: 2, + DOMAIN_DEPOSIT: 3, DOMAIN_VOLUNTARY_EXIT: 4, DOMAIN_TRANSFER: 5} + verify_signatures: false + initial_state: + slot: 4294967296 + genesis_time: 0 + fork: {previous_version: 0, current_version: 0, epoch: 536870912} + validator_registry: + - {pubkey: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x0a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x0c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x0d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x0e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x0f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x130000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x140000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x150000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x170000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x190000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x1a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x1d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x1e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + - {pubkey: '0x1f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + withdrawal_credentials: '0x2222222222222222222222222222222222222222222222222222222222222222', + activation_epoch: 536870912, exit_epoch: 18446744073709551615, withdrawable_epoch: 18446744073709551615, + initiated_exit: false, slashed: false} + validator_balances: [32000000000, 32000000000, 32000000000, 32000000000, 32000000000, + 32000000000, 32000000000, 32000000000, 32000000000, 32000000000, 32000000000, + 32000000000, 32000000000, 32000000000, 32000000000, 32000000000, 32000000000, + 32000000000, 32000000000, 32000000000, 32000000000, 32000000000, 32000000000, + 32000000000, 32000000000, 32000000000, 32000000000, 32000000000, 32000000000, + 32000000000, 32000000000, 32000000000] + validator_registry_update_epoch: 536870912 + latest_randao_mixes: ['0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000'] + previous_shuffling_start_shard: 0 + current_shuffling_start_shard: 0 + previous_shuffling_epoch: 536870912 + current_shuffling_epoch: 536870912 + previous_shuffling_seed: '0x0000000000000000000000000000000000000000000000000000000000000000' + current_shuffling_seed: '0x94ab448e948e6d501a2b48c1e9a0946f871100969f6fa70a990acf2348c9b185' + previous_epoch_attestations: [] + current_epoch_attestations: [] + previous_justified_epoch: 536870912 + current_justified_epoch: 536870912 + previous_justified_root: '0x0000000000000000000000000000000000000000000000000000000000000000' + current_justified_root: '0x0000000000000000000000000000000000000000000000000000000000000000' + justification_bitfield: 0 + finalized_epoch: 536870912 + finalized_root: '0x0000000000000000000000000000000000000000000000000000000000000000' + latest_crosslinks: + - {epoch: 536870912, crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'} + - {epoch: 536870912, crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'} + - {epoch: 536870912, crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'} + - {epoch: 536870912, crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'} + - {epoch: 536870912, crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'} + - {epoch: 536870912, crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'} + - {epoch: 536870912, crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'} + - {epoch: 536870912, crosslink_data_root: '0x0000000000000000000000000000000000000000000000000000000000000000'} + latest_block_roots: ['0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000'] + latest_state_roots: ['0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000000000'] + latest_active_index_roots: ['0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42', + '0xf6b8ca96e524598ba62d563347f5aea6ce2d81d644e2788687e5a92844df1b42'] + latest_slashed_balances: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + latest_block_header: {slot: 4294967296, previous_block_root: '0x0000000000000000000000000000000000000000000000000000000000000000', + state_root: '0x0000000000000000000000000000000000000000000000000000000000000000', + block_body_root: '0x5359b62990beb1d78e1cec479f5a4d80af84709886a8e16c535dff0556dc0e2d', + signature: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'} + historical_roots: [] + latest_eth1_data: {deposit_root: '0xb05de6a9059df0c9a2ab5f76708d256941dfe9eb89e6fda549b30713087d2a5e', + block_hash: '0x0000000000000000000000000000000000000000000000000000000000000000'} + eth1_data_votes: [] + deposit_index: 32 + blocks: + - slot: 4294967297 + previous_block_root: '0x92ed652508d2b4c109a857107101716b18e257e7ce0d199d4b16232956e9e27e' + state_root: '0x0000000000000000000000000000000000000000000000000000000000000000' + body: + randao_reveal: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + eth1_data: {deposit_root: '0x0000000000000000000000000000000000000000000000000000000000000000', + block_hash: '0x0000000000000000000000000000000000000000000000000000000000000000'} + proposer_slashings: [] + attester_slashings: [] + attestations: [] + deposits: [] + voluntary_exits: [] + transfers: [] + signature: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + expected_state: {slot: 4294967297} diff --git a/eth2/state_processing/tests/tests.rs b/eth2/state_processing/tests/tests.rs new file mode 100644 index 0000000000..9cee7b34ce --- /dev/null +++ b/eth2/state_processing/tests/tests.rs @@ -0,0 +1,42 @@ +use serde_derive::Deserialize; +use types::*; + +#[derive(Debug, Deserialize)] +pub struct TestCase { + pub name: String, + pub config: ChainSpec, + pub verify_signatures: bool, + pub initial_state: BeaconState, + pub blocks: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct TestDoc { + pub title: String, + pub summary: String, + pub fork: String, + pub version: String, + pub test_cases: Vec, +} + +#[test] +#[ignore] +fn yaml() { + use serde_yaml; + use std::{fs::File, io::prelude::*, path::PathBuf}; + + let mut file = { + let mut file_path_buf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + file_path_buf.push("specs/example.yml"); + + File::open(file_path_buf).unwrap() + }; + + let mut yaml_str = String::new(); + + file.read_to_string(&mut yaml_str).unwrap(); + + let yaml_str = yaml_str.to_lowercase(); + + let _doc: TestDoc = serde_yaml::from_str(&yaml_str.as_str()).unwrap(); +} From f9964ebd8b04439ff222db5b2e803413141b73ba Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 13:33:07 +1100 Subject: [PATCH 103/144] Update Fork struct to v0.5.0 --- beacon_node/src/main.rs | 6 +-- eth2/types/src/chain_spec.rs | 35 ++++++++++++++-- eth2/types/src/fork.rs | 45 +++++++++++++++++++++ validator_client/src/duties/epoch_duties.rs | 4 +- 4 files changed, 79 insertions(+), 11 deletions(-) diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index c3182c789b..eacbffa3ef 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -119,11 +119,7 @@ fn main() { // Get domain from genesis fork_version spec.genesis_epoch, Domain::Deposit, - &Fork { - previous_version: spec.genesis_fork_version, - current_version: spec.genesis_fork_version, - epoch: spec.genesis_epoch, - }, + &Fork::genesis(&spec), ), ), }, diff --git a/eth2/types/src/chain_spec.rs b/eth2/types/src/chain_spec.rs index 108516695d..ae521cc92a 100644 --- a/eth2/types/src/chain_spec.rs +++ b/eth2/types/src/chain_spec.rs @@ -1,4 +1,4 @@ -use crate::{Address, Epoch, Fork, Hash256, Slot}; +use crate::*; use bls::Signature; use int_to_bytes::int_to_bytes4; use serde_derive::Deserialize; @@ -127,7 +127,7 @@ impl ChainSpec { /// Get the domain number that represents the fork meta and signature domain. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn get_domain(&self, epoch: Epoch, domain: Domain, fork: &Fork) -> u64 { let domain_constant = match domain { Domain::Deposit => self.domain_deposit, @@ -138,9 +138,11 @@ impl ChainSpec { Domain::Transfer => self.domain_transfer, }; + let mut bytes: Vec = fork.get_fork_version(epoch).to_vec(); + bytes.append(&mut int_to_bytes4(domain_constant)); + let mut fork_and_domain = [0; 8]; - fork_and_domain.copy_from_slice(&fork.get_fork_version(epoch)); - fork_and_domain.copy_from_slice(&int_to_bytes4(domain_constant)); + fork_and_domain.copy_from_slice(&bytes); u64::from_le_bytes(fork_and_domain) } @@ -269,9 +271,34 @@ impl Default for ChainSpec { #[cfg(test)] mod tests { use super::*; + use int_to_bytes::int_to_bytes8; #[test] fn test_foundation_spec_can_be_constructed() { let _ = ChainSpec::foundation(); } + + fn test_domain(domain_type: Domain, raw_domain: u32, spec: &ChainSpec) { + let fork = Fork::genesis(&spec); + let epoch = Epoch::new(0); + + let domain = spec.get_domain(epoch, domain_type, &fork); + + let mut expected = fork.get_fork_version(epoch).to_vec(); + expected.append(&mut int_to_bytes4(raw_domain)); + + assert_eq!(int_to_bytes8(domain), expected); + } + + #[test] + fn test_get_domain() { + let spec = ChainSpec::foundation(); + + test_domain(Domain::Deposit, spec.domain_deposit, &spec); + test_domain(Domain::Attestation, spec.domain_attestation, &spec); + test_domain(Domain::Proposal, spec.domain_proposal, &spec); + test_domain(Domain::Exit, spec.domain_exit, &spec); + test_domain(Domain::Randao, spec.domain_randao, &spec); + test_domain(Domain::Transfer, spec.domain_transfer, &spec); + } } diff --git a/eth2/types/src/fork.rs b/eth2/types/src/fork.rs index f0e3d10467..b780b95ef3 100644 --- a/eth2/types/src/fork.rs +++ b/eth2/types/src/fork.rs @@ -48,4 +48,49 @@ mod tests { use super::*; ssz_tests!(Fork); + + fn test_genesis(version: u32, epoch: Epoch) { + let mut spec = ChainSpec::foundation(); + + spec.genesis_fork_version = version; + spec.genesis_epoch = epoch; + + let fork = Fork::genesis(&spec); + + assert_eq!(fork.epoch, spec.genesis_epoch, "epoch incorrect"); + assert_eq!( + fork.previous_version, fork.current_version, + "previous and current are not identical" + ); + assert_eq!( + fork.current_version, + version.to_le_bytes(), + "current version incorrect" + ); + } + + #[test] + fn genesis() { + test_genesis(0, Epoch::new(0)); + test_genesis(9, Epoch::new(11)); + test_genesis(2_u32.pow(31), Epoch::new(2_u64.pow(63))); + test_genesis(u32::max_value(), Epoch::max_value()); + } + + #[test] + fn get_fork_version() { + let previous_version = [1; 4]; + let current_version = [2; 4]; + let epoch = Epoch::new(10); + + let fork = Fork { + previous_version, + current_version, + epoch, + }; + + assert_eq!(fork.get_fork_version(epoch - 1), previous_version); + assert_eq!(fork.get_fork_version(epoch), current_version); + assert_eq!(fork.get_fork_version(epoch + 1), current_version); + } } diff --git a/validator_client/src/duties/epoch_duties.rs b/validator_client/src/duties/epoch_duties.rs index 35668b4a9d..71f5f26ab5 100644 --- a/validator_client/src/duties/epoch_duties.rs +++ b/validator_client/src/duties/epoch_duties.rs @@ -81,8 +81,8 @@ impl DutiesReader for EpochDutiesMap { // // It will almost certainly cause signatures to fail verification. Ok(Fork { - previous_version: 0, - current_version: 0, + previous_version: [0; 4], + current_version: [0; 4], epoch: Epoch::new(0), }) } From 8050ed7a2600ef17bab8c1cc43305dbad08456ec Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 14:33:40 +1100 Subject: [PATCH 104/144] Fast-forward unchanged 0.4.0 structs to 0.5.0 --- eth2/types/src/crosslink.rs | 2 +- eth2/types/src/eth1_data.rs | 2 +- eth2/types/src/eth1_data_vote.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eth2/types/src/crosslink.rs b/eth2/types/src/crosslink.rs index dfa0311eff..f91680c756 100644 --- a/eth2/types/src/crosslink.rs +++ b/eth2/types/src/crosslink.rs @@ -7,7 +7,7 @@ use test_random_derive::TestRandom; /// Specifies the block hash for a shard at an epoch. /// -/// Spec v0.4.0 +/// Spec v0.5.0 #[derive( Debug, Clone, diff --git a/eth2/types/src/eth1_data.rs b/eth2/types/src/eth1_data.rs index 0f1dbfec59..deced19fb6 100644 --- a/eth2/types/src/eth1_data.rs +++ b/eth2/types/src/eth1_data.rs @@ -7,7 +7,7 @@ use test_random_derive::TestRandom; /// Contains data obtained from the Eth1 chain. /// -/// Spec v0.4.0 +/// Spec v0.5.0 #[derive( Debug, PartialEq, Clone, Default, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom, )] diff --git a/eth2/types/src/eth1_data_vote.rs b/eth2/types/src/eth1_data_vote.rs index d709608d5f..2f3a1ade1a 100644 --- a/eth2/types/src/eth1_data_vote.rs +++ b/eth2/types/src/eth1_data_vote.rs @@ -7,7 +7,7 @@ use test_random_derive::TestRandom; /// A summation of votes for some `Eth1Data`. /// -/// Spec v0.4.0 +/// Spec v0.5.0 #[derive( Debug, PartialEq, Clone, Default, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom, )] From 20a439101e57dd55293721c01a7024384ccc6d10 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 15:19:17 +1100 Subject: [PATCH 105/144] Update "attestation" family of structs in types Also adds/splits up some testing builders. --- eth2/types/src/attestation_data.rs | 17 +++-- .../src/attestation_data_and_custody_bit.rs | 2 +- eth2/types/src/slashable_attestation.rs | 14 ++-- eth2/types/src/test_utils/mod.rs | 4 + .../test_utils/testing_attestation_builder.rs | 38 +--------- .../testing_attestation_data_builder.rs | 66 ++++++++++++++++ .../testing_attester_slashing_builder.rs | 48 +++++------- .../testing_beacon_state_builder.rs | 76 ++----------------- .../testing_pending_attestation_builder.rs | 55 ++++++++++++++ 9 files changed, 175 insertions(+), 145 deletions(-) create mode 100644 eth2/types/src/test_utils/testing_attestation_data_builder.rs create mode 100644 eth2/types/src/test_utils/testing_pending_attestation_builder.rs diff --git a/eth2/types/src/attestation_data.rs b/eth2/types/src/attestation_data.rs index 791ba00d27..4a6b57823e 100644 --- a/eth2/types/src/attestation_data.rs +++ b/eth2/types/src/attestation_data.rs @@ -8,7 +8,7 @@ use test_random_derive::TestRandom; /// The data upon which an attestation is based. /// -/// Spec v0.4.0 +/// Spec v0.5.0 #[derive( Debug, Clone, @@ -24,14 +24,19 @@ use test_random_derive::TestRandom; SignedRoot, )] pub struct AttestationData { + // LMD GHOST vote pub slot: Slot, - pub shard: u64, pub beacon_block_root: Hash256, - pub epoch_boundary_root: Hash256, + + // FFG Vote + pub source_epoch: Epoch, + pub source_root: Hash256, + pub target_root: Hash256, + + // Crosslink Vote + pub shard: u64, + pub previous_crosslink: Crosslink, pub crosslink_data_root: Hash256, - pub latest_crosslink: Crosslink, - pub justified_epoch: Epoch, - pub justified_block_root: Hash256, } impl Eq for AttestationData {} diff --git a/eth2/types/src/attestation_data_and_custody_bit.rs b/eth2/types/src/attestation_data_and_custody_bit.rs index 020b07d28e..2cc6bc80ca 100644 --- a/eth2/types/src/attestation_data_and_custody_bit.rs +++ b/eth2/types/src/attestation_data_and_custody_bit.rs @@ -6,7 +6,7 @@ use ssz_derive::{Decode, Encode, TreeHash}; /// Used for pairing an attestation with a proof-of-custody. /// -/// Spec v0.4.0 +/// Spec v0.5.0 #[derive(Debug, Clone, PartialEq, Default, Serialize, Encode, Decode, TreeHash)] pub struct AttestationDataAndCustodyBit { pub data: AttestationData, diff --git a/eth2/types/src/slashable_attestation.rs b/eth2/types/src/slashable_attestation.rs index bc9b2769a4..05c41a72b3 100644 --- a/eth2/types/src/slashable_attestation.rs +++ b/eth2/types/src/slashable_attestation.rs @@ -9,7 +9,7 @@ use test_random_derive::TestRandom; /// /// To be included in an `AttesterSlashing`. /// -/// Spec v0.4.0 +/// Spec v0.5.0 #[derive( Debug, PartialEq, @@ -33,17 +33,17 @@ pub struct SlashableAttestation { impl SlashableAttestation { /// Check if ``attestation_data_1`` and ``attestation_data_2`` have the same target. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn is_double_vote(&self, other: &SlashableAttestation, spec: &ChainSpec) -> bool { self.data.slot.epoch(spec.slots_per_epoch) == other.data.slot.epoch(spec.slots_per_epoch) } /// Check if ``attestation_data_1`` surrounds ``attestation_data_2``. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn is_surround_vote(&self, other: &SlashableAttestation, spec: &ChainSpec) -> bool { - let source_epoch_1 = self.data.justified_epoch; - let source_epoch_2 = other.data.justified_epoch; + let source_epoch_1 = self.data.source_epoch; + let source_epoch_2 = other.data.source_epoch; let target_epoch_1 = self.data.slot.epoch(spec.slots_per_epoch); let target_epoch_2 = other.data.slot.epoch(spec.slots_per_epoch); @@ -134,14 +134,14 @@ mod tests { fn create_slashable_attestation( slot_factor: u64, - justified_epoch: u64, + source_epoch: u64, spec: &ChainSpec, ) -> SlashableAttestation { let mut rng = XorShiftRng::from_seed([42; 16]); let mut slashable_vote = SlashableAttestation::random_for_test(&mut rng); slashable_vote.data.slot = Slot::new(slot_factor * spec.slots_per_epoch); - slashable_vote.data.justified_epoch = Epoch::new(justified_epoch); + slashable_vote.data.source_epoch = Epoch::new(source_epoch); slashable_vote } } diff --git a/eth2/types/src/test_utils/mod.rs b/eth2/types/src/test_utils/mod.rs index 9d04d1ca7c..bc8da05489 100644 --- a/eth2/types/src/test_utils/mod.rs +++ b/eth2/types/src/test_utils/mod.rs @@ -4,10 +4,12 @@ mod generate_deterministic_keypairs; mod keypairs_file; mod test_random; mod testing_attestation_builder; +mod testing_attestation_data_builder; mod testing_attester_slashing_builder; mod testing_beacon_block_builder; mod testing_beacon_state_builder; mod testing_deposit_builder; +mod testing_pending_attestation_builder; mod testing_proposer_slashing_builder; mod testing_transfer_builder; mod testing_voluntary_exit_builder; @@ -17,10 +19,12 @@ pub use keypairs_file::KeypairsFile; pub use rand::{prng::XorShiftRng, SeedableRng}; pub use test_random::TestRandom; pub use testing_attestation_builder::TestingAttestationBuilder; +pub use testing_attestation_data_builder::TestingAttestationDataBuilder; pub use testing_attester_slashing_builder::TestingAttesterSlashingBuilder; pub use testing_beacon_block_builder::TestingBeaconBlockBuilder; pub use testing_beacon_state_builder::{keypairs_path, TestingBeaconStateBuilder}; pub use testing_deposit_builder::TestingDepositBuilder; +pub use testing_pending_attestation_builder::TestingPendingAttestationBuilder; pub use testing_proposer_slashing_builder::TestingProposerSlashingBuilder; pub use testing_transfer_builder::TestingTransferBuilder; pub use testing_voluntary_exit_builder::TestingVoluntaryExitBuilder; diff --git a/eth2/types/src/test_utils/testing_attestation_builder.rs b/eth2/types/src/test_utils/testing_attestation_builder.rs index 8c86d756de..60624b48d7 100644 --- a/eth2/types/src/test_utils/testing_attestation_builder.rs +++ b/eth2/types/src/test_utils/testing_attestation_builder.rs @@ -1,3 +1,4 @@ +use crate::test_utils::TestingAttestationDataBuilder; use crate::*; use ssz::TreeHash; @@ -18,31 +19,7 @@ impl TestingAttestationBuilder { shard: u64, spec: &ChainSpec, ) -> Self { - let current_epoch = state.current_epoch(spec); - let previous_epoch = state.previous_epoch(spec); - - let is_previous_epoch = - state.slot.epoch(spec.slots_per_epoch) != slot.epoch(spec.slots_per_epoch); - - let justified_epoch = if is_previous_epoch { - state.previous_justified_epoch - } else { - state.justified_epoch - }; - - let epoch_boundary_root = if is_previous_epoch { - *state - .get_block_root(previous_epoch.start_slot(spec.slots_per_epoch), spec) - .unwrap() - } else { - *state - .get_block_root(current_epoch.start_slot(spec.slots_per_epoch), spec) - .unwrap() - }; - - let justified_block_root = *state - .get_block_root(justified_epoch.start_slot(spec.slots_per_epoch), spec) - .unwrap(); + let data_builder = TestingAttestationDataBuilder::new(state, shard, slot, spec); let mut aggregation_bitfield = Bitfield::new(); let mut custody_bitfield = Bitfield::new(); @@ -54,16 +31,7 @@ impl TestingAttestationBuilder { let attestation = Attestation { aggregation_bitfield, - data: AttestationData { - slot, - shard, - beacon_block_root: *state.get_block_root(slot, spec).unwrap(), - epoch_boundary_root, - crosslink_data_root: Hash256::zero(), - latest_crosslink: state.latest_crosslinks[shard as usize].clone(), - justified_epoch, - justified_block_root, - }, + data: data_builder.build(), custody_bitfield, aggregate_signature: AggregateSignature::new(), }; diff --git a/eth2/types/src/test_utils/testing_attestation_data_builder.rs b/eth2/types/src/test_utils/testing_attestation_data_builder.rs new file mode 100644 index 0000000000..f31de2fbd1 --- /dev/null +++ b/eth2/types/src/test_utils/testing_attestation_data_builder.rs @@ -0,0 +1,66 @@ +use crate::*; + +/// Builds an `AttestationData` to be used for testing purposes. +/// +/// This struct should **never be used for production purposes.** +pub struct TestingAttestationDataBuilder { + data: AttestationData, +} + +impl TestingAttestationDataBuilder { + /// Configures a new `AttestationData` which attests to all of the same parameters as the + /// state. + pub fn new(state: &BeaconState, shard: u64, slot: Slot, spec: &ChainSpec) -> Self { + let current_epoch = state.current_epoch(spec); + let previous_epoch = state.previous_epoch(spec); + + let is_previous_epoch = + state.slot.epoch(spec.slots_per_epoch) != slot.epoch(spec.slots_per_epoch); + + let source_epoch = if is_previous_epoch { + state.previous_justified_epoch + } else { + state.justified_epoch + }; + + let target_root = if is_previous_epoch { + *state + .get_block_root(previous_epoch.start_slot(spec.slots_per_epoch), spec) + .unwrap() + } else { + *state + .get_block_root(current_epoch.start_slot(spec.slots_per_epoch), spec) + .unwrap() + }; + + let source_root = *state + .get_block_root(source_epoch.start_slot(spec.slots_per_epoch), spec) + .unwrap(); + + let data = AttestationData { + // LMD GHOST vote + slot, + beacon_block_root: *state.get_block_root(slot, spec).unwrap(), + + // FFG Vote + source_epoch, + source_root, + target_root, + + // Crosslink vote + shard, + previous_crosslink: Crosslink { + epoch: slot.epoch(spec.slots_per_epoch), + crosslink_data_root: spec.zero_hash, + }, + crosslink_data_root: spec.zero_hash, + }; + + Self { data } + } + + /// Returns the `AttestationData`, consuming the builder. + pub fn build(self) -> AttestationData { + self.data + } +} diff --git a/eth2/types/src/test_utils/testing_attester_slashing_builder.rs b/eth2/types/src/test_utils/testing_attester_slashing_builder.rs index 92c7fe814a..fcaa3285be 100644 --- a/eth2/types/src/test_utils/testing_attester_slashing_builder.rs +++ b/eth2/types/src/test_utils/testing_attester_slashing_builder.rs @@ -23,45 +23,39 @@ impl TestingAttesterSlashingBuilder { { let double_voted_slot = Slot::new(0); let shard = 0; - let justified_epoch = Epoch::new(0); let epoch = Epoch::new(0); let hash_1 = Hash256::from_low_u64_le(1); let hash_2 = Hash256::from_low_u64_le(2); + let data_1 = AttestationData { + slot: double_voted_slot, + beacon_block_root: hash_1, + source_epoch: epoch, + source_root: hash_1, + target_root: hash_1, + shard, + previous_crosslink: Crosslink { + epoch, + crosslink_data_root: hash_1, + }, + crosslink_data_root: hash_1, + }; + + let data_2 = AttestationData { + beacon_block_root: hash_2, + ..data_1.clone() + }; + let mut slashable_attestation_1 = SlashableAttestation { validator_indices: validator_indices.to_vec(), - data: AttestationData { - slot: double_voted_slot, - shard, - beacon_block_root: hash_1, - epoch_boundary_root: hash_1, - crosslink_data_root: hash_1, - latest_crosslink: Crosslink { - epoch, - crosslink_data_root: hash_1, - }, - justified_epoch, - justified_block_root: hash_1, - }, + data: data_1, custody_bitfield: Bitfield::new(), aggregate_signature: AggregateSignature::new(), }; let mut slashable_attestation_2 = SlashableAttestation { validator_indices: validator_indices.to_vec(), - data: AttestationData { - slot: double_voted_slot, - shard, - beacon_block_root: hash_2, - epoch_boundary_root: hash_2, - crosslink_data_root: hash_2, - latest_crosslink: Crosslink { - epoch, - crosslink_data_root: hash_2, - }, - justified_epoch, - justified_block_root: hash_2, - }, + data: data_2, custody_bitfield: Bitfield::new(), aggregate_signature: AggregateSignature::new(), }; diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index d3033634a2..5b96dc455c 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -1,5 +1,6 @@ use super::{generate_deterministic_keypairs, KeypairsFile}; use crate::beacon_state::BeaconStateBuilder; +use crate::test_utils::TestingPendingAttestationBuilder; use crate::*; use bls::get_withdrawal_credentials; use dirs; @@ -227,76 +228,13 @@ impl TestingBeaconStateBuilder { .clone(); for (committee, shard) in committees { - state - .latest_attestations - .push(committee_to_pending_attestation( - state, &committee, shard, slot, spec, - )) + let mut builder = TestingPendingAttestationBuilder::new(state, shard, slot, spec); + // The entire committee should have signed the pending attestation. + let signers = vec![true; committee.len()]; + builder.add_committee_participation(signers); + + state.latest_attestations.push(builder.build()) } } } } - -/// Maps a committee to a `PendingAttestation`. -/// -/// The committee will be signed by all validators in the committee. -fn committee_to_pending_attestation( - state: &BeaconState, - committee: &[usize], - shard: u64, - slot: Slot, - spec: &ChainSpec, -) -> PendingAttestation { - let current_epoch = state.current_epoch(spec); - let previous_epoch = state.previous_epoch(spec); - - let mut aggregation_bitfield = Bitfield::new(); - let mut custody_bitfield = Bitfield::new(); - - for (i, _) in committee.iter().enumerate() { - aggregation_bitfield.set(i, true); - custody_bitfield.set(i, true); - } - - let is_previous_epoch = - state.slot.epoch(spec.slots_per_epoch) != slot.epoch(spec.slots_per_epoch); - - let justified_epoch = if is_previous_epoch { - state.previous_justified_epoch - } else { - state.justified_epoch - }; - - let epoch_boundary_root = if is_previous_epoch { - *state - .get_block_root(previous_epoch.start_slot(spec.slots_per_epoch), spec) - .unwrap() - } else { - *state - .get_block_root(current_epoch.start_slot(spec.slots_per_epoch), spec) - .unwrap() - }; - - let justified_block_root = *state - .get_block_root(justified_epoch.start_slot(spec.slots_per_epoch), spec) - .unwrap(); - - PendingAttestation { - aggregation_bitfield, - data: AttestationData { - slot, - shard, - beacon_block_root: *state.get_block_root(slot, spec).unwrap(), - epoch_boundary_root, - crosslink_data_root: Hash256::zero(), - latest_crosslink: Crosslink { - epoch: slot.epoch(spec.slots_per_epoch), - crosslink_data_root: Hash256::zero(), - }, - justified_epoch, - justified_block_root, - }, - custody_bitfield, - inclusion_slot: slot + spec.min_attestation_inclusion_delay, - } -} diff --git a/eth2/types/src/test_utils/testing_pending_attestation_builder.rs b/eth2/types/src/test_utils/testing_pending_attestation_builder.rs new file mode 100644 index 0000000000..655b3d1e83 --- /dev/null +++ b/eth2/types/src/test_utils/testing_pending_attestation_builder.rs @@ -0,0 +1,55 @@ +use crate::test_utils::TestingAttestationDataBuilder; +use crate::*; + +/// Builds an `AttesterSlashing` to be used for testing purposes. +/// +/// This struct should **never be used for production purposes.** +pub struct TestingPendingAttestationBuilder { + pending_attestation: PendingAttestation, +} + +impl TestingPendingAttestationBuilder { + /// Create a new valid* `PendingAttestation` for the given parameters. + /// + /// The `inclusion_slot` will be set to be the earliest possible slot the `Attestation` could + /// have been included (`slot + MIN_ATTESTATION_INCLUSION_DELAY`). + /// + /// * The aggregation and custody bitfields will all be empty, they need to be set with + /// `Self::add_committee_participation`. + pub fn new(state: &BeaconState, shard: u64, slot: Slot, spec: &ChainSpec) -> Self { + let data_builder = TestingAttestationDataBuilder::new(state, shard, slot, spec); + + let pending_attestation = PendingAttestation { + aggregation_bitfield: Bitfield::new(), + data: data_builder.build(), + custody_bitfield: Bitfield::new(), + inclusion_slot: slot + spec.min_attestation_inclusion_delay, + }; + + Self { + pending_attestation, + } + } + + /// Sets the committee participation in the `PendingAttestation`. + /// + /// The `PendingAttestation` will appear to be signed by each committee member who's value in + /// `signers` is true. + pub fn add_committee_participation(&mut self, signers: Vec) { + let mut aggregation_bitfield = Bitfield::new(); + let mut custody_bitfield = Bitfield::new(); + + for (i, signed) in signers.iter().enumerate() { + aggregation_bitfield.set(i, *signed); + custody_bitfield.set(i, false); // Fixed to `false` for phase 0. + } + + self.pending_attestation.aggregation_bitfield = aggregation_bitfield; + self.pending_attestation.custody_bitfield = custody_bitfield; + } + + /// Returns the `PendingAttestation`, consuming the builder. + pub fn build(self) -> PendingAttestation { + self.pending_attestation + } +} From 2295322e3c3d1921b205fab3cf13ec5bde5ad85e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 15:33:14 +1100 Subject: [PATCH 106/144] Update DepositInput to spec v0.5.0 Also modifies the API for creating a proof of possession and adds a test --- eth2/types/src/deposit_data.rs | 2 +- eth2/types/src/deposit_input.rs | 43 +++++++++++++------ .../src/test_utils/testing_deposit_builder.rs | 9 ++-- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/eth2/types/src/deposit_data.rs b/eth2/types/src/deposit_data.rs index 9d6c1bda73..a1e30032fb 100644 --- a/eth2/types/src/deposit_data.rs +++ b/eth2/types/src/deposit_data.rs @@ -7,7 +7,7 @@ use test_random_derive::TestRandom; /// Data generated by the deposit contract. /// -/// Spec v0.4.0 +/// Spec v0.5.0 #[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)] pub struct DepositData { pub amount: u64, diff --git a/eth2/types/src/deposit_input.rs b/eth2/types/src/deposit_input.rs index 9a90319017..9da53a042d 100644 --- a/eth2/types/src/deposit_input.rs +++ b/eth2/types/src/deposit_input.rs @@ -9,7 +9,7 @@ use test_random_derive::TestRandom; /// The data supplied by the user to the deposit contract. /// -/// Spec v0.4.0 +/// Spec v0.5.0 #[derive( Debug, PartialEq, @@ -31,25 +31,23 @@ pub struct DepositInput { impl DepositInput { /// Generate the 'proof_of_posession' signature for a given DepositInput details. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn create_proof_of_possession( - keypair: &Keypair, - withdrawal_credentials: &Hash256, - domain: u64, + &self, + secret_key: &SecretKey, + epoch: Epoch, + fork: &Fork, + spec: &ChainSpec, ) -> Signature { - let signable_deposit_input = DepositInput { - pubkey: keypair.pk.clone(), - withdrawal_credentials: withdrawal_credentials.clone(), - proof_of_possession: Signature::empty_signature(), - }; - let msg = signable_deposit_input.signed_root(); + let msg = self.signed_root(); + let domain = spec.get_domain(epoch, Domain::Deposit, fork); - Signature::new(msg.as_slice(), domain, &keypair.sk) + Signature::new(msg.as_slice(), domain, secret_key) } /// Verify that proof-of-possession is valid. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn validate_proof_of_possession( &self, epoch: Epoch, @@ -68,4 +66,23 @@ mod tests { use super::*; ssz_tests!(DepositInput); + + #[test] + fn can_create_and_validate() { + let spec = ChainSpec::foundation(); + let fork = Fork::genesis(&spec); + let keypair = Keypair::random(); + let epoch = Epoch::new(0); + + let mut deposit_input = DepositInput { + pubkey: keypair.pk.clone(), + withdrawal_credentials: Hash256::zero(), + proof_of_possession: Signature::empty_signature(), + }; + + deposit_input.proof_of_possession = + deposit_input.create_proof_of_possession(&keypair.sk, epoch, &fork, &spec); + + assert!(deposit_input.validate_proof_of_possession(epoch, &fork, &spec)); + } } diff --git a/eth2/types/src/test_utils/testing_deposit_builder.rs b/eth2/types/src/test_utils/testing_deposit_builder.rs index 7293114688..90c8d325d2 100644 --- a/eth2/types/src/test_utils/testing_deposit_builder.rs +++ b/eth2/types/src/test_utils/testing_deposit_builder.rs @@ -46,15 +46,18 @@ impl TestingDepositBuilder { ); let epoch = state.current_epoch(spec); - let domain = spec.get_domain(epoch, Domain::Deposit, &state.fork); self.deposit.deposit_data.deposit_input.pubkey = keypair.pk.clone(); self.deposit .deposit_data .deposit_input .withdrawal_credentials = withdrawal_credentials.clone(); - self.deposit.deposit_data.deposit_input.proof_of_possession = - DepositInput::create_proof_of_possession(&keypair, &withdrawal_credentials, domain); + + self.deposit.deposit_data.deposit_input.proof_of_possession = self + .deposit + .deposit_data + .deposit_input + .create_proof_of_possession(&keypair.sk, epoch, &state.fork, spec); } /// Builds the deposit, consuming the builder. From d84850b892ba2bc5ffaafb4d9b6f267e64ad88c5 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 15:40:28 +1100 Subject: [PATCH 107/144] Add `BeaconBlockHeader` type. --- eth2/types/src/beacon_block_header.rs | 38 +++++++++++++++++++++++++++ eth2/types/src/lib.rs | 2 ++ 2 files changed, 40 insertions(+) create mode 100644 eth2/types/src/beacon_block_header.rs diff --git a/eth2/types/src/beacon_block_header.rs b/eth2/types/src/beacon_block_header.rs new file mode 100644 index 0000000000..029c7e56b2 --- /dev/null +++ b/eth2/types/src/beacon_block_header.rs @@ -0,0 +1,38 @@ +use crate::test_utils::TestRandom; +use crate::*; +use bls::Signature; +use rand::RngCore; +use serde_derive::{Deserialize, Serialize}; +use ssz::TreeHash; +use ssz_derive::{Decode, Encode, SignedRoot, TreeHash}; +use test_random_derive::TestRandom; + +/// A header of a `BeaconBlock`. +/// +/// Spec v0.5.0 +#[derive( + Debug, + PartialEq, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + TestRandom, + SignedRoot, +)] +pub struct BeaconBlockHeader { + pub slot: Slot, + pub previous_block_root: Hash256, + pub state_root: Hash256, + pub block_body_root: Hash256, + pub signature: Signature, +} + +#[cfg(test)] +mod tests { + use super::*; + + ssz_tests!(BeaconBlockHeader); +} diff --git a/eth2/types/src/lib.rs b/eth2/types/src/lib.rs index 7b1d848377..2fcb3237d5 100644 --- a/eth2/types/src/lib.rs +++ b/eth2/types/src/lib.rs @@ -9,6 +9,7 @@ pub mod attestation_data_and_custody_bit; pub mod attester_slashing; pub mod beacon_block; pub mod beacon_block_body; +pub mod beacon_block_header; pub mod beacon_state; pub mod chain_spec; pub mod crosslink; @@ -43,6 +44,7 @@ pub use crate::attestation_data_and_custody_bit::AttestationDataAndCustodyBit; pub use crate::attester_slashing::AttesterSlashing; pub use crate::beacon_block::BeaconBlock; pub use crate::beacon_block_body::BeaconBlockBody; +pub use crate::beacon_block_header::BeaconBlockHeader; pub use crate::beacon_state::{BeaconState, Error as BeaconStateError, RelativeEpoch}; pub use crate::chain_spec::{ChainSpec, Domain}; pub use crate::crosslink::Crosslink; From 4ffdfbc9938f3fa253a4d6bf944e2e24e82d7ef0 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 15:48:33 +1100 Subject: [PATCH 108/144] Update `Validator` to 0.5.0, add tests --- eth2/types/src/validator.rs | 69 +++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 19 deletions(-) diff --git a/eth2/types/src/validator.rs b/eth2/types/src/validator.rs index 6d1936bfd3..f572611757 100644 --- a/eth2/types/src/validator.rs +++ b/eth2/types/src/validator.rs @@ -6,7 +6,7 @@ use test_random_derive::TestRandom; /// Information about a `BeaconChain` validator. /// -/// Spec v0.4.0 +/// Spec v0.5.0 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TestRandom, TreeHash)] pub struct Validator { pub pubkey: PublicKey, @@ -53,29 +53,60 @@ impl Default for Validator { #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; #[test] - fn test_validator_can_be_active() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let mut validator = Validator::random_for_test(&mut rng); + fn default() { + let v = Validator::default(); - let activation_epoch = u64::random_for_test(&mut rng); - let exit_epoch = activation_epoch + 234; + let epoch = Epoch::new(0); - validator.activation_epoch = Epoch::from(activation_epoch); - validator.exit_epoch = Epoch::from(exit_epoch); + assert_eq!(v.is_active_at(epoch), false); + assert_eq!(v.is_exited_at(epoch), false); + assert_eq!(v.is_withdrawable_at(epoch), false); + assert_eq!(v.initiated_exit, false); + assert_eq!(v.slashed, false); + } - for slot in (activation_epoch - 100)..(exit_epoch + 100) { - let slot = Epoch::from(slot); - if slot < activation_epoch { - assert!(!validator.is_active_at(slot)); - } else if slot >= exit_epoch { - assert!(!validator.is_active_at(slot)); - } else { - assert!(validator.is_active_at(slot)); - } - } + #[test] + fn is_active_at() { + let epoch = Epoch::new(10); + + let v = Validator { + activation_epoch: epoch, + ..Validator::default() + }; + + assert_eq!(v.is_active_at(epoch - 1), false); + assert_eq!(v.is_active_at(epoch), true); + assert_eq!(v.is_active_at(epoch + 1), true); + } + + #[test] + fn is_exited_at() { + let epoch = Epoch::new(10); + + let v = Validator { + exit_epoch: epoch, + ..Validator::default() + }; + + assert_eq!(v.is_exited_at(epoch - 1), false); + assert_eq!(v.is_exited_at(epoch), true); + assert_eq!(v.is_exited_at(epoch + 1), true); + } + + #[test] + fn is_withdrawable_at() { + let epoch = Epoch::new(10); + + let v = Validator { + withdrawable_epoch: epoch, + ..Validator::default() + }; + + assert_eq!(v.is_withdrawable_at(epoch - 1), false); + assert_eq!(v.is_withdrawable_at(epoch), true); + assert_eq!(v.is_withdrawable_at(epoch + 1), true); } ssz_tests!(Validator); From 142aaae8ce6cedaefdd57a670568bae22e712369 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 15:51:57 +1100 Subject: [PATCH 109/144] Fast-forward PendingAttestation to v0.5.0 --- eth2/types/src/pending_attestation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth2/types/src/pending_attestation.rs b/eth2/types/src/pending_attestation.rs index 70907c29df..ca50b6d1c4 100644 --- a/eth2/types/src/pending_attestation.rs +++ b/eth2/types/src/pending_attestation.rs @@ -7,7 +7,7 @@ use test_random_derive::TestRandom; /// An attestation that has been included in the state but not yet fully processed. /// -/// Spec v0.4.0 +/// Spec v0.5.0 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)] pub struct PendingAttestation { pub aggregation_bitfield: Bitfield, From 15c3e5eab586feccd1082664395cd6227c6c8af2 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 15:52:17 +1100 Subject: [PATCH 110/144] Add HistoricalBatch type --- eth2/types/src/historical_batch.rs | 22 ++++++++++++++++++++++ eth2/types/src/lib.rs | 2 ++ 2 files changed, 24 insertions(+) create mode 100644 eth2/types/src/historical_batch.rs diff --git a/eth2/types/src/historical_batch.rs b/eth2/types/src/historical_batch.rs new file mode 100644 index 0000000000..77859ed1ae --- /dev/null +++ b/eth2/types/src/historical_batch.rs @@ -0,0 +1,22 @@ +use crate::test_utils::TestRandom; +use crate::Hash256; +use rand::RngCore; +use serde_derive::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode, TreeHash}; +use test_random_derive::TestRandom; + +/// Historical block and state roots. +/// +/// Spec v0.5.0 +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)] +pub struct HistoricalBatch { + pub block_roots: Vec, + pub state_roots: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + + ssz_tests!(HistoricalBatch); +} diff --git a/eth2/types/src/lib.rs b/eth2/types/src/lib.rs index 2fcb3237d5..4f50e0ea5c 100644 --- a/eth2/types/src/lib.rs +++ b/eth2/types/src/lib.rs @@ -20,6 +20,7 @@ pub mod eth1_data; pub mod eth1_data_vote; pub mod fork; pub mod free_attestation; +pub mod historical_batch; pub mod pending_attestation; pub mod proposal; pub mod proposer_slashing; @@ -55,6 +56,7 @@ pub use crate::eth1_data::Eth1Data; pub use crate::eth1_data_vote::Eth1DataVote; pub use crate::fork::Fork; pub use crate::free_attestation::FreeAttestation; +pub use crate::historical_batch::HistoricalBatch; pub use crate::pending_attestation::PendingAttestation; pub use crate::proposal::Proposal; pub use crate::proposer_slashing::ProposerSlashing; From dc2755c4ea1c7d96dacf0b361ef5685cae59dd89 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 15:59:04 +1100 Subject: [PATCH 111/144] Update ProposerSlashing to v0.5.0 --- eth2/types/src/deposit_input.rs | 2 +- eth2/types/src/proposer_slashing.rs | 8 ++++---- .../testing_proposer_slashing_builder.rs | 18 +++++++++--------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/eth2/types/src/deposit_input.rs b/eth2/types/src/deposit_input.rs index 9da53a042d..3f8a6177a8 100644 --- a/eth2/types/src/deposit_input.rs +++ b/eth2/types/src/deposit_input.rs @@ -1,6 +1,6 @@ use crate::test_utils::TestRandom; use crate::*; -use bls::{Keypair, PublicKey, Signature}; +use bls::{PublicKey, Signature}; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; use ssz::{SignedRoot, TreeHash}; diff --git a/eth2/types/src/proposer_slashing.rs b/eth2/types/src/proposer_slashing.rs index bc5b8665ef..881f0e4059 100644 --- a/eth2/types/src/proposer_slashing.rs +++ b/eth2/types/src/proposer_slashing.rs @@ -1,4 +1,4 @@ -use super::Proposal; +use super::BeaconBlockHeader; use crate::test_utils::TestRandom; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; @@ -7,12 +7,12 @@ use test_random_derive::TestRandom; /// Two conflicting proposals from the same proposer (validator). /// -/// Spec v0.4.0 +/// Spec v0.5.0 #[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)] pub struct ProposerSlashing { pub proposer_index: u64, - pub proposal_1: Proposal, - pub proposal_2: Proposal, + pub proposal_1: BeaconBlockHeader, + pub proposal_2: BeaconBlockHeader, } #[cfg(test)] diff --git a/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs b/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs index 7f16b679f7..fc38c185de 100644 --- a/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs +++ b/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs @@ -22,20 +22,20 @@ impl TestingProposerSlashingBuilder { F: Fn(u64, &[u8], Epoch, Domain) -> Signature, { let slot = Slot::new(0); - let shard = 0; + let hash_1 = Hash256::from([1; 32]); + let hash_2 = Hash256::from([2; 32]); - let mut proposal_1 = Proposal { + let mut proposal_1 = BeaconBlockHeader { slot, - shard, - block_root: Hash256::from_low_u64_le(1), + previous_block_root: hash_1, + state_root: hash_1, + block_body_root: hash_1, signature: Signature::empty_signature(), }; - let mut proposal_2 = Proposal { - slot, - shard, - block_root: Hash256::from_low_u64_le(2), - signature: Signature::empty_signature(), + let mut proposal_2 = BeaconBlockHeader { + previous_block_root: hash_2, + ..proposal_1.clone() }; proposal_1.signature = { From 77db79900e5911fcd2027bbe9e87aae4555194af Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 16:02:23 +1100 Subject: [PATCH 112/144] Fast-forward some unchanged types to v0.5.0 --- eth2/types/src/attestation.rs | 2 +- eth2/types/src/attester_slashing.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eth2/types/src/attestation.rs b/eth2/types/src/attestation.rs index 4b3c2e89cc..0b660466e1 100644 --- a/eth2/types/src/attestation.rs +++ b/eth2/types/src/attestation.rs @@ -8,7 +8,7 @@ use test_random_derive::TestRandom; /// Details an attestation that can be slashable. /// -/// Spec v0.4.0 +/// Spec v0.5.0 #[derive( Debug, Clone, diff --git a/eth2/types/src/attester_slashing.rs b/eth2/types/src/attester_slashing.rs index 195c0fdccd..6fc404f422 100644 --- a/eth2/types/src/attester_slashing.rs +++ b/eth2/types/src/attester_slashing.rs @@ -6,7 +6,7 @@ use test_random_derive::TestRandom; /// Two conflicting attestations. /// -/// Spec v0.4.0 +/// Spec v0.5.0 #[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)] pub struct AttesterSlashing { pub slashable_attestation_1: SlashableAttestation, From db26b8fde732167839ed53ed84a0545fbbbe1fc7 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 16:02:47 +1100 Subject: [PATCH 113/144] Update Deposit to v0.5.0 --- eth2/types/src/deposit.rs | 4 ++-- eth2/types/src/test_utils/testing_deposit_builder.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eth2/types/src/deposit.rs b/eth2/types/src/deposit.rs index 14eb19ad69..ff8d83d77d 100644 --- a/eth2/types/src/deposit.rs +++ b/eth2/types/src/deposit.rs @@ -7,10 +7,10 @@ use test_random_derive::TestRandom; /// A deposit to potentially become a beacon chain validator. /// -/// Spec v0.4.0 +/// Spec v0.5.0 #[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)] pub struct Deposit { - pub branch: Vec, + pub proof: Vec, pub index: u64, pub deposit_data: DepositData, } diff --git a/eth2/types/src/test_utils/testing_deposit_builder.rs b/eth2/types/src/test_utils/testing_deposit_builder.rs index 90c8d325d2..0d1c962f0c 100644 --- a/eth2/types/src/test_utils/testing_deposit_builder.rs +++ b/eth2/types/src/test_utils/testing_deposit_builder.rs @@ -14,7 +14,7 @@ impl TestingDepositBuilder { let keypair = Keypair::random(); let deposit = Deposit { - branch: vec![], + proof: vec![], index: 0, deposit_data: DepositData { amount, From a1ee1a45237bb769c3bf0abb7636f8c6038c05b2 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 16:05:53 +1100 Subject: [PATCH 114/144] Update Transfer to v0.5.0 --- eth2/types/src/test_utils/testing_transfer_builder.rs | 6 +++--- eth2/types/src/transfer.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/eth2/types/src/test_utils/testing_transfer_builder.rs b/eth2/types/src/test_utils/testing_transfer_builder.rs index c4256ebea2..354e29aa56 100644 --- a/eth2/types/src/test_utils/testing_transfer_builder.rs +++ b/eth2/types/src/test_utils/testing_transfer_builder.rs @@ -10,12 +10,12 @@ pub struct TestingTransferBuilder { impl TestingTransferBuilder { /// Instantiates a new builder. - pub fn new(from: u64, to: u64, amount: u64, slot: Slot) -> Self { + pub fn new(sender: u64, recipient: u64, amount: u64, slot: Slot) -> Self { let keypair = Keypair::random(); let transfer = Transfer { - from, - to, + sender, + recipient, amount, fee: 0, slot, diff --git a/eth2/types/src/transfer.rs b/eth2/types/src/transfer.rs index a46e24e241..1c99687029 100644 --- a/eth2/types/src/transfer.rs +++ b/eth2/types/src/transfer.rs @@ -23,8 +23,8 @@ use test_random_derive::TestRandom; SignedRoot, )] pub struct Transfer { - pub from: u64, - pub to: u64, + pub sender: u64, + pub recipient: u64, pub amount: u64, pub fee: u64, pub slot: Slot, From a51de99d4093704a8013326f0e959f44632b26d3 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 16:06:02 +1100 Subject: [PATCH 115/144] Fast-forward unchanged exit to v0.5.0 --- eth2/types/src/voluntary_exit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth2/types/src/voluntary_exit.rs b/eth2/types/src/voluntary_exit.rs index 5fdfcdd82f..f64f950cbf 100644 --- a/eth2/types/src/voluntary_exit.rs +++ b/eth2/types/src/voluntary_exit.rs @@ -8,7 +8,7 @@ use test_random_derive::TestRandom; /// An exit voluntarily submitted a validator who wishes to withdraw. /// -/// Spec v0.4.0 +/// Spec v0.5.0 #[derive( Debug, PartialEq, From 563304c8d7e9b33dd3bd1d2d92baa5ab2a2a76f9 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 16:30:05 +1100 Subject: [PATCH 116/144] Update "block" family types to 0.5.0 - Removes Proposal - Removes "readers" as they aren't actually being used anywhere. --- eth2/types/src/beacon_block.rs | 40 ++++------- eth2/types/src/beacon_block_body.rs | 10 +-- eth2/types/src/lib.rs | 3 - eth2/types/src/proposal.rs | 67 ------------------- eth2/types/src/readers/block_reader.rs | 35 ---------- eth2/types/src/readers/mod.rs | 5 -- eth2/types/src/readers/state_reader.rs | 25 ------- .../testing_beacon_block_builder.rs | 5 +- 8 files changed, 20 insertions(+), 170 deletions(-) delete mode 100644 eth2/types/src/proposal.rs delete mode 100644 eth2/types/src/readers/block_reader.rs delete mode 100644 eth2/types/src/readers/mod.rs delete mode 100644 eth2/types/src/readers/state_reader.rs diff --git a/eth2/types/src/beacon_block.rs b/eth2/types/src/beacon_block.rs index 56f77c8d25..7fa3f5e117 100644 --- a/eth2/types/src/beacon_block.rs +++ b/eth2/types/src/beacon_block.rs @@ -1,15 +1,15 @@ use crate::test_utils::TestRandom; -use crate::{BeaconBlockBody, ChainSpec, Eth1Data, Hash256, Proposal, Slot}; +use crate::{BeaconBlockBody, ChainSpec, Eth1Data, Hash256, Slot}; use bls::Signature; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; -use ssz::{SignedRoot, TreeHash}; +use ssz::TreeHash; use ssz_derive::{Decode, Encode, SignedRoot, TreeHash}; use test_random_derive::TestRandom; /// A block of the `BeaconChain`. /// -/// Spec v0.4.0 +/// Spec v0.5.0 #[derive( Debug, PartialEq, @@ -24,29 +24,27 @@ use test_random_derive::TestRandom; )] pub struct BeaconBlock { pub slot: Slot, - pub parent_root: Hash256, + pub previous_block_root: Hash256, pub state_root: Hash256, - pub randao_reveal: Signature, - pub eth1_data: Eth1Data, pub body: BeaconBlockBody, pub signature: Signature, } impl BeaconBlock { - /// Produce the first block of the Beacon Chain. + /// The first block of the Beacon Chain. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn genesis(state_root: Hash256, spec: &ChainSpec) -> BeaconBlock { BeaconBlock { slot: spec.genesis_slot, - parent_root: spec.zero_hash, + previous_block_root: spec.zero_hash, state_root, - randao_reveal: spec.empty_signature.clone(), - eth1_data: Eth1Data { - deposit_root: spec.zero_hash, - block_hash: spec.zero_hash, - }, body: BeaconBlockBody { + randao_reveal: spec.empty_signature.clone(), + eth1_data: Eth1Data { + deposit_root: spec.zero_hash, + block_hash: spec.zero_hash, + }, proposer_slashings: vec![], attester_slashings: vec![], attestations: vec![], @@ -60,22 +58,10 @@ impl BeaconBlock { /// Returns the `hash_tree_root` of the block. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn canonical_root(&self) -> Hash256 { Hash256::from_slice(&self.hash_tree_root()[..]) } - - /// Returns an unsigned proposal for block. - /// - /// Spec v0.4.0 - pub fn proposal(&self, spec: &ChainSpec) -> Proposal { - Proposal { - slot: self.slot, - shard: spec.beacon_chain_shard_number, - block_root: Hash256::from_slice(&self.signed_root()), - signature: spec.empty_signature.clone(), - } - } } #[cfg(test)] diff --git a/eth2/types/src/beacon_block_body.rs b/eth2/types/src/beacon_block_body.rs index ce8020fec0..677e24cec3 100644 --- a/eth2/types/src/beacon_block_body.rs +++ b/eth2/types/src/beacon_block_body.rs @@ -1,5 +1,5 @@ -use super::{Attestation, AttesterSlashing, Deposit, ProposerSlashing, Transfer, VoluntaryExit}; use crate::test_utils::TestRandom; +use crate::*; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; use ssz_derive::{Decode, Encode, TreeHash}; @@ -7,11 +7,11 @@ use test_random_derive::TestRandom; /// The body of a `BeaconChain` block, containing operations. /// -/// Spec v0.4.0 -#[derive( - Debug, PartialEq, Clone, Default, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom, -)] +/// Spec v0.5.0 +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)] pub struct BeaconBlockBody { + pub randao_reveal: Signature, + pub eth1_data: Eth1Data, pub proposer_slashings: Vec, pub attester_slashings: Vec, pub attestations: Vec, diff --git a/eth2/types/src/lib.rs b/eth2/types/src/lib.rs index 4f50e0ea5c..a1a58198b1 100644 --- a/eth2/types/src/lib.rs +++ b/eth2/types/src/lib.rs @@ -22,9 +22,7 @@ pub mod fork; pub mod free_attestation; pub mod historical_batch; pub mod pending_attestation; -pub mod proposal; pub mod proposer_slashing; -pub mod readers; pub mod shard_reassignment_record; pub mod slashable_attestation; pub mod transfer; @@ -58,7 +56,6 @@ pub use crate::fork::Fork; pub use crate::free_attestation::FreeAttestation; pub use crate::historical_batch::HistoricalBatch; pub use crate::pending_attestation::PendingAttestation; -pub use crate::proposal::Proposal; pub use crate::proposer_slashing::ProposerSlashing; pub use crate::slashable_attestation::SlashableAttestation; pub use crate::slot_epoch::{Epoch, Slot}; diff --git a/eth2/types/src/proposal.rs b/eth2/types/src/proposal.rs deleted file mode 100644 index 36fba5603e..0000000000 --- a/eth2/types/src/proposal.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::test_utils::TestRandom; -use crate::{Hash256, Slot}; -use bls::Signature; -use rand::RngCore; -use serde_derive::{Deserialize, Serialize}; -use ssz::TreeHash; -use ssz_derive::{Decode, Encode, SignedRoot, TreeHash}; -use test_random_derive::TestRandom; - -/// A proposal for some shard or beacon block. -/// -/// Spec v0.4.0 -#[derive( - Debug, - PartialEq, - Clone, - Serialize, - Deserialize, - Encode, - Decode, - TreeHash, - TestRandom, - SignedRoot, -)] -pub struct Proposal { - pub slot: Slot, - /// Shard number (spec.beacon_chain_shard_number for beacon chain) - pub shard: u64, - pub block_root: Hash256, - pub signature: Signature, -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - use ssz::{SignedRoot, TreeHash}; - - #[derive(TreeHash)] - struct SignedProposal { - pub slot: Slot, - pub shard: u64, - pub block_root: Hash256, - } - - impl Into for Proposal { - fn into(self) -> SignedProposal { - SignedProposal { - slot: self.slot, - shard: self.shard, - block_root: self.block_root, - } - } - } - - #[test] - pub fn test_signed_root() { - let mut rng = XorShiftRng::from_seed([42; 16]); - let original = Proposal::random_for_test(&mut rng); - - let other: SignedProposal = original.clone().into(); - - assert_eq!(original.signed_root(), other.hash_tree_root()); - } - - ssz_tests!(Proposal); -} diff --git a/eth2/types/src/readers/block_reader.rs b/eth2/types/src/readers/block_reader.rs deleted file mode 100644 index 93157a1a3c..0000000000 --- a/eth2/types/src/readers/block_reader.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::{BeaconBlock, Hash256, Slot}; -use std::fmt::Debug; - -/// The `BeaconBlockReader` provides interfaces for reading a subset of fields of a `BeaconBlock`. -/// -/// The purpose of this trait is to allow reading from either; -/// - a standard `BeaconBlock` struct, or -/// - a SSZ serialized byte array. -/// -/// Note: presently, direct SSZ reading has not been implemented so this trait is being used for -/// "future proofing". -pub trait BeaconBlockReader: Debug + PartialEq { - fn slot(&self) -> Slot; - fn parent_root(&self) -> Hash256; - fn state_root(&self) -> Hash256; - fn into_beacon_block(self) -> Option; -} - -impl BeaconBlockReader for BeaconBlock { - fn slot(&self) -> Slot { - self.slot - } - - fn parent_root(&self) -> Hash256 { - self.parent_root - } - - fn state_root(&self) -> Hash256 { - self.state_root - } - - fn into_beacon_block(self) -> Option { - Some(self) - } -} diff --git a/eth2/types/src/readers/mod.rs b/eth2/types/src/readers/mod.rs deleted file mode 100644 index 4ccb14a8cd..0000000000 --- a/eth2/types/src/readers/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod block_reader; -mod state_reader; - -pub use self::block_reader::BeaconBlockReader; -pub use self::state_reader::BeaconStateReader; diff --git a/eth2/types/src/readers/state_reader.rs b/eth2/types/src/readers/state_reader.rs deleted file mode 100644 index e469bee57f..0000000000 --- a/eth2/types/src/readers/state_reader.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::{BeaconState, Slot}; -use std::fmt::Debug; - -/// The `BeaconStateReader` provides interfaces for reading a subset of fields of a `BeaconState`. -/// -/// The purpose of this trait is to allow reading from either; -/// - a standard `BeaconState` struct, or -/// - a SSZ serialized byte array. -/// -/// Note: presently, direct SSZ reading has not been implemented so this trait is being used for -/// "future proofing". -pub trait BeaconStateReader: Debug + PartialEq { - fn slot(&self) -> Slot; - fn into_beacon_state(self) -> Option; -} - -impl BeaconStateReader for BeaconState { - fn slot(&self) -> Slot { - self.slot - } - - fn into_beacon_state(self) -> Option { - Some(self) - } -} diff --git a/eth2/types/src/test_utils/testing_beacon_block_builder.rs b/eth2/types/src/test_utils/testing_beacon_block_builder.rs index 58633b5cee..e0e4677d48 100644 --- a/eth2/types/src/test_utils/testing_beacon_block_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_block_builder.rs @@ -32,8 +32,7 @@ impl TestingBeaconBlockBuilder { /// /// Modifying the block after signing may invalidate the signature. pub fn sign(&mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) { - let proposal = self.block.proposal(spec); - let message = proposal.signed_root(); + let message = self.block.signed_root(); let epoch = self.block.slot.epoch(spec.slots_per_epoch); let domain = spec.get_domain(epoch, Domain::Proposal, fork); self.block.signature = Signature::new(&message, domain, sk); @@ -46,7 +45,7 @@ impl TestingBeaconBlockBuilder { let epoch = self.block.slot.epoch(spec.slots_per_epoch); let message = epoch.hash_tree_root(); let domain = spec.get_domain(epoch, Domain::Randao, fork); - self.block.randao_reveal = Signature::new(&message, domain, sk); + self.block.body.randao_reveal = Signature::new(&message, domain, sk); } /// Inserts a signed, valid `ProposerSlashing` for the validator. From dffc26a466c10c9f1397f6ab6e226f519a1eea5e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 18:33:32 +1100 Subject: [PATCH 117/144] Add field idents support to ssz_derive. - Adds idents to skip ser, deser and tree hashing --- eth2/utils/ssz_derive/src/lib.rs | 123 +++++++++++++++++++++++++++---- 1 file changed, 110 insertions(+), 13 deletions(-) diff --git a/eth2/utils/ssz_derive/src/lib.rs b/eth2/utils/ssz_derive/src/lib.rs index a7802a274f..9ba1de4160 100644 --- a/eth2/utils/ssz_derive/src/lib.rs +++ b/eth2/utils/ssz_derive/src/lib.rs @@ -56,10 +56,46 @@ fn get_named_field_idents<'a>(struct_data: &'a syn::DataStruct) -> Vec<&'a syn:: .collect() } +/// Returns a Vec of `syn::Ident` for each named field in the struct, whilst filtering out fields +/// that should not be serialized. +/// +/// # Panics +/// Any unnamed struct field (like in a tuple struct) will raise a panic at compile time. +fn get_serializable_named_field_idents<'a>( + struct_data: &'a syn::DataStruct, +) -> Vec<&'a syn::Ident> { + struct_data + .fields + .iter() + .filter_map(|f| { + if should_skip_serializing(&f) { + None + } else { + Some(match &f.ident { + Some(ref ident) => ident, + _ => panic!("ssz_derive only supports named struct fields."), + }) + } + }) + .collect() +} + +/// Returns true if some field has an attribute declaring it should not be serialized. +/// +/// The field attribute is: `#[ssz(skip_serializing)]` +fn should_skip_serializing(field: &syn::Field) -> bool { + for attr in &field.attrs { + if attr.tts.to_string() == "( skip_serializing )" { + return true; + } + } + false +} + /// Implements `ssz::Encodable` for some `struct`. /// /// Fields are encoded in the order they are defined. -#[proc_macro_derive(Encode)] +#[proc_macro_derive(Encode, attributes(ssz))] pub fn ssz_encode_derive(input: TokenStream) -> TokenStream { let item = parse_macro_input!(input as DeriveInput); @@ -70,7 +106,7 @@ pub fn ssz_encode_derive(input: TokenStream) -> TokenStream { _ => panic!("ssz_derive only supports structs."), }; - let field_idents = get_named_field_idents(&struct_data); + let field_idents = get_serializable_named_field_idents(&struct_data); let output = quote! { impl ssz::Encodable for #name { @@ -84,6 +120,18 @@ pub fn ssz_encode_derive(input: TokenStream) -> TokenStream { output.into() } +/// Returns true if some field has an attribute declaring it should not be deserialized. +/// +/// The field attribute is: `#[ssz(skip_deserializing)]` +fn should_skip_deserializing(field: &syn::Field) -> bool { + for attr in &field.attrs { + if attr.tts.to_string() == "( skip_deserializing )" { + return true; + } + } + false +} + /// Implements `ssz::Decodable` for some `struct`. /// /// Fields are decoded in the order they are defined. @@ -98,26 +146,39 @@ pub fn ssz_decode_derive(input: TokenStream) -> TokenStream { _ => panic!("ssz_derive only supports structs."), }; - let field_idents = get_named_field_idents(&struct_data); + let all_idents = get_named_field_idents(&struct_data); - // Using a var in an iteration always consumes the var, therefore we must make a `fields_a` and - // a `fields_b` in order to perform two loops. - // - // https://github.com/dtolnay/quote/issues/8 - let field_idents_a = &field_idents; - let field_idents_b = &field_idents; + // Build quotes for fields that should be deserialized and those that should be built from + // `Default`. + let mut quotes = vec![]; + for field in &struct_data.fields { + match &field.ident { + Some(ref ident) => { + if should_skip_deserializing(field) { + quotes.push(quote! { + let #ident = <_>::default(); + }); + } else { + quotes.push(quote! { + let (#ident, i) = <_>::ssz_decode(bytes, i)?; + }); + } + } + _ => panic!("ssz_derive only supports named struct fields."), + }; + } let output = quote! { impl ssz::Decodable for #name { fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), ssz::DecodeError> { #( - let (#field_idents_a, i) = <_>::ssz_decode(bytes, i)?; + #quotes )* Ok(( Self { #( - #field_idents_b, + #all_idents, )* }, i @@ -128,10 +189,46 @@ pub fn ssz_decode_derive(input: TokenStream) -> TokenStream { output.into() } +/// Returns a Vec of `syn::Ident` for each named field in the struct, whilst filtering out fields +/// that should not be tree hashed. +/// +/// # Panics +/// Any unnamed struct field (like in a tuple struct) will raise a panic at compile time. +fn get_tree_hashable_named_field_idents<'a>( + struct_data: &'a syn::DataStruct, +) -> Vec<&'a syn::Ident> { + struct_data + .fields + .iter() + .filter_map(|f| { + if should_skip_tree_hash(&f) { + None + } else { + Some(match &f.ident { + Some(ref ident) => ident, + _ => panic!("ssz_derive only supports named struct fields."), + }) + } + }) + .collect() +} + +/// Returns true if some field has an attribute declaring it should not be tree-hashed. +/// +/// The field attribute is: `#[tree_hash(skip_hashing)]` +fn should_skip_tree_hash(field: &syn::Field) -> bool { + for attr in &field.attrs { + if attr.tts.to_string() == "( skip_hashing )" { + return true; + } + } + false +} + /// Implements `ssz::TreeHash` for some `struct`. /// /// Fields are processed in the order they are defined. -#[proc_macro_derive(TreeHash)] +#[proc_macro_derive(TreeHash, attributes(tree_hash))] pub fn ssz_tree_hash_derive(input: TokenStream) -> TokenStream { let item = parse_macro_input!(input as DeriveInput); @@ -142,7 +239,7 @@ pub fn ssz_tree_hash_derive(input: TokenStream) -> TokenStream { _ => panic!("ssz_derive only supports structs."), }; - let field_idents = get_named_field_idents(&struct_data); + let field_idents = get_tree_hashable_named_field_idents(&struct_data); let output = quote! { impl ssz::TreeHash for #name { From 57c4389f9c476c96cbad13484642e6bacb101e8e Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 18:34:54 +1100 Subject: [PATCH 118/144] Add further BeaconBlock v0.5.0 updates --- eth2/types/src/beacon_block.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/eth2/types/src/beacon_block.rs b/eth2/types/src/beacon_block.rs index 7fa3f5e117..bfe266cbd6 100644 --- a/eth2/types/src/beacon_block.rs +++ b/eth2/types/src/beacon_block.rs @@ -1,5 +1,5 @@ use crate::test_utils::TestRandom; -use crate::{BeaconBlockBody, ChainSpec, Eth1Data, Hash256, Slot}; +use crate::*; use bls::Signature; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; @@ -31,14 +31,14 @@ pub struct BeaconBlock { } impl BeaconBlock { - /// The first block of the Beacon Chain. + /// Returns an empty block to be used during genesis. /// /// Spec v0.5.0 - pub fn genesis(state_root: Hash256, spec: &ChainSpec) -> BeaconBlock { + pub fn empty(spec: &ChainSpec) -> BeaconBlock { BeaconBlock { slot: spec.genesis_slot, previous_block_root: spec.zero_hash, - state_root, + state_root: spec.zero_hash, body: BeaconBlockBody { randao_reveal: spec.empty_signature.clone(), eth1_data: Eth1Data { @@ -62,6 +62,19 @@ impl BeaconBlock { pub fn canonical_root(&self) -> Hash256 { Hash256::from_slice(&self.hash_tree_root()[..]) } + + /// Returns a "temporary" header, where the `state_root` is `spec.zero_hash`. + /// + /// Spec v0.5.0 + pub fn into_temporary_header(&self, spec: &ChainSpec) -> BeaconBlockHeader { + BeaconBlockHeader { + slot: self.slot, + previous_block_root: self.previous_block_root, + state_root: spec.zero_hash, + block_body_root: Hash256::from_slice(&self.hash_tree_root()), + signature: self.signature, + } + } } #[cfg(test)] From 7660cbd4190acabafe64a4ded1668a7568c842c5 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 18:35:27 +1100 Subject: [PATCH 119/144] Update ChainSpec to v0.5.0 --- eth2/types/src/chain_spec.rs | 59 ++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/eth2/types/src/chain_spec.rs b/eth2/types/src/chain_spec.rs index ae521cc92a..f4b113056f 100644 --- a/eth2/types/src/chain_spec.rs +++ b/eth2/types/src/chain_spec.rs @@ -5,18 +5,21 @@ use serde_derive::Deserialize; const GWEI: u64 = 1_000_000_000; +/// Each of the BLS signature domains. +/// +/// Spec v0.5.0 pub enum Domain { - Deposit, - Attestation, - Proposal, - Exit, + BeaconBlock, Randao, + Attestation, + Deposit, + Exit, Transfer, } /// Holds all the "constants" for a BeaconChain. /// -/// Spec v0.4.0 +/// Spec v0.5.0 #[derive(PartialEq, Debug, Clone, Deserialize)] #[serde(default)] pub struct ChainSpec { @@ -26,7 +29,6 @@ pub struct ChainSpec { pub shard_count: u64, pub target_committee_size: u64, pub max_balance_churn_quotient: u64, - pub beacon_chain_shard_number: u64, pub max_indices_per_slashable_vote: u64, pub max_exit_dequeues_per_epoch: u64, pub shuffle_round_count: u8, @@ -66,12 +68,13 @@ pub struct ChainSpec { pub min_seed_lookahead: Epoch, pub activation_exit_delay: u64, pub epochs_per_eth1_voting_period: u64, + pub slots_per_historical_root: usize, pub min_validator_withdrawability_delay: Epoch, + pub persistent_committee_period: u64, /* * State list lengths */ - pub latest_block_roots_length: usize, pub latest_randao_mixes_length: usize, pub latest_active_index_roots_length: usize, pub latest_slashed_exit_length: usize, @@ -103,11 +106,11 @@ pub struct ChainSpec { * * Use `ChainSpec::get_domain(..)` to access these values. */ - domain_deposit: u32, - domain_attestation: u32, - domain_proposal: u32, - domain_exit: u32, + domain_beacon_block: u32, domain_randao: u32, + domain_attestation: u32, + domain_deposit: u32, + domain_exit: u32, domain_transfer: u32, } @@ -130,11 +133,11 @@ impl ChainSpec { /// Spec v0.5.0 pub fn get_domain(&self, epoch: Epoch, domain: Domain, fork: &Fork) -> u64 { let domain_constant = match domain { - Domain::Deposit => self.domain_deposit, - Domain::Attestation => self.domain_attestation, - Domain::Proposal => self.domain_proposal, - Domain::Exit => self.domain_exit, + Domain::BeaconBlock => self.domain_beacon_block, Domain::Randao => self.domain_randao, + Domain::Attestation => self.domain_attestation, + Domain::Deposit => self.domain_deposit, + Domain::Exit => self.domain_exit, Domain::Transfer => self.domain_transfer, }; @@ -149,7 +152,7 @@ impl ChainSpec { /// Returns a `ChainSpec` compatible with the Ethereum Foundation specification. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn foundation() -> Self { let genesis_slot = Slot::new(2_u64.pow(32)); let slots_per_epoch = 64; @@ -162,7 +165,6 @@ impl ChainSpec { shard_count: 1_024, target_committee_size: 128, max_balance_churn_quotient: 32, - beacon_chain_shard_number: u64::max_value(), max_indices_per_slashable_vote: 4_096, max_exit_dequeues_per_epoch: 4, shuffle_round_count: 90, @@ -202,12 +204,13 @@ impl ChainSpec { min_seed_lookahead: Epoch::new(1), activation_exit_delay: 4, epochs_per_eth1_voting_period: 16, + slots_per_historical_root: 8_192, min_validator_withdrawability_delay: Epoch::new(256), + persistent_committee_period: 2_048, /* * State list lengths */ - latest_block_roots_length: 8_192, latest_randao_mixes_length: 8_192, latest_active_index_roots_length: 8_192, latest_slashed_exit_length: 8_192, @@ -234,18 +237,16 @@ impl ChainSpec { /* * Signature domains */ - domain_deposit: 0, - domain_attestation: 1, - domain_proposal: 2, - domain_exit: 3, - domain_randao: 4, + domain_beacon_block: 0, + domain_randao: 1, + domain_attestation: 2, + domain_deposit: 3, + domain_exit: 4, domain_transfer: 5, } } /// Returns a `ChainSpec` compatible with the specification suitable for 8 validators. - /// - /// Spec v0.4.0 pub fn few_validators() -> Self { let genesis_slot = Slot::new(2_u64.pow(32)); let slots_per_epoch = 8; @@ -294,11 +295,11 @@ mod tests { fn test_get_domain() { let spec = ChainSpec::foundation(); - test_domain(Domain::Deposit, spec.domain_deposit, &spec); - test_domain(Domain::Attestation, spec.domain_attestation, &spec); - test_domain(Domain::Proposal, spec.domain_proposal, &spec); - test_domain(Domain::Exit, spec.domain_exit, &spec); + test_domain(Domain::BeaconBlock, spec.domain_beacon_block, &spec); test_domain(Domain::Randao, spec.domain_randao, &spec); + test_domain(Domain::Attestation, spec.domain_attestation, &spec); + test_domain(Domain::Deposit, spec.domain_deposit, &spec); + test_domain(Domain::Exit, spec.domain_exit, &spec); test_domain(Domain::Transfer, spec.domain_transfer, &spec); } } From b37cf3a2698562e3c523e798e580b44ed42d2646 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 18:36:16 +1100 Subject: [PATCH 120/144] Add TreeHash derives for cache objects. This allows us to avoid a verbose manual impl for BeaconState --- eth2/types/src/beacon_state/epoch_cache.rs | 13 +++++++++++++ eth2/types/src/beacon_state/pubkey_cache.rs | 9 +++++++++ 2 files changed, 22 insertions(+) diff --git a/eth2/types/src/beacon_state/epoch_cache.rs b/eth2/types/src/beacon_state/epoch_cache.rs index ddcca0a9a0..3e580eee1a 100644 --- a/eth2/types/src/beacon_state/epoch_cache.rs +++ b/eth2/types/src/beacon_state/epoch_cache.rs @@ -1,5 +1,7 @@ use super::{AttestationDuty, BeaconState, CrosslinkCommittees, Error}; +use crate::test_utils::TestRandom; use crate::{ChainSpec, Epoch}; +use rand::RngCore; use serde_derive::{Deserialize, Serialize}; #[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)] @@ -67,3 +69,14 @@ impl EpochCache { }) } } + +impl TestRandom for [EpochCache; 3] { + /// Test random should generate an empty cache. + fn random_for_test(rng: &mut T) -> Self { + [ + EpochCache::default(), + EpochCache::default(), + EpochCache::default(), + ] + } +} diff --git a/eth2/types/src/beacon_state/pubkey_cache.rs b/eth2/types/src/beacon_state/pubkey_cache.rs index 340bdb3119..22fe32694c 100644 --- a/eth2/types/src/beacon_state/pubkey_cache.rs +++ b/eth2/types/src/beacon_state/pubkey_cache.rs @@ -1,4 +1,6 @@ +use crate::test_utils::TestRandom; use crate::*; +use rand::RngCore; use serde_derive::{Deserialize, Serialize}; use std::collections::HashMap; @@ -36,3 +38,10 @@ impl PubkeyCache { self.map.get(pubkey).cloned() } } + +impl TestRandom for PubkeyCache { + /// Test random should generate an empty cache. + fn random_for_test(rng: &mut T) -> Self { + Self::default() + } +} From 49d3f3d1d64126dd926ca5a3a26247542704882c Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 18:36:55 +1100 Subject: [PATCH 121/144] Partially update BeaconState to v0.5.0 - Uses new ssz attributes to remove manual impl of ssz. - Updates struct def to v0.5.0 - Does not update all methods --- eth2/types/src/beacon_state.rs | 261 ++++++++------------------------- 1 file changed, 58 insertions(+), 203 deletions(-) diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 2644b3e73d..9bcfa5cecc 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -8,9 +8,11 @@ use log::{debug, error, trace}; use pubkey_cache::PubkeyCache; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; -use ssz::{hash, Decodable, DecodeError, Encodable, SignedRoot, SszStream, TreeHash}; +use ssz::{hash, Decodable, SignedRoot, TreeHash}; +use ssz_derive::{Decode, Encode, TreeHash}; use std::collections::HashMap; use swap_or_not_shuffle::shuffle_list; +use test_random_derive::TestRandom; pub use builder::BeaconStateBuilder; @@ -72,7 +74,10 @@ macro_rules! safe_sub_assign { }; } -#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)] +/// The state of the `BeaconChain` at some slot. +/// +/// Spec v0.5.0 +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, TestRandom, Encode, Decode, TreeHash)] pub struct BeaconState { // Misc pub slot: Slot, @@ -94,18 +99,24 @@ pub struct BeaconState { pub current_shuffling_seed: Hash256, // Finality + pub previous_epoch_attestations: Vec, + pub current_epoch_attestations: Vec, pub previous_justified_epoch: Epoch, - pub justified_epoch: Epoch, + pub current_justified_epoch: Epoch, + pub previous_justified_root: Hash256, + pub current_justified_root: Hash256, pub justification_bitfield: u64, pub finalized_epoch: Epoch, + pub finalized_root: Hash256, // Recent state pub latest_crosslinks: Vec, pub latest_block_roots: Vec, + pub latest_state_roots: Vec, pub latest_active_index_roots: Vec, pub latest_slashed_balances: Vec, - pub latest_attestations: Vec, - pub batched_block_roots: Vec, + pub latest_block_header: BeaconBlockHeader, + pub historical_roots: Vec, // Ethereum 1.0 chain data pub latest_eth1_data: Eth1Data, @@ -113,10 +124,19 @@ pub struct BeaconState { pub deposit_index: u64, // Caching (not in the spec) + #[serde(default)] + #[ssz(skip_serializing)] + #[ssz(skip_deserializing)] + #[tree_hash(skip_hashing)] pub cache_index_offset: usize, + #[ssz(skip_serializing)] + #[ssz(skip_deserializing)] + #[tree_hash(skip_hashing)] + pub caches: [EpochCache; CACHED_EPOCHS], #[serde(default)] - pub caches: Vec, - #[serde(default)] + #[ssz(skip_serializing)] + #[ssz(skip_deserializing)] + #[tree_hash(skip_hashing)] pub pubkey_cache: PubkeyCache, } @@ -126,7 +146,7 @@ impl BeaconState { /// This does not fully build a genesis beacon state, it omits processing of initial validator /// deposits. To obtain a full genesis beacon state, use the `BeaconStateBuilder`. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn genesis(genesis_time: u64, latest_eth1_data: Eth1Data, spec: &ChainSpec) -> BeaconState { let initial_crosslink = Crosslink { epoch: spec.genesis_epoch, @@ -134,23 +154,17 @@ impl BeaconState { }; BeaconState { - /* - * Misc - */ + // Misc slot: spec.genesis_slot, genesis_time, fork: Fork::genesis(spec), - /* - * Validator registry - */ + // Validator registry validator_registry: vec![], // Set later in the function. validator_balances: vec![], // Set later in the function. validator_registry_update_epoch: spec.genesis_epoch, - /* - * Randomness and committees - */ + // Randomness and committees latest_randao_mixes: vec![spec.zero_hash; spec.latest_randao_mixes_length as usize], previous_shuffling_start_shard: spec.genesis_start_shard, current_shuffling_start_shard: spec.genesis_start_shard, @@ -159,26 +173,25 @@ impl BeaconState { previous_shuffling_seed: spec.zero_hash, current_shuffling_seed: spec.zero_hash, - /* - * Finality - */ + // Finality + previous_epoch_attestations: vec![], + current_epoch_attestations: vec![], previous_justified_epoch: spec.genesis_epoch, - justified_epoch: spec.genesis_epoch, + current_justified_epoch: spec.genesis_epoch, + previous_justified_root: spec.zero_hash, + current_justified_root: spec.zero_hash, justification_bitfield: 0, finalized_epoch: spec.genesis_epoch, + finalized_root: spec.zero_hash, - /* - * Recent state - */ + // Recent state latest_crosslinks: vec![initial_crosslink; spec.shard_count as usize], - latest_block_roots: vec![spec.zero_hash; spec.latest_block_roots_length as usize], - latest_active_index_roots: vec![ - spec.zero_hash; - spec.latest_active_index_roots_length as usize - ], - latest_slashed_balances: vec![0; spec.latest_slashed_exit_length as usize], - latest_attestations: vec![], - batched_block_roots: vec![], + latest_block_roots: vec![spec.zero_hash; spec.slots_per_historical_root], + latest_state_roots: vec![spec.zero_hash; spec.slots_per_historical_root], + latest_active_index_roots: vec![spec.zero_hash; spec.latest_active_index_roots_length], + latest_slashed_balances: vec![0; spec.latest_slashed_exit_length], + latest_block_header: BeaconBlock::empty(spec).into_temporary_header(spec), + historical_roots: vec![], /* * PoW receipt root @@ -191,18 +204,26 @@ impl BeaconState { * Caching (not in spec) */ cache_index_offset: 0, - caches: vec![EpochCache::default(); CACHED_EPOCHS], + caches: [ + EpochCache::default(), + EpochCache::default(), + EpochCache::default(), + ], pubkey_cache: PubkeyCache::default(), } } + /* + /// Returns the `hash_tree_root` of the state. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn canonical_root(&self) -> Hash256 { Hash256::from_slice(&self.hash_tree_root()[..]) } + */ + /// Build an epoch cache, unless it is has already been built. pub fn build_epoch_cache( &mut self, @@ -426,11 +447,11 @@ impl BeaconState { /// Return the block root at a recent `slot`. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn get_block_root(&self, slot: Slot, spec: &ChainSpec) -> Option<&Hash256> { - if (self.slot <= slot + spec.latest_block_roots_length as u64) && (slot < self.slot) { + if (self.slot <= slot + spec.slots_per_historical_root as u64) && (slot < self.slot) { self.latest_block_roots - .get(slot.as_usize() % spec.latest_block_roots_length) + .get(slot.as_usize() % spec.slots_per_historical_root) } else { None } @@ -1135,169 +1156,3 @@ impl BeaconState { Ok(all_participants) } } - -impl Encodable for BeaconState { - fn ssz_append(&self, s: &mut SszStream) { - s.append(&self.slot); - s.append(&self.genesis_time); - s.append(&self.fork); - s.append(&self.validator_registry); - s.append(&self.validator_balances); - s.append(&self.validator_registry_update_epoch); - s.append(&self.latest_randao_mixes); - s.append(&self.previous_shuffling_start_shard); - s.append(&self.current_shuffling_start_shard); - s.append(&self.previous_shuffling_epoch); - s.append(&self.current_shuffling_epoch); - s.append(&self.previous_shuffling_seed); - s.append(&self.current_shuffling_seed); - s.append(&self.previous_justified_epoch); - s.append(&self.justified_epoch); - s.append(&self.justification_bitfield); - s.append(&self.finalized_epoch); - s.append(&self.latest_crosslinks); - s.append(&self.latest_block_roots); - s.append(&self.latest_active_index_roots); - s.append(&self.latest_slashed_balances); - s.append(&self.latest_attestations); - s.append(&self.batched_block_roots); - s.append(&self.latest_eth1_data); - s.append(&self.eth1_data_votes); - s.append(&self.deposit_index); - } -} - -impl Decodable for BeaconState { - fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { - let (slot, i) = <_>::ssz_decode(bytes, i)?; - let (genesis_time, i) = <_>::ssz_decode(bytes, i)?; - let (fork, i) = <_>::ssz_decode(bytes, i)?; - let (validator_registry, i) = <_>::ssz_decode(bytes, i)?; - let (validator_balances, i) = <_>::ssz_decode(bytes, i)?; - let (validator_registry_update_epoch, i) = <_>::ssz_decode(bytes, i)?; - let (latest_randao_mixes, i) = <_>::ssz_decode(bytes, i)?; - let (previous_shuffling_start_shard, i) = <_>::ssz_decode(bytes, i)?; - let (current_shuffling_start_shard, i) = <_>::ssz_decode(bytes, i)?; - let (previous_shuffling_epoch, i) = <_>::ssz_decode(bytes, i)?; - let (current_shuffling_epoch, i) = <_>::ssz_decode(bytes, i)?; - let (previous_shuffling_seed, i) = <_>::ssz_decode(bytes, i)?; - let (current_shuffling_seed, i) = <_>::ssz_decode(bytes, i)?; - let (previous_justified_epoch, i) = <_>::ssz_decode(bytes, i)?; - let (justified_epoch, i) = <_>::ssz_decode(bytes, i)?; - let (justification_bitfield, i) = <_>::ssz_decode(bytes, i)?; - let (finalized_epoch, i) = <_>::ssz_decode(bytes, i)?; - let (latest_crosslinks, i) = <_>::ssz_decode(bytes, i)?; - let (latest_block_roots, i) = <_>::ssz_decode(bytes, i)?; - let (latest_active_index_roots, i) = <_>::ssz_decode(bytes, i)?; - let (latest_slashed_balances, i) = <_>::ssz_decode(bytes, i)?; - let (latest_attestations, i) = <_>::ssz_decode(bytes, i)?; - let (batched_block_roots, i) = <_>::ssz_decode(bytes, i)?; - let (latest_eth1_data, i) = <_>::ssz_decode(bytes, i)?; - let (eth1_data_votes, i) = <_>::ssz_decode(bytes, i)?; - let (deposit_index, i) = <_>::ssz_decode(bytes, i)?; - - Ok(( - Self { - slot, - genesis_time, - fork, - validator_registry, - validator_balances, - validator_registry_update_epoch, - latest_randao_mixes, - previous_shuffling_start_shard, - current_shuffling_start_shard, - previous_shuffling_epoch, - current_shuffling_epoch, - previous_shuffling_seed, - current_shuffling_seed, - previous_justified_epoch, - justified_epoch, - justification_bitfield, - finalized_epoch, - latest_crosslinks, - latest_block_roots, - latest_active_index_roots, - latest_slashed_balances, - latest_attestations, - batched_block_roots, - latest_eth1_data, - eth1_data_votes, - deposit_index, - cache_index_offset: 0, - caches: vec![EpochCache::default(); CACHED_EPOCHS], - pubkey_cache: PubkeyCache::default(), - }, - i, - )) - } -} - -impl TreeHash for BeaconState { - fn hash_tree_root(&self) -> Vec { - let mut result: Vec = vec![]; - result.append(&mut self.slot.hash_tree_root()); - result.append(&mut self.genesis_time.hash_tree_root()); - result.append(&mut self.fork.hash_tree_root()); - result.append(&mut self.validator_registry.hash_tree_root()); - result.append(&mut self.validator_balances.hash_tree_root()); - result.append(&mut self.validator_registry_update_epoch.hash_tree_root()); - result.append(&mut self.latest_randao_mixes.hash_tree_root()); - result.append(&mut self.previous_shuffling_start_shard.hash_tree_root()); - result.append(&mut self.current_shuffling_start_shard.hash_tree_root()); - result.append(&mut self.previous_shuffling_epoch.hash_tree_root()); - result.append(&mut self.current_shuffling_epoch.hash_tree_root()); - result.append(&mut self.previous_shuffling_seed.hash_tree_root()); - result.append(&mut self.current_shuffling_seed.hash_tree_root()); - result.append(&mut self.previous_justified_epoch.hash_tree_root()); - result.append(&mut self.justified_epoch.hash_tree_root()); - result.append(&mut self.justification_bitfield.hash_tree_root()); - result.append(&mut self.finalized_epoch.hash_tree_root()); - result.append(&mut self.latest_crosslinks.hash_tree_root()); - result.append(&mut self.latest_block_roots.hash_tree_root()); - result.append(&mut self.latest_active_index_roots.hash_tree_root()); - result.append(&mut self.latest_slashed_balances.hash_tree_root()); - result.append(&mut self.latest_attestations.hash_tree_root()); - result.append(&mut self.batched_block_roots.hash_tree_root()); - result.append(&mut self.latest_eth1_data.hash_tree_root()); - result.append(&mut self.eth1_data_votes.hash_tree_root()); - result.append(&mut self.deposit_index.hash_tree_root()); - hash(&result) - } -} - -impl TestRandom for BeaconState { - fn random_for_test(rng: &mut T) -> Self { - Self { - slot: <_>::random_for_test(rng), - genesis_time: <_>::random_for_test(rng), - fork: <_>::random_for_test(rng), - validator_registry: <_>::random_for_test(rng), - validator_balances: <_>::random_for_test(rng), - validator_registry_update_epoch: <_>::random_for_test(rng), - latest_randao_mixes: <_>::random_for_test(rng), - previous_shuffling_start_shard: <_>::random_for_test(rng), - current_shuffling_start_shard: <_>::random_for_test(rng), - previous_shuffling_epoch: <_>::random_for_test(rng), - current_shuffling_epoch: <_>::random_for_test(rng), - previous_shuffling_seed: <_>::random_for_test(rng), - current_shuffling_seed: <_>::random_for_test(rng), - previous_justified_epoch: <_>::random_for_test(rng), - justified_epoch: <_>::random_for_test(rng), - justification_bitfield: <_>::random_for_test(rng), - finalized_epoch: <_>::random_for_test(rng), - latest_crosslinks: <_>::random_for_test(rng), - latest_block_roots: <_>::random_for_test(rng), - latest_active_index_roots: <_>::random_for_test(rng), - latest_slashed_balances: <_>::random_for_test(rng), - latest_attestations: <_>::random_for_test(rng), - batched_block_roots: <_>::random_for_test(rng), - latest_eth1_data: <_>::random_for_test(rng), - eth1_data_votes: <_>::random_for_test(rng), - deposit_index: <_>::random_for_test(rng), - cache_index_offset: 0, - caches: vec![EpochCache::default(); CACHED_EPOCHS], - pubkey_cache: PubkeyCache::default(), - } - } -} From f739bb55510b9683aa0721f8d5ee125c58eb096d Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Fri, 15 Mar 2019 18:38:58 +1100 Subject: [PATCH 122/144] Add serde default field attr I accidentally deleted it in the last commit. --- eth2/types/src/beacon_state.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 9bcfa5cecc..ec1008d3c5 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -129,6 +129,7 @@ pub struct BeaconState { #[ssz(skip_deserializing)] #[tree_hash(skip_hashing)] pub cache_index_offset: usize, + #[serde(default)] #[ssz(skip_serializing)] #[ssz(skip_deserializing)] #[tree_hash(skip_hashing)] From 01bfd386375f69e20bc157c8bc26217077b8e97b Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 16 Mar 2019 14:30:21 +1100 Subject: [PATCH 123/144] Add `default` attribute to `TestRandom` derive. Allows for generating the item from default instead of randomizing it. --- eth2/utils/test_random_derive/src/lib.rs | 48 +++++++++++++++++------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/eth2/utils/test_random_derive/src/lib.rs b/eth2/utils/test_random_derive/src/lib.rs index 9a456606ce..7920ea6958 100644 --- a/eth2/utils/test_random_derive/src/lib.rs +++ b/eth2/utils/test_random_derive/src/lib.rs @@ -4,7 +4,20 @@ use crate::proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, DeriveInput}; -#[proc_macro_derive(TestRandom)] +/// Returns true if some field has an attribute declaring it should be generated from default (not +/// randomized). +/// +/// The field attribute is: `#[test_random(default)]` +fn should_use_default(field: &syn::Field) -> bool { + for attr in &field.attrs { + if attr.tts.to_string() == "( default )" { + return true; + } + } + false +} + +#[proc_macro_derive(TestRandom, attributes(test_random))] pub fn test_random_derive(input: TokenStream) -> TokenStream { let derived_input = parse_macro_input!(input as DeriveInput); let name = &derived_input.ident; @@ -14,14 +27,32 @@ pub fn test_random_derive(input: TokenStream) -> TokenStream { _ => panic!("test_random_derive only supports structs."), }; - let field_names = get_named_field_idents(&struct_data); + // Build quotes for fields that should be generated and those that should be built from + // `Default`. + let mut quotes = vec![]; + for field in &struct_data.fields { + match &field.ident { + Some(ref ident) => { + if should_use_default(field) { + quotes.push(quote! { + #ident: <_>::default(), + }); + } else { + quotes.push(quote! { + #ident: <_>::random_for_test(rng), + }); + } + } + _ => panic!("test_random_derive only supports named struct fields."), + }; + } let output = quote! { impl TestRandom for #name { fn random_for_test(rng: &mut T) -> Self { Self { #( - #field_names: <_>::random_for_test(rng), + #quotes )* } } @@ -30,14 +61,3 @@ pub fn test_random_derive(input: TokenStream) -> TokenStream { output.into() } - -fn get_named_field_idents(struct_data: &syn::DataStruct) -> Vec<(&syn::Ident)> { - struct_data - .fields - .iter() - .map(|f| match &f.ident { - Some(ref ident) => ident, - _ => panic!("test_random_derive only supports named struct fields."), - }) - .collect() -} From 6ae5d34d0f3f1063841dab726514067c96e7dcf6 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 16 Mar 2019 14:31:43 +1100 Subject: [PATCH 124/144] Update testing builders as per state 0.5.0 update --- .../src/test_utils/testing_attestation_data_builder.rs | 2 +- .../types/src/test_utils/testing_beacon_block_builder.rs | 4 ++-- .../types/src/test_utils/testing_beacon_state_builder.rs | 9 +++++++-- .../src/test_utils/testing_proposer_slashing_builder.rs | 4 ++-- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/eth2/types/src/test_utils/testing_attestation_data_builder.rs b/eth2/types/src/test_utils/testing_attestation_data_builder.rs index f31de2fbd1..a270e38599 100644 --- a/eth2/types/src/test_utils/testing_attestation_data_builder.rs +++ b/eth2/types/src/test_utils/testing_attestation_data_builder.rs @@ -20,7 +20,7 @@ impl TestingAttestationDataBuilder { let source_epoch = if is_previous_epoch { state.previous_justified_epoch } else { - state.justified_epoch + state.current_justified_epoch }; let target_root = if is_previous_epoch { diff --git a/eth2/types/src/test_utils/testing_beacon_block_builder.rs b/eth2/types/src/test_utils/testing_beacon_block_builder.rs index e0e4677d48..7fb3d8e09b 100644 --- a/eth2/types/src/test_utils/testing_beacon_block_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_block_builder.rs @@ -19,7 +19,7 @@ impl TestingBeaconBlockBuilder { /// Create a new builder from genesis. pub fn new(spec: &ChainSpec) -> Self { Self { - block: BeaconBlock::genesis(spec.zero_hash, spec), + block: BeaconBlock::empty(spec), } } @@ -34,7 +34,7 @@ impl TestingBeaconBlockBuilder { pub fn sign(&mut self, sk: &SecretKey, fork: &Fork, spec: &ChainSpec) { let message = self.block.signed_root(); let epoch = self.block.slot.epoch(spec.slots_per_epoch); - let domain = spec.get_domain(epoch, Domain::Proposal, fork); + let domain = spec.get_domain(epoch, Domain::BeaconBlock, fork); self.block.signature = Signature::new(&message, domain, sk); } diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index 5b96dc455c..8ef4f76ce1 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -190,7 +190,7 @@ impl TestingBeaconStateBuilder { state.current_shuffling_seed = Hash256::from_low_u64_le(1); state.previous_justified_epoch = epoch - 3; - state.justified_epoch = epoch - 2; + state.current_justified_epoch = epoch - 2; state.justification_bitfield = u64::max_value(); state.finalized_epoch = epoch - 3; @@ -232,8 +232,13 @@ impl TestingBeaconStateBuilder { // The entire committee should have signed the pending attestation. let signers = vec![true; committee.len()]; builder.add_committee_participation(signers); + let attestation = builder.build(); - state.latest_attestations.push(builder.build()) + if attestation.data.slot.epoch(spec.slots_per_epoch) < state.current_epoch(spec) { + state.previous_epoch_attestations.push(attestation) + } else { + state.current_epoch_attestations.push(attestation) + } } } } diff --git a/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs b/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs index fc38c185de..0773cd6da4 100644 --- a/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs +++ b/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs @@ -41,13 +41,13 @@ impl TestingProposerSlashingBuilder { proposal_1.signature = { let message = proposal_1.signed_root(); let epoch = slot.epoch(spec.slots_per_epoch); - signer(proposer_index, &message[..], epoch, Domain::Proposal) + signer(proposer_index, &message[..], epoch, Domain::BeaconBlock) }; proposal_2.signature = { let message = proposal_2.signed_root(); let epoch = slot.epoch(spec.slots_per_epoch); - signer(proposer_index, &message[..], epoch, Domain::Proposal) + signer(proposer_index, &message[..], epoch, Domain::BeaconBlock) }; ProposerSlashing { From 33783d4baa2d7edb8be4aa75c6cf997df4da2c37 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 16 Mar 2019 14:32:07 +1100 Subject: [PATCH 125/144] Fix borrow issue in `BeaconBlock` --- eth2/types/src/beacon_block.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth2/types/src/beacon_block.rs b/eth2/types/src/beacon_block.rs index bfe266cbd6..2dcf91d955 100644 --- a/eth2/types/src/beacon_block.rs +++ b/eth2/types/src/beacon_block.rs @@ -72,7 +72,7 @@ impl BeaconBlock { previous_block_root: self.previous_block_root, state_root: spec.zero_hash, block_body_root: Hash256::from_slice(&self.hash_tree_root()), - signature: self.signature, + signature: self.signature.clone(), } } } From c648491c45fdf858c55006a57cd4b48db6e08a91 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 16 Mar 2019 14:32:23 +1100 Subject: [PATCH 126/144] Add TestRandom `default` field attr to BeaconState --- eth2/types/src/beacon_state.rs | 5 ++++- eth2/types/src/beacon_state/epoch_cache.rs | 13 ------------- eth2/types/src/beacon_state/pubkey_cache.rs | 9 --------- 3 files changed, 4 insertions(+), 23 deletions(-) diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index ec1008d3c5..ba9c5cd4d5 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -8,7 +8,7 @@ use log::{debug, error, trace}; use pubkey_cache::PubkeyCache; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; -use ssz::{hash, Decodable, SignedRoot, TreeHash}; +use ssz::{hash, SignedRoot}; use ssz_derive::{Decode, Encode, TreeHash}; use std::collections::HashMap; use swap_or_not_shuffle::shuffle_list; @@ -128,16 +128,19 @@ pub struct BeaconState { #[ssz(skip_serializing)] #[ssz(skip_deserializing)] #[tree_hash(skip_hashing)] + #[test_random(default)] pub cache_index_offset: usize, #[serde(default)] #[ssz(skip_serializing)] #[ssz(skip_deserializing)] #[tree_hash(skip_hashing)] + #[test_random(default)] pub caches: [EpochCache; CACHED_EPOCHS], #[serde(default)] #[ssz(skip_serializing)] #[ssz(skip_deserializing)] #[tree_hash(skip_hashing)] + #[test_random(default)] pub pubkey_cache: PubkeyCache, } diff --git a/eth2/types/src/beacon_state/epoch_cache.rs b/eth2/types/src/beacon_state/epoch_cache.rs index 3e580eee1a..ddcca0a9a0 100644 --- a/eth2/types/src/beacon_state/epoch_cache.rs +++ b/eth2/types/src/beacon_state/epoch_cache.rs @@ -1,7 +1,5 @@ use super::{AttestationDuty, BeaconState, CrosslinkCommittees, Error}; -use crate::test_utils::TestRandom; use crate::{ChainSpec, Epoch}; -use rand::RngCore; use serde_derive::{Deserialize, Serialize}; #[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)] @@ -69,14 +67,3 @@ impl EpochCache { }) } } - -impl TestRandom for [EpochCache; 3] { - /// Test random should generate an empty cache. - fn random_for_test(rng: &mut T) -> Self { - [ - EpochCache::default(), - EpochCache::default(), - EpochCache::default(), - ] - } -} diff --git a/eth2/types/src/beacon_state/pubkey_cache.rs b/eth2/types/src/beacon_state/pubkey_cache.rs index 22fe32694c..340bdb3119 100644 --- a/eth2/types/src/beacon_state/pubkey_cache.rs +++ b/eth2/types/src/beacon_state/pubkey_cache.rs @@ -1,6 +1,4 @@ -use crate::test_utils::TestRandom; use crate::*; -use rand::RngCore; use serde_derive::{Deserialize, Serialize}; use std::collections::HashMap; @@ -38,10 +36,3 @@ impl PubkeyCache { self.map.get(pubkey).cloned() } } - -impl TestRandom for PubkeyCache { - /// Test random should generate an empty cache. - fn random_for_test(rng: &mut T) -> Self { - Self::default() - } -} From d6456a948685af408c913229db1b626e18226123 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 16 Mar 2019 14:35:45 +1100 Subject: [PATCH 127/144] Remove old types structs. They've been outdated be v0.5.0 or prior --- eth2/types/src/lib.rs | 1 - eth2/types/src/proposal_signed_data.rs | 20 --- eth2/types/src/shard_reassignment_record.rs | 19 --- eth2/types/src/slashable_vote_data.rs | 132 ------------------ .../src/validator_registry_delta_block.rs | 36 ----- 5 files changed, 208 deletions(-) delete mode 100644 eth2/types/src/proposal_signed_data.rs delete mode 100644 eth2/types/src/shard_reassignment_record.rs delete mode 100644 eth2/types/src/slashable_vote_data.rs delete mode 100644 eth2/types/src/validator_registry_delta_block.rs diff --git a/eth2/types/src/lib.rs b/eth2/types/src/lib.rs index a1a58198b1..c38fa80311 100644 --- a/eth2/types/src/lib.rs +++ b/eth2/types/src/lib.rs @@ -23,7 +23,6 @@ pub mod free_attestation; pub mod historical_batch; pub mod pending_attestation; pub mod proposer_slashing; -pub mod shard_reassignment_record; pub mod slashable_attestation; pub mod transfer; pub mod voluntary_exit; diff --git a/eth2/types/src/proposal_signed_data.rs b/eth2/types/src/proposal_signed_data.rs deleted file mode 100644 index 58f45a41df..0000000000 --- a/eth2/types/src/proposal_signed_data.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::test_utils::TestRandom; -use crate::{Hash256, Slot}; -use rand::RngCore; -use serde_derive::Serialize; -use ssz_derive::{Decode, Encode, TreeHash}; -use test_random_derive::TestRandom; - -#[derive(Debug, PartialEq, Clone, Default, Serialize, Encode, Decode, TreeHash, TestRandom)] -pub struct ProposalSignedData { - pub slot: Slot, - pub shard: u64, - pub block_root: Hash256, -} - -#[cfg(test)] -mod tests { - use super::*; - - ssz_tests!(ProposalSignedData); -} diff --git a/eth2/types/src/shard_reassignment_record.rs b/eth2/types/src/shard_reassignment_record.rs deleted file mode 100644 index 9f1705f163..0000000000 --- a/eth2/types/src/shard_reassignment_record.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::{test_utils::TestRandom, Slot}; -use rand::RngCore; -use serde_derive::Serialize; -use ssz_derive::{Decode, Encode, TreeHash}; -use test_random_derive::TestRandom; - -#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)] -pub struct ShardReassignmentRecord { - pub validator_index: u64, - pub shard: u64, - pub slot: Slot, -} - -#[cfg(test)] -mod tests { - use super::*; - - ssz_tests!(ShardReassignmentRecord); -} diff --git a/eth2/types/src/slashable_vote_data.rs b/eth2/types/src/slashable_vote_data.rs deleted file mode 100644 index 73cf91c61b..0000000000 --- a/eth2/types/src/slashable_vote_data.rs +++ /dev/null @@ -1,132 +0,0 @@ -use super::AttestationData; -use crate::chain_spec::ChainSpec; -use crate::test_utils::TestRandom; -use bls::AggregateSignature; -use rand::RngCore; -use serde_derive::Serialize; -use ssz_derive::{Decode, Encode, TreeHash}; -use test_random_derive::TestRandom; - -#[derive(Debug, PartialEq, Clone, Serialize, Encode, Decode, TreeHash, TestRandom)] -pub struct SlashableVoteData { - pub custody_bit_0_indices: Vec, - pub custody_bit_1_indices: Vec, - pub data: AttestationData, - pub aggregate_signature: AggregateSignature, -} - -impl SlashableVoteData { - /// Check if ``attestation_data_1`` and ``attestation_data_2`` have the same target. - /// - /// Spec v0.3.0 - pub fn is_double_vote(&self, other: &SlashableVoteData, spec: &ChainSpec) -> bool { - self.data.slot.epoch(spec.epoch_length) == other.data.slot.epoch(spec.epoch_length) - } - - /// Check if ``attestation_data_1`` surrounds ``attestation_data_2``. - /// - /// Spec v0.3.0 - pub fn is_surround_vote(&self, other: &SlashableVoteData, spec: &ChainSpec) -> bool { - let source_epoch_1 = self.data.justified_epoch; - let source_epoch_2 = other.data.justified_epoch; - let target_epoch_1 = self.data.slot.epoch(spec.epoch_length); - let target_epoch_2 = other.data.slot.epoch(spec.epoch_length); - - (source_epoch_1 < source_epoch_2) && (target_epoch_2 < target_epoch_1) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::chain_spec::ChainSpec; - use crate::slot_epoch::{Epoch, Slot}; - use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; - - #[test] - pub fn test_is_double_vote_true() { - let spec = ChainSpec::foundation(); - let slashable_vote_first = create_slashable_vote_data(1, 1, &spec); - let slashable_vote_second = create_slashable_vote_data(1, 1, &spec); - - assert_eq!( - slashable_vote_first.is_double_vote(&slashable_vote_second, &spec), - true - ) - } - - #[test] - pub fn test_is_double_vote_false() { - let spec = ChainSpec::foundation(); - let slashable_vote_first = create_slashable_vote_data(1, 1, &spec); - let slashable_vote_second = create_slashable_vote_data(2, 1, &spec); - - assert_eq!( - slashable_vote_first.is_double_vote(&slashable_vote_second, &spec), - false - ); - } - - #[test] - pub fn test_is_surround_vote_true() { - let spec = ChainSpec::foundation(); - let slashable_vote_first = create_slashable_vote_data(2, 1, &spec); - let slashable_vote_second = create_slashable_vote_data(1, 2, &spec); - - assert_eq!( - slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec), - true - ); - } - - #[test] - pub fn test_is_surround_vote_true_realistic() { - let spec = ChainSpec::foundation(); - let slashable_vote_first = create_slashable_vote_data(4, 1, &spec); - let slashable_vote_second = create_slashable_vote_data(3, 2, &spec); - - assert_eq!( - slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec), - true - ); - } - - #[test] - pub fn test_is_surround_vote_false_source_epoch_fails() { - let spec = ChainSpec::foundation(); - let slashable_vote_first = create_slashable_vote_data(2, 2, &spec); - let slashable_vote_second = create_slashable_vote_data(1, 1, &spec); - - assert_eq!( - slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec), - false - ); - } - - #[test] - pub fn test_is_surround_vote_false_target_epoch_fails() { - let spec = ChainSpec::foundation(); - let slashable_vote_first = create_slashable_vote_data(1, 1, &spec); - let slashable_vote_second = create_slashable_vote_data(2, 2, &spec); - - assert_eq!( - slashable_vote_first.is_surround_vote(&slashable_vote_second, &spec), - false - ); - } - - ssz_tests!(SlashableVoteData); - - fn create_slashable_vote_data( - slot_factor: u64, - justified_epoch: u64, - spec: &ChainSpec, - ) -> SlashableVoteData { - let mut rng = XorShiftRng::from_seed([42; 16]); - let mut slashable_vote = SlashableVoteData::random_for_test(&mut rng); - - slashable_vote.data.slot = Slot::new(slot_factor * spec.epoch_length); - slashable_vote.data.justified_epoch = Epoch::new(justified_epoch); - slashable_vote - } -} diff --git a/eth2/types/src/validator_registry_delta_block.rs b/eth2/types/src/validator_registry_delta_block.rs deleted file mode 100644 index e9a0750527..0000000000 --- a/eth2/types/src/validator_registry_delta_block.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::{test_utils::TestRandom, Hash256, Slot}; -use bls::PublicKey; -use rand::RngCore; -use serde_derive::Serialize; -use ssz_derive::{Decode, Encode, TreeHash}; -use test_random_derive::TestRandom; - -// The information gathered from the PoW chain validator registration function. -#[derive(Debug, Clone, PartialEq, Serialize, Encode, Decode, TreeHash, TestRandom)] -pub struct ValidatorRegistryDeltaBlock { - pub latest_registry_delta_root: Hash256, - pub validator_index: u32, - pub pubkey: PublicKey, - pub slot: Slot, - pub flag: u64, -} - -impl Default for ValidatorRegistryDeltaBlock { - /// Yields a "default" `Validator`. Primarily used for testing. - fn default() -> Self { - Self { - latest_registry_delta_root: Hash256::zero(), - validator_index: std::u32::MAX, - pubkey: PublicKey::default(), - slot: Slot::from(std::u64::MAX), - flag: std::u64::MAX, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - ssz_tests!(ValidatorRegistryDeltaBlock); -} From 7f4af20212c4b07dbb4c682b163f9c99cf3144f5 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sat, 16 Mar 2019 19:14:49 +1100 Subject: [PATCH 128/144] Refactor shuffling generation --- eth2/types/src/attestation_duty.rs | 9 + eth2/types/src/beacon_state.rs | 459 ++++-------------- eth2/types/src/beacon_state/builder.rs | 6 +- eth2/types/src/beacon_state/epoch_cache.rs | 292 +++++++++-- eth2/types/src/beacon_state/tests.rs | 48 -- eth2/types/src/crosslink_committee.rs | 9 + eth2/types/src/epoch_cache.rs | 0 eth2/types/src/lib.rs | 12 +- eth2/types/src/relative_epoch.rs | 76 +++ .../testing_beacon_block_builder.rs | 12 +- .../testing_beacon_state_builder.rs | 17 +- 11 files changed, 476 insertions(+), 464 deletions(-) create mode 100644 eth2/types/src/attestation_duty.rs create mode 100644 eth2/types/src/crosslink_committee.rs create mode 100644 eth2/types/src/epoch_cache.rs create mode 100644 eth2/types/src/relative_epoch.rs diff --git a/eth2/types/src/attestation_duty.rs b/eth2/types/src/attestation_duty.rs new file mode 100644 index 0000000000..f6e86d2632 --- /dev/null +++ b/eth2/types/src/attestation_duty.rs @@ -0,0 +1,9 @@ +use crate::*; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)] +pub struct AttestationDuty { + pub slot: Slot, + pub shard: Shard, + pub committee_index: usize, +} diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index ba9c5cd4d5..32f8204e38 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -1,17 +1,14 @@ use self::epoch_cache::EpochCache; use crate::test_utils::TestRandom; use crate::{validator_registry::get_active_validator_indices, *}; -use helpers::*; -use honey_badger_split::SplitExt; use int_to_bytes::int_to_bytes32; -use log::{debug, error, trace}; +use log::{debug, trace}; use pubkey_cache::PubkeyCache; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; -use ssz::{hash, SignedRoot}; +use ssz::{hash, SignedRoot, TreeHash}; use ssz_derive::{Decode, Encode, TreeHash}; use std::collections::HashMap; -use swap_or_not_shuffle::shuffle_list; use test_random_derive::TestRandom; pub use builder::BeaconStateBuilder; @@ -22,22 +19,7 @@ pub mod helpers; mod pubkey_cache; mod tests; -pub type Committee = Vec; -pub type CrosslinkCommittees = Vec<(Committee, u64)>; -pub type Shard = u64; -pub type CommitteeIndex = u64; -pub type AttestationDuty = (Slot, Shard, CommitteeIndex); -pub type AttestationDutyMap = HashMap; -pub type ShardCommitteeIndexMap = HashMap; - -pub const CACHED_EPOCHS: usize = 3; - -#[derive(Debug, PartialEq, Clone, Copy)] -pub enum RelativeEpoch { - Previous, - Current, - Next, -} +pub const CACHED_EPOCHS: usize = 4; #[derive(Debug, PartialEq)] pub enum Error { @@ -61,6 +43,7 @@ pub enum Error { cache_len: usize, registry_len: usize, }, + RelativeEpochError(RelativeEpochError), } macro_rules! safe_add_assign { @@ -212,13 +195,12 @@ impl BeaconState { EpochCache::default(), EpochCache::default(), EpochCache::default(), + EpochCache::default(), ], pubkey_cache: PubkeyCache::default(), } } - /* - /// Returns the `hash_tree_root` of the state. /// /// Spec v0.5.0 @@ -226,8 +208,6 @@ impl BeaconState { Hash256::from_slice(&self.hash_tree_root()[..]) } - */ - /// Build an epoch cache, unless it is has already been built. pub fn build_epoch_cache( &mut self, @@ -236,7 +216,8 @@ impl BeaconState { ) -> Result<(), Error> { let cache_index = self.cache_index(relative_epoch); - if self.caches[cache_index].initialized { + if self.caches[cache_index].initialized_epoch == Some(self.slot.epoch(spec.slots_per_epoch)) + { Ok(()) } else { self.force_build_epoch_cache(relative_epoch, spec) @@ -249,36 +230,13 @@ impl BeaconState { relative_epoch: RelativeEpoch, spec: &ChainSpec, ) -> Result<(), Error> { - let epoch = self.absolute_epoch(relative_epoch, spec); let cache_index = self.cache_index(relative_epoch); - self.caches[cache_index] = EpochCache::initialized(&self, epoch, spec)?; + self.caches[cache_index] = EpochCache::initialized(&self, relative_epoch, spec)?; Ok(()) } - /// Converts a `RelativeEpoch` into an `Epoch` with respect to the epoch of this state. - fn absolute_epoch(&self, relative_epoch: RelativeEpoch, spec: &ChainSpec) -> Epoch { - match relative_epoch { - RelativeEpoch::Previous => self.previous_epoch(spec), - RelativeEpoch::Current => self.current_epoch(spec), - RelativeEpoch::Next => self.next_epoch(spec), - } - } - - /// Converts an `Epoch` into a `RelativeEpoch` with respect to the epoch of this state. - /// - /// Returns an error if the given `epoch` not "previous", "current" or "next" compared to the - /// epoch of this tate. - fn relative_epoch(&self, epoch: Epoch, spec: &ChainSpec) -> Result { - match epoch { - e if e == self.current_epoch(spec) => Ok(RelativeEpoch::Current), - e if e == self.previous_epoch(spec) => Ok(RelativeEpoch::Previous), - e if e == self.next_epoch(spec) => Ok(RelativeEpoch::Next), - _ => Err(Error::EpochOutOfBounds), - } - } - /// Advances the cache for this state into the next epoch. /// /// This should be used if the `slot` of this state is advanced beyond an epoch boundary. @@ -305,9 +263,10 @@ impl BeaconState { /// Returns the index of `self.caches` for some `RelativeEpoch`. fn cache_index(&self, relative_epoch: RelativeEpoch) -> usize { let base_index = match relative_epoch { - RelativeEpoch::Current => 1, RelativeEpoch::Previous => 0, - RelativeEpoch::Next => 2, + RelativeEpoch::Current => 1, + RelativeEpoch::NextWithoutRegistryChange => 2, + RelativeEpoch::NextWithRegistryChange => 3, }; (base_index + self.cache_index_offset) % CACHED_EPOCHS @@ -315,10 +274,10 @@ impl BeaconState { /// Returns the cache for some `RelativeEpoch`. Returns an error if the cache has not been /// initialized. - fn cache(&self, relative_epoch: RelativeEpoch) -> Result<&EpochCache, Error> { + fn cache(&self, relative_epoch: RelativeEpoch, spec: &ChainSpec) -> Result<&EpochCache, Error> { let cache = &self.caches[self.cache_index(relative_epoch)]; - if cache.initialized { + if cache.initialized_epoch == Some(self.slot.epoch(spec.slots_per_epoch)) { Ok(cache) } else { Err(Error::EpochCacheUninitialized(relative_epoch)) @@ -367,7 +326,7 @@ impl BeaconState { /// The epoch corresponding to `self.slot`. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn current_epoch(&self, spec: &ChainSpec) -> Epoch { self.slot.epoch(spec.slots_per_epoch) } @@ -376,58 +335,16 @@ impl BeaconState { /// /// If the current epoch is the genesis epoch, the genesis_epoch is returned. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn previous_epoch(&self, spec: &ChainSpec) -> Epoch { - let current_epoch = self.current_epoch(&spec); - std::cmp::max(current_epoch - 1, spec.genesis_epoch) + self.current_epoch(&spec) - 1 } /// The epoch following `self.current_epoch()`. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn next_epoch(&self, spec: &ChainSpec) -> Epoch { - self.current_epoch(spec).saturating_add(1_u64) - } - - /// The first slot of the epoch corresponding to `self.slot`. - /// - /// Spec v0.4.0 - pub fn current_epoch_start_slot(&self, spec: &ChainSpec) -> Slot { - self.current_epoch(spec).start_slot(spec.slots_per_epoch) - } - - /// The first slot of the epoch preceding the one corresponding to `self.slot`. - /// - /// Spec v0.4.0 - pub fn previous_epoch_start_slot(&self, spec: &ChainSpec) -> Slot { - self.previous_epoch(spec).start_slot(spec.slots_per_epoch) - } - - /// Return the number of committees in the previous epoch. - /// - /// Spec v0.4.0 - fn get_previous_epoch_committee_count(&self, spec: &ChainSpec) -> u64 { - let previous_active_validators = - get_active_validator_indices(&self.validator_registry, self.previous_shuffling_epoch); - spec.get_epoch_committee_count(previous_active_validators.len()) - } - - /// Return the number of committees in the current epoch. - /// - /// Spec v0.4.0 - pub fn get_current_epoch_committee_count(&self, spec: &ChainSpec) -> u64 { - let current_active_validators = - get_active_validator_indices(&self.validator_registry, self.current_shuffling_epoch); - spec.get_epoch_committee_count(current_active_validators.len()) - } - - /// Return the number of committees in the next epoch. - /// - /// Spec v0.4.0 - pub fn get_next_epoch_committee_count(&self, spec: &ChainSpec) -> u64 { - let next_active_validators = - get_active_validator_indices(&self.validator_registry, self.next_epoch(spec)); - spec.get_epoch_committee_count(next_active_validators.len()) + self.current_epoch(spec) + 1 } /// Returns the crosslink committees for some slot. @@ -438,15 +355,14 @@ impl BeaconState { pub fn get_crosslink_committees_at_slot( &self, slot: Slot, + relative_epoch: RelativeEpoch, spec: &ChainSpec, - ) -> Result<&CrosslinkCommittees, Error> { - let epoch = slot.epoch(spec.slots_per_epoch); - let relative_epoch = self.relative_epoch(epoch, spec)?; - let cache = self.cache(relative_epoch)?; + ) -> Result<&Vec, Error> { + let cache = self.cache(relative_epoch, spec)?; - let slot_offset = slot - epoch.start_slot(spec.slots_per_epoch); - - Ok(&cache.committees[slot_offset.as_usize()]) + Ok(cache + .get_crosslink_committees_at_slot(slot, spec) + .ok_or_else(|| Error::SlotOutOfBounds)?) } /// Return the block root at a recent `slot`. @@ -525,8 +441,13 @@ impl BeaconState { /// If the state does not contain an index for a beacon proposer at the requested `slot`, then `None` is returned. /// /// Spec v0.4.0 - pub fn get_beacon_proposer_index(&self, slot: Slot, spec: &ChainSpec) -> Result { - let committees = self.get_crosslink_committees_at_slot(slot, spec)?; + pub fn get_beacon_proposer_index( + &self, + slot: Slot, + relative_epoch: RelativeEpoch, + spec: &ChainSpec, + ) -> Result { + let committees = self.get_crosslink_committees_at_slot(slot, relative_epoch, spec)?; trace!( "get_beacon_proposer_index: slot: {}, committees_count: {}", slot, @@ -535,71 +456,28 @@ impl BeaconState { committees .first() .ok_or(Error::InsufficientValidators) - .and_then(|(first_committee, _)| { + .and_then(|first| { let index = slot .as_usize() - .checked_rem(first_committee.len()) + .checked_rem(first.committee.len()) .ok_or(Error::InsufficientValidators)?; - Ok(first_committee[index]) + Ok(first.committee[index]) }) } - /// Returns the list of validator indices which participiated in the attestation. - /// - /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.4.0 - pub fn get_attestation_participants( - &self, - attestation_data: &AttestationData, - bitfield: &Bitfield, - spec: &ChainSpec, - ) -> Result, Error> { - let epoch = attestation_data.slot.epoch(spec.slots_per_epoch); - let relative_epoch = self.relative_epoch(epoch, spec)?; - let cache = self.cache(relative_epoch)?; - - let (committee_slot_index, committee_index) = cache - .shard_committee_indices - .get(attestation_data.shard as usize) - .ok_or_else(|| Error::ShardOutOfBounds)?; - let (committee, shard) = &cache.committees[*committee_slot_index][*committee_index]; - - assert_eq!(*shard, attestation_data.shard, "Bad epoch cache build."); - - if !verify_bitfield_length(&bitfield, committee.len()) { - return Err(Error::InvalidBitfield); - } - - let mut participants = Vec::with_capacity(committee.len()); - for (i, validator_index) in committee.iter().enumerate() { - match bitfield.get(i) { - Ok(bit) if bit == true => participants.push(*validator_index), - _ => {} - } - } - participants.shrink_to_fit(); - - Ok(participants) - } - /// Return the effective balance (also known as "balance at stake") for a validator with the given ``index``. /// /// Spec v0.4.0 - pub fn get_effective_balance(&self, validator_index: usize, spec: &ChainSpec) -> u64 { - std::cmp::min( - self.validator_balances[validator_index], - spec.max_deposit_amount, - ) - } - - /// Return the combined effective balance of an array of validators. - /// - /// Spec v0.4.0 - pub fn get_total_balance(&self, validator_indices: &[usize], spec: &ChainSpec) -> u64 { - validator_indices - .iter() - .fold(0, |acc, i| acc + self.get_effective_balance(*i, spec)) + pub fn get_effective_balance( + &self, + validator_index: usize, + spec: &ChainSpec, + ) -> Result { + let balance = self + .validator_balances + .get(validator_index) + .ok_or_else(|| Error::UnknownValidator)?; + Ok(std::cmp::min(*balance, spec.max_deposit_amount)) } /// Return the epoch at which an activation or exit triggered in ``epoch`` takes effect. @@ -767,11 +645,15 @@ impl BeaconState { self.exit_validator(validator_index, spec); - self.latest_slashed_balances[current_epoch.as_usize() % spec.latest_slashed_exit_length] += - self.get_effective_balance(validator_index, spec); + let effective_balance = self.get_effective_balance(validator_index, spec)?; - let whistleblower_index = self.get_beacon_proposer_index(self.slot, spec)?; - let whistleblower_reward = self.get_effective_balance(validator_index, spec); + self.latest_slashed_balances[current_epoch.as_usize() % spec.latest_slashed_exit_length] += + effective_balance; + + let whistleblower_index = + self.get_beacon_proposer_index(self.slot, RelativeEpoch::Current, spec)?; + + let whistleblower_reward = effective_balance; safe_add_assign!( self.validator_balances[whistleblower_index as usize], whistleblower_reward @@ -801,166 +683,6 @@ impl BeaconState { self.current_epoch(spec) + spec.min_validator_withdrawability_delay; } - /// Returns the crosslink committees for some slot. - /// - /// Utilizes the cache and will fail if the appropriate cache is not initialized. - /// - /// Spec v0.4.0 - pub(crate) fn get_shuffling_for_slot( - &self, - slot: Slot, - registry_change: bool, - spec: &ChainSpec, - ) -> Result>, Error> { - let (_committees_per_epoch, seed, shuffling_epoch, _shuffling_start_shard) = - self.get_committee_params_at_slot(slot, registry_change, spec)?; - - self.get_shuffling(seed, shuffling_epoch, spec) - } - - /// Shuffle ``validators`` into crosslink committees seeded by ``seed`` and ``epoch``. - /// - /// Return a list of ``committees_per_epoch`` committees where each - /// committee is itself a list of validator indices. - /// - /// Spec v0.4.0 - pub(crate) fn get_shuffling( - &self, - seed: Hash256, - epoch: Epoch, - spec: &ChainSpec, - ) -> Result>, Error> { - let active_validator_indices = - get_active_validator_indices(&self.validator_registry, epoch); - if active_validator_indices.is_empty() { - error!("get_shuffling: no validators."); - return Err(Error::InsufficientValidators); - } - - debug!("Shuffling {} validators...", active_validator_indices.len()); - - let committees_per_epoch = spec.get_epoch_committee_count(active_validator_indices.len()); - - trace!( - "get_shuffling: active_validator_indices.len() == {}, committees_per_epoch: {}", - active_validator_indices.len(), - committees_per_epoch - ); - - let active_validator_indices: Vec = active_validator_indices.to_vec(); - - let shuffled_active_validator_indices = shuffle_list( - active_validator_indices, - spec.shuffle_round_count, - &seed[..], - true, - ) - .ok_or_else(|| Error::UnableToShuffle)?; - - Ok(shuffled_active_validator_indices - .honey_badger_split(committees_per_epoch as usize) - .map(|slice: &[usize]| slice.to_vec()) - .collect()) - } - - /// Returns the following params for the given slot: - /// - /// - epoch committee count - /// - epoch seed - /// - calculation epoch - /// - start shard - /// - /// In the spec, this functionality is included in the `get_crosslink_committees_at_slot(..)` - /// function. It is separated here to allow the division of shuffling and committee building, - /// as is required for efficient operations. - /// - /// Spec v0.4.0 - pub(crate) fn get_committee_params_at_slot( - &self, - slot: Slot, - registry_change: bool, - spec: &ChainSpec, - ) -> Result<(u64, Hash256, Epoch, u64), Error> { - let epoch = slot.epoch(spec.slots_per_epoch); - let current_epoch = self.current_epoch(spec); - let previous_epoch = self.previous_epoch(spec); - let next_epoch = self.next_epoch(spec); - - if epoch == current_epoch { - Ok(( - self.get_current_epoch_committee_count(spec), - self.current_shuffling_seed, - self.current_shuffling_epoch, - self.current_shuffling_start_shard, - )) - } else if epoch == previous_epoch { - Ok(( - self.get_previous_epoch_committee_count(spec), - self.previous_shuffling_seed, - self.previous_shuffling_epoch, - self.previous_shuffling_start_shard, - )) - } else if epoch == next_epoch { - let current_committees_per_epoch = self.get_current_epoch_committee_count(spec); - let epochs_since_last_registry_update = - current_epoch - self.validator_registry_update_epoch; - let (seed, shuffling_start_shard) = if registry_change { - let next_seed = self.generate_seed(next_epoch, spec)?; - ( - next_seed, - (self.current_shuffling_start_shard + current_committees_per_epoch) - % spec.shard_count, - ) - } else if (epochs_since_last_registry_update > 1) - & epochs_since_last_registry_update.is_power_of_two() - { - let next_seed = self.generate_seed(next_epoch, spec)?; - (next_seed, self.current_shuffling_start_shard) - } else { - ( - self.current_shuffling_seed, - self.current_shuffling_start_shard, - ) - }; - Ok(( - self.get_next_epoch_committee_count(spec), - seed, - next_epoch, - shuffling_start_shard, - )) - } else { - Err(Error::EpochOutOfBounds) - } - } - - /// Return the ordered list of shards tuples for the `slot`. - /// - /// Note: There are two possible shufflings for crosslink committees for a - /// `slot` in the next epoch: with and without a `registry_change` - /// - /// Spec v0.4.0 - pub(crate) fn get_shards_for_slot( - &self, - slot: Slot, - registry_change: bool, - spec: &ChainSpec, - ) -> Result, Error> { - let (committees_per_epoch, _seed, _shuffling_epoch, shuffling_start_shard) = - self.get_committee_params_at_slot(slot, registry_change, spec)?; - - let offset = slot.as_u64() % spec.slots_per_epoch; - let committees_per_slot = committees_per_epoch / spec.slots_per_epoch; - let slot_start_shard = - (shuffling_start_shard + committees_per_slot * offset) % spec.shard_count; - - let mut shards_at_slot = vec![]; - for i in 0..committees_per_slot { - shards_at_slot.push((slot_start_shard + i) % spec.shard_count) - } - - Ok(shards_at_slot) - } - /// Returns the `slot`, `shard` and `committee_index` for which a validator must produce an /// attestation. /// @@ -969,14 +691,14 @@ impl BeaconState { /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. /// /// Spec v0.4.0 - pub fn attestation_slot_and_shard_for_validator( + pub fn get_attestation_duties( &self, validator_index: usize, - _spec: &ChainSpec, - ) -> Result, Error> { - let cache = self.cache(RelativeEpoch::Current)?; + spec: &ChainSpec, + ) -> Result<&Option, Error> { + let cache = self.cache(RelativeEpoch::Current, spec)?; - Ok(*cache + Ok(cache .attestation_duties .get(validator_index) .ok_or_else(|| Error::UnknownValidator)?) @@ -985,11 +707,11 @@ impl BeaconState { /// Process the slashings. /// /// Spec v0.4.0 - pub fn process_slashings(&mut self, spec: &ChainSpec) { + pub fn process_slashings(&mut self, spec: &ChainSpec) -> Result<(), Error> { let current_epoch = self.current_epoch(spec); let active_validator_indices = get_active_validator_indices(&self.validator_registry, current_epoch); - let total_balance = self.get_total_balance(&active_validator_indices[..], spec); + let total_balance = self.get_total_balance(&active_validator_indices[..], spec)?; for (index, validator) in self.validator_registry.iter().enumerate() { if validator.slashed @@ -1003,16 +725,19 @@ impl BeaconState { [(epoch_index + 1) % spec.latest_slashed_exit_length]; let total_at_end = self.latest_slashed_balances[epoch_index]; let total_penalities = total_at_end.saturating_sub(total_at_start); + + let effective_balance = self.get_effective_balance(index, spec)?; let penalty = std::cmp::max( - self.get_effective_balance(index, spec) - * std::cmp::min(total_penalities * 3, total_balance) + effective_balance * std::cmp::min(total_penalities * 3, total_balance) / total_balance, - self.get_effective_balance(index, spec) / spec.min_penalty_quotient, + effective_balance / spec.min_penalty_quotient, ); safe_sub_assign!(self.validator_balances[index], penalty); } } + + Ok(()) } /// Process the exit queue. @@ -1047,11 +772,11 @@ impl BeaconState { /// Update validator registry, activating/exiting validators if possible. /// /// Spec v0.4.0 - pub fn update_validator_registry(&mut self, spec: &ChainSpec) { + pub fn update_validator_registry(&mut self, spec: &ChainSpec) -> Result<(), Error> { let current_epoch = self.current_epoch(spec); let active_validator_indices = get_active_validator_indices(&self.validator_registry, current_epoch); - let total_balance = self.get_total_balance(&active_validator_indices[..], spec); + let total_balance = self.get_total_balance(&active_validator_indices[..], spec)?; let max_balance_churn = std::cmp::max( spec.max_deposit_amount, @@ -1065,7 +790,7 @@ impl BeaconState { if (validator.activation_epoch == spec.far_future_epoch) & (self.validator_balances[index] == spec.max_deposit_amount) { - balance_churn += self.get_effective_balance(index, spec); + balance_churn += self.get_effective_balance(index, spec)?; if balance_churn > max_balance_churn { break; } @@ -1078,7 +803,7 @@ impl BeaconState { let validator = &self.validator_registry[index]; if (validator.exit_epoch == spec.far_future_epoch) & (validator.initiated_exit) { - balance_churn += self.get_effective_balance(index, spec); + balance_churn += self.get_effective_balance(index, spec)?; if balance_churn > max_balance_churn { break; } @@ -1088,6 +813,8 @@ impl BeaconState { } self.validator_registry_update_epoch = current_epoch; + + Ok(()) } /// Iterate through the validator registry and eject active validators with balance below @@ -1115,12 +842,13 @@ impl BeaconState { epochs_since_finality: Epoch, base_reward_quotient: u64, spec: &ChainSpec, - ) -> u64 { - let effective_balance = self.get_effective_balance(validator_index, spec); - self.base_reward(validator_index, base_reward_quotient, spec) + ) -> Result { + let effective_balance = self.get_effective_balance(validator_index, spec)?; + let base_reward = self.base_reward(validator_index, base_reward_quotient, spec)?; + Ok(base_reward + effective_balance * epochs_since_finality.as_u64() / spec.inactivity_penalty_quotient - / 2 + / 2) } /// Returns the base reward for some validator. @@ -1133,30 +861,27 @@ impl BeaconState { validator_index: usize, base_reward_quotient: u64, spec: &ChainSpec, - ) -> u64 { - self.get_effective_balance(validator_index, spec) / base_reward_quotient / 5 + ) -> Result { + Ok(self.get_effective_balance(validator_index, spec)? / base_reward_quotient / 5) } - /// Returns the union of all participants in the provided attestations + /// Return the combined effective balance of an array of validators. /// /// Spec v0.4.0 - pub fn get_attestation_participants_union( + pub fn get_total_balance( &self, - attestations: &[&PendingAttestation], + validator_indices: &[usize], spec: &ChainSpec, - ) -> Result, Error> { - let mut all_participants = attestations - .iter() - .try_fold::<_, _, Result, Error>>(vec![], |mut acc, a| { - acc.append(&mut self.get_attestation_participants( - &a.data, - &a.aggregation_bitfield, - spec, - )?); - Ok(acc) - })?; - all_participants.sort_unstable(); - all_participants.dedup(); - Ok(all_participants) + ) -> Result { + validator_indices.iter().try_fold(0_u64, |acc, i| { + self.get_effective_balance(*i, spec) + .and_then(|bal| Ok(bal + acc)) + }) + } +} + +impl From for Error { + fn from(e: RelativeEpochError) -> Error { + Error::RelativeEpochError(e) } } diff --git a/eth2/types/src/beacon_state/builder.rs b/eth2/types/src/beacon_state/builder.rs index 22ca3e6224..780ec9b8ba 100644 --- a/eth2/types/src/beacon_state/builder.rs +++ b/eth2/types/src/beacon_state/builder.rs @@ -43,12 +43,14 @@ impl BeaconStateBuilder { self.state.deposit_index = initial_validator_deposits.len() as u64; } - fn activate_genesis_validators(&mut self, spec: &ChainSpec) { + fn activate_genesis_validators(&mut self, spec: &ChainSpec) -> Result<(), BeaconStateError> { for validator_index in 0..self.state.validator_registry.len() { - if self.state.get_effective_balance(validator_index, spec) >= spec.max_deposit_amount { + if self.state.get_effective_balance(validator_index, spec)? >= spec.max_deposit_amount { self.state.activate_validator(validator_index, true, spec); } } + + Ok(()) } /// Instantiate the validator registry from a YAML file. diff --git a/eth2/types/src/beacon_state/epoch_cache.rs b/eth2/types/src/beacon_state/epoch_cache.rs index ddcca0a9a0..6312ea5a54 100644 --- a/eth2/types/src/beacon_state/epoch_cache.rs +++ b/eth2/types/src/beacon_state/epoch_cache.rs @@ -1,69 +1,283 @@ -use super::{AttestationDuty, BeaconState, CrosslinkCommittees, Error}; -use crate::{ChainSpec, Epoch}; +use super::{BeaconState, Error}; +use crate::*; +use honey_badger_split::SplitExt; use serde_derive::{Deserialize, Serialize}; +use swap_or_not_shuffle::shuffle_list; #[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)] pub struct EpochCache { - /// True if this cache has been initialized. - pub initialized: bool, - /// The crosslink committees for an epoch. - pub committees: Vec, + /// `Some(epoch)` if the cache is initialized, where `epoch` is the cache it holds. + pub initialized_epoch: Option, + /// All crosslink committees for an epoch. + pub epoch_crosslink_committees: EpochCrosslinkCommittees, /// Maps validator index to a slot, shard and committee index for attestation. pub attestation_duties: Vec>, /// Maps a shard to an index of `self.committees`. - pub shard_committee_indices: Vec<(usize, usize)>, + pub shard_committee_indices: Vec<(Slot, usize)>, } impl EpochCache { /// Return a new, fully initialized cache. pub fn initialized( state: &BeaconState, - epoch: Epoch, + relative_epoch: RelativeEpoch, spec: &ChainSpec, ) -> Result { - let mut epoch_committees: Vec = - Vec::with_capacity(spec.slots_per_epoch as usize); + let epoch = relative_epoch.into_epoch(state.slot.epoch(spec.slots_per_epoch)); + let active_validator_indices = + get_active_validator_indices(&state.validator_registry, epoch); + + let builder = match relative_epoch { + RelativeEpoch::Previous => EpochCrosslinkCommitteesBuilder::for_previous_epoch( + state, + active_validator_indices, + spec, + ), + RelativeEpoch::Current => EpochCrosslinkCommitteesBuilder::for_current_epoch( + state, + active_validator_indices, + spec, + ), + RelativeEpoch::NextWithRegistryChange => { + EpochCrosslinkCommitteesBuilder::for_next_epoch( + state, + active_validator_indices, + true, + spec, + )? + } + RelativeEpoch::NextWithoutRegistryChange => { + EpochCrosslinkCommitteesBuilder::for_next_epoch( + state, + active_validator_indices, + false, + spec, + )? + } + }; + let epoch_crosslink_committees = builder.build(spec)?; + + // Loop through all the validators in the committees and create the following maps: + // + // 1. `attestation_duties`: maps `ValidatorIndex` to `AttestationDuty`. + // 2. `shard_committee_indices`: maps `Shard` into a `CrosslinkCommittee` in + // `EpochCrosslinkCommittees`. let mut attestation_duties = vec![None; state.validator_registry.len()]; + let mut shard_committee_indices = vec![(Slot::default(), 0); spec.shard_count as usize]; + for (i, slot_committees) in epoch_crosslink_committees + .crosslink_committees + .iter() + .enumerate() + { + let slot = epoch.start_slot(spec.slots_per_epoch) + i as u64; - let mut shard_committee_indices = vec![(0, 0); spec.shard_count as usize]; + for (j, crosslink_committee) in slot_committees.iter().enumerate() { + let shard = crosslink_committee.shard; - let mut shuffling = - state.get_shuffling_for_slot(epoch.start_slot(spec.slots_per_epoch), false, spec)?; + shard_committee_indices[shard as usize] = (slot, j); - for (epoch_committees_index, slot) in epoch.slot_iter(spec.slots_per_epoch).enumerate() { - let mut slot_committees: Vec<(Vec, u64)> = vec![]; - - let shards = state.get_shards_for_slot(slot, false, spec)?; - for shard in shards { - let committee = shuffling.remove(0); - slot_committees.push((committee, shard)); - } - - for (slot_committees_index, (committee, shard)) in slot_committees.iter().enumerate() { - if committee.is_empty() { - return Err(Error::InsufficientValidators); - } - - // Store the slot and committee index for this shard. - shard_committee_indices[*shard as usize] = - (epoch_committees_index, slot_committees_index); - - // For each validator, store their attestation duties. - for (committee_index, validator_index) in committee.iter().enumerate() { - attestation_duties[*validator_index] = - Some((slot, *shard, committee_index as u64)) + for (k, validator_index) in crosslink_committee.committee.iter().enumerate() { + let attestation_duty = AttestationDuty { + slot, + shard, + committee_index: k, + }; + attestation_duties[*validator_index] = Some(attestation_duty) } } - - epoch_committees.push(slot_committees) } Ok(EpochCache { - initialized: true, - committees: epoch_committees, + initialized_epoch: Some(epoch), + epoch_crosslink_committees, attestation_duties, shard_committee_indices, }) } + + pub fn get_crosslink_committees_at_slot( + &self, + slot: Slot, + spec: &ChainSpec, + ) -> Option<&Vec> { + self.epoch_crosslink_committees + .get_crosslink_committees_at_slot(slot, spec) + } + + pub fn get_crosslink_committee_for_shard( + &self, + shard: Shard, + spec: &ChainSpec, + ) -> Option<&CrosslinkCommittee> { + let (slot, committee) = self.shard_committee_indices.get(shard as usize)?; + let slot_committees = self.get_crosslink_committees_at_slot(*slot, spec)?; + slot_committees.get(*committee) + } +} + +pub fn get_active_validator_indices(validators: &[Validator], epoch: Epoch) -> Vec { + let mut active = Vec::with_capacity(validators.len()); + + for (index, validator) in validators.iter().enumerate() { + if validator.is_active_at(epoch) { + active.push(index) + } + } + + active.shrink_to_fit(); + + active +} + +#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)] +pub struct EpochCrosslinkCommittees { + epoch: Epoch, + pub crosslink_committees: Vec>, +} + +impl EpochCrosslinkCommittees { + fn new(epoch: Epoch, spec: &ChainSpec) -> Self { + Self { + epoch, + crosslink_committees: vec![vec![]; spec.slots_per_epoch as usize], + } + } + + fn get_crosslink_committees_at_slot( + &self, + slot: Slot, + spec: &ChainSpec, + ) -> Option<&Vec> { + let epoch_start_slot = self.epoch.start_slot(spec.slots_per_epoch); + let epoch_end_slot = self.epoch.end_slot(spec.slots_per_epoch); + + if (epoch_start_slot < slot) && (slot <= epoch_end_slot) { + let index = slot - epoch_start_slot; + self.crosslink_committees.get(index.as_usize()) + } else { + None + } + } +} + +pub struct EpochCrosslinkCommitteesBuilder { + epoch: Epoch, + shuffling_start_shard: Shard, + shuffling_seed: Hash256, + active_validator_indices: Vec, + committees_per_epoch: u64, +} + +impl EpochCrosslinkCommitteesBuilder { + pub fn for_previous_epoch( + state: &BeaconState, + active_validator_indices: Vec, + spec: &ChainSpec, + ) -> Self { + Self { + epoch: state.previous_epoch(spec), + shuffling_start_shard: state.previous_shuffling_start_shard, + shuffling_seed: state.previous_shuffling_seed, + committees_per_epoch: spec.get_epoch_committee_count(active_validator_indices.len()), + active_validator_indices, + } + } + + pub fn for_current_epoch( + state: &BeaconState, + active_validator_indices: Vec, + spec: &ChainSpec, + ) -> Self { + Self { + epoch: state.current_epoch(spec), + shuffling_start_shard: state.current_shuffling_start_shard, + shuffling_seed: state.current_shuffling_seed, + committees_per_epoch: spec.get_epoch_committee_count(active_validator_indices.len()), + active_validator_indices, + } + } + + pub fn for_next_epoch( + state: &BeaconState, + active_validator_indices: Vec, + registry_change: bool, + spec: &ChainSpec, + ) -> Result { + let current_epoch = state.current_epoch(spec); + let next_epoch = state.next_epoch(spec); + let committees_per_epoch = spec.get_epoch_committee_count(active_validator_indices.len()); + + let epochs_since_last_registry_update = + current_epoch - state.validator_registry_update_epoch; + + let (seed, shuffling_start_shard) = if registry_change { + let next_seed = state.generate_seed(next_epoch, spec)?; + ( + next_seed, + (state.current_shuffling_start_shard + committees_per_epoch) % spec.shard_count, + ) + } else if (epochs_since_last_registry_update > 1) + & epochs_since_last_registry_update.is_power_of_two() + { + let next_seed = state.generate_seed(next_epoch, spec)?; + (next_seed, state.current_shuffling_start_shard) + } else { + ( + state.current_shuffling_seed, + state.current_shuffling_start_shard, + ) + }; + + Ok(Self { + epoch: state.next_epoch(spec), + shuffling_start_shard, + shuffling_seed: seed, + active_validator_indices, + committees_per_epoch, + }) + } + + pub fn build(self, spec: &ChainSpec) -> Result { + if self.active_validator_indices.is_empty() { + return Err(Error::InsufficientValidators); + } + + let shuffled_active_validator_indices = shuffle_list( + self.active_validator_indices, + spec.shuffle_round_count, + &self.shuffling_seed[..], + true, + ) + .ok_or_else(|| Error::UnableToShuffle)?; + + let mut committees: Vec> = shuffled_active_validator_indices + .honey_badger_split(self.committees_per_epoch as usize) + .map(|slice: &[usize]| slice.to_vec()) + .collect(); + + let mut epoch_crosslink_committees = EpochCrosslinkCommittees::new(self.epoch, spec); + let mut shard = self.shuffling_start_shard; + + let committees_per_slot = (self.committees_per_epoch / spec.slots_per_epoch) as usize; + + for i in 0..spec.slots_per_epoch as usize { + for j in (0..committees.len()) + .into_iter() + .skip(i * committees_per_slot) + .take(committees_per_slot) + { + let crosslink_committee = CrosslinkCommittee { + shard, + committee: committees.remove(j), + }; + epoch_crosslink_committees.crosslink_committees[i].push(crosslink_committee); + + shard += 1; + shard %= spec.shard_count; + } + } + + Ok(epoch_crosslink_committees) + } } diff --git a/eth2/types/src/beacon_state/tests.rs b/eth2/types/src/beacon_state/tests.rs index 1e1a555fd6..6c10ebe86d 100644 --- a/eth2/types/src/beacon_state/tests.rs +++ b/eth2/types/src/beacon_state/tests.rs @@ -1,53 +1,5 @@ #![cfg(test)] use super::*; -use crate::test_utils::TestingBeaconStateBuilder; -use crate::test_utils::{SeedableRng, TestRandom, XorShiftRng}; -use crate::{BeaconState, ChainSpec}; - -/// Tests that `get_attestation_participants` is consistent with the result of -/// get_crosslink_committees_at_slot` with a full bitfield. -#[test] -pub fn get_attestation_participants_consistency() { - let mut rng = XorShiftRng::from_seed([42; 16]); - - let spec = ChainSpec::few_validators(); - let builder = TestingBeaconStateBuilder::from_deterministic_keypairs(8, &spec); - let (mut state, _keypairs) = builder.build(); - - state - .build_epoch_cache(RelativeEpoch::Previous, &spec) - .unwrap(); - state - .build_epoch_cache(RelativeEpoch::Current, &spec) - .unwrap(); - state.build_epoch_cache(RelativeEpoch::Next, &spec).unwrap(); - - for slot in state - .slot - .epoch(spec.slots_per_epoch) - .slot_iter(spec.slots_per_epoch) - { - let committees = state.get_crosslink_committees_at_slot(slot, &spec).unwrap(); - - for (committee, shard) in committees { - let mut attestation_data = AttestationData::random_for_test(&mut rng); - attestation_data.slot = slot; - attestation_data.shard = *shard; - - let mut bitfield = Bitfield::new(); - for (i, _) in committee.iter().enumerate() { - bitfield.set(i, true); - } - - assert_eq!( - state - .get_attestation_participants(&attestation_data, &bitfield, &spec) - .unwrap(), - *committee - ); - } - } -} ssz_tests!(BeaconState); diff --git a/eth2/types/src/crosslink_committee.rs b/eth2/types/src/crosslink_committee.rs new file mode 100644 index 0000000000..06a6562fcd --- /dev/null +++ b/eth2/types/src/crosslink_committee.rs @@ -0,0 +1,9 @@ +use crate::*; +use serde_derive::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode, TreeHash}; + +#[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize, Decode, Encode, TreeHash)] +pub struct CrosslinkCommittee { + pub shard: Shard, + pub committee: Vec, +} diff --git a/eth2/types/src/epoch_cache.rs b/eth2/types/src/epoch_cache.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/eth2/types/src/lib.rs b/eth2/types/src/lib.rs index c38fa80311..05f8254d5c 100644 --- a/eth2/types/src/lib.rs +++ b/eth2/types/src/lib.rs @@ -6,6 +6,7 @@ pub mod test_utils; pub mod attestation; pub mod attestation_data; pub mod attestation_data_and_custody_bit; +pub mod attestation_duty; pub mod attester_slashing; pub mod beacon_block; pub mod beacon_block_body; @@ -13,6 +14,7 @@ pub mod beacon_block_header; pub mod beacon_state; pub mod chain_spec; pub mod crosslink; +pub mod crosslink_committee; pub mod deposit; pub mod deposit_data; pub mod deposit_input; @@ -28,6 +30,7 @@ pub mod transfer; pub mod voluntary_exit; #[macro_use] pub mod slot_epoch_macros; +pub mod relative_epoch; pub mod slot_epoch; pub mod slot_height; pub mod validator; @@ -39,13 +42,15 @@ use std::collections::HashMap; pub use crate::attestation::Attestation; pub use crate::attestation_data::AttestationData; pub use crate::attestation_data_and_custody_bit::AttestationDataAndCustodyBit; +pub use crate::attestation_duty::AttestationDuty; pub use crate::attester_slashing::AttesterSlashing; pub use crate::beacon_block::BeaconBlock; pub use crate::beacon_block_body::BeaconBlockBody; pub use crate::beacon_block_header::BeaconBlockHeader; -pub use crate::beacon_state::{BeaconState, Error as BeaconStateError, RelativeEpoch}; +pub use crate::beacon_state::{BeaconState, Error as BeaconStateError}; pub use crate::chain_spec::{ChainSpec, Domain}; pub use crate::crosslink::Crosslink; +pub use crate::crosslink_committee::CrosslinkCommittee; pub use crate::deposit::Deposit; pub use crate::deposit_data::DepositData; pub use crate::deposit_input::DepositInput; @@ -56,6 +61,7 @@ pub use crate::free_attestation::FreeAttestation; pub use crate::historical_batch::HistoricalBatch; pub use crate::pending_attestation::PendingAttestation; pub use crate::proposer_slashing::ProposerSlashing; +pub use crate::relative_epoch::{Error as RelativeEpochError, RelativeEpoch}; pub use crate::slashable_attestation::SlashableAttestation; pub use crate::slot_epoch::{Epoch, Slot}; pub use crate::slot_height::SlotHeight; @@ -63,6 +69,10 @@ pub use crate::transfer::Transfer; pub use crate::validator::Validator; pub use crate::voluntary_exit::VoluntaryExit; +pub type Shard = u64; +pub type Committee = Vec; +pub type CrosslinkCommittees = Vec<(Committee, u64)>; + pub type Hash256 = H256; pub type Address = H160; pub type EthBalance = U256; diff --git a/eth2/types/src/relative_epoch.rs b/eth2/types/src/relative_epoch.rs new file mode 100644 index 0000000000..9439366059 --- /dev/null +++ b/eth2/types/src/relative_epoch.rs @@ -0,0 +1,76 @@ +use crate::*; + +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum Error { + EpochTooLow { base: Epoch, other: Epoch }, + EpochTooHigh { base: Epoch, other: Epoch }, + AmbiguiousNextEpoch, +} + +/// Defines the epochs relative to some epoch. Most useful when referring to the committees prior +/// to and following some epoch. +/// +/// Spec v0.5.0 +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum RelativeEpoch { + /// The prior epoch. + Previous, + /// The current epoch. + Current, + /// The next epoch if there _is_ a validator registry update. + /// + /// If the validator registry is updated during an epoch transition, a new shuffling seed is + /// generated, this changes the attestation and proposal roles. + NextWithRegistryChange, + /// The next epoch if there _is not_ a validator registry update. + /// + /// If the validator registry _is not_ updated during an epoch transition, the shuffling stays + /// the same. + NextWithoutRegistryChange, +} + +impl RelativeEpoch { + /// Returns the `epoch` that `self` refers to, with respect to the `base` epoch. + /// + /// Spec v0.5.0 + pub fn into_epoch(&self, base: Epoch) -> Epoch { + match self { + RelativeEpoch::Previous => base - 1, + RelativeEpoch::Current => base, + RelativeEpoch::NextWithoutRegistryChange => base + 1, + RelativeEpoch::NextWithRegistryChange => base + 1, + } + } + + /// Converts the `other` epoch into a `RelativeEpoch`, with respect to `base` + /// + /// ## Errors + /// Returns an error when: + /// - `EpochTooLow` when `other` is more than 1 prior to `base`. + /// - `EpochTooHigh` when `other` is more than 1 after `base`. + /// - `AmbiguiousNextEpoch` whenever `other` is one after `base`, because it's unknowable if + /// there will be a registry change. + /// + /// Spec v0.5.0 + pub fn from_epoch(base: Epoch, other: Epoch) -> Result { + if other == base - 1 { + Ok(RelativeEpoch::Previous) + } else if other == base { + Ok(RelativeEpoch::Current) + } else if other == base + 1 { + Err(Error::AmbiguiousNextEpoch) + } else if other < base { + Err(Error::EpochTooLow { base, other }) + } else { + Err(Error::EpochTooHigh { base, other }) + } + } + + /// Convenience function for `Self::from_epoch` where both slots are converted into epochs. + pub fn from_slot(base: Slot, other: Slot, spec: &ChainSpec) -> Result { + Self::from_epoch( + base.epoch(spec.slots_per_epoch), + other.epoch(spec.slots_per_epoch), + ) + } +} diff --git a/eth2/types/src/test_utils/testing_beacon_block_builder.rs b/eth2/types/src/test_utils/testing_beacon_block_builder.rs index 7fb3d8e09b..402bd79d63 100644 --- a/eth2/types/src/test_utils/testing_beacon_block_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_block_builder.rs @@ -109,12 +109,20 @@ impl TestingBeaconBlockBuilder { break; } - for (committee, shard) in state.get_crosslink_committees_at_slot(slot, spec)? { + let relative_epoch = RelativeEpoch::from_slot(state.slot, slot, spec).unwrap(); + for crosslink_committee in + state.get_crosslink_committees_at_slot(slot, relative_epoch, spec)? + { if attestations_added >= num_attestations { break; } - committees.push((slot, committee.clone(), committee.clone(), *shard)); + committees.push(( + slot, + crosslink_committee.committee.clone(), + crosslink_committee.committee.clone(), + crosslink_committee.shard, + )); attestations_added += 1; } diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index 8ef4f76ce1..9e613f0e91 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -159,7 +159,8 @@ impl TestingBeaconStateBuilder { state.build_epoch_cache(RelativeEpoch::Previous, &spec)?; state.build_epoch_cache(RelativeEpoch::Current, &spec)?; - state.build_epoch_cache(RelativeEpoch::Next, &spec)?; + state.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, &spec)?; + state.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, &spec)?; state.update_pubkey_cache()?; @@ -222,15 +223,21 @@ impl TestingBeaconStateBuilder { for slot in first_slot..last_slot + 1 { let slot = Slot::from(slot); + let relative_epoch = RelativeEpoch::from_slot(state.slot, slot, spec).unwrap(); let committees = state - .get_crosslink_committees_at_slot(slot, spec) + .get_crosslink_committees_at_slot(slot, relative_epoch, spec) .unwrap() .clone(); - for (committee, shard) in committees { - let mut builder = TestingPendingAttestationBuilder::new(state, shard, slot, spec); + for crosslink_committee in committees { + let mut builder = TestingPendingAttestationBuilder::new( + state, + crosslink_committee.shard, + slot, + spec, + ); // The entire committee should have signed the pending attestation. - let signers = vec![true; committee.len()]; + let signers = vec![true; crosslink_committee.committee.len()]; builder.add_committee_participation(signers); let attestation = builder.build(); From 6bd2055a0ac2f15eb47ce837fa56e01f9e5e161a Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 17 Mar 2019 12:25:37 +1100 Subject: [PATCH 129/144] Update block processing to v0.5.0 --- .../src/per_block_processing.rs | 131 +++++------- .../src/per_block_processing/errors.rs | 86 ++++---- .../validate_attestation.rs | 198 ++++++++++-------- .../verify_attester_slashing.rs | 16 +- .../per_block_processing/verify_deposit.rs | 33 ++- .../src/per_block_processing/verify_exit.rs | 30 ++- .../verify_proposer_slashing.rs | 50 ++--- .../per_block_processing/verify_transfer.rs | 74 ++++--- .../src/per_slot_processing.rs | 22 +- eth2/types/src/beacon_state.rs | 85 ++++++-- eth2/types/src/chain_spec.rs | 2 +- eth2/types/src/proposer_slashing.rs | 4 +- .../testing_proposer_slashing_builder.rs | 18 +- 13 files changed, 413 insertions(+), 336 deletions(-) diff --git a/eth2/state_processing/src/per_block_processing.rs b/eth2/state_processing/src/per_block_processing.rs index 13a47836b5..377f92e8b1 100644 --- a/eth2/state_processing/src/per_block_processing.rs +++ b/eth2/state_processing/src/per_block_processing.rs @@ -1,17 +1,14 @@ use self::verify_proposer_slashing::verify_proposer_slashing; use errors::{BlockInvalid as Invalid, BlockProcessingError as Error, IntoWithIndex}; -use hashing::hash; use rayon::prelude::*; -use ssz::{ssz_encode, SignedRoot, TreeHash}; +use ssz::{SignedRoot, TreeHash}; use types::*; pub use self::verify_attester_slashing::{ gather_attester_slashing_indices, verify_attester_slashing, }; pub use validate_attestation::{validate_attestation, validate_attestation_without_signature}; -pub use verify_deposit::{ - build_public_key_hashmap, get_existing_validator_index, verify_deposit, verify_deposit_index, -}; +pub use verify_deposit::{get_existing_validator_index, verify_deposit, verify_deposit_index}; pub use verify_exit::verify_exit; pub use verify_slashable_attestation::verify_slashable_attestation; pub use verify_transfer::{execute_transfer, verify_transfer}; @@ -72,8 +69,7 @@ fn per_block_processing_signature_optional( should_verify_block_signature: bool, spec: &ChainSpec, ) -> Result<(), Error> { - // Verify that `block.slot == state.slot`. - verify!(block.slot == state.slot, Invalid::StateSlotMismatch); + process_block_header(state, block, spec)?; // Ensure the current and previous epoch cache is built. state.build_epoch_cache(RelativeEpoch::Current, spec)?; @@ -83,7 +79,7 @@ fn per_block_processing_signature_optional( verify_block_signature(&state, &block, &spec)?; } process_randao(&mut state, &block, &spec)?; - process_eth1_data(&mut state, &block.eth1_data)?; + process_eth1_data(&mut state, &block.body.eth1_data)?; process_proposer_slashings(&mut state, &block.body.proposer_slashings, spec)?; process_attester_slashings(&mut state, &block.body.attester_slashings, spec)?; process_attestations(&mut state, &block.body.attestations, spec)?; @@ -94,33 +90,47 @@ fn per_block_processing_signature_optional( Ok(()) } +/// Processes the block header. +/// +/// Spec v0.5.0 +pub fn process_block_header( + state: &BeaconState, + block: &BeaconBlock, + spec: &ChainSpec, +) -> Result<(), Error> { + verify!(block.slot == state.slot, Invalid::StateSlotMismatch); + + verify!( + block.previous_block_root.as_bytes() == &state.latest_block_header.hash_tree_root()[..], + Invalid::ParentBlockRootMismatch + ); + + state.latest_block_header = block.into_temporary_header(spec); + + Ok(()) +} + /// Verifies the signature of a block. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn verify_block_signature( state: &BeaconState, block: &BeaconBlock, spec: &ChainSpec, ) -> Result<(), Error> { - let block_proposer = - &state.validator_registry[state.get_beacon_proposer_index(block.slot, spec)?]; + let block_proposer = &state.validator_registry + [state.get_beacon_proposer_index(block.slot, RelativeEpoch::Current, spec)?]; - let proposal = Proposal { - slot: block.slot, - shard: spec.beacon_chain_shard_number, - block_root: Hash256::from_slice(&block.signed_root()[..]), - signature: block.signature.clone(), - }; let domain = spec.get_domain( block.slot.epoch(spec.slots_per_epoch), - Domain::Proposal, + Domain::BeaconBlock, &state.fork, ); verify!( - proposal + block .signature - .verify(&proposal.signed_root()[..], domain, &block_proposer.pubkey), + .verify(&block.signed_root()[..], domain, &block_proposer.pubkey), Invalid::BadSignature ); @@ -130,21 +140,18 @@ pub fn verify_block_signature( /// Verifies the `randao_reveal` against the block's proposer pubkey and updates /// `state.latest_randao_mixes`. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn process_randao( state: &mut BeaconState, block: &BeaconBlock, spec: &ChainSpec, ) -> Result<(), Error> { - // Let `proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)]`. - let block_proposer = - &state.validator_registry[state.get_beacon_proposer_index(block.slot, spec)?]; + let block_proposer = &state.validator_registry + [state.get_beacon_proposer_index(block.slot, RelativeEpoch::Current, spec)?]; - // Verify that `bls_verify(pubkey=proposer.pubkey, - // message_hash=hash_tree_root(get_current_epoch(state)), signature=block.randao_reveal, - // domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_RANDAO))`. + // Verify the RANDAO is a valid signature of the proposer. verify!( - block.randao_reveal.verify( + block.body.randao_reveal.verify( &state.current_epoch(spec).hash_tree_root()[..], spec.get_domain( block.slot.epoch(spec.slots_per_epoch), @@ -156,21 +163,23 @@ pub fn process_randao( Invalid::BadRandaoSignature ); - // Update the state's RANDAO mix with the one revealed in the block. - update_randao(state, &block.randao_reveal, spec)?; + // Update the current epoch RANDAO mix. + state.update_randao_mix(state.current_epoch(spec), &block.body.randao_reveal, spec)?; Ok(()) } /// Update the `state.eth1_data_votes` based upon the `eth1_data` provided. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn process_eth1_data(state: &mut BeaconState, eth1_data: &Eth1Data) -> Result<(), Error> { - // Either increment the eth1_data vote count, or add a new eth1_data. + // Attempt to find a `Eth1DataVote` with matching `Eth1Data`. let matching_eth1_vote_index = state .eth1_data_votes .iter() .position(|vote| vote.eth1_data == *eth1_data); + + // If a vote exists, increment it's `vote_count`. Otherwise, create a new `Eth1DataVote`. if let Some(index) = matching_eth1_vote_index { state.eth1_data_votes[index].vote_count += 1; } else { @@ -183,46 +192,12 @@ pub fn process_eth1_data(state: &mut BeaconState, eth1_data: &Eth1Data) -> Resul Ok(()) } -/// Updates the present randao mix. -/// -/// Set `state.latest_randao_mixes[get_current_epoch(state) % LATEST_RANDAO_MIXES_LENGTH] = -/// xor(get_randao_mix(state, get_current_epoch(state)), hash(block.randao_reveal))`. -/// -/// Spec v0.4.0 -pub fn update_randao( - state: &mut BeaconState, - reveal: &Signature, - spec: &ChainSpec, -) -> Result<(), BeaconStateError> { - let hashed_reveal = { - let encoded_signature = ssz_encode(reveal); - Hash256::from_slice(&hash(&encoded_signature[..])[..]) - }; - - let current_epoch = state.slot.epoch(spec.slots_per_epoch); - - let current_mix = state - .get_randao_mix(current_epoch, spec) - .ok_or_else(|| BeaconStateError::InsufficientRandaoMixes)?; - - let new_mix = *current_mix ^ hashed_reveal; - - let index = current_epoch.as_usize() % spec.latest_randao_mixes_length; - - if index < state.latest_randao_mixes.len() { - state.latest_randao_mixes[index] = new_mix; - Ok(()) - } else { - Err(BeaconStateError::InsufficientRandaoMixes) - } -} - /// Validates each `ProposerSlashing` and updates the state, short-circuiting on an invalid object. /// /// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns /// an `Err` describing the invalid object or cause of failure. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn process_proposer_slashings( state: &mut BeaconState, proposer_slashings: &[ProposerSlashing], @@ -242,6 +217,7 @@ pub fn process_proposer_slashings( .map_err(|e| e.into_with_index(i)) })?; + // Update the state. for proposer_slashing in proposer_slashings { state.slash_validator(proposer_slashing.proposer_index as usize, spec)?; } @@ -254,7 +230,7 @@ pub fn process_proposer_slashings( /// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns /// an `Err` describing the invalid object or cause of failure. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn process_attester_slashings( state: &mut BeaconState, attester_slashings: &[AttesterSlashing], @@ -296,7 +272,7 @@ pub fn process_attester_slashings( ) .map_err(|e| e.into_with_index(i))?; - let slashable_indices = gather_attester_slashing_indices(&state, &attester_slashing) + let slashable_indices = gather_attester_slashing_indices(&state, &attester_slashing, spec) .map_err(|e| e.into_with_index(i))?; for i in slashable_indices { @@ -312,7 +288,7 @@ pub fn process_attester_slashings( /// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns /// an `Err` describing the invalid object or cause of failure. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn process_attestations( state: &mut BeaconState, attestations: &[Attestation], @@ -342,7 +318,14 @@ pub fn process_attestations( custody_bitfield: attestation.custody_bitfield.clone(), inclusion_slot: state.slot, }; - state.latest_attestations.push(pending_attestation); + + let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch); + + if attestation_epoch == state.current_epoch(spec) { + state.current_epoch_attestations.push(pending_attestation) + } else if attestation_epoch == state.previous_epoch(spec) { + state.previous_epoch_attestations.push(pending_attestation) + } } Ok(()) @@ -353,7 +336,7 @@ pub fn process_attestations( /// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns /// an `Err` describing the invalid object or cause of failure. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn process_deposits( state: &mut BeaconState, deposits: &[Deposit], @@ -423,7 +406,7 @@ pub fn process_deposits( /// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns /// an `Err` describing the invalid object or cause of failure. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn process_exits( state: &mut BeaconState, voluntary_exits: &[VoluntaryExit], @@ -455,7 +438,7 @@ pub fn process_exits( /// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns /// an `Err` describing the invalid object or cause of failure. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn process_transfers( state: &mut BeaconState, transfers: &[Transfer], diff --git a/eth2/state_processing/src/per_block_processing/errors.rs b/eth2/state_processing/src/per_block_processing/errors.rs index 8366a6584f..c0fe252de4 100644 --- a/eth2/state_processing/src/per_block_processing/errors.rs +++ b/eth2/state_processing/src/per_block_processing/errors.rs @@ -67,6 +67,7 @@ impl_from_beacon_state_error!(BlockProcessingError); #[derive(Debug, PartialEq)] pub enum BlockInvalid { StateSlotMismatch, + ParentBlockRootMismatch, BadSignature, BadRandaoSignature, MaxAttestationsExceeded, @@ -112,45 +113,53 @@ pub enum AttestationValidationError { #[derive(Debug, PartialEq)] pub enum AttestationInvalid { /// Attestation references a pre-genesis slot. - /// - /// (genesis_slot, attestation_slot) - PreGenesis(Slot, Slot), + PreGenesis { genesis: Slot, attestation: Slot }, /// Attestation included before the inclusion delay. - /// - /// (state_slot, inclusion_delay, attestation_slot) - IncludedTooEarly(Slot, u64, Slot), + IncludedTooEarly { + state: Slot, + delay: u64, + attestation: Slot, + }, /// Attestation slot is too far in the past to be included in a block. - /// - /// (state_slot, attestation_slot) - IncludedTooLate(Slot, Slot), + IncludedTooLate { state: Slot, attestation: Slot }, /// Attestation justified epoch does not match the states current or previous justified epoch. /// - /// (attestation_justified_epoch, state_epoch, used_previous_epoch) - WrongJustifiedEpoch(Epoch, Epoch, bool), + /// `is_current` is `true` if the attestation was compared to the + /// `state.current_justified_epoch`, `false` if compared to `state.previous_justified_epoch`. + WrongJustifiedEpoch { + state: Epoch, + attestation: Epoch, + is_current: bool, + }, /// Attestation justified epoch root does not match root known to the state. /// - /// (state_justified_root, attestation_justified_root) - WrongJustifiedRoot(Hash256, Hash256), + /// `is_current` is `true` if the attestation was compared to the + /// `state.current_justified_epoch`, `false` if compared to `state.previous_justified_epoch`. + WrongJustifiedRoot { + state: Hash256, + attestation: Hash256, + is_current: bool, + }, /// Attestation crosslink root does not match the state crosslink root for the attestations /// slot. - BadLatestCrosslinkRoot, + BadPreviousCrosslink, /// The custody bitfield has some bits set `true`. This is not allowed in phase 0. CustodyBitfieldHasSetBits, /// There are no set bits on the attestation -- an attestation must be signed by at least one /// validator. AggregationBitfieldIsEmpty, /// The custody bitfield length is not the smallest possible size to represent the committee. - /// - /// (committee_len, bitfield_len) - BadCustodyBitfieldLength(usize, usize), + BadCustodyBitfieldLength { + committee_len: usize, + bitfield_len: usize, + }, /// The aggregation bitfield length is not the smallest possible size to represent the committee. - /// - /// (committee_len, bitfield_len) - BadAggregationBitfieldLength(usize, usize), - /// There was no known committee for the given shard in the given slot. - /// - /// (attestation_data_shard, attestation_data_slot) - NoCommitteeForShard(u64, Slot), + BadAggregationBitfieldLength { + committee_len: usize, + bitfield_len: usize, + }, + /// There was no known committee in this `epoch` for the given shard and slot. + NoCommitteeForShard { shard: u64, slot: Slot }, /// The validator index was unknown. UnknownValidator(u64), /// The attestation signature verification failed. @@ -188,6 +197,8 @@ pub enum AttesterSlashingInvalid { SlashableAttestation2Invalid(SlashableAttestationInvalid), /// The validator index is unknown. One cannot slash one who does not exist. UnknownValidator(u64), + /// The specified validator has already been withdrawn. + ValidatorAlreadyWithdrawn(u64), /// There were no indices able to be slashed. NoSlashableIndices, } @@ -264,16 +275,12 @@ pub enum ProposerSlashingInvalid { /// /// (proposal_1_slot, proposal_2_slot) ProposalSlotMismatch(Slot, Slot), - /// The two proposal have different shards. - /// - /// (proposal_1_shard, proposal_2_shard) - ProposalShardMismatch(u64, u64), - /// The two proposal have different block roots. - /// - /// (proposal_1_root, proposal_2_root) - ProposalBlockRootMismatch(Hash256, Hash256), + /// The proposals are identical and therefore not slashable. + ProposalsIdentical, /// The specified proposer has already been slashed. ProposerAlreadySlashed, + /// The specified proposer has already been withdrawn. + ProposerAlreadyWithdrawn(u64), /// The first proposal signature was invalid. BadProposal1Signature, /// The second proposal signature was invalid. @@ -302,9 +309,7 @@ pub enum DepositValidationError { #[derive(Debug, PartialEq)] pub enum DepositInvalid { /// The deposit index does not match the state index. - /// - /// (state_index, deposit_index) - BadIndex(u64, u64), + BadIndex { state: u64, deposit: u64 }, /// The proof-of-possession does not match the given pubkey. BadProofOfPossession, /// The withdrawal credentials for the depositing validator did not match the withdrawal @@ -334,11 +339,14 @@ pub enum ExitValidationError { pub enum ExitInvalid { /// The specified validator is not in the state's validator registry. ValidatorUnknown(u64), - AlreadyExited, + /// The specified validator has a non-maximum exit epoch. + AlreadyExited(u64), + /// The specified validator has already initiated exit. + AlreadyInitiatedExited(u64), /// The exit is for a future epoch. - /// - /// (state_epoch, exit_epoch) - FutureEpoch(Epoch, Epoch), + FutureEpoch { state: Epoch, exit: Epoch }, + /// The validator has not been active for long enough. + TooYoungToLeave { lifespan: Epoch, expected: u64 }, /// The exit signature was not signed by the validator. BadSignature, } diff --git a/eth2/state_processing/src/per_block_processing/validate_attestation.rs b/eth2/state_processing/src/per_block_processing/validate_attestation.rs index b153608504..9d13214077 100644 --- a/eth2/state_processing/src/per_block_processing/validate_attestation.rs +++ b/eth2/state_processing/src/per_block_processing/validate_attestation.rs @@ -8,7 +8,7 @@ use types::*; /// /// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn validate_attestation( state: &BeaconState, attestation: &Attestation, @@ -22,7 +22,7 @@ pub fn validate_attestation( /// /// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn validate_attestation_without_signature( state: &BeaconState, attestation: &Attestation, @@ -35,74 +35,83 @@ pub fn validate_attestation_without_signature( /// given state, optionally validating the aggregate signature. /// /// -/// Spec v0.4.0 +/// Spec v0.5.0 fn validate_attestation_signature_optional( state: &BeaconState, attestation: &Attestation, spec: &ChainSpec, verify_signature: bool, ) -> Result<(), Error> { - // Verify that `attestation.data.slot >= GENESIS_SLOT`. + let state_epoch = state.slot.epoch(spec.slots_per_epoch); + let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch); + + // Can't submit pre-historic attestations. verify!( attestation.data.slot >= spec.genesis_slot, - Invalid::PreGenesis(spec.genesis_slot, attestation.data.slot) + Invalid::PreGenesis { + genesis: spec.genesis_slot, + attestation: attestation.data.slot + } ); - // Verify that `attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot`. + // Can't submit attestations too far in history. + verify!( + state.slot <= attestation.data.slot + spec.slots_per_epoch, + Invalid::IncludedTooLate { + state: spec.genesis_slot, + attestation: attestation.data.slot + } + ); + + // Can't submit attestation too quickly. verify!( attestation.data.slot + spec.min_attestation_inclusion_delay <= state.slot, - Invalid::IncludedTooEarly( - state.slot, - spec.min_attestation_inclusion_delay, - attestation.data.slot - ) + Invalid::IncludedTooEarly { + state: state.slot, + delay: spec.min_attestation_inclusion_delay, + attestation: attestation.data.slot + } ); - // Verify that `state.slot < attestation.data.slot + SLOTS_PER_EPOCH`. - verify!( - state.slot < attestation.data.slot + spec.slots_per_epoch, - Invalid::IncludedTooLate(state.slot, attestation.data.slot) - ); - - // Verify that `attestation.data.justified_epoch` is equal to `state.justified_epoch` if - // `slot_to_epoch(attestation.data.slot + 1) >= get_current_epoch(state) else - // state.previous_justified_epoch`. - if (attestation.data.slot + 1).epoch(spec.slots_per_epoch) >= state.current_epoch(spec) { + // Verify the justified epoch and root is correct. + if attestation_epoch >= state_epoch { verify!( - attestation.data.justified_epoch == state.justified_epoch, - Invalid::WrongJustifiedEpoch( - attestation.data.justified_epoch, - state.justified_epoch, - false - ) + attestation.data.source_epoch == state.current_justified_epoch, + Invalid::WrongJustifiedEpoch { + state: state.current_justified_epoch, + attestation: attestation.data.source_epoch, + is_current: true, + } + ); + verify!( + attestation.data.source_root == state.current_justified_root, + Invalid::WrongJustifiedRoot { + state: state.current_justified_root, + attestation: attestation.data.source_root, + is_current: true, + } ); } else { verify!( - attestation.data.justified_epoch == state.previous_justified_epoch, - Invalid::WrongJustifiedEpoch( - attestation.data.justified_epoch, - state.previous_justified_epoch, - true - ) + attestation.data.source_epoch == state.previous_justified_epoch, + Invalid::WrongJustifiedEpoch { + state: state.previous_justified_epoch, + attestation: attestation.data.source_epoch, + is_current: false, + } + ); + verify!( + attestation.data.source_root == state.previous_justified_root, + Invalid::WrongJustifiedRoot { + state: state.previous_justified_root, + attestation: attestation.data.source_root, + is_current: true, + } ); } - // Verify that `attestation.data.justified_block_root` is equal to `get_block_root(state, - // get_epoch_start_slot(attestation.data.justified_epoch))`. - let justified_block_root = *state - .get_block_root( - attestation - .data - .justified_epoch - .start_slot(spec.slots_per_epoch), - &spec, - ) - .ok_or(BeaconStateError::InsufficientBlockRoots)?; - verify!( - attestation.data.justified_block_root == justified_block_root, - Invalid::WrongJustifiedRoot(justified_block_root, attestation.data.justified_block_root) - ); - + // Check that the crosslink data is valid. + // // Verify that either: // // (i)`state.latest_crosslinks[attestation.data.shard] == attestation.data.latest_crosslink`, @@ -115,46 +124,59 @@ fn validate_attestation_signature_optional( epoch: attestation.data.slot.epoch(spec.slots_per_epoch), }; verify!( - (attestation.data.latest_crosslink + (attestation.data.previous_crosslink == state.latest_crosslinks[attestation.data.shard as usize]) | (state.latest_crosslinks[attestation.data.shard as usize] == potential_crosslink), - Invalid::BadLatestCrosslinkRoot + Invalid::BadPreviousCrosslink ); - // Get the committee for this attestation - let (committee, _shard) = state - .get_crosslink_committees_at_slot(attestation.data.slot, spec)? - .iter() - .find(|(_committee, shard)| *shard == attestation.data.shard) - .ok_or_else(|| { - Error::Invalid(Invalid::NoCommitteeForShard( - attestation.data.shard, - attestation.data.slot, - )) - })?; - - // Custody bitfield is all zeros (phase 0 requirement). - verify!( - attestation.custody_bitfield.num_set_bits() == 0, - Invalid::CustodyBitfieldHasSetBits - ); - // Custody bitfield length is correct. - verify!( - verify_bitfield_length(&attestation.custody_bitfield, committee.len()), - Invalid::BadCustodyBitfieldLength(committee.len(), attestation.custody_bitfield.len()) - ); - // Aggregation bitfield isn't empty. + // Attestation must be non-empty! verify!( attestation.aggregation_bitfield.num_set_bits() != 0, Invalid::AggregationBitfieldIsEmpty ); + // Custody bitfield must be empty (be be removed in phase 1) + verify!( + attestation.custody_bitfield.num_set_bits() == 0, + Invalid::CustodyBitfieldHasSetBits + ); + + // Get the committee for the specific shard that this attestation is for. + let crosslink_committee = state + .get_crosslink_committees_at_slot( + attestation.data.slot, + RelativeEpoch::NextWithoutRegistryChange, + spec, + )? + .iter() + .find(|c| c.shard == attestation.data.shard) + .ok_or_else(|| { + Error::Invalid(Invalid::NoCommitteeForShard { + shard: attestation.data.shard, + slot: attestation.data.slot, + }) + })?; + let committee = &crosslink_committee.committee; + + // Custody bitfield length is correct. + // + // This is not directly in the spec, but it is inferred. + verify!( + verify_bitfield_length(&attestation.custody_bitfield, committee.len()), + Invalid::BadCustodyBitfieldLength { + committee_len: committee.len(), + bitfield_len: attestation.custody_bitfield.len() + } + ); // Aggregation bitfield length is correct. + // + // This is not directly in the spec, but it is inferred. verify!( verify_bitfield_length(&attestation.aggregation_bitfield, committee.len()), - Invalid::BadAggregationBitfieldLength( - committee.len(), - attestation.aggregation_bitfield.len() - ) + Invalid::BadAggregationBitfieldLength { + committee_len: committee.len(), + bitfield_len: attestation.custody_bitfield.len() + } ); if verify_signature { @@ -171,7 +193,7 @@ fn validate_attestation_signature_optional( )?; } - // [TO BE REMOVED IN PHASE 1] Verify that `attestation.data.crosslink_data_root == ZERO_HASH`. + // Crosslink data root is zero (to be removed in phase 1). verify!( attestation.data.crosslink_data_root == spec.zero_hash, Invalid::ShardBlockRootNotZero @@ -188,7 +210,7 @@ fn validate_attestation_signature_optional( /// - `custody_bitfield` does not have a bit for each index of `committee`. /// - A `validator_index` in `committee` is not in `state.validator_registry`. /// -/// Spec v0.4.0 +/// Spec v0.5.0 fn verify_attestation_signature( state: &BeaconState, committee: &[usize], @@ -204,10 +226,10 @@ fn verify_attestation_signature( for (i, v) in committee.iter().enumerate() { let validator_signed = aggregation_bitfield.get(i).map_err(|_| { - Error::Invalid(Invalid::BadAggregationBitfieldLength( - committee.len(), - aggregation_bitfield.len(), - )) + Error::Invalid(Invalid::BadAggregationBitfieldLength { + committee_len: committee.len(), + bitfield_len: aggregation_bitfield.len(), + }) })?; if validator_signed { @@ -215,10 +237,10 @@ fn verify_attestation_signature( Ok(bit) => bit, // Invalidate signature if custody_bitfield.len() < committee Err(_) => { - return Err(Error::Invalid(Invalid::BadCustodyBitfieldLength( - committee.len(), - custody_bitfield.len(), - ))); + return Err(Error::Invalid(Invalid::BadCustodyBitfieldLength { + committee_len: committee.len(), + bitfield_len: aggregation_bitfield.len(), + })); } }; diff --git a/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs b/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs index d126849b63..a198d2a3e2 100644 --- a/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs +++ b/eth2/state_processing/src/per_block_processing/verify_attester_slashing.rs @@ -7,7 +7,7 @@ use types::*; /// /// Returns `Ok(())` if the `AttesterSlashing` is valid, otherwise indicates the reason for invalidity. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn verify_attester_slashing( state: &BeaconState, attester_slashing: &AttesterSlashing, @@ -41,15 +41,16 @@ pub fn verify_attester_slashing( /// /// Returns Ok(indices) if `indices.len() > 0`. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn gather_attester_slashing_indices( state: &BeaconState, attester_slashing: &AttesterSlashing, + spec: &ChainSpec, ) -> Result, Error> { let slashable_attestation_1 = &attester_slashing.slashable_attestation_1; let slashable_attestation_2 = &attester_slashing.slashable_attestation_2; - let mut slashable_indices = vec![]; + let mut slashable_indices = Vec::with_capacity(spec.max_indices_per_slashable_vote); for i in &slashable_attestation_1.validator_indices { let validator = state .validator_registry @@ -57,11 +58,20 @@ pub fn gather_attester_slashing_indices( .ok_or_else(|| Error::Invalid(Invalid::UnknownValidator(*i)))?; if slashable_attestation_2.validator_indices.contains(&i) & !validator.slashed { + // TODO: verify that we should reject any slashable attestation which includes a + // withdrawn validator. PH has asked the question on gitter, awaiting response. + verify!( + validator.withdrawable_epoch > state.slot.epoch(spec.slots_per_epoch), + Invalid::ValidatorAlreadyWithdrawn(*i) + ); + slashable_indices.push(*i); } } verify!(!slashable_indices.is_empty(), Invalid::NoSlashableIndices); + slashable_indices.shrink_to_fit(); + Ok(slashable_indices) } diff --git a/eth2/state_processing/src/per_block_processing/verify_deposit.rs b/eth2/state_processing/src/per_block_processing/verify_deposit.rs index aad38f616c..2aeab6c5a6 100644 --- a/eth2/state_processing/src/per_block_processing/verify_deposit.rs +++ b/eth2/state_processing/src/per_block_processing/verify_deposit.rs @@ -18,7 +18,7 @@ pub type PublicKeyValidatorIndexHashmap = HashMap; /// /// Note: this function is incomplete. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn verify_deposit( state: &BeaconState, deposit: &Deposit, @@ -49,26 +49,25 @@ pub fn verify_deposit( /// Verify that the `Deposit` index is correct. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn verify_deposit_index(state: &BeaconState, deposit: &Deposit) -> Result<(), Error> { verify!( deposit.index == state.deposit_index, - Invalid::BadIndex(state.deposit_index, deposit.index) + Invalid::BadIndex { + state: state.deposit_index, + deposit: deposit.index + } ); Ok(()) } -pub fn build_public_key_hashmap(state: &BeaconState) -> PublicKeyValidatorIndexHashmap { - let mut hashmap = HashMap::with_capacity(state.validator_registry.len()); - - for (i, validator) in state.validator_registry.iter().enumerate() { - hashmap.insert(validator.pubkey.clone(), i as u64); - } - - hashmap -} - +/// Returns a `Some(validator index)` if a pubkey already exists in the `validator_registry`, +/// otherwise returns `None`. +/// +/// ## Errors +/// +/// Errors if the state's `pubkey_cache` is not current. pub fn get_existing_validator_index( state: &BeaconState, deposit: &Deposit, @@ -94,12 +93,12 @@ pub fn get_existing_validator_index( /// Verify that a deposit is included in the state's eth1 deposit root. /// -/// Spec v0.4.0 +/// Spec v0.5.0 fn verify_deposit_merkle_proof(state: &BeaconState, deposit: &Deposit, spec: &ChainSpec) -> bool { let leaf = hash(&get_serialized_deposit_data(deposit)); verify_merkle_proof( Hash256::from_slice(&leaf), - &deposit.branch, + &deposit.proof, spec.deposit_contract_tree_depth as usize, deposit.index as usize, state.latest_eth1_data.deposit_root, @@ -108,7 +107,7 @@ fn verify_deposit_merkle_proof(state: &BeaconState, deposit: &Deposit, spec: &Ch /// Helper struct for easily getting the serialized data generated by the deposit contract. /// -/// Spec v0.4.0 +/// Spec v0.5.0 #[derive(Encode)] struct SerializedDepositData { amount: u64, @@ -119,7 +118,7 @@ struct SerializedDepositData { /// Return the serialized data generated by the deposit contract that is used to generate the /// merkle proof. /// -/// Spec v0.4.0 +/// Spec v0.5.0 fn get_serialized_deposit_data(deposit: &Deposit) -> Vec { let serialized_deposit_data = SerializedDepositData { amount: deposit.deposit_data.amount, diff --git a/eth2/state_processing/src/per_block_processing/verify_exit.rs b/eth2/state_processing/src/per_block_processing/verify_exit.rs index 8cd54fb690..7893cea966 100644 --- a/eth2/state_processing/src/per_block_processing/verify_exit.rs +++ b/eth2/state_processing/src/per_block_processing/verify_exit.rs @@ -7,7 +7,7 @@ use types::*; /// /// Returns `Ok(())` if the `Exit` is valid, otherwise indicates the reason for invalidity. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn verify_exit( state: &BeaconState, exit: &VoluntaryExit, @@ -18,15 +18,35 @@ pub fn verify_exit( .get(exit.validator_index as usize) .ok_or_else(|| Error::Invalid(Invalid::ValidatorUnknown(exit.validator_index)))?; + // Verify that the validator has not yet exited. verify!( - validator.exit_epoch - > state.get_delayed_activation_exit_epoch(state.current_epoch(spec), spec), - Invalid::AlreadyExited + validator.exit_epoch == spec.far_future_epoch, + Invalid::AlreadyExited(exit.validator_index) ); + // Verify that the validator has not yet initiated. + verify!( + !validator.initiated_exit, + Invalid::AlreadyInitiatedExited(exit.validator_index) + ); + + // Exits must specify an epoch when they become valid; they are not valid before then. verify!( state.current_epoch(spec) >= exit.epoch, - Invalid::FutureEpoch(state.current_epoch(spec), exit.epoch) + Invalid::FutureEpoch { + state: state.current_epoch(spec), + exit: exit.epoch + } + ); + + // Must have been in the validator set long enough. + let lifespan = state.slot.epoch(spec.slots_per_epoch) - validator.activation_epoch; + verify!( + lifespan >= spec.persistent_committee_period, + Invalid::TooYoungToLeave { + lifespan, + expected: spec.persistent_committee_period, + } ); let message = exit.signed_root(); diff --git a/eth2/state_processing/src/per_block_processing/verify_proposer_slashing.rs b/eth2/state_processing/src/per_block_processing/verify_proposer_slashing.rs index c3c0079a96..dffb9d898c 100644 --- a/eth2/state_processing/src/per_block_processing/verify_proposer_slashing.rs +++ b/eth2/state_processing/src/per_block_processing/verify_proposer_slashing.rs @@ -7,7 +7,7 @@ use types::*; /// /// Returns `Ok(())` if the `ProposerSlashing` is valid, otherwise indicates the reason for invalidity. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn verify_proposer_slashing( proposer_slashing: &ProposerSlashing, state: &BeaconState, @@ -21,34 +21,28 @@ pub fn verify_proposer_slashing( })?; verify!( - proposer_slashing.proposal_1.slot == proposer_slashing.proposal_2.slot, + proposer_slashing.header_1.slot == proposer_slashing.header_2.slot, Invalid::ProposalSlotMismatch( - proposer_slashing.proposal_1.slot, - proposer_slashing.proposal_2.slot + proposer_slashing.header_1.slot, + proposer_slashing.header_2.slot ) ); verify!( - proposer_slashing.proposal_1.shard == proposer_slashing.proposal_2.shard, - Invalid::ProposalShardMismatch( - proposer_slashing.proposal_1.shard, - proposer_slashing.proposal_2.shard - ) - ); - - verify!( - proposer_slashing.proposal_1.block_root != proposer_slashing.proposal_2.block_root, - Invalid::ProposalBlockRootMismatch( - proposer_slashing.proposal_1.block_root, - proposer_slashing.proposal_2.block_root - ) + proposer_slashing.header_1 != proposer_slashing.header_2, + Invalid::ProposalsIdentical ); verify!(!proposer.slashed, Invalid::ProposerAlreadySlashed); verify!( - verify_proposal_signature( - &proposer_slashing.proposal_1, + proposer.withdrawable_epoch > state.slot.epoch(spec.slots_per_epoch), + Invalid::ProposerAlreadyWithdrawn(proposer_slashing.proposer_index) + ); + + verify!( + verify_header_signature( + &proposer_slashing.header_1, &proposer.pubkey, &state.fork, spec @@ -56,8 +50,8 @@ pub fn verify_proposer_slashing( Invalid::BadProposal1Signature ); verify!( - verify_proposal_signature( - &proposer_slashing.proposal_2, + verify_header_signature( + &proposer_slashing.header_2, &proposer.pubkey, &state.fork, spec @@ -71,17 +65,19 @@ pub fn verify_proposer_slashing( /// Verifies the signature of a proposal. /// /// Returns `true` if the signature is valid. -fn verify_proposal_signature( - proposal: &Proposal, +/// +/// Spec v0.5.0 +fn verify_header_signature( + header: &BeaconBlockHeader, pubkey: &PublicKey, fork: &Fork, spec: &ChainSpec, ) -> bool { - let message = proposal.signed_root(); + let message = header.signed_root(); let domain = spec.get_domain( - proposal.slot.epoch(spec.slots_per_epoch), - Domain::Proposal, + header.slot.epoch(spec.slots_per_epoch), + Domain::BeaconBlock, fork, ); - proposal.signature.verify(&message[..], domain, pubkey) + header.signature.verify(&message[..], domain, pubkey) } diff --git a/eth2/state_processing/src/per_block_processing/verify_transfer.rs b/eth2/state_processing/src/per_block_processing/verify_transfer.rs index 4746fc75cc..546760fd09 100644 --- a/eth2/state_processing/src/per_block_processing/verify_transfer.rs +++ b/eth2/state_processing/src/per_block_processing/verify_transfer.rs @@ -10,16 +10,16 @@ use types::*; /// /// Note: this function is incomplete. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn verify_transfer( state: &BeaconState, transfer: &Transfer, spec: &ChainSpec, ) -> Result<(), Error> { - let from_balance = *state + let sender_balance = *state .validator_balances - .get(transfer.from as usize) - .ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.from)))?; + .get(transfer.sender as usize) + .ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?; let total_amount = transfer .amount @@ -27,19 +27,22 @@ pub fn verify_transfer( .ok_or_else(|| Error::Invalid(Invalid::FeeOverflow(transfer.amount, transfer.fee)))?; verify!( - from_balance >= transfer.amount, - Invalid::FromBalanceInsufficient(transfer.amount, from_balance) + sender_balance >= transfer.amount, + Invalid::FromBalanceInsufficient(transfer.amount, sender_balance) ); verify!( - from_balance >= transfer.fee, - Invalid::FromBalanceInsufficient(transfer.fee, from_balance) + sender_balance >= transfer.fee, + Invalid::FromBalanceInsufficient(transfer.fee, sender_balance) ); verify!( - (from_balance == total_amount) - || (from_balance >= (total_amount + spec.min_deposit_amount)), - Invalid::InvalidResultingFromBalance(from_balance - total_amount, spec.min_deposit_amount) + (sender_balance == total_amount) + || (sender_balance >= (total_amount + spec.min_deposit_amount)), + Invalid::InvalidResultingFromBalance( + sender_balance - total_amount, + spec.min_deposit_amount + ) ); verify!( @@ -47,25 +50,25 @@ pub fn verify_transfer( Invalid::StateSlotMismatch(state.slot, transfer.slot) ); - let from_validator = state + let sender_validator = state .validator_registry - .get(transfer.from as usize) - .ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.from)))?; + .get(transfer.sender as usize) + .ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?; let epoch = state.slot.epoch(spec.slots_per_epoch); verify!( - from_validator.is_withdrawable_at(epoch) - || from_validator.activation_epoch == spec.far_future_epoch, - Invalid::FromValidatorIneligableForTransfer(transfer.from) + sender_validator.is_withdrawable_at(epoch) + || sender_validator.activation_epoch == spec.far_future_epoch, + Invalid::FromValidatorIneligableForTransfer(transfer.sender) ); let transfer_withdrawal_credentials = Hash256::from_slice( &get_withdrawal_credentials(&transfer.pubkey, spec.bls_withdrawal_prefix_byte)[..], ); verify!( - from_validator.withdrawal_credentials == transfer_withdrawal_credentials, + sender_validator.withdrawal_credentials == transfer_withdrawal_credentials, Invalid::WithdrawalCredentialsMismatch( - from_validator.withdrawal_credentials, + sender_validator.withdrawal_credentials, transfer_withdrawal_credentials ) ); @@ -97,16 +100,17 @@ pub fn execute_transfer( transfer: &Transfer, spec: &ChainSpec, ) -> Result<(), Error> { - let from_balance = *state + let sender_balance = *state .validator_balances - .get(transfer.from as usize) - .ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.from)))?; - let to_balance = *state + .get(transfer.sender as usize) + .ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?; + let recipient_balance = *state .validator_balances - .get(transfer.to as usize) - .ok_or_else(|| Error::Invalid(Invalid::ToValidatorUnknown(transfer.to)))?; + .get(transfer.recipient as usize) + .ok_or_else(|| Error::Invalid(Invalid::ToValidatorUnknown(transfer.recipient)))?; - let proposer_index = state.get_beacon_proposer_index(state.slot, spec)?; + let proposer_index = + state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)?; let proposer_balance = state.validator_balances[proposer_index]; let total_amount = transfer @@ -114,14 +118,22 @@ pub fn execute_transfer( .checked_add(transfer.fee) .ok_or_else(|| Error::Invalid(Invalid::FeeOverflow(transfer.amount, transfer.fee)))?; - state.validator_balances[transfer.from as usize] = - from_balance.checked_sub(total_amount).ok_or_else(|| { - Error::Invalid(Invalid::FromBalanceInsufficient(total_amount, from_balance)) + state.validator_balances[transfer.sender as usize] = + sender_balance.checked_sub(total_amount).ok_or_else(|| { + Error::Invalid(Invalid::FromBalanceInsufficient( + total_amount, + sender_balance, + )) })?; - state.validator_balances[transfer.to as usize] = to_balance + state.validator_balances[transfer.recipient as usize] = recipient_balance .checked_add(transfer.amount) - .ok_or_else(|| Error::Invalid(Invalid::ToBalanceOverflow(to_balance, transfer.amount)))?; + .ok_or_else(|| { + Error::Invalid(Invalid::ToBalanceOverflow( + recipient_balance, + transfer.amount, + )) + })?; state.validator_balances[proposer_index] = proposer_balance.checked_add(transfer.fee).ok_or_else(|| { diff --git a/eth2/state_processing/src/per_slot_processing.rs b/eth2/state_processing/src/per_slot_processing.rs index 0bb405c986..aafc7166ad 100644 --- a/eth2/state_processing/src/per_slot_processing.rs +++ b/eth2/state_processing/src/per_slot_processing.rs @@ -9,7 +9,7 @@ pub enum Error { /// Advances a state forward by one slot, performing per-epoch processing if required. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn per_slot_processing( state: &mut BeaconState, previous_block_root: Hash256, @@ -22,29 +22,9 @@ pub fn per_slot_processing( state.slot += 1; - update_block_roots(state, previous_block_root, spec); - Ok(()) } -/// Updates the state's block roots as per-slot processing is performed. -/// -/// Spec v0.4.0 -pub fn update_block_roots(state: &mut BeaconState, previous_block_root: Hash256, spec: &ChainSpec) { - state.latest_block_roots[(state.slot.as_usize() - 1) % spec.latest_block_roots_length] = - previous_block_root; - - if state.slot.as_usize() % spec.latest_block_roots_length == 0 { - let root = merkle_root(&state.latest_block_roots[..]); - state.batched_block_roots.push(root); - } -} - -fn merkle_root(_input: &[Hash256]) -> Hash256 { - // TODO: implement correctly. - Hash256::zero() -} - impl From for Error { fn from(e: BeaconStateError) -> Error { Error::BeaconStateError(e) diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 32f8204e38..7c77a5a3e5 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -2,11 +2,11 @@ use self::epoch_cache::EpochCache; use crate::test_utils::TestRandom; use crate::{validator_registry::get_active_validator_indices, *}; use int_to_bytes::int_to_bytes32; -use log::{debug, trace}; +use log::trace; use pubkey_cache::PubkeyCache; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; -use ssz::{hash, SignedRoot, TreeHash}; +use ssz::{hash, ssz_encode, SignedRoot, TreeHash}; use ssz_derive::{Decode, Encode, TreeHash}; use std::collections::HashMap; use test_random_derive::TestRandom; @@ -31,12 +31,14 @@ pub enum Error { UnableToShuffle, UnknownValidator, InvalidBitfield, + ValidatorIsWithdrawable, InsufficientRandaoMixes, InsufficientValidators, InsufficientBlockRoots, InsufficientIndexRoots, InsufficientAttestations, InsufficientCommittees, + InsufficientSlashedBalances, EpochCacheUninitialized(RelativeEpoch), PubkeyCacheInconsistent, PubkeyCacheIncomplete { @@ -377,10 +379,37 @@ impl BeaconState { } } + /// XOR-assigns the existing `epoch` randao mix with the hash of the `signature`. + /// + /// # Errors: + /// + /// See `Self::get_randao_mix`. + /// + /// Spec v0.5.0 + pub fn update_randao_mix( + &mut self, + epoch: Epoch, + signature: &Signature, + spec: &ChainSpec, + ) -> Result<(), Error> { + let i = epoch.as_usize() % spec.latest_randao_mixes_length; + + let signature_hash = Hash256::from_slice(&hash(&ssz_encode(signature))); + + self.latest_randao_mixes[i] = *self.get_randao_mix(epoch, spec)? ^ signature_hash; + + Ok(()) + } + /// Return the randao mix at a recent ``epoch``. /// - /// Spec v0.4.0 - pub fn get_randao_mix(&self, epoch: Epoch, spec: &ChainSpec) -> Option<&Hash256> { + /// # Errors: + /// - `InsufficientRandaoMixes` if `self.latest_randao_mixes` is shorter than + /// `spec.latest_randao_mixes_length`. + /// - `EpochOutOfBounds` if the state no longer stores randao mixes for the given `epoch`. + /// + /// Spec v0.5.0 + pub fn get_randao_mix(&self, epoch: Epoch, spec: &ChainSpec) -> Result<&Hash256, Error> { let current_epoch = self.current_epoch(spec); if (current_epoch - (spec.latest_randao_mixes_length as u64) < epoch) @@ -388,8 +417,9 @@ impl BeaconState { { self.latest_randao_mixes .get(epoch.as_usize() % spec.latest_randao_mixes_length) + .ok_or_else(|| Error::InsufficientRandaoMixes) } else { - None + Err(Error::EpochOutOfBounds) } } @@ -418,8 +448,7 @@ impl BeaconState { /// Spec v0.4.0 pub fn generate_seed(&self, epoch: Epoch, spec: &ChainSpec) -> Result { let mut input = self - .get_randao_mix(epoch - spec.min_seed_lookahead, spec) - .ok_or_else(|| Error::InsufficientRandaoMixes)? + .get_randao_mix(epoch - spec.min_seed_lookahead, spec)? .as_bytes() .to_vec(); @@ -601,7 +630,7 @@ impl BeaconState { /// Initiate an exit for the validator of the given `index`. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn initiate_validator_exit(&mut self, validator_index: usize) { self.validator_registry[validator_index].initiated_exit = true; } @@ -622,7 +651,7 @@ impl BeaconState { /// Slash the validator with index ``index``. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn slash_validator( &mut self, validator_index: usize, @@ -634,26 +663,27 @@ impl BeaconState { .validator_registry .get(validator_index) .ok_or_else(|| Error::UnknownValidator)?; + let effective_balance = self.get_effective_balance(validator_index, spec)?; + // A validator that is withdrawn cannot be slashed. + // + // This constraint will be lifted in Phase 0. if self.slot >= validator .withdrawable_epoch .start_slot(spec.slots_per_epoch) { - return Err(Error::SlotOutOfBounds); + return Err(Error::ValidatorIsWithdrawable); } self.exit_validator(validator_index, spec); - let effective_balance = self.get_effective_balance(validator_index, spec)?; - - self.latest_slashed_balances[current_epoch.as_usize() % spec.latest_slashed_exit_length] += - effective_balance; + self.increment_current_epoch_slashed_balances(effective_balance, spec)?; let whistleblower_index = self.get_beacon_proposer_index(self.slot, RelativeEpoch::Current, spec)?; + let whistleblower_reward = effective_balance / spec.whistleblower_reward_quotient; - let whistleblower_reward = effective_balance; safe_add_assign!( self.validator_balances[whistleblower_index as usize], whistleblower_reward @@ -662,14 +692,31 @@ impl BeaconState { self.validator_balances[validator_index], whistleblower_reward ); + self.validator_registry[validator_index].slashed = true; + self.validator_registry[validator_index].withdrawable_epoch = current_epoch + Epoch::from(spec.latest_slashed_exit_length); - debug!( - "Whistleblower {} penalized validator {}.", - whistleblower_index, validator_index - ); + Ok(()) + } + + /// Increment `self.latest_slashed_balances` with a slashing from the current epoch. + /// + /// Spec v0.5.0. + fn increment_current_epoch_slashed_balances( + &mut self, + increment: u64, + spec: &ChainSpec, + ) -> Result<(), Error> { + let current_epoch = self.current_epoch(spec); + + let slashed_balances_index = current_epoch.as_usize() % spec.latest_slashed_exit_length; + if slashed_balances_index >= self.latest_slashed_balances.len() { + return Err(Error::InsufficientSlashedBalances); + } + + self.latest_slashed_balances[slashed_balances_index] += increment; Ok(()) } diff --git a/eth2/types/src/chain_spec.rs b/eth2/types/src/chain_spec.rs index f4b113056f..e9ade2c913 100644 --- a/eth2/types/src/chain_spec.rs +++ b/eth2/types/src/chain_spec.rs @@ -29,7 +29,7 @@ pub struct ChainSpec { pub shard_count: u64, pub target_committee_size: u64, pub max_balance_churn_quotient: u64, - pub max_indices_per_slashable_vote: u64, + pub max_indices_per_slashable_vote: usize, pub max_exit_dequeues_per_epoch: u64, pub shuffle_round_count: u8, diff --git a/eth2/types/src/proposer_slashing.rs b/eth2/types/src/proposer_slashing.rs index 881f0e4059..02216a2fc6 100644 --- a/eth2/types/src/proposer_slashing.rs +++ b/eth2/types/src/proposer_slashing.rs @@ -11,8 +11,8 @@ use test_random_derive::TestRandom; #[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom)] pub struct ProposerSlashing { pub proposer_index: u64, - pub proposal_1: BeaconBlockHeader, - pub proposal_2: BeaconBlockHeader, + pub header_1: BeaconBlockHeader, + pub header_2: BeaconBlockHeader, } #[cfg(test)] diff --git a/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs b/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs index 0773cd6da4..2cfebd9153 100644 --- a/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs +++ b/eth2/types/src/test_utils/testing_proposer_slashing_builder.rs @@ -25,7 +25,7 @@ impl TestingProposerSlashingBuilder { let hash_1 = Hash256::from([1; 32]); let hash_2 = Hash256::from([2; 32]); - let mut proposal_1 = BeaconBlockHeader { + let mut header_1 = BeaconBlockHeader { slot, previous_block_root: hash_1, state_root: hash_1, @@ -33,27 +33,27 @@ impl TestingProposerSlashingBuilder { signature: Signature::empty_signature(), }; - let mut proposal_2 = BeaconBlockHeader { + let mut header_2 = BeaconBlockHeader { previous_block_root: hash_2, - ..proposal_1.clone() + ..header_1.clone() }; - proposal_1.signature = { - let message = proposal_1.signed_root(); + header_1.signature = { + let message = header_1.signed_root(); let epoch = slot.epoch(spec.slots_per_epoch); signer(proposer_index, &message[..], epoch, Domain::BeaconBlock) }; - proposal_2.signature = { - let message = proposal_2.signed_root(); + header_2.signature = { + let message = header_2.signed_root(); let epoch = slot.epoch(spec.slots_per_epoch); signer(proposer_index, &message[..], epoch, Domain::BeaconBlock) }; ProposerSlashing { proposer_index, - proposal_1, - proposal_2, + header_1, + header_2, } } } From 3b8c1df5da123170c23482d86c1bb064ce557b09 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 17 Mar 2019 12:49:55 +1100 Subject: [PATCH 130/144] Fix bug in per block processing. --- .../src/per_block_processing/validate_attestation.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/eth2/state_processing/src/per_block_processing/validate_attestation.rs b/eth2/state_processing/src/per_block_processing/validate_attestation.rs index 9d13214077..68a51b2df6 100644 --- a/eth2/state_processing/src/per_block_processing/validate_attestation.rs +++ b/eth2/state_processing/src/per_block_processing/validate_attestation.rs @@ -142,12 +142,10 @@ fn validate_attestation_signature_optional( ); // Get the committee for the specific shard that this attestation is for. + let relative_epoch = RelativeEpoch::from_slot(state.slot, attestation.data.slot, spec) + .map_err(|_| BeaconStateError::EpochOutOfBounds)?; // Should not fail due to previous checks. let crosslink_committee = state - .get_crosslink_committees_at_slot( - attestation.data.slot, - RelativeEpoch::NextWithoutRegistryChange, - spec, - )? + .get_crosslink_committees_at_slot(attestation.data.slot, relative_epoch, spec)? .iter() .find(|c| c.shard == attestation.data.shard) .ok_or_else(|| { From 3a384d93f85001bd316819571a635a4cfab1c2a4 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 17 Mar 2019 17:47:12 +1100 Subject: [PATCH 131/144] Allow state processing to compile under v0.5.0 --- .../src/per_block_processing.rs | 4 +- .../validate_attestation.rs | 4 +- .../src/per_epoch_processing.rs | 172 ++++++++---------- .../get_attestation_participants.rs | 37 ++++ .../inclusion_distance.rs | 15 +- .../process_validator_registry.rs | 72 ++++++++ .../validator_statuses.rs | 60 +++--- .../src/per_epoch_processing/winning_root.rs | 100 +++++++--- eth2/types/src/beacon_state.rs | 161 ++++++++++------ eth2/types/src/beacon_state/epoch_cache.rs | 30 +-- eth2/types/src/crosslink_committee.rs | 1 + .../testing_beacon_block_builder.rs | 5 +- .../testing_beacon_state_builder.rs | 3 +- 13 files changed, 422 insertions(+), 242 deletions(-) create mode 100644 eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs create mode 100644 eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs diff --git a/eth2/state_processing/src/per_block_processing.rs b/eth2/state_processing/src/per_block_processing.rs index 377f92e8b1..c6b22fa752 100644 --- a/eth2/state_processing/src/per_block_processing.rs +++ b/eth2/state_processing/src/per_block_processing.rs @@ -72,8 +72,8 @@ fn per_block_processing_signature_optional( process_block_header(state, block, spec)?; // Ensure the current and previous epoch cache is built. - state.build_epoch_cache(RelativeEpoch::Current, spec)?; state.build_epoch_cache(RelativeEpoch::Previous, spec)?; + state.build_epoch_cache(RelativeEpoch::Current, spec)?; if should_verify_block_signature { verify_block_signature(&state, &block, &spec)?; @@ -94,7 +94,7 @@ fn per_block_processing_signature_optional( /// /// Spec v0.5.0 pub fn process_block_header( - state: &BeaconState, + state: &mut BeaconState, block: &BeaconBlock, spec: &ChainSpec, ) -> Result<(), Error> { diff --git a/eth2/state_processing/src/per_block_processing/validate_attestation.rs b/eth2/state_processing/src/per_block_processing/validate_attestation.rs index 68a51b2df6..272eeb18b8 100644 --- a/eth2/state_processing/src/per_block_processing/validate_attestation.rs +++ b/eth2/state_processing/src/per_block_processing/validate_attestation.rs @@ -142,10 +142,8 @@ fn validate_attestation_signature_optional( ); // Get the committee for the specific shard that this attestation is for. - let relative_epoch = RelativeEpoch::from_slot(state.slot, attestation.data.slot, spec) - .map_err(|_| BeaconStateError::EpochOutOfBounds)?; // Should not fail due to previous checks. let crosslink_committee = state - .get_crosslink_committees_at_slot(attestation.data.slot, relative_epoch, spec)? + .get_crosslink_committees_at_slot(attestation.data.slot, spec)? .iter() .find(|c| c.shard == attestation.data.shard) .ok_or_else(|| { diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index 8c4b8e88ba..2f1cc35518 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -1,5 +1,6 @@ use errors::EpochProcessingError as Error; use integer_sqrt::IntegerSquareRoot; +use process_validator_registry::process_validator_registry; use rayon::prelude::*; use ssz::TreeHash; use std::collections::HashMap; @@ -8,7 +9,9 @@ use validator_statuses::{TotalBalances, ValidatorStatuses}; use winning_root::{winning_root, WinningRoot}; pub mod errors; +pub mod get_attestation_participants; pub mod inclusion_distance; +pub mod process_validator_registry; pub mod tests; pub mod validator_statuses; pub mod winning_root; @@ -25,10 +28,9 @@ pub type WinningRootHashSet = HashMap; /// /// Spec v0.4.0 pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { - // Ensure all of the caches are built. + // Ensure the previous and next epoch caches are built. state.build_epoch_cache(RelativeEpoch::Previous, spec)?; state.build_epoch_cache(RelativeEpoch::Current, spec)?; - state.build_epoch_cache(RelativeEpoch::Next, spec)?; let mut statuses = initialize_validator_statuses(&state, spec)?; @@ -61,7 +63,7 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result /// Returns a list of active validator indices for the state's current epoch. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn calculate_active_validator_indices(state: &BeaconState, spec: &ChainSpec) -> Vec { get_active_validator_indices( &state.validator_registry, @@ -76,26 +78,28 @@ pub fn calculate_active_validator_indices(state: &BeaconState, spec: &ChainSpec) /// - previous epoch attesters /// - etc. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn initialize_validator_statuses( state: &BeaconState, spec: &ChainSpec, ) -> Result { - let mut statuses = ValidatorStatuses::new(state, spec); + let mut statuses = ValidatorStatuses::new(state, spec)?; - statuses.process_attestations(&state, &state.latest_attestations, spec)?; + statuses.process_attestations(&state, spec)?; Ok(statuses) } -/// Spec v0.4.0 +/// Maybe resets the eth1 period. +/// +/// Spec v0.5.0 pub fn process_eth1_data(state: &mut BeaconState, spec: &ChainSpec) { let next_epoch = state.next_epoch(spec); let voting_period = spec.epochs_per_eth1_voting_period; if next_epoch % voting_period == 0 { for eth1_data_vote in &state.eth1_data_votes { - if eth1_data_vote.vote_count * 2 > voting_period { + if eth1_data_vote.vote_count * 2 > voting_period * spec.slots_per_epoch { state.latest_eth1_data = eth1_data_vote.eth1_data.clone(); } } @@ -119,7 +123,7 @@ pub fn process_justification( let previous_epoch = state.previous_epoch(spec); let current_epoch = state.current_epoch(spec); - let mut new_justified_epoch = state.justified_epoch; + let mut new_justified_epoch = state.current_justified_epoch; state.justification_bitfield <<= 1; // If > 2/3 of the total balance attested to the previous epoch boundary @@ -168,8 +172,10 @@ pub fn process_justification( // - The presently justified epoch was two epochs ago. // // Then, set the finalized epoch to two epochs ago. - if (state.justification_bitfield % 8 == 0b111) & (state.justified_epoch == previous_epoch - 1) { - state.finalized_epoch = state.justified_epoch; + if (state.justification_bitfield % 8 == 0b111) + & (state.current_justified_epoch == previous_epoch - 1) + { + state.finalized_epoch = state.current_justified_epoch; } // If: // @@ -177,12 +183,14 @@ pub fn process_justification( // - Set the previous epoch to be justified. // // Then, set the finalized epoch to be the previous epoch. - if (state.justification_bitfield % 4 == 0b11) & (state.justified_epoch == previous_epoch) { - state.finalized_epoch = state.justified_epoch; + if (state.justification_bitfield % 4 == 0b11) + & (state.current_justified_epoch == previous_epoch) + { + state.finalized_epoch = state.current_justified_epoch; } - state.previous_justified_epoch = state.justified_epoch; - state.justified_epoch = new_justified_epoch; + state.previous_justified_epoch = state.current_justified_epoch; + state.current_justified_epoch = new_justified_epoch; } /// Updates the following fields on the `BeaconState`: @@ -191,23 +199,11 @@ pub fn process_justification( /// /// Also returns a `WinningRootHashSet` for later use during epoch processing. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn process_crosslinks( state: &mut BeaconState, spec: &ChainSpec, ) -> Result { - let current_epoch_attestations: Vec<&PendingAttestation> = state - .latest_attestations - .par_iter() - .filter(|a| a.data.slot.epoch(spec.slots_per_epoch) == state.current_epoch(spec)) - .collect(); - - let previous_epoch_attestations: Vec<&PendingAttestation> = state - .latest_attestations - .par_iter() - .filter(|a| a.data.slot.epoch(spec.slots_per_epoch) == state.previous_epoch(spec)) - .collect(); - let mut winning_root_for_shards: WinningRootHashSet = HashMap::new(); let previous_and_current_epoch_slots: Vec = state @@ -221,24 +217,18 @@ pub fn process_crosslinks( let crosslink_committees_at_slot = state.get_crosslink_committees_at_slot(slot, spec)?.clone(); - for (crosslink_committee, shard) in crosslink_committees_at_slot { - let shard = shard as u64; + for c in crosslink_committees_at_slot { + let shard = c.shard as u64; - let winning_root = winning_root( - state, - shard, - ¤t_epoch_attestations[..], - &previous_epoch_attestations[..], - spec, - )?; + let winning_root = winning_root(state, shard, spec)?; if let Some(winning_root) = winning_root { - let total_committee_balance = state.get_total_balance(&crosslink_committee, spec); + let total_committee_balance = state.get_total_balance(&c.committee, spec)?; // TODO: I think this has a bug. if (3 * winning_root.total_attesting_balance) >= (2 * total_committee_balance) { state.latest_crosslinks[shard as usize] = Crosslink { - epoch: state.current_epoch(spec), + epoch: slot.epoch(spec.slots_per_epoch), crosslink_data_root: winning_root.crosslink_data_root, } } @@ -294,7 +284,10 @@ pub fn process_rewards_and_penalities( .map(|(index, &balance)| { let mut balance = balance; let status = &statuses.statuses[index]; - let base_reward = state.base_reward(index, base_reward_quotient, spec); + let base_reward = get_base_reward(state, index, total_balances.previous_epoch, spec) + .expect( + "Cannot fail to access a validator balance when iterating validator balances.", + ); if epochs_since_finality <= 4 { // Expected FFG source @@ -330,11 +323,15 @@ pub fn process_rewards_and_penalities( safe_sub_assign!(balance, base_reward); }; } else { - let inactivity_penalty = state.inactivity_penalty( + let inactivity_penalty = get_inactivity_penalty( + state, index, - epochs_since_finality, - base_reward_quotient, + epochs_since_finality.as_u64(), + total_balances.previous_epoch, spec, + ) + .expect( + "Cannot fail to access a validator balance when iterating validator balances.", ); if status.is_active_in_previous_epoch { @@ -349,7 +346,10 @@ pub fn process_rewards_and_penalities( } if state.validator_registry[index].slashed { - let base_reward = state.base_reward(index, base_reward_quotient, spec); + let base_reward = + get_base_reward(state, index, total_balances.previous_epoch, spec).expect( + "Cannot fail to access a validator balance when iterating validator balances.", + ); safe_sub_assign!(balance, 2 * inactivity_penalty + base_reward); } } @@ -384,7 +384,10 @@ pub fn process_rewards_and_penalities( let proposer_index = status.inclusion_info.proposer_index; let inclusion_distance = status.inclusion_info.distance; - let base_reward = state.base_reward(proposer_index, base_reward_quotient, spec); + let base_reward = + get_base_reward(state, proposer_index, total_balances.previous_epoch, spec).expect( + "Cannot fail to access a validator balance when iterating validator balances.", + ); if inclusion_distance > 0 && inclusion_distance < Slot::max_value() { safe_add_assign!( @@ -399,53 +402,37 @@ pub fn process_rewards_and_penalities( Ok(()) } -/// Peforms a validator registry update, if required. +/// Returns the base reward for some validator. /// -/// Spec v0.4.0 -pub fn process_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { - let current_epoch = state.current_epoch(spec); - let next_epoch = state.next_epoch(spec); - - state.previous_shuffling_epoch = state.current_shuffling_epoch; - state.previous_shuffling_start_shard = state.current_shuffling_start_shard; - - state.previous_shuffling_seed = state.current_shuffling_seed; - - let should_update_validator_registy = if state.finalized_epoch - > state.validator_registry_update_epoch - { - (0..state.get_current_epoch_committee_count(spec)).all(|i| { - let shard = (state.current_shuffling_start_shard + i as u64) % spec.shard_count; - state.latest_crosslinks[shard as usize].epoch > state.validator_registry_update_epoch - }) +/// Spec v0.5.0 +pub fn get_base_reward( + state: &BeaconState, + index: usize, + previous_total_balance: u64, + spec: &ChainSpec, +) -> Result { + if previous_total_balance == 0 { + Ok(0) } else { - false - }; - - if should_update_validator_registy { - state.update_validator_registry(spec); - - state.current_shuffling_epoch = next_epoch; - state.current_shuffling_start_shard = (state.current_shuffling_start_shard - + state.get_current_epoch_committee_count(spec) as u64) - % spec.shard_count; - state.current_shuffling_seed = state.generate_seed(state.current_shuffling_epoch, spec)? - } else { - let epochs_since_last_registry_update = - current_epoch - state.validator_registry_update_epoch; - if (epochs_since_last_registry_update > 1) - & epochs_since_last_registry_update.is_power_of_two() - { - state.current_shuffling_epoch = next_epoch; - state.current_shuffling_seed = - state.generate_seed(state.current_shuffling_epoch, spec)? - } + let adjusted_quotient = previous_total_balance.integer_sqrt() / spec.base_reward_quotient; + Ok(state.get_effective_balance(index, spec)? / adjusted_quotient / 5) } +} - state.process_slashings(spec); - state.process_exit_queue(spec); - - Ok(()) +/// Returns the inactivity penalty for some validator. +/// +/// Spec v0.5.0 +pub fn get_inactivity_penalty( + state: &BeaconState, + index: usize, + epochs_since_finality: u64, + previous_total_balance: u64, + spec: &ChainSpec, +) -> Result { + Ok(get_base_reward(state, index, previous_total_balance, spec)? + + state.get_effective_balance(index, spec)? * epochs_since_finality + / spec.inactivity_penalty_quotient + / 2) } /// Updates the state's `latest_active_index_roots` field with a tree hash the active validator @@ -486,12 +473,5 @@ pub fn update_latest_slashed_balances(state: &mut BeaconState, spec: &ChainSpec) /// /// Spec v0.4.0 pub fn clean_attestations(state: &mut BeaconState, spec: &ChainSpec) { - let current_epoch = state.current_epoch(spec); - - state.latest_attestations = state - .latest_attestations - .iter() - .filter(|a| a.data.slot.epoch(spec.slots_per_epoch) >= current_epoch) - .cloned() - .collect(); + state.previous_epoch_attestations = vec![]; } diff --git a/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs b/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs new file mode 100644 index 0000000000..d822e434d1 --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/get_attestation_participants.rs @@ -0,0 +1,37 @@ +use types::{beacon_state::helpers::verify_bitfield_length, *}; + +/// Returns validator indices which participated in the attestation. +/// +/// Spec v0.5.0 +pub fn get_attestation_participants( + state: &BeaconState, + attestation_data: &AttestationData, + bitfield: &Bitfield, + spec: &ChainSpec, +) -> Result, BeaconStateError> { + let epoch = attestation_data.slot.epoch(spec.slots_per_epoch); + + let crosslink_committee = + state.get_crosslink_committee_for_shard(epoch, attestation_data.shard, spec)?; + + if crosslink_committee.slot != attestation_data.slot { + return Err(BeaconStateError::NoCommitteeForShard); + } + + let committee = &crosslink_committee.committee; + + if !verify_bitfield_length(&bitfield, committee.len()) { + return Err(BeaconStateError::InvalidBitfield); + } + + let mut participants = Vec::with_capacity(committee.len()); + for (i, validator_index) in committee.iter().enumerate() { + match bitfield.get(i) { + Ok(bit) if bit == true => participants.push(*validator_index), + _ => {} + } + } + participants.shrink_to_fit(); + + Ok(participants) +} diff --git a/eth2/state_processing/src/per_epoch_processing/inclusion_distance.rs b/eth2/state_processing/src/per_epoch_processing/inclusion_distance.rs index 243dc67f0a..b52485947d 100644 --- a/eth2/state_processing/src/per_epoch_processing/inclusion_distance.rs +++ b/eth2/state_processing/src/per_epoch_processing/inclusion_distance.rs @@ -1,12 +1,11 @@ use super::errors::InclusionError; +use super::get_attestation_participants::get_attestation_participants; use types::*; /// Returns the distance between the first included attestation for some validator and this /// slot. /// -/// Note: In the spec this is defined "inline", not as a helper function. -/// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn inclusion_distance( state: &BeaconState, attestations: &[&PendingAttestation], @@ -19,9 +18,7 @@ pub fn inclusion_distance( /// Returns the slot of the earliest included attestation for some validator. /// -/// Note: In the spec this is defined "inline", not as a helper function. -/// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn inclusion_slot( state: &BeaconState, attestations: &[&PendingAttestation], @@ -34,9 +31,7 @@ pub fn inclusion_slot( /// Finds the earliest included attestation for some validator. /// -/// Note: In the spec this is defined "inline", not as a helper function. -/// -/// Spec v0.4.0 +/// Spec v0.5.0 fn earliest_included_attestation( state: &BeaconState, attestations: &[&PendingAttestation], @@ -47,7 +42,7 @@ fn earliest_included_attestation( for (i, a) in attestations.iter().enumerate() { let participants = - state.get_attestation_participants(&a.data, &a.aggregation_bitfield, spec)?; + get_attestation_participants(state, &a.data, &a.aggregation_bitfield, spec)?; if participants.iter().any(|i| *i == validator_index) { included_attestations.push(i); } diff --git a/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs b/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs new file mode 100644 index 0000000000..c830bfc243 --- /dev/null +++ b/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs @@ -0,0 +1,72 @@ +use super::Error; +use types::*; + +/// Peforms a validator registry update, if required. +/// +/// Spec v0.4.0 +pub fn process_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> Result<(), Error> { + let current_epoch = state.current_epoch(spec); + let next_epoch = state.next_epoch(spec); + + state.previous_shuffling_epoch = state.current_shuffling_epoch; + state.previous_shuffling_start_shard = state.current_shuffling_start_shard; + + state.previous_shuffling_seed = state.current_shuffling_seed; + + if should_update_validator_registry(state, spec)? { + state.update_validator_registry(spec); + + state.current_shuffling_epoch = next_epoch; + state.current_shuffling_start_shard = (state.current_shuffling_start_shard + + spec.get_epoch_committee_count( + state + .get_active_validator_indices(current_epoch, spec)? + .len(), + ) as u64) + % spec.shard_count; + state.current_shuffling_seed = state.generate_seed(state.current_shuffling_epoch, spec)? + } else { + let epochs_since_last_registry_update = + current_epoch - state.validator_registry_update_epoch; + if (epochs_since_last_registry_update > 1) + & epochs_since_last_registry_update.is_power_of_two() + { + state.current_shuffling_epoch = next_epoch; + state.current_shuffling_seed = + state.generate_seed(state.current_shuffling_epoch, spec)? + } + } + + state.process_slashings(spec); + state.process_exit_queue(spec); + + Ok(()) +} + +/// Returns `true` if the validator registry should be updated during an epoch processing. +/// +/// Spec v0.5.0 +pub fn should_update_validator_registry( + state: &BeaconState, + spec: &ChainSpec, +) -> Result { + if state.finalized_epoch <= state.validator_registry_update_epoch { + return Ok(false); + } + + let num_active_validators = state + .get_active_validator_indices(state.current_epoch(spec), spec)? + .len(); + let current_epoch_committee_count = spec.get_epoch_committee_count(num_active_validators); + + for shard in (0..current_epoch_committee_count) + .into_iter() + .map(|i| (state.current_shuffling_start_shard + i as u64) % spec.shard_count) + { + if state.latest_crosslinks[shard as usize].epoch <= state.validator_registry_update_epoch { + return Ok(false); + } + } + + Ok(true) +} diff --git a/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs index f76900f3b4..bcbca82440 100644 --- a/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs +++ b/eth2/state_processing/src/per_epoch_processing/validator_statuses.rs @@ -1,3 +1,4 @@ +use super::get_attestation_participants::get_attestation_participants; use super::WinningRootHashSet; use types::*; @@ -147,8 +148,8 @@ impl ValidatorStatuses { /// - Active validators /// - Total balances for the current and previous epochs. /// - /// Spec v0.4.0 - pub fn new(state: &BeaconState, spec: &ChainSpec) -> Self { + /// Spec v0.5.0 + pub fn new(state: &BeaconState, spec: &ChainSpec) -> Result { let mut statuses = Vec::with_capacity(state.validator_registry.len()); let mut total_balances = TotalBalances::default(); @@ -157,37 +158,40 @@ impl ValidatorStatuses { if validator.is_active_at(state.current_epoch(spec)) { status.is_active_in_current_epoch = true; - total_balances.current_epoch += state.get_effective_balance(i, spec); + total_balances.current_epoch += state.get_effective_balance(i, spec)?; } if validator.is_active_at(state.previous_epoch(spec)) { status.is_active_in_previous_epoch = true; - total_balances.previous_epoch += state.get_effective_balance(i, spec); + total_balances.previous_epoch += state.get_effective_balance(i, spec)?; } statuses.push(status); } - Self { + Ok(Self { statuses, total_balances, - } + }) } /// Process some attestations from the given `state` updating the `statuses` and /// `total_balances` fields. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn process_attestations( &mut self, state: &BeaconState, - attestations: &[PendingAttestation], spec: &ChainSpec, ) -> Result<(), BeaconStateError> { - for a in attestations { + for a in state + .previous_epoch_attestations + .iter() + .chain(state.current_epoch_attestations.iter()) + { let attesting_indices = - state.get_attestation_participants(&a.data, &a.aggregation_bitfield, spec)?; - let attesting_balance = state.get_total_balance(&attesting_indices, spec); + get_attestation_participants(state, &a.data, &a.aggregation_bitfield, spec)?; + let attesting_balance = state.get_total_balance(&attesting_indices, spec)?; let mut status = AttesterStatus::default(); @@ -206,10 +210,15 @@ impl ValidatorStatuses { status.is_previous_epoch_attester = true; // The inclusion slot and distance are only required for previous epoch attesters. + let relative_epoch = RelativeEpoch::from_slot(state.slot, a.data.slot, spec)?; status.inclusion_info = InclusionInfo { slot: a.inclusion_slot, distance: inclusion_distance(a), - proposer_index: state.get_beacon_proposer_index(a.inclusion_slot, spec)?, + proposer_index: state.get_beacon_proposer_index( + a.inclusion_slot, + relative_epoch, + spec, + )?, }; if has_common_epoch_boundary_root(a, state, state.previous_epoch(spec), spec)? { @@ -235,7 +244,7 @@ impl ValidatorStatuses { /// Update the `statuses` for each validator based upon whether or not they attested to the /// "winning" shard block root for the previous epoch. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn process_winning_roots( &mut self, state: &BeaconState, @@ -248,11 +257,10 @@ impl ValidatorStatuses { state.get_crosslink_committees_at_slot(slot, spec)?; // Loop through each committee in the slot. - for (crosslink_committee, shard) in crosslink_committees_at_slot { + for c in crosslink_committees_at_slot { // If there was some winning crosslink root for the committee's shard. - if let Some(winning_root) = winning_roots.get(&shard) { - let total_committee_balance = - state.get_total_balance(&crosslink_committee, spec); + if let Some(winning_root) = winning_roots.get(&c.shard) { + let total_committee_balance = state.get_total_balance(&c.committee, spec)?; for &validator_index in &winning_root.attesting_validator_indices { // Take note of the balance information for the winning root, it will be // used later to calculate rewards for that validator. @@ -272,14 +280,14 @@ impl ValidatorStatuses { /// Returns the distance between when the attestation was created and when it was included in a /// block. /// -/// Spec v0.4.0 +/// Spec v0.5.0 fn inclusion_distance(a: &PendingAttestation) -> Slot { a.inclusion_slot - a.data.slot } /// Returns `true` if some `PendingAttestation` is from the supplied `epoch`. /// -/// Spec v0.4.0 +/// Spec v0.5.0 fn is_from_epoch(a: &PendingAttestation, epoch: Epoch, spec: &ChainSpec) -> bool { a.data.slot.epoch(spec.slots_per_epoch) == epoch } @@ -287,7 +295,7 @@ fn is_from_epoch(a: &PendingAttestation, epoch: Epoch, spec: &ChainSpec) -> bool /// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for /// the first slot of the given epoch. /// -/// Spec v0.4.0 +/// Spec v0.5.0 fn has_common_epoch_boundary_root( a: &PendingAttestation, state: &BeaconState, @@ -295,25 +303,21 @@ fn has_common_epoch_boundary_root( spec: &ChainSpec, ) -> Result { let slot = epoch.start_slot(spec.slots_per_epoch); - let state_boundary_root = *state - .get_block_root(slot, spec) - .ok_or_else(|| BeaconStateError::InsufficientBlockRoots)?; + let state_boundary_root = *state.get_block_root(slot, spec)?; - Ok(a.data.epoch_boundary_root == state_boundary_root) + Ok(a.data.target_root == state_boundary_root) } /// Returns `true` if a `PendingAttestation` and `BeaconState` share the same beacon block hash for /// the current slot of the `PendingAttestation`. /// -/// Spec v0.4.0 +/// Spec v0.5.0 fn has_common_beacon_block_root( a: &PendingAttestation, state: &BeaconState, spec: &ChainSpec, ) -> Result { - let state_block_root = *state - .get_block_root(a.data.slot, spec) - .ok_or_else(|| BeaconStateError::InsufficientBlockRoots)?; + let state_block_root = *state.get_block_root(a.data.slot, spec)?; Ok(a.data.beacon_block_root == state_block_root) } diff --git a/eth2/state_processing/src/per_epoch_processing/winning_root.rs b/eth2/state_processing/src/per_epoch_processing/winning_root.rs index 07678f93b6..97cff3e13a 100644 --- a/eth2/state_processing/src/per_epoch_processing/winning_root.rs +++ b/eth2/state_processing/src/per_epoch_processing/winning_root.rs @@ -1,3 +1,4 @@ +use super::get_attestation_participants::get_attestation_participants; use std::collections::HashSet; use std::iter::FromIterator; use types::*; @@ -13,14 +14,14 @@ impl WinningRoot { /// Returns `true` if `self` is a "better" candidate than `other`. /// /// A winning root is "better" than another if it has a higher `total_attesting_balance`. Ties - /// are broken by favouring the lower `crosslink_data_root` value. + /// are broken by favouring the higher `crosslink_data_root` value. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn is_better_than(&self, other: &Self) -> bool { if self.total_attesting_balance > other.total_attesting_balance { true } else if self.total_attesting_balance == other.total_attesting_balance { - self.crosslink_data_root < other.crosslink_data_root + self.crosslink_data_root > other.crosslink_data_root } else { false } @@ -33,22 +34,21 @@ impl WinningRoot { /// The `WinningRoot` object also contains additional fields that are useful in later stages of /// per-epoch processing. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn winning_root( state: &BeaconState, shard: u64, - current_epoch_attestations: &[&PendingAttestation], - previous_epoch_attestations: &[&PendingAttestation], spec: &ChainSpec, ) -> Result, BeaconStateError> { let mut winning_root: Option = None; let crosslink_data_roots: HashSet = HashSet::from_iter( - previous_epoch_attestations + state + .previous_epoch_attestations .iter() - .chain(current_epoch_attestations.iter()) + .chain(state.current_epoch_attestations.iter()) .filter_map(|a| { - if a.data.shard == shard { + if is_eligible_for_winning_root(state, a, shard) { Some(a.data.crosslink_data_root) } else { None @@ -57,18 +57,17 @@ pub fn winning_root( ); for crosslink_data_root in crosslink_data_roots { - let attesting_validator_indices = get_attesting_validator_indices( - state, - shard, - current_epoch_attestations, - previous_epoch_attestations, - &crosslink_data_root, - spec, - )?; + let attesting_validator_indices = + get_attesting_validator_indices(state, shard, &crosslink_data_root, spec)?; - let total_attesting_balance: u64 = attesting_validator_indices - .iter() - .fold(0, |acc, i| acc + state.get_effective_balance(*i, spec)); + let total_attesting_balance: u64 = + attesting_validator_indices + .iter() + .try_fold(0_u64, |acc, i| { + state + .get_effective_balance(*i, spec) + .and_then(|bal| Ok(acc + bal)) + })?; let candidate = WinningRoot { crosslink_data_root, @@ -88,25 +87,36 @@ pub fn winning_root( Ok(winning_root) } -/// Returns all indices which voted for a given crosslink. May contain duplicates. +/// Returns `true` if pending attestation `a` is eligible to become a winning root. /// -/// Spec v0.4.0 +/// Spec v0.5.0 +fn is_eligible_for_winning_root(state: &BeaconState, a: &PendingAttestation, shard: Shard) -> bool { + if shard >= state.latest_crosslinks.len() as u64 { + return false; + } + + a.data.previous_crosslink == state.latest_crosslinks[shard as usize] +} + +/// Returns all indices which voted for a given crosslink. Does not contain duplicates. +/// +/// Spec v0.5.0 fn get_attesting_validator_indices( state: &BeaconState, shard: u64, - current_epoch_attestations: &[&PendingAttestation], - previous_epoch_attestations: &[&PendingAttestation], crosslink_data_root: &Hash256, spec: &ChainSpec, ) -> Result, BeaconStateError> { let mut indices = vec![]; - for a in current_epoch_attestations + for a in state + .current_epoch_attestations .iter() - .chain(previous_epoch_attestations.iter()) + .chain(state.previous_epoch_attestations.iter()) { if (a.data.shard == shard) && (a.data.crosslink_data_root == *crosslink_data_root) { - indices.append(&mut state.get_attestation_participants( + indices.append(&mut get_attestation_participants( + state, &a.data, &a.aggregation_bitfield, spec, @@ -114,5 +124,41 @@ fn get_attesting_validator_indices( } } + // Sort the list (required for dedup). "Unstable" means the sort may re-order equal elements, + // this causes no issue here. + // + // These sort + dedup ops are potentially good CPU time optimisation targets. + indices.sort_unstable(); + // Remove all duplicate indices (requires a sorted list). + indices.dedup(); + Ok(indices) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn is_better_than() { + let worse = WinningRoot { + crosslink_data_root: Hash256::from_slice(&[1; 32]), + attesting_validator_indices: vec![], + total_attesting_balance: 42, + }; + + let better = WinningRoot { + crosslink_data_root: Hash256::from_slice(&[2; 32]), + ..worse.clone() + }; + + assert!(better.is_better_than(&worse)); + + let better = WinningRoot { + total_attesting_balance: worse.total_attesting_balance + 1, + ..worse.clone() + }; + + assert!(better.is_better_than(&worse)); + } +} diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 7c77a5a3e5..a90f09759d 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -1,8 +1,7 @@ use self::epoch_cache::EpochCache; use crate::test_utils::TestRandom; -use crate::{validator_registry::get_active_validator_indices, *}; +use crate::*; use int_to_bytes::int_to_bytes32; -use log::trace; use pubkey_cache::PubkeyCache; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; @@ -39,6 +38,7 @@ pub enum Error { InsufficientAttestations, InsufficientCommittees, InsufficientSlashedBalances, + NoCommitteeForShard, EpochCacheUninitialized(RelativeEpoch), PubkeyCacheInconsistent, PubkeyCacheIncomplete { @@ -349,17 +349,49 @@ impl BeaconState { self.current_epoch(spec) + 1 } + /// Returns the active validator indices for the given epoch, assuming there is no validator + /// registry update in the next epoch. + /// + /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. + /// + /// Spec v0.5.0 + pub fn get_active_validator_indices( + &self, + epoch: Epoch, + spec: &ChainSpec, + ) -> Result<&[usize], Error> { + // If the slot is in the next epoch, assume there was no validator registry update. + let relative_epoch = + match RelativeEpoch::from_epoch(self.slot.epoch(spec.slots_per_epoch), epoch) { + Err(RelativeEpochError::AmbiguiousNextEpoch) => { + Ok(RelativeEpoch::NextWithoutRegistryChange) + } + e => e, + }?; + + let cache = self.cache(relative_epoch, spec)?; + + Ok(&cache.active_validator_indices) + } + /// Returns the crosslink committees for some slot. /// /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn get_crosslink_committees_at_slot( &self, slot: Slot, - relative_epoch: RelativeEpoch, spec: &ChainSpec, ) -> Result<&Vec, Error> { + // If the slot is in the next epoch, assume there was no validator registry update. + let relative_epoch = match RelativeEpoch::from_slot(self.slot, slot, spec) { + Err(RelativeEpochError::AmbiguiousNextEpoch) => { + Ok(RelativeEpoch::NextWithoutRegistryChange) + } + e => e, + }?; + let cache = self.cache(relative_epoch, spec)?; Ok(cache @@ -367,15 +399,46 @@ impl BeaconState { .ok_or_else(|| Error::SlotOutOfBounds)?) } + /// Returns the crosslink committees for some shard in an epoch. + /// + /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. + /// + /// Spec v0.4.0 + pub fn get_crosslink_committee_for_shard( + &self, + epoch: Epoch, + shard: Shard, + spec: &ChainSpec, + ) -> Result<&CrosslinkCommittee, Error> { + // If the slot is in the next epoch, assume there was no validator registry update. + let relative_epoch = match RelativeEpoch::from_epoch(self.current_epoch(spec), epoch) { + Err(RelativeEpochError::AmbiguiousNextEpoch) => { + Ok(RelativeEpoch::NextWithoutRegistryChange) + } + e => e, + }?; + + let cache = self.cache(relative_epoch, spec)?; + + Ok(cache + .get_crosslink_committee_for_shard(shard, spec) + .ok_or_else(|| Error::NoCommitteeForShard)?) + } + /// Return the block root at a recent `slot`. /// /// Spec v0.5.0 - pub fn get_block_root(&self, slot: Slot, spec: &ChainSpec) -> Option<&Hash256> { + pub fn get_block_root( + &self, + slot: Slot, + spec: &ChainSpec, + ) -> Result<&Hash256, BeaconStateError> { if (self.slot <= slot + spec.slots_per_historical_root as u64) && (slot < self.slot) { self.latest_block_roots .get(slot.as_usize() % spec.slots_per_historical_root) + .ok_or_else(|| Error::InsufficientBlockRoots) } else { - None + Err(Error::EpochOutOfBounds) } } @@ -476,12 +539,12 @@ impl BeaconState { relative_epoch: RelativeEpoch, spec: &ChainSpec, ) -> Result { - let committees = self.get_crosslink_committees_at_slot(slot, relative_epoch, spec)?; - trace!( - "get_beacon_proposer_index: slot: {}, committees_count: {}", - slot, - committees.len() - ); + let cache = self.cache(relative_epoch, spec)?; + + let committees = cache + .get_crosslink_committees_at_slot(slot, spec) + .ok_or_else(|| Error::SlotOutOfBounds)?; + committees .first() .ok_or(Error::InsufficientValidators) @@ -751,13 +814,14 @@ impl BeaconState { .ok_or_else(|| Error::UnknownValidator)?) } - /// Process the slashings. + /// Process slashings. + /// + /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. /// /// Spec v0.4.0 pub fn process_slashings(&mut self, spec: &ChainSpec) -> Result<(), Error> { let current_epoch = self.current_epoch(spec); - let active_validator_indices = - get_active_validator_indices(&self.validator_registry, current_epoch); + let active_validator_indices = self.get_active_validator_indices(current_epoch, spec)?; let total_balance = self.get_total_balance(&active_validator_indices[..], spec)?; for (index, validator) in self.validator_registry.iter().enumerate() { @@ -818,11 +882,12 @@ impl BeaconState { /// Update validator registry, activating/exiting validators if possible. /// + /// Note: Utilizes the cache and will fail if the appropriate cache is not initialized. + /// /// Spec v0.4.0 pub fn update_validator_registry(&mut self, spec: &ChainSpec) -> Result<(), Error> { let current_epoch = self.current_epoch(spec); - let active_validator_indices = - get_active_validator_indices(&self.validator_registry, current_epoch); + let active_validator_indices = self.get_active_validator_indices(current_epoch, spec)?; let total_balance = self.get_total_balance(&active_validator_indices[..], spec)?; let max_balance_churn = std::cmp::max( @@ -867,54 +932,32 @@ impl BeaconState { /// Iterate through the validator registry and eject active validators with balance below /// ``EJECTION_BALANCE``. /// - /// Spec v0.4.0 - pub fn process_ejections(&mut self, spec: &ChainSpec) { - for validator_index in - get_active_validator_indices(&self.validator_registry, self.current_epoch(spec)) - { - if self.validator_balances[validator_index] < spec.ejection_balance { - self.exit_validator(validator_index, spec) - } + /// Spec v0.5.0 + pub fn process_ejections(&mut self, spec: &ChainSpec) -> Result<(), Error> { + // There is an awkward double (triple?) loop here because we can't loop across the borrowed + // active validator indices and mutate state in the one loop. + let exitable: Vec = self + .get_active_validator_indices(self.current_epoch(spec), spec)? + .iter() + .filter_map(|&i| { + if self.validator_balances[i as usize] < spec.ejection_balance { + Some(i) + } else { + None + } + }) + .collect(); + + for validator_index in exitable { + self.exit_validator(validator_index, spec) } - } - /// Returns the penality that should be applied to some validator for inactivity. - /// - /// Note: this is defined "inline" in the spec, not as a helper function. - /// - /// Spec v0.4.0 - pub fn inactivity_penalty( - &self, - validator_index: usize, - epochs_since_finality: Epoch, - base_reward_quotient: u64, - spec: &ChainSpec, - ) -> Result { - let effective_balance = self.get_effective_balance(validator_index, spec)?; - let base_reward = self.base_reward(validator_index, base_reward_quotient, spec)?; - Ok(base_reward - + effective_balance * epochs_since_finality.as_u64() - / spec.inactivity_penalty_quotient - / 2) - } - - /// Returns the base reward for some validator. - /// - /// Note: In the spec this is defined "inline", not as a helper function. - /// - /// Spec v0.4.0 - pub fn base_reward( - &self, - validator_index: usize, - base_reward_quotient: u64, - spec: &ChainSpec, - ) -> Result { - Ok(self.get_effective_balance(validator_index, spec)? / base_reward_quotient / 5) + Ok(()) } /// Return the combined effective balance of an array of validators. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn get_total_balance( &self, validator_indices: &[usize], diff --git a/eth2/types/src/beacon_state/epoch_cache.rs b/eth2/types/src/beacon_state/epoch_cache.rs index 6312ea5a54..0759a76170 100644 --- a/eth2/types/src/beacon_state/epoch_cache.rs +++ b/eth2/types/src/beacon_state/epoch_cache.rs @@ -13,7 +13,9 @@ pub struct EpochCache { /// Maps validator index to a slot, shard and committee index for attestation. pub attestation_duties: Vec>, /// Maps a shard to an index of `self.committees`. - pub shard_committee_indices: Vec<(Slot, usize)>, + pub shard_committee_indices: Vec>, + /// Indices of all active validators in the epoch + pub active_validator_indices: Vec, } impl EpochCache { @@ -31,18 +33,18 @@ impl EpochCache { let builder = match relative_epoch { RelativeEpoch::Previous => EpochCrosslinkCommitteesBuilder::for_previous_epoch( state, - active_validator_indices, + active_validator_indices.clone(), spec, ), RelativeEpoch::Current => EpochCrosslinkCommitteesBuilder::for_current_epoch( state, - active_validator_indices, + active_validator_indices.clone(), spec, ), RelativeEpoch::NextWithRegistryChange => { EpochCrosslinkCommitteesBuilder::for_next_epoch( state, - active_validator_indices, + active_validator_indices.clone(), true, spec, )? @@ -50,7 +52,7 @@ impl EpochCache { RelativeEpoch::NextWithoutRegistryChange => { EpochCrosslinkCommitteesBuilder::for_next_epoch( state, - active_validator_indices, + active_validator_indices.clone(), false, spec, )? @@ -64,7 +66,7 @@ impl EpochCache { // 2. `shard_committee_indices`: maps `Shard` into a `CrosslinkCommittee` in // `EpochCrosslinkCommittees`. let mut attestation_duties = vec![None; state.validator_registry.len()]; - let mut shard_committee_indices = vec![(Slot::default(), 0); spec.shard_count as usize]; + let mut shard_committee_indices = vec![None; spec.shard_count as usize]; for (i, slot_committees) in epoch_crosslink_committees .crosslink_committees .iter() @@ -75,7 +77,7 @@ impl EpochCache { for (j, crosslink_committee) in slot_committees.iter().enumerate() { let shard = crosslink_committee.shard; - shard_committee_indices[shard as usize] = (slot, j); + shard_committee_indices[shard as usize] = Some((slot, j)); for (k, validator_index) in crosslink_committee.committee.iter().enumerate() { let attestation_duty = AttestationDuty { @@ -93,6 +95,7 @@ impl EpochCache { epoch_crosslink_committees, attestation_duties, shard_committee_indices, + active_validator_indices, }) } @@ -110,9 +113,13 @@ impl EpochCache { shard: Shard, spec: &ChainSpec, ) -> Option<&CrosslinkCommittee> { - let (slot, committee) = self.shard_committee_indices.get(shard as usize)?; - let slot_committees = self.get_crosslink_committees_at_slot(*slot, spec)?; - slot_committees.get(*committee) + if shard > self.shard_committee_indices.len() as u64 { + None + } else { + let (slot, committee) = self.shard_committee_indices[shard as usize]?; + let slot_committees = self.get_crosslink_committees_at_slot(slot, spec)?; + slot_committees.get(committee) + } } } @@ -261,13 +268,14 @@ impl EpochCrosslinkCommitteesBuilder { let committees_per_slot = (self.committees_per_epoch / spec.slots_per_epoch) as usize; - for i in 0..spec.slots_per_epoch as usize { + for (i, slot) in self.epoch.slot_iter(spec.slots_per_epoch).enumerate() { for j in (0..committees.len()) .into_iter() .skip(i * committees_per_slot) .take(committees_per_slot) { let crosslink_committee = CrosslinkCommittee { + slot, shard, committee: committees.remove(j), }; diff --git a/eth2/types/src/crosslink_committee.rs b/eth2/types/src/crosslink_committee.rs index 06a6562fcd..af1778a1b7 100644 --- a/eth2/types/src/crosslink_committee.rs +++ b/eth2/types/src/crosslink_committee.rs @@ -4,6 +4,7 @@ use ssz_derive::{Decode, Encode, TreeHash}; #[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize, Decode, Encode, TreeHash)] pub struct CrosslinkCommittee { + pub slot: Slot, pub shard: Shard, pub committee: Vec, } diff --git a/eth2/types/src/test_utils/testing_beacon_block_builder.rs b/eth2/types/src/test_utils/testing_beacon_block_builder.rs index 402bd79d63..6e48c8c178 100644 --- a/eth2/types/src/test_utils/testing_beacon_block_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_block_builder.rs @@ -109,10 +109,7 @@ impl TestingBeaconBlockBuilder { break; } - let relative_epoch = RelativeEpoch::from_slot(state.slot, slot, spec).unwrap(); - for crosslink_committee in - state.get_crosslink_committees_at_slot(slot, relative_epoch, spec)? - { + for crosslink_committee in state.get_crosslink_committees_at_slot(slot, spec)? { if attestations_added >= num_attestations { break; } diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index 9e613f0e91..54e2fbe964 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -223,9 +223,8 @@ impl TestingBeaconStateBuilder { for slot in first_slot..last_slot + 1 { let slot = Slot::from(slot); - let relative_epoch = RelativeEpoch::from_slot(state.slot, slot, spec).unwrap(); let committees = state - .get_crosslink_committees_at_slot(slot, relative_epoch, spec) + .get_crosslink_committees_at_slot(slot, spec) .unwrap() .clone(); From 6b3cdc34dd4197a87468ba67549da376a678c834 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 17 Mar 2019 17:50:22 +1100 Subject: [PATCH 132/144] Update block proposer to v0.5.0 --- eth2/block_proposer/src/lib.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/eth2/block_proposer/src/lib.rs b/eth2/block_proposer/src/lib.rs index 5cddbaedc5..e62c4b71d5 100644 --- a/eth2/block_proposer/src/lib.rs +++ b/eth2/block_proposer/src/lib.rs @@ -4,7 +4,7 @@ mod traits; use slot_clock::SlotClock; use ssz::{SignedRoot, TreeHash}; use std::sync::Arc; -use types::{BeaconBlock, ChainSpec, Domain, Hash256, Proposal, Slot}; +use types::{BeaconBlock, ChainSpec, Domain, Slot}; pub use self::traits::{ BeaconNode, BeaconNodeError, DutiesReader, DutiesReaderError, PublishOutcome, Signer, @@ -158,7 +158,7 @@ impl BlockProducer BlockProducer Option { self.store_produce(&block); - let proposal = Proposal { - slot: block.slot, - shard: self.spec.beacon_chain_shard_number, - block_root: Hash256::from_slice(&block.signed_root()[..]), - signature: block.signature.clone(), - }; - match self .signer - .sign_block_proposal(&proposal.signed_root()[..], domain) + .sign_block_proposal(&block.signed_root()[..], domain) { None => None, Some(signature) => { From 8b08e9dd2e2fe90f3e5c9269397cbf82e24bd413 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 17 Mar 2019 17:54:43 +1100 Subject: [PATCH 133/144] Remove block and state readers from db crate --- .../db/src/stores/beacon_block_store.rs | 37 +++++-------------- .../db/src/stores/beacon_state_store.rs | 22 +---------- 2 files changed, 12 insertions(+), 47 deletions(-) diff --git a/beacon_node/db/src/stores/beacon_block_store.rs b/beacon_node/db/src/stores/beacon_block_store.rs index 92d296c373..e2e16e60b8 100644 --- a/beacon_node/db/src/stores/beacon_block_store.rs +++ b/beacon_node/db/src/stores/beacon_block_store.rs @@ -2,7 +2,7 @@ use super::BLOCKS_DB_COLUMN as DB_COLUMN; use super::{ClientDB, DBError}; use ssz::Decodable; use std::sync::Arc; -use types::{readers::BeaconBlockReader, BeaconBlock, Hash256, Slot}; +use types::{BeaconBlock, Hash256, Slot}; #[derive(Clone, Debug, PartialEq)] pub enum BeaconBlockAtSlotError { @@ -38,23 +38,6 @@ impl BeaconBlockStore { } } - /// Retuns an object implementing `BeaconBlockReader`, or `None` (if hash not known). - /// - /// Note: Presently, this function fully deserializes a `BeaconBlock` and returns that. In the - /// future, it would be ideal to return an object capable of reading directly from serialized - /// SSZ bytes. - pub fn get_reader(&self, hash: &Hash256) -> Result, DBError> { - match self.get(&hash)? { - None => Ok(None), - Some(ssz) => { - let (block, _) = BeaconBlock::ssz_decode(&ssz, 0).map_err(|_| DBError { - message: "Bad BeaconBlock SSZ.".to_string(), - })?; - Ok(Some(block)) - } - } - } - /// Retrieve the block at a slot given a "head_hash" and a slot. /// /// A "head_hash" must be a block hash with a slot number greater than or equal to the desired @@ -72,17 +55,17 @@ impl BeaconBlockStore { &self, head_hash: &Hash256, slot: Slot, - ) -> Result, BeaconBlockAtSlotError> { + ) -> Result, BeaconBlockAtSlotError> { let mut current_hash = *head_hash; loop { - if let Some(block_reader) = self.get_reader(¤t_hash)? { - if block_reader.slot() == slot { - break Ok(Some((current_hash, block_reader))); - } else if block_reader.slot() < slot { + if let Some(block) = self.get_deserialized(¤t_hash)? { + if block.slot == slot { + break Ok(Some((current_hash, block))); + } else if block.slot < slot { break Ok(None); } else { - current_hash = block_reader.parent_root(); + current_hash = block.previous_block_root; } } else { break Err(BeaconBlockAtSlotError::UnknownBeaconBlock(current_hash)); @@ -228,7 +211,7 @@ mod tests { for i in 0..block_count { let mut block = BeaconBlock::random_for_test(&mut rng); - block.parent_root = parent_hashes[i]; + block.previous_block_root = parent_hashes[i]; block.slot = slots[i]; let ssz = ssz_encode(&block); @@ -240,12 +223,12 @@ mod tests { // Test that certain slots can be reached from certain hashes. let test_cases = vec![(4, 4), (4, 3), (4, 2), (4, 1), (4, 0)]; for (hashes_index, slot_index) in test_cases { - let (matched_block_hash, reader) = bs + let (matched_block_hash, block) = bs .block_at_slot(&hashes[hashes_index], slots[slot_index]) .unwrap() .unwrap(); assert_eq!(matched_block_hash, hashes[slot_index]); - assert_eq!(reader.slot(), slots[slot_index]); + assert_eq!(block.slot, slots[slot_index]); } let ssz = bs.block_at_slot(&hashes[4], Slot::new(2)).unwrap(); diff --git a/beacon_node/db/src/stores/beacon_state_store.rs b/beacon_node/db/src/stores/beacon_state_store.rs index ed22696cb2..fd6ff569af 100644 --- a/beacon_node/db/src/stores/beacon_state_store.rs +++ b/beacon_node/db/src/stores/beacon_state_store.rs @@ -2,7 +2,7 @@ use super::STATES_DB_COLUMN as DB_COLUMN; use super::{ClientDB, DBError}; use ssz::Decodable; use std::sync::Arc; -use types::{readers::BeaconStateReader, BeaconState, Hash256}; +use types::{BeaconState, Hash256}; pub struct BeaconStateStore where @@ -30,23 +30,6 @@ impl BeaconStateStore { } } } - - /// Retuns an object implementing `BeaconStateReader`, or `None` (if hash not known). - /// - /// Note: Presently, this function fully deserializes a `BeaconState` and returns that. In the - /// future, it would be ideal to return an object capable of reading directly from serialized - /// SSZ bytes. - pub fn get_reader(&self, hash: &Hash256) -> Result, DBError> { - match self.get(&hash)? { - None => Ok(None), - Some(ssz) => { - let (state, _) = BeaconState::ssz_decode(&ssz, 0).map_err(|_| DBError { - message: "Bad State SSZ.".to_string(), - })?; - Ok(Some(state)) - } - } - } } #[cfg(test)] @@ -72,8 +55,7 @@ mod tests { store.put(&state_root, &ssz_encode(&state)).unwrap(); - let reader = store.get_reader(&state_root).unwrap().unwrap(); - let decoded = reader.into_beacon_state().unwrap(); + let decoded = store.get_deserialized(&state_root).unwrap().unwrap(); assert_eq!(state, decoded); } From d94540c85c6f98068c6a5cadac312c55eff22353 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 17 Mar 2019 17:59:29 +1100 Subject: [PATCH 134/144] Remove readers from fork choice crate. --- eth2/fork_choice/src/bitwise_lmd_ghost.rs | 24 ++++++++++----------- eth2/fork_choice/src/optimized_lmd_ghost.rs | 24 ++++++++++----------- eth2/fork_choice/src/slow_lmd_ghost.rs | 15 ++++++------- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/eth2/fork_choice/src/bitwise_lmd_ghost.rs b/eth2/fork_choice/src/bitwise_lmd_ghost.rs index d7b10015be..9410fd2034 100644 --- a/eth2/fork_choice/src/bitwise_lmd_ghost.rs +++ b/eth2/fork_choice/src/bitwise_lmd_ghost.rs @@ -11,8 +11,8 @@ use log::{debug, trace}; use std::collections::HashMap; use std::sync::Arc; use types::{ - readers::BeaconBlockReader, validator_registry::get_active_validator_indices, BeaconBlock, - ChainSpec, Hash256, Slot, SlotHeight, + validator_registry::get_active_validator_indices, BeaconBlock, ChainSpec, Hash256, Slot, + SlotHeight, }; //TODO: Pruning - Children @@ -255,17 +255,17 @@ impl ForkChoice for BitwiseLMDGhost { // get the height of the parent let parent_height = self .block_store - .get_deserialized(&block.parent_root)? - .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(block.parent_root))? - .slot() + .get_deserialized(&block.previous_block_root)? + .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(block.previous_block_root))? + .slot .height(spec.genesis_slot); - let parent_hash = &block.parent_root; + let parent_hash = &block.previous_block_root; // add the new block to the children of parent (*self .children - .entry(block.parent_root) + .entry(block.previous_block_root) .or_insert_with(|| vec![])) .push(block_hash.clone()); @@ -309,7 +309,7 @@ impl ForkChoice for BitwiseLMDGhost { .block_store .get_deserialized(&target_block_root)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*target_block_root))? - .slot() + .slot .height(spec.genesis_slot); // get the height of the past target block @@ -317,7 +317,7 @@ impl ForkChoice for BitwiseLMDGhost { .block_store .get_deserialized(&attestation_target)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*attestation_target))? - .slot() + .slot .height(spec.genesis_slot); // update the attestation only if the new target is higher if past_block_height < block_height { @@ -343,8 +343,8 @@ impl ForkChoice for BitwiseLMDGhost { .get_deserialized(&justified_block_start)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))?; - let block_slot = block.slot(); - let state_root = block.state_root(); + let block_slot = block.slot; + let state_root = block.state_root; let mut block_height = block_slot.height(spec.genesis_slot); let mut current_head = *justified_block_start; @@ -434,7 +434,7 @@ impl ForkChoice for BitwiseLMDGhost { .block_store .get_deserialized(¤t_head)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(current_head))? - .slot() + .slot .height(spec.genesis_slot); // prune the latest votes for votes that are not part of current chosen chain // more specifically, only keep votes that have head as an ancestor diff --git a/eth2/fork_choice/src/optimized_lmd_ghost.rs b/eth2/fork_choice/src/optimized_lmd_ghost.rs index 30c84e9e14..e1b8914a63 100644 --- a/eth2/fork_choice/src/optimized_lmd_ghost.rs +++ b/eth2/fork_choice/src/optimized_lmd_ghost.rs @@ -11,8 +11,8 @@ use std::cmp::Ordering; use std::collections::HashMap; use std::sync::Arc; use types::{ - readers::BeaconBlockReader, validator_registry::get_active_validator_indices, BeaconBlock, - ChainSpec, Hash256, Slot, SlotHeight, + validator_registry::get_active_validator_indices, BeaconBlock, ChainSpec, Hash256, Slot, + SlotHeight, }; //TODO: Pruning - Children @@ -226,17 +226,17 @@ impl ForkChoice for OptimizedLMDGhost { // get the height of the parent let parent_height = self .block_store - .get_deserialized(&block.parent_root)? - .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(block.parent_root))? - .slot() + .get_deserialized(&block.previous_block_root)? + .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(block.previous_block_root))? + .slot .height(spec.genesis_slot); - let parent_hash = &block.parent_root; + let parent_hash = &block.previous_block_root; // add the new block to the children of parent (*self .children - .entry(block.parent_root) + .entry(block.previous_block_root) .or_insert_with(|| vec![])) .push(block_hash.clone()); @@ -280,7 +280,7 @@ impl ForkChoice for OptimizedLMDGhost { .block_store .get_deserialized(&target_block_root)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*target_block_root))? - .slot() + .slot .height(spec.genesis_slot); // get the height of the past target block @@ -288,7 +288,7 @@ impl ForkChoice for OptimizedLMDGhost { .block_store .get_deserialized(&attestation_target)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*attestation_target))? - .slot() + .slot .height(spec.genesis_slot); // update the attestation only if the new target is higher if past_block_height < block_height { @@ -314,8 +314,8 @@ impl ForkChoice for OptimizedLMDGhost { .get_deserialized(&justified_block_start)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))?; - let block_slot = block.slot(); - let state_root = block.state_root(); + let block_slot = block.slot; + let state_root = block.state_root; let mut block_height = block_slot.height(spec.genesis_slot); let mut current_head = *justified_block_start; @@ -405,7 +405,7 @@ impl ForkChoice for OptimizedLMDGhost { .block_store .get_deserialized(¤t_head)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(current_head))? - .slot() + .slot .height(spec.genesis_slot); // prune the latest votes for votes that are not part of current chosen chain // more specifically, only keep votes that have head as an ancestor diff --git a/eth2/fork_choice/src/slow_lmd_ghost.rs b/eth2/fork_choice/src/slow_lmd_ghost.rs index abf13f21b0..af58aa7b83 100644 --- a/eth2/fork_choice/src/slow_lmd_ghost.rs +++ b/eth2/fork_choice/src/slow_lmd_ghost.rs @@ -9,8 +9,7 @@ use log::{debug, trace}; use std::collections::HashMap; use std::sync::Arc; use types::{ - readers::BeaconBlockReader, validator_registry::get_active_validator_indices, BeaconBlock, - ChainSpec, Hash256, Slot, + validator_registry::get_active_validator_indices, BeaconBlock, ChainSpec, Hash256, Slot, }; //TODO: Pruning and syncing @@ -95,7 +94,7 @@ where .block_store .get_deserialized(&block_root)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*block_root))? - .slot(); + .slot; for (vote_hash, votes) in latest_votes.iter() { let (root_at_slot, _) = self @@ -122,7 +121,7 @@ impl ForkChoice for SlowLMDGhost { // add the new block to the children of parent (*self .children - .entry(block.parent_root) + .entry(block.previous_block_root) .or_insert_with(|| vec![])) .push(block_hash.clone()); @@ -155,7 +154,7 @@ impl ForkChoice for SlowLMDGhost { .block_store .get_deserialized(&target_block_root)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*target_block_root))? - .slot() + .slot .height(spec.genesis_slot); // get the height of the past target block @@ -163,7 +162,7 @@ impl ForkChoice for SlowLMDGhost { .block_store .get_deserialized(&attestation_target)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*attestation_target))? - .slot() + .slot .height(spec.genesis_slot); // update the attestation only if the new target is higher if past_block_height < block_height { @@ -186,9 +185,9 @@ impl ForkChoice for SlowLMDGhost { .get_deserialized(&justified_block_start)? .ok_or_else(|| ForkChoiceError::MissingBeaconBlock(*justified_block_start))?; - let start_state_root = start.state_root(); + let start_state_root = start.state_root; - let latest_votes = self.get_latest_votes(&start_state_root, start.slot(), spec)?; + let latest_votes = self.get_latest_votes(&start_state_root, start.slot, spec)?; let mut head_hash = *justified_block_start; From 6df5eee7f49809da48868629c1567860c957d3fc Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 17 Mar 2019 18:10:20 +1100 Subject: [PATCH 135/144] Update beacon_chain crate with v0.5.0 updates --- .../src/attestation_aggregator.rs | 53 ++++----- beacon_node/beacon_chain/src/beacon_chain.rs | 104 ++++++++---------- 2 files changed, 67 insertions(+), 90 deletions(-) diff --git a/beacon_node/beacon_chain/src/attestation_aggregator.rs b/beacon_node/beacon_chain/src/attestation_aggregator.rs index 75cfd7ee5f..9b4e5a6874 100644 --- a/beacon_node/beacon_chain/src/attestation_aggregator.rs +++ b/beacon_node/beacon_chain/src/attestation_aggregator.rs @@ -1,4 +1,3 @@ -use log::trace; use ssz::TreeHash; use state_processing::per_block_processing::validate_attestation_without_signature; use std::collections::{HashMap, HashSet}; @@ -86,34 +85,22 @@ impl AttestationAggregator { free_attestation: &FreeAttestation, spec: &ChainSpec, ) -> Result { - let attestation_duties = match state.attestation_slot_and_shard_for_validator( - free_attestation.validator_index as usize, - spec, - ) { - Err(BeaconStateError::EpochCacheUninitialized(e)) => { - panic!("Attempted to access unbuilt cache {:?}.", e) - } - Err(BeaconStateError::EpochOutOfBounds) => invalid_outcome!(Message::TooOld), - Err(BeaconStateError::ShardOutOfBounds) => invalid_outcome!(Message::BadShard), - Err(e) => return Err(e), - Ok(None) => invalid_outcome!(Message::BadValidatorIndex), - Ok(Some(attestation_duties)) => attestation_duties, - }; + let duties = + match state.get_attestation_duties(free_attestation.validator_index as usize, spec) { + Err(BeaconStateError::EpochCacheUninitialized(e)) => { + panic!("Attempted to access unbuilt cache {:?}.", e) + } + Err(BeaconStateError::EpochOutOfBounds) => invalid_outcome!(Message::TooOld), + Err(BeaconStateError::ShardOutOfBounds) => invalid_outcome!(Message::BadShard), + Err(e) => return Err(e), + Ok(None) => invalid_outcome!(Message::BadValidatorIndex), + Ok(Some(attestation_duties)) => attestation_duties, + }; - let (slot, shard, committee_index) = attestation_duties; - - trace!( - "slot: {}, shard: {}, committee_index: {}, val_index: {}", - slot, - shard, - committee_index, - free_attestation.validator_index - ); - - if free_attestation.data.slot != slot { + if free_attestation.data.slot != duties.slot { invalid_outcome!(Message::BadSlot); } - if free_attestation.data.shard != shard { + if free_attestation.data.shard != duties.shard { invalid_outcome!(Message::BadShard); } @@ -143,7 +130,7 @@ impl AttestationAggregator { if let Some(updated_attestation) = aggregate_attestation( existing_attestation, &free_attestation.signature, - committee_index as usize, + duties.committee_index as usize, ) { self.store.insert(signable_message, updated_attestation); valid_outcome!(Message::Aggregated); @@ -154,7 +141,7 @@ impl AttestationAggregator { let mut aggregate_signature = AggregateSignature::new(); aggregate_signature.add(&free_attestation.signature); let mut aggregation_bitfield = Bitfield::new(); - aggregation_bitfield.set(committee_index as usize, true); + aggregation_bitfield.set(duties.committee_index as usize, true); let new_attestation = Attestation { data: free_attestation.data.clone(), aggregation_bitfield, @@ -177,9 +164,13 @@ impl AttestationAggregator { ) -> Vec { let mut known_attestation_data: HashSet = HashSet::new(); - state.latest_attestations.iter().for_each(|attestation| { - known_attestation_data.insert(attestation.data.clone()); - }); + state + .previous_epoch_attestations + .iter() + .chain(state.current_epoch_attestations.iter()) + .for_each(|attestation| { + known_attestation_data.insert(attestation.data.clone()); + }); self.store .values() diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index b0e84e1e12..1082f6cabf 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -15,10 +15,7 @@ use state_processing::{ per_slot_processing, BlockProcessingError, SlotProcessingError, }; use std::sync::Arc; -use types::{ - readers::{BeaconBlockReader, BeaconStateReader}, - *, -}; +use types::*; #[derive(Debug, PartialEq)] pub enum ValidBlock { @@ -106,7 +103,8 @@ where genesis_state.build_epoch_cache(RelativeEpoch::Previous, &spec)?; genesis_state.build_epoch_cache(RelativeEpoch::Current, &spec)?; - genesis_state.build_epoch_cache(RelativeEpoch::Next, &spec)?; + genesis_state.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, &spec)?; + genesis_state.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, &spec)?; Ok(Self { block_store, @@ -248,19 +246,15 @@ where /// present and prior epoch is available. pub fn block_proposer(&self, slot: Slot) -> Result { trace!("BeaconChain::block_proposer: slot: {}", slot); - let index = self - .state - .read() - .get_beacon_proposer_index(slot, &self.spec)?; + let index = self.state.read().get_beacon_proposer_index( + slot, + RelativeEpoch::Current, + &self.spec, + )?; Ok(index) } - /// Returns the justified slot for the present state. - pub fn justified_epoch(&self) -> Epoch { - self.state.read().justified_epoch - } - /// Returns the attestation slot and shard for a given validator index. /// /// Information is read from the current state, so only information from the present and prior @@ -273,12 +267,12 @@ where "BeaconChain::validator_attestion_slot_and_shard: validator_index: {}", validator_index ); - if let Some((slot, shard, _committee)) = self + if let Some(attestation_duty) = self .state .read() - .attestation_slot_and_shard_for_validator(validator_index, &self.spec)? + .get_attestation_duties(validator_index, &self.spec)? { - Ok(Some((slot, shard))) + Ok(Some((attestation_duty.slot, attestation_duty.shard))) } else { Ok(None) } @@ -287,37 +281,33 @@ where /// Produce an `AttestationData` that is valid for the present `slot` and given `shard`. pub fn produce_attestation_data(&self, shard: u64) -> Result { trace!("BeaconChain::produce_attestation_data: shard: {}", shard); - let justified_epoch = self.justified_epoch(); - let justified_block_root = *self - .state - .read() - .get_block_root( - justified_epoch.start_slot(self.spec.slots_per_epoch), - &self.spec, - ) - .ok_or_else(|| Error::BadRecentBlockRoots)?; + let source_epoch = self.state.read().current_justified_epoch; + let source_root = *self.state.read().get_block_root( + source_epoch.start_slot(self.spec.slots_per_epoch), + &self.spec, + )?; - let epoch_boundary_root = *self - .state - .read() - .get_block_root( - self.state.read().current_epoch_start_slot(&self.spec), - &self.spec, - ) - .ok_or_else(|| Error::BadRecentBlockRoots)?; + let target_root = *self.state.read().get_block_root( + self.state + .read() + .slot + .epoch(self.spec.slots_per_epoch) + .start_slot(self.spec.slots_per_epoch), + &self.spec, + )?; Ok(AttestationData { slot: self.state.read().slot, shard, beacon_block_root: self.head().beacon_block_root, - epoch_boundary_root, + target_root, crosslink_data_root: Hash256::zero(), - latest_crosslink: Crosslink { + previous_crosslink: Crosslink { epoch: self.state.read().slot.epoch(self.spec.slots_per_epoch), crosslink_data_root: Hash256::zero(), }, - justified_epoch, - justified_block_root, + source_epoch, + source_root, }) } @@ -581,7 +571,7 @@ where dump.push(last_slot.clone()); loop { - let beacon_block_root = last_slot.beacon_block.parent_root; + let beacon_block_root = last_slot.beacon_block.previous_block_root; if beacon_block_root == self.spec.zero_hash { break; // Genesis has been reached. @@ -621,7 +611,7 @@ where /// /// Will accept blocks from prior slots, however it will reject any block from a future slot. pub fn process_block(&self, block: BeaconBlock) -> Result { - debug!("Processing block with slot {}...", block.slot()); + debug!("Processing block with slot {}...", block.slot); let block_root = block.canonical_root(); @@ -635,9 +625,9 @@ where // Load the blocks parent block from the database, returning invalid if that block is not // found. - let parent_block_root = block.parent_root; - let parent_block = match self.block_store.get_reader(&parent_block_root)? { - Some(parent_root) => parent_root, + let parent_block_root = block.previous_block_root; + let parent_block = match self.block_store.get_deserialized(&parent_block_root)? { + Some(previous_block_root) => previous_block_root, None => { return Ok(BlockProcessingOutcome::InvalidBlock( InvalidBlock::ParentUnknown, @@ -647,15 +637,11 @@ where // Load the parent blocks state from the database, returning an error if it is not found. // It is an error because if know the parent block we should also know the parent state. - let parent_state_root = parent_block.state_root(); + let parent_state_root = parent_block.state_root; let parent_state = self .state_store - .get_reader(&parent_state_root)? - .ok_or_else(|| Error::DBInconsistent(format!("Missing state {}", parent_state_root)))? - .into_beacon_state() - .ok_or_else(|| { - Error::DBInconsistent(format!("State SSZ invalid {}", parent_state_root)) - })?; + .get_deserialized(&parent_state_root)? + .ok_or_else(|| Error::DBInconsistent(format!("Missing state {}", parent_state_root)))?; // TODO: check the block proposer signature BEFORE doing a state transition. This will // significantly lower exposure surface to DoS attacks. @@ -739,22 +725,22 @@ where attestations.len() ); - let parent_root = *state + let previous_block_root = *state .get_block_root(state.slot.saturating_sub(1_u64), &self.spec) - .ok_or_else(|| BlockProductionError::UnableToGetBlockRootFromState)?; + .map_err(|_| BlockProductionError::UnableToGetBlockRootFromState)?; let mut block = BeaconBlock { slot: state.slot, - parent_root, + previous_block_root, state_root: Hash256::zero(), // Updated after the state is calculated. - randao_reveal, - eth1_data: Eth1Data { - // TODO: replace with real data - deposit_root: Hash256::zero(), - block_hash: Hash256::zero(), - }, signature: self.spec.empty_signature.clone(), // To be completed by a validator. body: BeaconBlockBody { + randao_reveal, + eth1_data: Eth1Data { + // TODO: replace with real data + deposit_root: Hash256::zero(), + block_hash: Hash256::zero(), + }, proposer_slashings: self.get_proposer_slashings_for_block(), attester_slashings: self.get_attester_slashings_for_block(), attestations, From df3f8df7bdd5b00c4d81bf89f3ee1350478ea04d Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 17 Mar 2019 18:56:05 +1100 Subject: [PATCH 136/144] Ensure fork_choice tests pass under v0.5.0 --- eth2/fork_choice/src/longest_chain.rs | 2 +- eth2/fork_choice/tests/tests.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/eth2/fork_choice/src/longest_chain.rs b/eth2/fork_choice/src/longest_chain.rs index 333553c025..423edc567f 100644 --- a/eth2/fork_choice/src/longest_chain.rs +++ b/eth2/fork_choice/src/longest_chain.rs @@ -34,7 +34,7 @@ impl ForkChoice for LongestChain { ) -> Result<(), ForkChoiceError> { // add the block hash to head_block_hashes removing the parent if it exists self.head_block_hashes - .retain(|hash| *hash != block.parent_root); + .retain(|hash| *hash != block.previous_block_root); self.head_block_hashes.push(*block_hash); Ok(()) } diff --git a/eth2/fork_choice/tests/tests.rs b/eth2/fork_choice/tests/tests.rs index cd5ff360f8..80fbbbe209 100644 --- a/eth2/fork_choice/tests/tests.rs +++ b/eth2/fork_choice/tests/tests.rs @@ -90,6 +90,8 @@ fn test_yaml_vectors( let randao_reveal = Signature::empty_signature(); let signature = Signature::empty_signature(); let body = BeaconBlockBody { + eth1_data, + randao_reveal, proposer_slashings: vec![], attester_slashings: vec![], attestations: vec![], @@ -117,14 +119,14 @@ fn test_yaml_vectors( // default params for genesis let block_hash = id_to_hash(&block_id); let mut slot = spec.genesis_slot; - let parent_root = id_to_hash(&parent_id); + let previous_block_root = id_to_hash(&parent_id); // set the slot and parent based off the YAML. Start with genesis; // if not the genesis, update slot if parent_id != block_id { // find parent slot slot = *(block_slot - .get(&parent_root) + .get(&previous_block_root) .expect("Parent should have a slot number")) + 1; } else { @@ -137,10 +139,8 @@ fn test_yaml_vectors( // build the BeaconBlock let beacon_block = BeaconBlock { slot, - parent_root, + previous_block_root, state_root: state_root.clone(), - randao_reveal: randao_reveal.clone(), - eth1_data: eth1_data.clone(), signature: signature.clone(), body: body.clone(), }; From 446ff0c27e7f9a7a71d0135f3090e37ce8f745d7 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 17 Mar 2019 19:19:52 +1100 Subject: [PATCH 137/144] Ensure test_harness crate compiles under v0.5.0 --- .../test_harness/src/beacon_chain_harness.rs | 29 ++++++-- .../test_harness/src/test_case.rs | 67 +++++-------------- .../testing_beacon_block_builder.rs | 9 ++- .../src/test_utils/testing_deposit_builder.rs | 12 ++-- 4 files changed, 51 insertions(+), 66 deletions(-) diff --git a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs index d74464ad4c..bc5c93b94a 100644 --- a/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs +++ b/beacon_node/beacon_chain/test_harness/src/beacon_chain_harness.rs @@ -46,8 +46,8 @@ impl BeaconChainHarness { TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, &spec); let (genesis_state, keypairs) = state_builder.build(); - let state_root = Hash256::from_slice(&genesis_state.hash_tree_root()); - let genesis_block = BeaconBlock::genesis(state_root, &spec); + let mut genesis_block = BeaconBlock::empty(&spec); + genesis_block.state_root = Hash256::from_slice(&genesis_state.hash_tree_root()); // Create the Beacon Chain let beacon_chain = Arc::new( @@ -127,8 +127,8 @@ impl BeaconChainHarness { .get_crosslink_committees_at_slot(present_slot, &self.spec) .unwrap() .iter() - .fold(vec![], |mut acc, (committee, _slot)| { - acc.append(&mut committee.clone()); + .fold(vec![], |mut acc, c| { + acc.append(&mut c.committee.clone()); acc }); let attesting_validators: HashSet = @@ -233,6 +233,27 @@ impl BeaconChainHarness { Some(Signature::new(message, domain, &validator.keypair.sk)) } + /// Returns the current `Fork` of the `beacon_chain`. + pub fn fork(&self) -> Fork { + self.beacon_chain.state.read().fork.clone() + } + + /// Returns the current `epoch` of the `beacon_chain`. + pub fn epoch(&self) -> Epoch { + self.beacon_chain + .state + .read() + .slot + .epoch(self.spec.slots_per_epoch) + } + + /// Returns the keypair for some validator index. + pub fn validator_keypair(&self, validator_index: usize) -> Option<&Keypair> { + self.validators + .get(validator_index) + .and_then(|v| Some(&v.keypair)) + } + /// Submit a deposit to the `BeaconChain` and, if given a keypair, create a new /// `ValidatorHarness` instance for this validator. /// diff --git a/beacon_node/beacon_chain/test_harness/src/test_case.rs b/beacon_node/beacon_chain/test_harness/src/test_case.rs index 0a62069724..1361127a19 100644 --- a/beacon_node/beacon_chain/test_harness/src/test_case.rs +++ b/beacon_node/beacon_chain/test_harness/src/test_case.rs @@ -3,12 +3,11 @@ use crate::beacon_chain_harness::BeaconChainHarness; use beacon_chain::CheckPoint; -use bls::get_withdrawal_credentials; use log::{info, warn}; use ssz::SignedRoot; use types::*; -use types::test_utils::{TestingAttesterSlashingBuilder, TestingProposerSlashingBuilder}; +use types::test_utils::*; use yaml_rust::Yaml; mod config; @@ -222,27 +221,20 @@ impl TestCase { } /// Builds a `Deposit` this is valid for the given `BeaconChainHarness` at its next slot. -fn build_transfer(harness: &BeaconChainHarness, from: u64, to: u64, amount: u64) -> Transfer { +fn build_transfer( + harness: &BeaconChainHarness, + sender: u64, + recipient: u64, + amount: u64, +) -> Transfer { let slot = harness.beacon_chain.state.read().slot + 1; - let mut transfer = Transfer { - from, - to, - amount, - fee: 0, - slot, - pubkey: harness.validators[from as usize].keypair.pk.clone(), - signature: Signature::empty_signature(), - }; + let mut builder = TestingTransferBuilder::new(sender, recipient, amount, slot); - let message = transfer.signed_root(); - let epoch = slot.epoch(harness.spec.slots_per_epoch); + let keypair = harness.validator_keypair(sender as usize).unwrap(); + builder.sign(keypair.clone(), &harness.fork(), &harness.spec); - transfer.signature = harness - .validator_sign(from as usize, &message[..], epoch, Domain::Transfer) - .expect("Unable to sign Transfer"); - - transfer + builder.build() } /// Builds a `Deposit` this is valid for the given `BeaconChainHarness`. @@ -255,41 +247,12 @@ fn build_deposit( index_offset: u64, ) -> (Deposit, Keypair) { let keypair = Keypair::random(); - let withdrawal_credentials = Hash256::from_slice( - &get_withdrawal_credentials(&keypair.pk, harness.spec.bls_withdrawal_prefix_byte)[..], - ); - let proof_of_possession = DepositInput::create_proof_of_possession( - &keypair, - &withdrawal_credentials, - harness.spec.get_domain( - harness - .beacon_chain - .state - .read() - .current_epoch(&harness.spec), - Domain::Deposit, - &harness.beacon_chain.state.read().fork, - ), - ); - let index = harness.beacon_chain.state.read().deposit_index + index_offset; - let deposit = Deposit { - // Note: `branch` and `index` will need to be updated once the spec defines their - // validity. - branch: vec![], - index, - deposit_data: DepositData { - amount, - timestamp: 1, - deposit_input: DepositInput { - pubkey: keypair.pk.clone(), - withdrawal_credentials, - proof_of_possession, - }, - }, - }; + let mut builder = TestingDepositBuilder::new(keypair.pk.clone(), amount); + builder.set_index(harness.beacon_chain.state.read().deposit_index + index_offset); + builder.sign(&keypair, harness.epoch(), &harness.fork(), &harness.spec); - (deposit, keypair) + (builder.build(), keypair) } /// Builds a `VoluntaryExit` this is valid for the given `BeaconChainHarness`. diff --git a/eth2/types/src/test_utils/testing_beacon_block_builder.rs b/eth2/types/src/test_utils/testing_beacon_block_builder.rs index 6e48c8c178..c5cd22ed46 100644 --- a/eth2/types/src/test_utils/testing_beacon_block_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_block_builder.rs @@ -180,9 +180,14 @@ impl TestingBeaconBlockBuilder { ) { let keypair = Keypair::random(); - let mut builder = TestingDepositBuilder::new(amount); + let mut builder = TestingDepositBuilder::new(keypair.pk.clone(), amount); builder.set_index(index); - builder.sign(&keypair, state, spec); + builder.sign( + &keypair, + state.slot.epoch(spec.slots_per_epoch), + &state.fork, + spec, + ); self.block.body.deposits.push(builder.build()) } diff --git a/eth2/types/src/test_utils/testing_deposit_builder.rs b/eth2/types/src/test_utils/testing_deposit_builder.rs index 0d1c962f0c..ee258e7fe7 100644 --- a/eth2/types/src/test_utils/testing_deposit_builder.rs +++ b/eth2/types/src/test_utils/testing_deposit_builder.rs @@ -10,9 +10,7 @@ pub struct TestingDepositBuilder { impl TestingDepositBuilder { /// Instantiates a new builder. - pub fn new(amount: u64) -> Self { - let keypair = Keypair::random(); - + pub fn new(pubkey: PublicKey, amount: u64) -> Self { let deposit = Deposit { proof: vec![], index: 0, @@ -20,7 +18,7 @@ impl TestingDepositBuilder { amount, timestamp: 1, deposit_input: DepositInput { - pubkey: keypair.pk, + pubkey, withdrawal_credentials: Hash256::zero(), proof_of_possession: Signature::empty_signature(), }, @@ -40,13 +38,11 @@ impl TestingDepositBuilder { /// - `pubkey` to the signing pubkey. /// - `withdrawal_credentials` to the signing pubkey. /// - `proof_of_possesssion` - pub fn sign(&mut self, keypair: &Keypair, state: &BeaconState, spec: &ChainSpec) { + pub fn sign(&mut self, keypair: &Keypair, epoch: Epoch, fork: &Fork, spec: &ChainSpec) { let withdrawal_credentials = Hash256::from_slice( &get_withdrawal_credentials(&keypair.pk, spec.bls_withdrawal_prefix_byte)[..], ); - let epoch = state.current_epoch(spec); - self.deposit.deposit_data.deposit_input.pubkey = keypair.pk.clone(); self.deposit .deposit_data @@ -57,7 +53,7 @@ impl TestingDepositBuilder { .deposit .deposit_data .deposit_input - .create_proof_of_possession(&keypair.sk, epoch, &state.fork, spec); + .create_proof_of_possession(&keypair.sk, epoch, fork, spec); } /// Builds the deposit, consuming the builder. From 919a15de229fa97e90bb3082cc552dc9f2edb466 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 17 Mar 2019 19:21:19 +1100 Subject: [PATCH 138/144] Ensure validator client compiles under v0.5.0 --- .../beacon_block_grpc_client.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/validator_client/src/block_producer_service/beacon_block_grpc_client.rs b/validator_client/src/block_producer_service/beacon_block_grpc_client.rs index 6ce5c0fa03..04a02a2213 100644 --- a/validator_client/src/block_producer_service/beacon_block_grpc_client.rs +++ b/validator_client/src/block_producer_service/beacon_block_grpc_client.rs @@ -50,15 +50,15 @@ impl BeaconNode for BeaconBlockGrpcClient { // TODO: this conversion is incomplete; fix it. Ok(Some(BeaconBlock { slot: Slot::new(block.get_slot()), - parent_root: Hash256::zero(), + previous_block_root: Hash256::zero(), state_root: Hash256::zero(), - randao_reveal, - eth1_data: Eth1Data { - deposit_root: Hash256::zero(), - block_hash: Hash256::zero(), - }, signature, body: BeaconBlockBody { + randao_reveal, + eth1_data: Eth1Data { + deposit_root: Hash256::zero(), + block_hash: Hash256::zero(), + }, proposer_slashings: vec![], attester_slashings: vec![], attestations: vec![], @@ -83,7 +83,7 @@ impl BeaconNode for BeaconBlockGrpcClient { let mut grpc_block = GrpcBeaconBlock::new(); grpc_block.set_slot(block.slot.as_u64()); grpc_block.set_block_root(vec![0]); - grpc_block.set_randao_reveal(ssz_encode(&block.randao_reveal)); + grpc_block.set_randao_reveal(ssz_encode(&block.body.randao_reveal)); grpc_block.set_signature(ssz_encode(&block.signature)); req.set_block(grpc_block); From f71cab8ba2525142019c8adfc1386145f90cb2f9 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 17 Mar 2019 19:28:29 +1100 Subject: [PATCH 139/144] Ensure project tests compile on v0.5.0 --- beacon_node/src/main.rs | 47 +++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index eacbffa3ef..780a3d338a 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -20,7 +20,7 @@ use ssz::TreeHash; use std::sync::Arc; use types::{ beacon_state::BeaconStateBuilder, BeaconBlock, ChainSpec, Deposit, DepositData, DepositInput, - Domain, Eth1Data, Fork, Hash256, Keypair, + Eth1Data, Fork, Hash256, Keypair, }; fn main() { @@ -103,35 +103,36 @@ fn main() { let initial_validator_deposits: Vec = keypairs .iter() - .map(|keypair| Deposit { - branch: vec![], // branch verification is not specified. - index: 0, // index verification is not specified. - deposit_data: DepositData { - amount: 32_000_000_000, // 32 ETH (in Gwei) - timestamp: genesis_time - 1, - deposit_input: DepositInput { - pubkey: keypair.pk.clone(), - withdrawal_credentials: Hash256::zero(), // Withdrawal not possible. - proof_of_possession: DepositInput::create_proof_of_possession( - &keypair, - &Hash256::zero(), - spec.get_domain( - // Get domain from genesis fork_version - spec.genesis_epoch, - Domain::Deposit, - &Fork::genesis(&spec), - ), - ), + .map(|keypair| { + let mut deposit_input = DepositInput { + pubkey: keypair.pk.clone(), + withdrawal_credentials: Hash256::zero(), + proof_of_possession: spec.empty_signature.clone(), + }; + deposit_input.proof_of_possession = deposit_input.create_proof_of_possession( + &keypair.sk, + spec.genesis_epoch, + &Fork::genesis(&spec), + &spec, + ); + + Deposit { + proof: vec![], // branch verification is not specified. + index: 0, // index verification is not specified. + deposit_data: DepositData { + amount: 32_000_000_000, // 32 ETH (in Gwei) + timestamp: genesis_time - 1, + deposit_input, }, - }, + } }) .collect(); let mut state_builder = BeaconStateBuilder::new(genesis_time, latest_eth1_data, &spec); state_builder.process_initial_deposits(&initial_validator_deposits, &spec); let genesis_state = state_builder.build(&spec).unwrap(); - let state_root = Hash256::from_slice(&genesis_state.hash_tree_root()); - let genesis_block = BeaconBlock::genesis(state_root, &spec); + let mut genesis_block = BeaconBlock::empty(&spec); + genesis_block.state_root = Hash256::from_slice(&genesis_state.hash_tree_root()); // Genesis chain let _chain_result = BeaconChain::from_genesis( From 8677b9e9cc61fd6792fb2ae4f18ee7be92b4d9da Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 17 Mar 2019 21:07:19 +1100 Subject: [PATCH 140/144] Fix bug with epoch caches, add tests --- .../src/per_block_processing.rs | 6 +- .../verify_slashable_attestation.rs | 2 +- .../per_block_processing/verify_transfer.rs | 2 +- eth2/types/src/beacon_state.rs | 4 +- eth2/types/src/beacon_state/epoch_cache.rs | 2 +- eth2/types/src/beacon_state/tests.rs | 54 ++++++++++++++++- eth2/types/src/relative_epoch.rs | 58 +++++++++++++++++++ 7 files changed, 120 insertions(+), 8 deletions(-) diff --git a/eth2/state_processing/src/per_block_processing.rs b/eth2/state_processing/src/per_block_processing.rs index c6b22fa752..78cf927f5e 100644 --- a/eth2/state_processing/src/per_block_processing.rs +++ b/eth2/state_processing/src/per_block_processing.rs @@ -32,7 +32,7 @@ const VERIFY_DEPOSIT_MERKLE_PROOFS: bool = false; /// Returns `Ok(())` if the block is valid and the state was successfully updated. Otherwise /// returns an error describing why the block was invalid or how the function failed to execute. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn per_block_processing( state: &mut BeaconState, block: &BeaconBlock, @@ -47,7 +47,7 @@ pub fn per_block_processing( /// Returns `Ok(())` if the block is valid and the state was successfully updated. Otherwise /// returns an error describing why the block was invalid or how the function failed to execute. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn per_block_processing_without_verifying_block_signature( state: &mut BeaconState, block: &BeaconBlock, @@ -62,7 +62,7 @@ pub fn per_block_processing_without_verifying_block_signature( /// Returns `Ok(())` if the block is valid and the state was successfully updated. Otherwise /// returns an error describing why the block was invalid or how the function failed to execute. /// -/// Spec v0.4.0 +/// Spec v0.5.0 fn per_block_processing_signature_optional( mut state: &mut BeaconState, block: &BeaconBlock, diff --git a/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs b/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs index f0d371043b..aa9a321969 100644 --- a/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs +++ b/eth2/state_processing/src/per_block_processing/verify_slashable_attestation.rs @@ -10,7 +10,7 @@ use types::*; /// /// Returns `Ok(())` if the `SlashableAttestation` is valid, otherwise indicates the reason for invalidity. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn verify_slashable_attestation( state: &BeaconState, slashable_attestation: &SlashableAttestation, diff --git a/eth2/state_processing/src/per_block_processing/verify_transfer.rs b/eth2/state_processing/src/per_block_processing/verify_transfer.rs index 546760fd09..f873cd8503 100644 --- a/eth2/state_processing/src/per_block_processing/verify_transfer.rs +++ b/eth2/state_processing/src/per_block_processing/verify_transfer.rs @@ -94,7 +94,7 @@ pub fn verify_transfer( /// /// Does not check that the transfer is valid, however checks for overflow in all actions. /// -/// Spec v0.4.0 +/// Spec v0.5.0 pub fn execute_transfer( state: &mut BeaconState, transfer: &Transfer, diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index a90f09759d..8999d8be84 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -279,7 +279,9 @@ impl BeaconState { fn cache(&self, relative_epoch: RelativeEpoch, spec: &ChainSpec) -> Result<&EpochCache, Error> { let cache = &self.caches[self.cache_index(relative_epoch)]; - if cache.initialized_epoch == Some(self.slot.epoch(spec.slots_per_epoch)) { + let epoch = relative_epoch.into_epoch(self.slot.epoch(spec.slots_per_epoch)); + + if cache.initialized_epoch == Some(epoch) { Ok(cache) } else { Err(Error::EpochCacheUninitialized(relative_epoch)) diff --git a/eth2/types/src/beacon_state/epoch_cache.rs b/eth2/types/src/beacon_state/epoch_cache.rs index 0759a76170..4436972f1e 100644 --- a/eth2/types/src/beacon_state/epoch_cache.rs +++ b/eth2/types/src/beacon_state/epoch_cache.rs @@ -159,7 +159,7 @@ impl EpochCrosslinkCommittees { let epoch_start_slot = self.epoch.start_slot(spec.slots_per_epoch); let epoch_end_slot = self.epoch.end_slot(spec.slots_per_epoch); - if (epoch_start_slot < slot) && (slot <= epoch_end_slot) { + if (epoch_start_slot <= slot) && (slot <= epoch_end_slot) { let index = slot - epoch_start_slot; self.crosslink_committees.get(index.as_usize()) } else { diff --git a/eth2/types/src/beacon_state/tests.rs b/eth2/types/src/beacon_state/tests.rs index 6c10ebe86d..dc16a013b3 100644 --- a/eth2/types/src/beacon_state/tests.rs +++ b/eth2/types/src/beacon_state/tests.rs @@ -1,5 +1,57 @@ #![cfg(test)] - use super::*; +use crate::test_utils::*; ssz_tests!(BeaconState); + +/// Test that +/// +/// 1. Using the cache before it's built fails. +/// 2. Using the cache after it's build passes. +/// 3. Using the cache after it's dropped fails. +fn test_cache_initialization<'a>( + state: &'a mut BeaconState, + relative_epoch: RelativeEpoch, + spec: &ChainSpec, +) { + let slot = relative_epoch + .into_epoch(state.slot.epoch(spec.slots_per_epoch)) + .start_slot(spec.slots_per_epoch); + + // Assuming the cache isn't already built, assert that a call to a cache-using function fails. + assert_eq!( + state.get_beacon_proposer_index(slot, relative_epoch, spec), + Err(BeaconStateError::EpochCacheUninitialized(relative_epoch)) + ); + + // Build the cache. + state.build_epoch_cache(relative_epoch, spec).unwrap(); + + // Assert a call to a cache-using function passes. + let _ = state + .get_beacon_proposer_index(slot, relative_epoch, spec) + .unwrap(); + + // Drop the cache. + state.drop_cache(relative_epoch); + + // Assert a call to a cache-using function fail. + assert_eq!( + state.get_beacon_proposer_index(slot, relative_epoch, spec), + Err(BeaconStateError::EpochCacheUninitialized(relative_epoch)) + ); +} + +#[test] +fn cache_initialization() { + let spec = ChainSpec::few_validators(); + let (mut state, _keypairs) = + TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(16, &spec).build(); + + state.slot = (spec.genesis_epoch + 1).start_slot(spec.slots_per_epoch); + + test_cache_initialization(&mut state, RelativeEpoch::Previous, &spec); + test_cache_initialization(&mut state, RelativeEpoch::Current, &spec); + test_cache_initialization(&mut state, RelativeEpoch::NextWithRegistryChange, &spec); + test_cache_initialization(&mut state, RelativeEpoch::NextWithoutRegistryChange, &spec); +} diff --git a/eth2/types/src/relative_epoch.rs b/eth2/types/src/relative_epoch.rs index 9439366059..6c135b1a6d 100644 --- a/eth2/types/src/relative_epoch.rs +++ b/eth2/types/src/relative_epoch.rs @@ -74,3 +74,61 @@ impl RelativeEpoch { ) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_into_epoch() { + let base = Epoch::new(10); + + assert_eq!(RelativeEpoch::Current.into_epoch(base), base); + assert_eq!(RelativeEpoch::Previous.into_epoch(base), base - 1); + assert_eq!( + RelativeEpoch::NextWithRegistryChange.into_epoch(base), + base + 1 + ); + assert_eq!( + RelativeEpoch::NextWithoutRegistryChange.into_epoch(base), + base + 1 + ); + } + + #[test] + fn from_epoch() { + let base = Epoch::new(10); + + assert_eq!( + RelativeEpoch::from_epoch(base, base - 1), + Ok(RelativeEpoch::Previous) + ); + assert_eq!( + RelativeEpoch::from_epoch(base, base), + Ok(RelativeEpoch::Current) + ); + assert_eq!( + RelativeEpoch::from_epoch(base, base + 1), + Err(RelativeEpochError::AmbiguiousNextEpoch) + ); + } + + #[test] + fn from_slot() { + let spec = ChainSpec::foundation(); + let base = Epoch::new(10).start_slot(spec.slots_per_epoch); + + assert_eq!( + RelativeEpoch::from_slot(base, base - 1, &spec), + Ok(RelativeEpoch::Previous) + ); + assert_eq!( + RelativeEpoch::from_slot(base, base, &spec), + Ok(RelativeEpoch::Current) + ); + assert_eq!( + RelativeEpoch::from_slot(base, base + spec.slots_per_epoch, &spec), + Err(RelativeEpochError::AmbiguiousNextEpoch) + ); + } +} From 816c2c651bc953472ecab72d2b44a5b0d1c8d417 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 17 Mar 2019 23:11:07 +1100 Subject: [PATCH 141/144] Modify genesis processing process. - Removed BeaconStateBuilder - Added genesis code to `state_processing`. --- beacon_node/Cargo.toml | 1 + beacon_node/src/main.rs | 63 ++------ .../state_processing/src/get_genesis_state.rs | 59 ++++++++ eth2/state_processing/src/lib.rs | 2 + .../src/per_epoch_processing.rs | 6 +- eth2/types/src/beacon_state.rs | 112 ++------------ eth2/types/src/beacon_state/builder.rs | 101 ------------- eth2/types/src/beacon_state/epoch_cache.rs | 6 +- .../src/beacon_state/epoch_cache/tests.rs | 142 ++++++++++++++++++ .../testing_beacon_state_builder.rs | 16 +- 10 files changed, 234 insertions(+), 274 deletions(-) create mode 100644 eth2/state_processing/src/get_genesis_state.rs delete mode 100644 eth2/types/src/beacon_state/builder.rs create mode 100644 eth2/types/src/beacon_state/epoch_cache/tests.rs diff --git a/beacon_node/Cargo.toml b/beacon_node/Cargo.toml index a4804e07e6..b76bc3e82f 100644 --- a/beacon_node/Cargo.toml +++ b/beacon_node/Cargo.toml @@ -19,6 +19,7 @@ slog = "^2.2.3" slot_clock = { path = "../eth2/utils/slot_clock" } slog-term = "^2.4.0" slog-async = "^2.3.0" +state_processing = { path = "../eth2/state_processing" } types = { path = "../eth2/types" } ssz = { path = "../eth2/utils/ssz" } tokio = "0.1" diff --git a/beacon_node/src/main.rs b/beacon_node/src/main.rs index 780a3d338a..2436d4f7c5 100644 --- a/beacon_node/src/main.rs +++ b/beacon_node/src/main.rs @@ -18,10 +18,8 @@ use slog::{error, info, o, Drain}; use slot_clock::SystemTimeSlotClock; use ssz::TreeHash; use std::sync::Arc; -use types::{ - beacon_state::BeaconStateBuilder, BeaconBlock, ChainSpec, Deposit, DepositData, DepositInput, - Eth1Data, Fork, Hash256, Keypair, -}; +use types::test_utils::TestingBeaconStateBuilder; +use types::*; fn main() { let decorator = slog_term::TermDecorator::new().build(); @@ -79,61 +77,18 @@ fn main() { let block_store = Arc::new(BeaconBlockStore::new(db.clone())); let state_store = Arc::new(BeaconStateStore::new(db.clone())); + let state_builder = TestingBeaconStateBuilder::from_deterministic_keypairs(8, &spec); + let (genesis_state, _keypairs) = state_builder.build(); + + let mut genesis_block = BeaconBlock::empty(&spec); + genesis_block.state_root = Hash256::from_slice(&genesis_state.hash_tree_root()); + // Slot clock - let genesis_time = 1_549_935_547; // 12th Feb 2018 (arbitrary value in the past). - let slot_clock = SystemTimeSlotClock::new(genesis_time, spec.seconds_per_slot) + let slot_clock = SystemTimeSlotClock::new(genesis_state.genesis_time, spec.seconds_per_slot) .expect("Unable to load SystemTimeSlotClock"); // Choose the fork choice let fork_choice = BitwiseLMDGhost::new(block_store.clone(), state_store.clone()); - /* - * Generate some random data to start a chain with. - * - * This is will need to be replace for production usage. - */ - let latest_eth1_data = Eth1Data { - deposit_root: Hash256::zero(), - block_hash: Hash256::zero(), - }; - let keypairs: Vec = (0..10) - .collect::>() - .iter() - .map(|_| Keypair::random()) - .collect(); - - let initial_validator_deposits: Vec = keypairs - .iter() - .map(|keypair| { - let mut deposit_input = DepositInput { - pubkey: keypair.pk.clone(), - withdrawal_credentials: Hash256::zero(), - proof_of_possession: spec.empty_signature.clone(), - }; - deposit_input.proof_of_possession = deposit_input.create_proof_of_possession( - &keypair.sk, - spec.genesis_epoch, - &Fork::genesis(&spec), - &spec, - ); - - Deposit { - proof: vec![], // branch verification is not specified. - index: 0, // index verification is not specified. - deposit_data: DepositData { - amount: 32_000_000_000, // 32 ETH (in Gwei) - timestamp: genesis_time - 1, - deposit_input, - }, - } - }) - .collect(); - - let mut state_builder = BeaconStateBuilder::new(genesis_time, latest_eth1_data, &spec); - state_builder.process_initial_deposits(&initial_validator_deposits, &spec); - let genesis_state = state_builder.build(&spec).unwrap(); - let mut genesis_block = BeaconBlock::empty(&spec); - genesis_block.state_root = Hash256::from_slice(&genesis_state.hash_tree_root()); - // Genesis chain let _chain_result = BeaconChain::from_genesis( state_store.clone(), diff --git a/eth2/state_processing/src/get_genesis_state.rs b/eth2/state_processing/src/get_genesis_state.rs new file mode 100644 index 0000000000..3c6612349a --- /dev/null +++ b/eth2/state_processing/src/get_genesis_state.rs @@ -0,0 +1,59 @@ +use super::per_block_processing::{errors::BlockProcessingError, process_deposits}; +use ssz::TreeHash; +use types::*; + +pub enum GenesisError { + BlockProcessingError(BlockProcessingError), + BeaconStateError(BeaconStateError), +} + +/// Returns the genesis `BeaconState` +/// +/// Spec v0.5.0 +pub fn get_genesis_state( + genesis_validator_deposits: &[Deposit], + genesis_time: u64, + genesis_eth1_data: Eth1Data, + spec: &ChainSpec, +) -> Result<(), BlockProcessingError> { + // Get the genesis `BeaconState` + let mut state = BeaconState::genesis(genesis_time, genesis_eth1_data, spec); + + // Process genesis deposits. + process_deposits(&mut state, genesis_validator_deposits, spec)?; + + // Process genesis activations. + for i in 0..state.validator_registry.len() { + if state.get_effective_balance(i, spec)? >= spec.max_deposit_amount { + state.validator_registry[i].activation_epoch = spec.genesis_epoch; + } + } + + // Ensure the current epoch cache is built. + state.build_epoch_cache(RelativeEpoch::Current, spec)?; + + // Set all the active index roots to be the genesis active index root. + let active_validator_indices = state + .get_active_validator_indices(spec.genesis_epoch, spec)? + .to_vec(); + let genesis_active_index_root = Hash256::from_slice(&active_validator_indices.hash_tree_root()); + state.latest_active_index_roots = + vec![genesis_active_index_root; spec.latest_active_index_roots_length as usize]; + + // Generate the current shuffling seed. + state.current_shuffling_seed = state.generate_seed(spec.genesis_epoch, spec)?; + + Ok(()) +} + +impl From for GenesisError { + fn from(e: BlockProcessingError) -> GenesisError { + GenesisError::BlockProcessingError(e) + } +} + +impl From for GenesisError { + fn from(e: BeaconStateError) -> GenesisError { + GenesisError::BeaconStateError(e) + } +} diff --git a/eth2/state_processing/src/lib.rs b/eth2/state_processing/src/lib.rs index 2b30844cb2..78dc7270d2 100644 --- a/eth2/state_processing/src/lib.rs +++ b/eth2/state_processing/src/lib.rs @@ -1,10 +1,12 @@ #[macro_use] mod macros; +pub mod get_genesis_state; pub mod per_block_processing; pub mod per_epoch_processing; pub mod per_slot_processing; +pub use get_genesis_state::get_genesis_state; pub use per_block_processing::{ errors::{BlockInvalid, BlockProcessingError}, per_block_processing, per_block_processing_without_verifying_block_signature, diff --git a/eth2/state_processing/src/per_epoch_processing.rs b/eth2/state_processing/src/per_epoch_processing.rs index 2f1cc35518..d1bb4269a7 100644 --- a/eth2/state_processing/src/per_epoch_processing.rs +++ b/eth2/state_processing/src/per_epoch_processing.rs @@ -45,7 +45,7 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result process_rewards_and_penalities(state, &mut statuses, &winning_root_for_shards, spec)?; // Ejections - state.process_ejections(spec); + state.process_ejections(spec)?; // Validator Registry process_validator_registry(state, spec)?; @@ -53,7 +53,7 @@ pub fn per_epoch_processing(state: &mut BeaconState, spec: &ChainSpec) -> Result // Final updates update_active_tree_index_roots(state, spec)?; update_latest_slashed_balances(state, spec); - clean_attestations(state, spec); + clean_attestations(state); // Rotate the epoch caches to suit the epoch transition. state.advance_caches(); @@ -472,6 +472,6 @@ pub fn update_latest_slashed_balances(state: &mut BeaconState, spec: &ChainSpec) /// Removes all pending attestations from the previous epoch. /// /// Spec v0.4.0 -pub fn clean_attestations(state: &mut BeaconState, spec: &ChainSpec) { +pub fn clean_attestations(state: &mut BeaconState) { state.previous_epoch_attestations = vec![]; } diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 8999d8be84..d7dbda782e 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -10,9 +10,6 @@ use ssz_derive::{Decode, Encode, TreeHash}; use std::collections::HashMap; use test_random_derive::TestRandom; -pub use builder::BeaconStateBuilder; - -mod builder; mod epoch_cache; pub mod helpers; mod pubkey_cache; @@ -32,7 +29,8 @@ pub enum Error { InvalidBitfield, ValidatorIsWithdrawable, InsufficientRandaoMixes, - InsufficientValidators, + NoValidators, + UnableToDetermineProducer, InsufficientBlockRoots, InsufficientIndexRoots, InsufficientAttestations, @@ -534,7 +532,7 @@ impl BeaconState { /// /// If the state does not contain an index for a beacon proposer at the requested `slot`, then `None` is returned. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn get_beacon_proposer_index( &self, slot: Slot, @@ -547,14 +545,16 @@ impl BeaconState { .get_crosslink_committees_at_slot(slot, spec) .ok_or_else(|| Error::SlotOutOfBounds)?; + let epoch = slot.epoch(spec.slots_per_epoch); + committees .first() - .ok_or(Error::InsufficientValidators) + .ok_or(Error::UnableToDetermineProducer) .and_then(|first| { - let index = slot + let index = epoch .as_usize() .checked_rem(first.committee.len()) - .ok_or(Error::InsufficientValidators)?; + .ok_or(Error::UnableToDetermineProducer)?; Ok(first.committee[index]) }) } @@ -581,103 +581,9 @@ impl BeaconState { epoch + 1 + spec.activation_exit_delay } - /// Process multiple deposits in sequence. - /// - /// Builds a hashmap of validator pubkeys to validator index and passes it to each successive - /// call to `process_deposit(..)`. This requires much less computation than successive calls to - /// `process_deposits(..)` without the hashmap. - /// - /// Spec v0.4.0 - pub fn process_deposits( - &mut self, - deposits: Vec<&DepositData>, - spec: &ChainSpec, - ) -> Vec { - let mut added_indices = vec![]; - let mut pubkey_map: HashMap = HashMap::new(); - - for (i, validator) in self.validator_registry.iter().enumerate() { - pubkey_map.insert(validator.pubkey.clone(), i); - } - - for deposit_data in deposits { - let result = self.process_deposit( - deposit_data.deposit_input.clone(), - deposit_data.amount, - Some(&pubkey_map), - spec, - ); - if let Ok(index) = result { - added_indices.push(index); - } - } - added_indices - } - - /// Process a validator deposit, returning the validator index if the deposit is valid. - /// - /// Optionally accepts a hashmap of all validator pubkeys to their validator index. Without - /// this hashmap, each call to `process_deposits` requires an iteration though - /// `self.validator_registry`. This becomes highly inefficient at scale. - /// - /// TODO: this function also exists in a more optimal form in the `state_processing` crate as - /// `process_deposits`; unify these two functions. - /// - /// Spec v0.4.0 - pub fn process_deposit( - &mut self, - deposit_input: DepositInput, - amount: u64, - pubkey_map: Option<&HashMap>, - spec: &ChainSpec, - ) -> Result { - let proof_is_valid = deposit_input.proof_of_possession.verify( - &deposit_input.signed_root(), - spec.get_domain(self.current_epoch(&spec), Domain::Deposit, &self.fork), - &deposit_input.pubkey, - ); - - if !proof_is_valid { - return Err(()); - } - - let pubkey = deposit_input.pubkey.clone(); - let withdrawal_credentials = deposit_input.withdrawal_credentials.clone(); - - let validator_index = if let Some(pubkey_map) = pubkey_map { - pubkey_map.get(&pubkey).and_then(|i| Some(*i)) - } else { - self.validator_registry - .iter() - .position(|v| v.pubkey == pubkey) - }; - - if let Some(index) = validator_index { - if self.validator_registry[index].withdrawal_credentials == withdrawal_credentials { - safe_add_assign!(self.validator_balances[index], amount); - Ok(index) - } else { - Err(()) - } - } else { - let validator = Validator { - pubkey, - withdrawal_credentials, - activation_epoch: spec.far_future_epoch, - exit_epoch: spec.far_future_epoch, - withdrawable_epoch: spec.far_future_epoch, - initiated_exit: false, - slashed: false, - }; - self.validator_registry.push(validator); - self.validator_balances.push(amount); - Ok(self.validator_registry.len() - 1) - } - } - /// Activate the validator of the given ``index``. /// - /// Spec v0.4.0 + /// Spec v0.5.0 pub fn activate_validator( &mut self, validator_index: usize, diff --git a/eth2/types/src/beacon_state/builder.rs b/eth2/types/src/beacon_state/builder.rs deleted file mode 100644 index 780ec9b8ba..0000000000 --- a/eth2/types/src/beacon_state/builder.rs +++ /dev/null @@ -1,101 +0,0 @@ -use super::BeaconStateError; -use crate::validator_registry::get_active_validator_indices; -use crate::*; -use rayon::prelude::*; -use ssz::TreeHash; - -/// Builds a `BeaconState` for use in production. -/// -/// This struct should _not_ be modified for use in testing scenarios. Use `TestingBeaconStateBuilder` for that purpose. -/// -/// This struct should remain safe and sensible for production usage. -pub struct BeaconStateBuilder { - pub state: BeaconState, -} - -impl BeaconStateBuilder { - /// Create a new builder with the given number of validators. - /// - /// Spec v0.4.0 - pub fn new(genesis_time: u64, latest_eth1_data: Eth1Data, spec: &ChainSpec) -> Self { - Self { - state: BeaconState::genesis(genesis_time, latest_eth1_data, spec), - } - } - - /// Process deposit objects. - /// - /// Spec v0.4.0 - pub fn process_initial_deposits( - &mut self, - initial_validator_deposits: &[Deposit], - spec: &ChainSpec, - ) { - let deposit_data = initial_validator_deposits - .par_iter() - .map(|deposit| &deposit.deposit_data) - .collect(); - - self.state.process_deposits(deposit_data, spec); - - self.activate_genesis_validators(spec); - - self.state.deposit_index = initial_validator_deposits.len() as u64; - } - - fn activate_genesis_validators(&mut self, spec: &ChainSpec) -> Result<(), BeaconStateError> { - for validator_index in 0..self.state.validator_registry.len() { - if self.state.get_effective_balance(validator_index, spec)? >= spec.max_deposit_amount { - self.state.activate_validator(validator_index, true, spec); - } - } - - Ok(()) - } - - /// Instantiate the validator registry from a YAML file. - /// - /// This skips a lot of signing and verification, useful if signing and verification has been - /// completed previously. - /// - /// Spec v0.4.0 - pub fn import_existing_validators( - &mut self, - validators: Vec, - initial_balances: Vec, - deposit_index: u64, - spec: &ChainSpec, - ) { - self.state.validator_registry = validators; - - assert_eq!( - self.state.validator_registry.len(), - initial_balances.len(), - "Not enough balances for validators" - ); - - self.state.validator_balances = initial_balances; - - self.activate_genesis_validators(spec); - - self.state.deposit_index = deposit_index; - } - - /// Updates the final state variables and returns a fully built genesis state. - /// - /// Spec v0.4.0 - pub fn build(mut self, spec: &ChainSpec) -> Result { - let genesis_active_index_root = - get_active_validator_indices(&self.state.validator_registry, spec.genesis_epoch) - .hash_tree_root(); - - self.state.latest_active_index_roots = vec![ - Hash256::from_slice(&genesis_active_index_root); - spec.latest_active_index_roots_length - ]; - - self.state.current_shuffling_seed = self.state.generate_seed(spec.genesis_epoch, spec)?; - - Ok(self.state) - } -} diff --git a/eth2/types/src/beacon_state/epoch_cache.rs b/eth2/types/src/beacon_state/epoch_cache.rs index 4436972f1e..0dbdf40546 100644 --- a/eth2/types/src/beacon_state/epoch_cache.rs +++ b/eth2/types/src/beacon_state/epoch_cache.rs @@ -4,6 +4,8 @@ use honey_badger_split::SplitExt; use serde_derive::{Deserialize, Serialize}; use swap_or_not_shuffle::shuffle_list; +mod tests; + #[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)] pub struct EpochCache { /// `Some(epoch)` if the cache is initialized, where `epoch` is the cache it holds. @@ -247,7 +249,7 @@ impl EpochCrosslinkCommitteesBuilder { pub fn build(self, spec: &ChainSpec) -> Result { if self.active_validator_indices.is_empty() { - return Err(Error::InsufficientValidators); + return Err(Error::NoValidators); } let shuffled_active_validator_indices = shuffle_list( @@ -277,7 +279,7 @@ impl EpochCrosslinkCommitteesBuilder { let crosslink_committee = CrosslinkCommittee { slot, shard, - committee: committees.remove(j), + committee: committees[j].drain(..).collect(), }; epoch_crosslink_committees.crosslink_committees[i].push(crosslink_committee); diff --git a/eth2/types/src/beacon_state/epoch_cache/tests.rs b/eth2/types/src/beacon_state/epoch_cache/tests.rs new file mode 100644 index 0000000000..10df635f28 --- /dev/null +++ b/eth2/types/src/beacon_state/epoch_cache/tests.rs @@ -0,0 +1,142 @@ +#![cfg(test)] + +use super::*; +use crate::test_utils::*; +use swap_or_not_shuffle::shuffle_list; + +fn do_sane_cache_test( + state: BeaconState, + epoch: Epoch, + validator_count: usize, + expected_seed: Hash256, + expected_shuffling_start: u64, + spec: &ChainSpec, +) { + let active_indices: Vec = (0..validator_count).collect(); + assert_eq!( + &active_indices[..], + state.get_active_validator_indices(epoch, &spec).unwrap(), + "Validator indices mismatch" + ); + + let shuffling = shuffle_list( + active_indices, + spec.shuffle_round_count, + &expected_seed[..], + true, + ) + .unwrap(); + + let committees_per_epoch = spec.get_epoch_committee_count(shuffling.len()); + let committees_per_slot = committees_per_epoch / spec.slots_per_epoch; + + let mut expected_indices_iter = shuffling.iter(); + let mut shard_counter = expected_shuffling_start; + + for (i, slot) in epoch.slot_iter(spec.slots_per_epoch).enumerate() { + let crosslink_committees_at_slot = + state.get_crosslink_committees_at_slot(slot, &spec).unwrap(); + + assert_eq!( + crosslink_committees_at_slot.len(), + committees_per_slot as usize, + "Bad committees per slot ({})", + i + ); + + for c in crosslink_committees_at_slot { + assert_eq!(c.shard, shard_counter, "Bad shard"); + shard_counter += 1; + shard_counter %= spec.shard_count; + + for &i in &c.committee { + assert_eq!( + i, + *expected_indices_iter.next().unwrap(), + "Non-sequential validators." + ); + } + } + } +} + +fn setup_sane_cache_test(validator_count: usize, spec: &ChainSpec) -> BeaconState { + let mut builder = + TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, spec); + + let epoch = spec.genesis_epoch + 4; + let slot = epoch.start_slot(spec.slots_per_epoch); + builder.teleport_to_slot(slot, spec); + + let (mut state, _keypairs) = builder.build(); + + state.current_shuffling_start_shard = 0; + state.current_shuffling_seed = Hash256::from_slice(&[1; 32]); + + state.previous_shuffling_start_shard = spec.shard_count - 1; + state.previous_shuffling_seed = Hash256::from_slice(&[2; 32]); + + state + .build_epoch_cache(RelativeEpoch::Previous, spec) + .unwrap(); + state + .build_epoch_cache(RelativeEpoch::Current, spec) + .unwrap(); + state + .build_epoch_cache(RelativeEpoch::NextWithRegistryChange, spec) + .unwrap(); + state + .build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, spec) + .unwrap(); + + state +} + +#[test] +fn builds_sane_current_epoch_cache() { + let mut spec = ChainSpec::few_validators(); + spec.shard_count = 4; + let validator_count = (spec.shard_count * spec.target_committee_size) + 1; + let state = setup_sane_cache_test(validator_count as usize, &spec); + do_sane_cache_test( + state.clone(), + state.current_epoch(&spec), + validator_count as usize, + state.current_shuffling_seed, + state.current_shuffling_start_shard, + &spec, + ); +} + +#[test] +fn builds_sane_previous_epoch_cache() { + let mut spec = ChainSpec::few_validators(); + spec.shard_count = 2; + let validator_count = (spec.shard_count * spec.target_committee_size) + 1; + let state = setup_sane_cache_test(validator_count as usize, &spec); + do_sane_cache_test( + state.clone(), + state.previous_epoch(&spec), + validator_count as usize, + state.previous_shuffling_seed, + state.previous_shuffling_start_shard, + &spec, + ); +} + +#[test] +fn builds_sane_next_without_update_epoch_cache() { + let mut spec = ChainSpec::few_validators(); + spec.shard_count = 2; + let validator_count = (spec.shard_count * spec.target_committee_size) + 1; + let mut state = setup_sane_cache_test(validator_count as usize, &spec); + state.validator_registry_update_epoch = state.slot.epoch(spec.slots_per_epoch); + do_sane_cache_test( + state.clone(), + state.next_epoch(&spec), + validator_count as usize, + state.current_shuffling_seed, + state.current_shuffling_start_shard, + &spec, + ); +} diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index 54e2fbe964..8180673d16 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -1,5 +1,4 @@ use super::{generate_deterministic_keypairs, KeypairsFile}; -use crate::beacon_state::BeaconStateBuilder; use crate::test_utils::TestingPendingAttestationBuilder; use crate::*; use bls::get_withdrawal_credentials; @@ -110,7 +109,8 @@ impl TestingBeaconStateBuilder { Validator { pubkey: keypair.pk.clone(), withdrawal_credentials, - activation_epoch: spec.far_future_epoch, + // All validators start active. + activation_epoch: spec.genesis_epoch, exit_epoch: spec.far_future_epoch, withdrawable_epoch: spec.far_future_epoch, initiated_exit: false, @@ -119,7 +119,7 @@ impl TestingBeaconStateBuilder { }) .collect(); - let mut state_builder = BeaconStateBuilder::new( + let mut state = BeaconState::genesis( 0, Eth1Data { deposit_root: Hash256::zero(), @@ -131,14 +131,8 @@ impl TestingBeaconStateBuilder { let balances = vec![32_000_000_000; validator_count]; debug!("Importing {} existing validators...", validator_count); - state_builder.import_existing_validators( - validators, - balances, - validator_count as u64, - spec, - ); - - let state = state_builder.build(spec).unwrap(); + state.validator_registry = validators; + state.validator_balances = balances; debug!("BeaconState built."); From 979353f136b7c9c434050483c1a244c1bdb48d12 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 17 Mar 2019 23:23:45 +1100 Subject: [PATCH 142/144] Make separate errors for epoch cache. Helps with troubleshooting. --- eth2/types/src/beacon_state.rs | 12 ++++++++--- eth2/types/src/beacon_state/epoch_cache.rs | 23 ++++++++++++++++------ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index d7dbda782e..1a77d34490 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -1,13 +1,12 @@ -use self::epoch_cache::EpochCache; +use self::epoch_cache::{EpochCache, Error as EpochCacheError}; use crate::test_utils::TestRandom; use crate::*; use int_to_bytes::int_to_bytes32; use pubkey_cache::PubkeyCache; use rand::RngCore; use serde_derive::{Deserialize, Serialize}; -use ssz::{hash, ssz_encode, SignedRoot, TreeHash}; +use ssz::{hash, ssz_encode, TreeHash}; use ssz_derive::{Decode, Encode, TreeHash}; -use std::collections::HashMap; use test_random_derive::TestRandom; mod epoch_cache; @@ -44,6 +43,7 @@ pub enum Error { registry_len: usize, }, RelativeEpochError(RelativeEpochError), + EpochCacheError(EpochCacheError), } macro_rules! safe_add_assign { @@ -883,3 +883,9 @@ impl From for Error { Error::RelativeEpochError(e) } } + +impl From for Error { + fn from(e: EpochCacheError) -> Error { + Error::EpochCacheError(e) + } +} diff --git a/eth2/types/src/beacon_state/epoch_cache.rs b/eth2/types/src/beacon_state/epoch_cache.rs index 0dbdf40546..75d791e8f3 100644 --- a/eth2/types/src/beacon_state/epoch_cache.rs +++ b/eth2/types/src/beacon_state/epoch_cache.rs @@ -1,9 +1,16 @@ -use super::{BeaconState, Error}; +use super::BeaconState; use crate::*; use honey_badger_split::SplitExt; use serde_derive::{Deserialize, Serialize}; use swap_or_not_shuffle::shuffle_list; +#[derive(Debug, PartialEq)] +pub enum Error { + UnableToShuffle, + NoValidators { epoch: Epoch }, + UnableToGenerateSeed, +} + mod tests; #[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)] @@ -212,7 +219,7 @@ impl EpochCrosslinkCommitteesBuilder { active_validator_indices: Vec, registry_change: bool, spec: &ChainSpec, - ) -> Result { + ) -> Result { let current_epoch = state.current_epoch(spec); let next_epoch = state.next_epoch(spec); let committees_per_epoch = spec.get_epoch_committee_count(active_validator_indices.len()); @@ -221,7 +228,9 @@ impl EpochCrosslinkCommitteesBuilder { current_epoch - state.validator_registry_update_epoch; let (seed, shuffling_start_shard) = if registry_change { - let next_seed = state.generate_seed(next_epoch, spec)?; + let next_seed = state + .generate_seed(next_epoch, spec) + .map_err(|_| Error::UnableToGenerateSeed)?; ( next_seed, (state.current_shuffling_start_shard + committees_per_epoch) % spec.shard_count, @@ -229,7 +238,9 @@ impl EpochCrosslinkCommitteesBuilder { } else if (epochs_since_last_registry_update > 1) & epochs_since_last_registry_update.is_power_of_two() { - let next_seed = state.generate_seed(next_epoch, spec)?; + let next_seed = state + .generate_seed(next_epoch, spec) + .map_err(|_| Error::UnableToGenerateSeed)?; (next_seed, state.current_shuffling_start_shard) } else { ( @@ -247,9 +258,9 @@ impl EpochCrosslinkCommitteesBuilder { }) } - pub fn build(self, spec: &ChainSpec) -> Result { + pub fn build(self, spec: &ChainSpec) -> Result { if self.active_validator_indices.is_empty() { - return Err(Error::NoValidators); + return Err(Error::NoValidators { epoch: self.epoch }); } let shuffled_active_validator_indices = shuffle_list( From 191761f356bb72b2ed06790360f40d56e0ab3856 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Sun, 17 Mar 2019 23:32:27 +1100 Subject: [PATCH 143/144] Allow epoch cache with zero validators. --- .../per_block_processing/verify_deposit.rs | 3 --- .../process_validator_registry.rs | 4 +-- eth2/types/src/beacon_state/epoch_cache.rs | 25 ++++++++++--------- .../testing_beacon_state_builder.rs | 2 +- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/eth2/state_processing/src/per_block_processing/verify_deposit.rs b/eth2/state_processing/src/per_block_processing/verify_deposit.rs index 2aeab6c5a6..80d8bc24fa 100644 --- a/eth2/state_processing/src/per_block_processing/verify_deposit.rs +++ b/eth2/state_processing/src/per_block_processing/verify_deposit.rs @@ -3,11 +3,8 @@ use hashing::hash; use merkle_proof::verify_merkle_proof; use ssz::ssz_encode; use ssz_derive::Encode; -use std::collections::HashMap; use types::*; -pub type PublicKeyValidatorIndexHashmap = HashMap; - /// Indicates if a `Deposit` is valid to be included in a block in the current epoch of the given /// state. /// diff --git a/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs b/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs index c830bfc243..26ebd60b33 100644 --- a/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs +++ b/eth2/state_processing/src/per_epoch_processing/process_validator_registry.rs @@ -14,7 +14,7 @@ pub fn process_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> state.previous_shuffling_seed = state.current_shuffling_seed; if should_update_validator_registry(state, spec)? { - state.update_validator_registry(spec); + state.update_validator_registry(spec)?; state.current_shuffling_epoch = next_epoch; state.current_shuffling_start_shard = (state.current_shuffling_start_shard @@ -37,7 +37,7 @@ pub fn process_validator_registry(state: &mut BeaconState, spec: &ChainSpec) -> } } - state.process_slashings(spec); + state.process_slashings(spec)?; state.process_exit_queue(spec); Ok(()) diff --git a/eth2/types/src/beacon_state/epoch_cache.rs b/eth2/types/src/beacon_state/epoch_cache.rs index 75d791e8f3..ca8bcc70e9 100644 --- a/eth2/types/src/beacon_state/epoch_cache.rs +++ b/eth2/types/src/beacon_state/epoch_cache.rs @@ -7,7 +7,6 @@ use swap_or_not_shuffle::shuffle_list; #[derive(Debug, PartialEq)] pub enum Error { UnableToShuffle, - NoValidators { epoch: Epoch }, UnableToGenerateSeed, } @@ -259,17 +258,19 @@ impl EpochCrosslinkCommitteesBuilder { } pub fn build(self, spec: &ChainSpec) -> Result { - if self.active_validator_indices.is_empty() { - return Err(Error::NoValidators { epoch: self.epoch }); - } - - let shuffled_active_validator_indices = shuffle_list( - self.active_validator_indices, - spec.shuffle_round_count, - &self.shuffling_seed[..], - true, - ) - .ok_or_else(|| Error::UnableToShuffle)?; + // The shuffler fails on a empty list, so if there are no active validator indices, simply + // return an empty list. + let shuffled_active_validator_indices = if self.active_validator_indices.is_empty() { + vec![] + } else { + shuffle_list( + self.active_validator_indices, + spec.shuffle_round_count, + &self.shuffling_seed[..], + true, + ) + .ok_or_else(|| Error::UnableToShuffle)? + }; let mut committees: Vec> = shuffled_active_validator_indices .honey_badger_split(self.committees_per_epoch as usize) diff --git a/eth2/types/src/test_utils/testing_beacon_state_builder.rs b/eth2/types/src/test_utils/testing_beacon_state_builder.rs index 8180673d16..e76a01e49b 100644 --- a/eth2/types/src/test_utils/testing_beacon_state_builder.rs +++ b/eth2/types/src/test_utils/testing_beacon_state_builder.rs @@ -134,7 +134,7 @@ impl TestingBeaconStateBuilder { state.validator_registry = validators; state.validator_balances = balances; - debug!("BeaconState built."); + debug!("BeaconState initialized."); Self { state, keypairs } } From edeace9e7596b93e30b0955b3b09a32e6b09e0e6 Mon Sep 17 00:00:00 2001 From: Paul Hauner Date: Mon, 18 Mar 2019 16:53:59 +1100 Subject: [PATCH 144/144] Fix issues with building on genesis block --- beacon_node/beacon_chain/src/beacon_chain.rs | 129 +++++++++--------- .../src/per_slot_processing.rs | 38 +++++- eth2/types/src/beacon_block.rs | 24 +++- eth2/types/src/beacon_block_header.rs | 9 ++ eth2/types/src/beacon_state.rs | 76 ++++++++++- 5 files changed, 202 insertions(+), 74 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 1082f6cabf..bf87adf10d 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -82,20 +82,18 @@ where let state_root = genesis_state.canonical_root(); state_store.put(&state_root, &ssz_encode(&genesis_state)[..])?; - let block_root = genesis_block.canonical_root(); + let block_root = genesis_block.into_header().canonical_root(); block_store.put(&block_root, &ssz_encode(&genesis_block)[..])?; let finalized_head = RwLock::new(CheckPoint::new( genesis_block.clone(), block_root, - // TODO: this is a memory waste; remove full clone. genesis_state.clone(), state_root, )); let canonical_head = RwLock::new(CheckPoint::new( genesis_block.clone(), block_root, - // TODO: this is a memory waste; remove full clone. genesis_state.clone(), state_root, )); @@ -190,10 +188,13 @@ where /// processing applied to it. pub fn advance_state(&self, slot: Slot) -> Result<(), SlotProcessingError> { let state_slot = self.state.read().slot; - let head_block_root = self.head().beacon_block_root; + + let latest_block_header = self.head().beacon_block.into_header(); + for _ in state_slot.as_u64()..slot.as_u64() { - per_slot_processing(&mut *self.state.write(), head_block_root, &self.spec)?; + per_slot_processing(&mut *self.state.write(), &latest_block_header, &self.spec)?; } + Ok(()) } @@ -554,66 +555,13 @@ where } } - /// Dumps the entire canonical chain, from the head to genesis to a vector for analysis. - /// - /// This could be a very expensive operation and should only be done in testing/analysis - /// activities. - pub fn chain_dump(&self) -> Result, Error> { - let mut dump = vec![]; - - let mut last_slot = CheckPoint { - beacon_block: self.head().beacon_block.clone(), - beacon_block_root: self.head().beacon_block_root, - beacon_state: self.head().beacon_state.clone(), - beacon_state_root: self.head().beacon_state_root, - }; - - dump.push(last_slot.clone()); - - loop { - let beacon_block_root = last_slot.beacon_block.previous_block_root; - - if beacon_block_root == self.spec.zero_hash { - break; // Genesis has been reached. - } - - let beacon_block = self - .block_store - .get_deserialized(&beacon_block_root)? - .ok_or_else(|| { - Error::DBInconsistent(format!("Missing block {}", beacon_block_root)) - })?; - let beacon_state_root = beacon_block.state_root; - let beacon_state = self - .state_store - .get_deserialized(&beacon_state_root)? - .ok_or_else(|| { - Error::DBInconsistent(format!("Missing state {}", beacon_state_root)) - })?; - - let slot = CheckPoint { - beacon_block, - beacon_block_root, - beacon_state, - beacon_state_root, - }; - - dump.push(slot.clone()); - last_slot = slot; - } - - dump.reverse(); - - Ok(dump) - } - /// Accept some block and attempt to add it to block DAG. /// /// Will accept blocks from prior slots, however it will reject any block from a future slot. pub fn process_block(&self, block: BeaconBlock) -> Result { debug!("Processing block with slot {}...", block.slot); - let block_root = block.canonical_root(); + let block_root = block.into_header().canonical_root(); let present_slot = self.present_slot(); @@ -648,8 +596,10 @@ where // Transition the parent state to the present slot. let mut state = parent_state; + println!("parent process state: {:?}", state.latest_block_header); + let previous_block_header = parent_block.into_header(); for _ in state.slot.as_u64()..present_slot.as_u64() { - if let Err(e) = per_slot_processing(&mut state, parent_block_root, &self.spec) { + if let Err(e) = per_slot_processing(&mut state, &previous_block_header, &self.spec) { return Ok(BlockProcessingOutcome::InvalidBlock( InvalidBlock::SlotProcessingError(e), )); @@ -664,6 +614,8 @@ where )); } + println!("process state: {:?}", state.latest_block_header); + let state_root = state.canonical_root(); if block.state_root != state_root { @@ -726,7 +678,7 @@ where ); let previous_block_root = *state - .get_block_root(state.slot.saturating_sub(1_u64), &self.spec) + .get_block_root(state.slot - 1, &self.spec) .map_err(|_| BlockProductionError::UnableToGetBlockRootFromState)?; let mut block = BeaconBlock { @@ -754,6 +706,8 @@ where per_block_processing_without_verifying_block_signature(&mut state, &block, &self.spec)?; + println!("produce state: {:?}", state.latest_block_header); + let state_root = state.canonical_root(); block.state_root = state_root; @@ -788,6 +742,59 @@ where Ok(()) } + + /// Dumps the entire canonical chain, from the head to genesis to a vector for analysis. + /// + /// This could be a very expensive operation and should only be done in testing/analysis + /// activities. + pub fn chain_dump(&self) -> Result, Error> { + let mut dump = vec![]; + + let mut last_slot = CheckPoint { + beacon_block: self.head().beacon_block.clone(), + beacon_block_root: self.head().beacon_block_root, + beacon_state: self.head().beacon_state.clone(), + beacon_state_root: self.head().beacon_state_root, + }; + + dump.push(last_slot.clone()); + + loop { + let beacon_block_root = last_slot.beacon_block.previous_block_root; + + if beacon_block_root == self.spec.zero_hash { + break; // Genesis has been reached. + } + + let beacon_block = self + .block_store + .get_deserialized(&beacon_block_root)? + .ok_or_else(|| { + Error::DBInconsistent(format!("Missing block {}", beacon_block_root)) + })?; + let beacon_state_root = beacon_block.state_root; + let beacon_state = self + .state_store + .get_deserialized(&beacon_state_root)? + .ok_or_else(|| { + Error::DBInconsistent(format!("Missing state {}", beacon_state_root)) + })?; + + let slot = CheckPoint { + beacon_block, + beacon_block_root, + beacon_state, + beacon_state_root, + }; + + dump.push(slot.clone()); + last_slot = slot; + } + + dump.reverse(); + + Ok(dump) + } } impl From for Error { diff --git a/eth2/state_processing/src/per_slot_processing.rs b/eth2/state_processing/src/per_slot_processing.rs index aafc7166ad..a90c5b4088 100644 --- a/eth2/state_processing/src/per_slot_processing.rs +++ b/eth2/state_processing/src/per_slot_processing.rs @@ -1,5 +1,6 @@ use crate::*; -use types::{BeaconState, BeaconStateError, ChainSpec, Hash256}; +use ssz::TreeHash; +use types::*; #[derive(Debug, PartialEq)] pub enum Error { @@ -12,9 +13,11 @@ pub enum Error { /// Spec v0.5.0 pub fn per_slot_processing( state: &mut BeaconState, - previous_block_root: Hash256, + latest_block_header: &BeaconBlockHeader, spec: &ChainSpec, ) -> Result<(), Error> { + cache_state(state, latest_block_header, spec)?; + if (state.slot + 1) % spec.slots_per_epoch == 0 { per_epoch_processing(state, spec)?; state.advance_caches(); @@ -22,6 +25,37 @@ pub fn per_slot_processing( state.slot += 1; + let latest_block_root = Hash256::from_slice(&state.latest_block_header.hash_tree_root()[..]); + state.set_block_root(state.slot - 1, latest_block_root, spec)?; + + Ok(()) +} + +fn cache_state( + state: &mut BeaconState, + latest_block_header: &BeaconBlockHeader, + spec: &ChainSpec, +) -> Result<(), Error> { + let previous_slot_state_root = Hash256::from_slice(&state.hash_tree_root()[..]); + + // Note: increment the state slot here to allow use of our `state_root` and `block_root` + // getter/setter functions. + // + // This is a bit hacky, however it gets the job safely without lots of code. + let previous_slot = state.slot; + state.slot += 1; + + // Store the previous slot's post-state transition root. + if state.latest_block_header.state_root == spec.zero_hash { + state.latest_block_header.state_root = previous_slot_state_root + } + + let latest_block_root = Hash256::from_slice(&latest_block_header.hash_tree_root()[..]); + state.set_block_root(previous_slot, latest_block_root, spec)?; + + // Set the state slot back to what it should be. + state.slot -= 1; + Ok(()) } diff --git a/eth2/types/src/beacon_block.rs b/eth2/types/src/beacon_block.rs index 2dcf91d955..b966751ed8 100644 --- a/eth2/types/src/beacon_block.rs +++ b/eth2/types/src/beacon_block.rs @@ -63,16 +63,32 @@ impl BeaconBlock { Hash256::from_slice(&self.hash_tree_root()[..]) } + /// Returns a full `BeaconBlockHeader` of this block. + /// + /// Note: This method is used instead of an `Into` impl to avoid a `Clone` of an entire block + /// when you want to have the block _and_ the header. + /// + /// Note: performs a full tree-hash of `self.body`. + /// + /// Spec v0.5.0 + pub fn into_header(&self) -> BeaconBlockHeader { + BeaconBlockHeader { + slot: self.slot, + previous_block_root: self.previous_block_root, + state_root: self.state_root, + block_body_root: Hash256::from_slice(&self.body.hash_tree_root()[..]), + signature: self.signature.clone(), + } + } + /// Returns a "temporary" header, where the `state_root` is `spec.zero_hash`. /// /// Spec v0.5.0 pub fn into_temporary_header(&self, spec: &ChainSpec) -> BeaconBlockHeader { BeaconBlockHeader { - slot: self.slot, - previous_block_root: self.previous_block_root, state_root: spec.zero_hash, - block_body_root: Hash256::from_slice(&self.hash_tree_root()), - signature: self.signature.clone(), + signature: spec.empty_signature.clone(), + ..self.into_header() } } } diff --git a/eth2/types/src/beacon_block_header.rs b/eth2/types/src/beacon_block_header.rs index 029c7e56b2..3d8b08cc84 100644 --- a/eth2/types/src/beacon_block_header.rs +++ b/eth2/types/src/beacon_block_header.rs @@ -30,6 +30,15 @@ pub struct BeaconBlockHeader { pub signature: Signature, } +impl BeaconBlockHeader { + /// Returns the `hash_tree_root` of the header. + /// + /// Spec v0.5.0 + pub fn canonical_root(&self) -> Hash256 { + Hash256::from_slice(&self.hash_tree_root()[..]) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/eth2/types/src/beacon_state.rs b/eth2/types/src/beacon_state.rs index 1a77d34490..1b2424774d 100644 --- a/eth2/types/src/beacon_state.rs +++ b/eth2/types/src/beacon_state.rs @@ -35,6 +35,7 @@ pub enum Error { InsufficientAttestations, InsufficientCommittees, InsufficientSlashedBalances, + InsufficientStateRoots, NoCommitteeForShard, EpochCacheUninitialized(RelativeEpoch), PubkeyCacheInconsistent, @@ -425,6 +426,22 @@ impl BeaconState { .ok_or_else(|| Error::NoCommitteeForShard)?) } + /// Safely obtains the index for latest block roots, given some `slot`. + /// + /// Spec v0.5.0 + fn get_latest_block_roots_index(&self, slot: Slot, spec: &ChainSpec) -> Result { + if (slot < self.slot) && (self.slot <= slot + spec.slots_per_historical_root as u64) { + let i = slot.as_usize() % spec.slots_per_historical_root; + if i >= self.latest_block_roots.len() { + Err(Error::InsufficientStateRoots) + } else { + Ok(i) + } + } else { + Err(BeaconStateError::SlotOutOfBounds) + } + } + /// Return the block root at a recent `slot`. /// /// Spec v0.5.0 @@ -433,13 +450,21 @@ impl BeaconState { slot: Slot, spec: &ChainSpec, ) -> Result<&Hash256, BeaconStateError> { - if (self.slot <= slot + spec.slots_per_historical_root as u64) && (slot < self.slot) { - self.latest_block_roots - .get(slot.as_usize() % spec.slots_per_historical_root) - .ok_or_else(|| Error::InsufficientBlockRoots) - } else { - Err(Error::EpochOutOfBounds) - } + let i = self.get_latest_block_roots_index(slot, spec)?; + Ok(&self.latest_block_roots[i]) + } + + /// Sets the block root for some given slot. + /// + /// Spec v0.5.0 + pub fn set_block_root( + &mut self, + slot: Slot, + block_root: Hash256, + spec: &ChainSpec, + ) -> Result<(), BeaconStateError> { + let i = self.get_latest_block_roots_index(slot, spec)?; + Ok(self.latest_block_roots[i] = block_root) } /// XOR-assigns the existing `epoch` randao mix with the hash of the `signature`. @@ -506,6 +531,43 @@ impl BeaconState { } } + /// Safely obtains the index for latest state roots, given some `slot`. + /// + /// Spec v0.5.0 + fn get_latest_state_roots_index(&self, slot: Slot, spec: &ChainSpec) -> Result { + if (slot < self.slot) && (self.slot <= slot + spec.slots_per_historical_root as u64) { + let i = slot.as_usize() % spec.slots_per_historical_root; + if i >= self.latest_state_roots.len() { + Err(Error::InsufficientStateRoots) + } else { + Ok(i) + } + } else { + Err(BeaconStateError::SlotOutOfBounds) + } + } + + /// Gets the state root for some slot. + /// + /// Spec v0.5.0 + pub fn get_state_root(&mut self, slot: Slot, spec: &ChainSpec) -> Result<&Hash256, Error> { + let i = self.get_latest_state_roots_index(slot, spec)?; + Ok(&self.latest_state_roots[i]) + } + + /// Sets the latest state root for slot. + /// + /// Spec v0.5.0 + pub fn set_state_root( + &mut self, + slot: Slot, + state_root: Hash256, + spec: &ChainSpec, + ) -> Result<(), Error> { + let i = self.get_latest_state_roots_index(slot, spec)?; + Ok(self.latest_state_roots[i] = state_root) + } + /// Generate a seed for the given `epoch`. /// /// Spec v0.4.0