Merge branch 'v0.6.1' into master.
On behalf of Paul/Michael.
This commit is contained in:
commit
20b7bdda4a
@ -17,14 +17,25 @@ check-fmt:
|
|||||||
|
|
||||||
test-dev:
|
test-dev:
|
||||||
stage: test
|
stage: test
|
||||||
|
variables:
|
||||||
|
GIT_SUBMODULE_STRATEGY: normal
|
||||||
script:
|
script:
|
||||||
- cargo test --verbose --all
|
- cargo test --verbose --all
|
||||||
|
|
||||||
test-release:
|
test-release:
|
||||||
stage: test
|
stage: test
|
||||||
|
variables:
|
||||||
|
GIT_SUBMODULE_STRATEGY: normal
|
||||||
script:
|
script:
|
||||||
- cargo test --verbose --all --release
|
- cargo test --verbose --all --release
|
||||||
|
|
||||||
|
test-fake-crypto:
|
||||||
|
stage: test
|
||||||
|
variables:
|
||||||
|
GIT_SUBMODULE_STRATEGY: normal
|
||||||
|
script:
|
||||||
|
- cargo test --manifest-path tests/ef_tests/Cargo.toml --release --features fake_crypto
|
||||||
|
|
||||||
documentation:
|
documentation:
|
||||||
stage: document
|
stage: document
|
||||||
script:
|
script:
|
||||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "tests/ef_tests/eth2.0-spec-tests"]
|
||||||
|
path = tests/ef_tests/eth2.0-spec-tests
|
||||||
|
url = https://github.com/ethereum/eth2.0-spec-tests
|
@ -7,6 +7,9 @@ members = [
|
|||||||
"eth2/utils/bls",
|
"eth2/utils/bls",
|
||||||
"eth2/utils/boolean-bitfield",
|
"eth2/utils/boolean-bitfield",
|
||||||
"eth2/utils/cached_tree_hash",
|
"eth2/utils/cached_tree_hash",
|
||||||
|
"eth2/utils/compare_fields",
|
||||||
|
"eth2/utils/compare_fields_derive",
|
||||||
|
"eth2/utils/eth2_config",
|
||||||
"eth2/utils/fixed_len_vec",
|
"eth2/utils/fixed_len_vec",
|
||||||
"eth2/utils/hashing",
|
"eth2/utils/hashing",
|
||||||
"eth2/utils/honey-badger-split",
|
"eth2/utils/honey-badger-split",
|
||||||
@ -19,7 +22,6 @@ members = [
|
|||||||
"eth2/utils/swap_or_not_shuffle",
|
"eth2/utils/swap_or_not_shuffle",
|
||||||
"eth2/utils/tree_hash",
|
"eth2/utils/tree_hash",
|
||||||
"eth2/utils/tree_hash_derive",
|
"eth2/utils/tree_hash_derive",
|
||||||
"eth2/utils/fisher_yates_shuffle",
|
|
||||||
"eth2/utils/test_random_derive",
|
"eth2/utils/test_random_derive",
|
||||||
"beacon_node",
|
"beacon_node",
|
||||||
"beacon_node/store",
|
"beacon_node/store",
|
||||||
@ -30,6 +32,7 @@ members = [
|
|||||||
"beacon_node/rpc",
|
"beacon_node/rpc",
|
||||||
"beacon_node/version",
|
"beacon_node/version",
|
||||||
"beacon_node/beacon_chain",
|
"beacon_node/beacon_chain",
|
||||||
|
"tests/ef_tests",
|
||||||
"protos",
|
"protos",
|
||||||
"validator_client",
|
"validator_client",
|
||||||
"account_manager",
|
"account_manager",
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
FROM rust:latest
|
FROM rust:latest
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y clang libclang-dev cmake build-essential git unzip autoconf libtool awscli
|
RUN apt-get update && apt-get install -y clang libclang-dev cmake build-essential git unzip autoconf libtool awscli software-properties-common
|
||||||
|
|
||||||
|
RUN add-apt-repository -y ppa:git-core/ppa
|
||||||
|
|
||||||
|
RUN curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash
|
||||||
|
|
||||||
|
RUN apt-get install -y git-lfs
|
||||||
|
|
||||||
RUN git clone https://github.com/google/protobuf.git && \
|
RUN git clone https://github.com/google/protobuf.git && \
|
||||||
cd protobuf && \
|
cd protobuf && \
|
||||||
|
@ -12,3 +12,4 @@ slog-term = "^2.4.0"
|
|||||||
slog-async = "^2.3.0"
|
slog-async = "^2.3.0"
|
||||||
validator_client = { path = "../validator_client" }
|
validator_client = { path = "../validator_client" }
|
||||||
types = { path = "../eth2/types" }
|
types = { path = "../eth2/types" }
|
||||||
|
eth2_config = { path = "../eth2/utils/eth2_config" }
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
use bls::Keypair;
|
use bls::Keypair;
|
||||||
use clap::{App, Arg, SubCommand};
|
use clap::{App, Arg, SubCommand};
|
||||||
use slog::{debug, info, o, Drain};
|
use eth2_config::get_data_dir;
|
||||||
|
use slog::{crit, debug, info, o, Drain};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use types::test_utils::generate_deterministic_keypair;
|
use types::test_utils::generate_deterministic_keypair;
|
||||||
use validator_client::Config as ValidatorClientConfig;
|
use validator_client::Config as ValidatorClientConfig;
|
||||||
|
|
||||||
|
pub const DEFAULT_DATA_DIR: &str = ".lighthouse-account-manager";
|
||||||
|
pub const CLIENT_CONFIG_FILENAME: &str = "account-manager.toml";
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Logging
|
// Logging
|
||||||
let decorator = slog_term::TermDecorator::new().build();
|
let decorator = slog_term::TermDecorator::new().build();
|
||||||
@ -20,6 +24,7 @@ fn main() {
|
|||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("datadir")
|
Arg::with_name("datadir")
|
||||||
.long("datadir")
|
.long("datadir")
|
||||||
|
.short("d")
|
||||||
.value_name("DIR")
|
.value_name("DIR")
|
||||||
.help("Data directory for keys and databases.")
|
.help("Data directory for keys and databases.")
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
@ -43,20 +48,98 @@ fn main() {
|
|||||||
.help("The index of the validator, for which the test key is generated")
|
.help("The index of the validator, for which the test key is generated")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.required(true),
|
.required(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("validator count")
|
||||||
|
.long("validator_count")
|
||||||
|
.short("n")
|
||||||
|
.value_name("validator_count")
|
||||||
|
.help("If supplied along with `index`, generates keys `i..i + n`.")
|
||||||
|
.takes_value(true)
|
||||||
|
.default_value("1"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
let config = ValidatorClientConfig::parse_args(&matches, &log)
|
let data_dir = match get_data_dir(&matches, PathBuf::from(DEFAULT_DATA_DIR)) {
|
||||||
.expect("Unable to build a configuration for the account manager.");
|
Ok(dir) => dir,
|
||||||
|
Err(e) => {
|
||||||
|
crit!(log, "Failed to initialize data dir"; "error" => format!("{:?}", e));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut client_config = ValidatorClientConfig::default();
|
||||||
|
|
||||||
|
if let Err(e) = client_config.apply_cli_args(&matches) {
|
||||||
|
crit!(log, "Failed to apply CLI args"; "error" => format!("{:?}", e));
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure the `data_dir` in the config matches that supplied to the CLI.
|
||||||
|
client_config.data_dir = data_dir.clone();
|
||||||
|
|
||||||
|
// Update the client config with any CLI args.
|
||||||
|
match client_config.apply_cli_args(&matches) {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(s) => {
|
||||||
|
crit!(log, "Failed to parse ClientConfig CLI arguments"; "error" => s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Log configuration
|
// Log configuration
|
||||||
info!(log, "";
|
info!(log, "";
|
||||||
"data_dir" => &config.data_dir.to_str());
|
"data_dir" => &client_config.data_dir.to_str());
|
||||||
|
|
||||||
match matches.subcommand() {
|
match matches.subcommand() {
|
||||||
("generate", Some(_gen_m)) => {
|
("generate", Some(_)) => generate_random(&client_config, &log),
|
||||||
let keypair = Keypair::random();
|
("generate_deterministic", Some(m)) => {
|
||||||
|
if let Some(string) = m.value_of("validator index") {
|
||||||
|
let i: usize = string.parse().expect("Invalid validator index");
|
||||||
|
if let Some(string) = m.value_of("validator count") {
|
||||||
|
let n: usize = string.parse().expect("Invalid end validator count");
|
||||||
|
|
||||||
|
let indices: Vec<usize> = (i..i + n).collect();
|
||||||
|
generate_deterministic_multiple(&indices, &client_config, &log)
|
||||||
|
} else {
|
||||||
|
generate_deterministic(i, &client_config, &log)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!(
|
||||||
|
"The account manager must be run with a subcommand. See help for more information."
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_random(config: &ValidatorClientConfig, log: &slog::Logger) {
|
||||||
|
save_key(&Keypair::random(), config, log)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_deterministic_multiple(
|
||||||
|
validator_indices: &[usize],
|
||||||
|
config: &ValidatorClientConfig,
|
||||||
|
log: &slog::Logger,
|
||||||
|
) {
|
||||||
|
for validator_index in validator_indices {
|
||||||
|
generate_deterministic(*validator_index, config, log)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_deterministic(
|
||||||
|
validator_index: usize,
|
||||||
|
config: &ValidatorClientConfig,
|
||||||
|
log: &slog::Logger,
|
||||||
|
) {
|
||||||
|
save_key(
|
||||||
|
&generate_deterministic_keypair(validator_index),
|
||||||
|
config,
|
||||||
|
log,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_key(keypair: &Keypair, config: &ValidatorClientConfig, log: &slog::Logger) {
|
||||||
let key_path: PathBuf = config
|
let key_path: PathBuf = config
|
||||||
.save_key(&keypair)
|
.save_key(&keypair)
|
||||||
.expect("Unable to save newly generated private key.");
|
.expect("Unable to save newly generated private key.");
|
||||||
@ -67,25 +150,3 @@ fn main() {
|
|||||||
key_path.to_string_lossy()
|
key_path.to_string_lossy()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
("generate_deterministic", Some(gen_d_matches)) => {
|
|
||||||
let validator_index = gen_d_matches
|
|
||||||
.value_of("validator index")
|
|
||||||
.expect("Validator index required.")
|
|
||||||
.parse::<u64>()
|
|
||||||
.expect("Invalid validator index.") as usize;
|
|
||||||
let keypair = generate_deterministic_keypair(validator_index);
|
|
||||||
let key_path: PathBuf = config
|
|
||||||
.save_key(&keypair)
|
|
||||||
.expect("Unable to save newly generated deterministic private key.");
|
|
||||||
debug!(
|
|
||||||
log,
|
|
||||||
"Deterministic Keypair generated {:?}, saved to: {:?}",
|
|
||||||
keypair.identifier(),
|
|
||||||
key_path.to_string_lossy()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => panic!(
|
|
||||||
"The account manager must be run with a subcommand. See help for more information."
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -5,11 +5,14 @@ authors = ["Paul Hauner <paul@paulhauner.com>", "Age Manning <Age@AgeManning.com
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
eth2_config = { path = "../eth2/utils/eth2_config" }
|
||||||
types = { path = "../eth2/types" }
|
types = { path = "../eth2/types" }
|
||||||
|
toml = "^0.5"
|
||||||
store = { path = "./store" }
|
store = { path = "./store" }
|
||||||
client = { path = "client" }
|
client = { path = "client" }
|
||||||
version = { path = "version" }
|
version = { path = "version" }
|
||||||
clap = "2.32.0"
|
clap = "2.32.0"
|
||||||
|
serde = "1.0"
|
||||||
slog = { version = "^2.2.3" , features = ["max_level_trace", "release_max_level_debug"] }
|
slog = { version = "^2.2.3" , features = ["max_level_trace", "release_max_level_debug"] }
|
||||||
slog-term = "^2.4.0"
|
slog-term = "^2.4.0"
|
||||||
slog-async = "^2.3.0"
|
slog-async = "^2.3.0"
|
||||||
|
@ -13,6 +13,7 @@ failure_derive = "0.1"
|
|||||||
hashing = { path = "../../eth2/utils/hashing" }
|
hashing = { path = "../../eth2/utils/hashing" }
|
||||||
fork_choice = { path = "../../eth2/fork_choice" }
|
fork_choice = { path = "../../eth2/fork_choice" }
|
||||||
parking_lot = "0.7"
|
parking_lot = "0.7"
|
||||||
|
prometheus = "^0.6"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
operation_pool = { path = "../../eth2/operation_pool" }
|
operation_pool = { path = "../../eth2/operation_pool" }
|
||||||
env_logger = "0.6"
|
env_logger = "0.6"
|
||||||
@ -21,6 +22,7 @@ serde_derive = "1.0"
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
slot_clock = { path = "../../eth2/utils/slot_clock" }
|
slot_clock = { path = "../../eth2/utils/slot_clock" }
|
||||||
ssz = { path = "../../eth2/utils/ssz" }
|
ssz = { path = "../../eth2/utils/ssz" }
|
||||||
|
ssz_derive = { path = "../../eth2/utils/ssz_derive" }
|
||||||
state_processing = { path = "../../eth2/state_processing" }
|
state_processing = { path = "../../eth2/state_processing" }
|
||||||
tree_hash = { path = "../../eth2/utils/tree_hash" }
|
tree_hash = { path = "../../eth2/utils/tree_hash" }
|
||||||
types = { path = "../../eth2/types" }
|
types = { path = "../../eth2/types" }
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
use crate::checkpoint::CheckPoint;
|
use crate::checkpoint::CheckPoint;
|
||||||
use crate::errors::{BeaconChainError as Error, BlockProductionError};
|
use crate::errors::{BeaconChainError as Error, BlockProductionError};
|
||||||
|
use crate::iter::{BlockIterator, BlockRootsIterator};
|
||||||
|
use crate::metrics::Metrics;
|
||||||
|
use crate::persisted_beacon_chain::{PersistedBeaconChain, BEACON_CHAIN_DB_KEY};
|
||||||
use fork_choice::{ForkChoice, ForkChoiceError};
|
use fork_choice::{ForkChoice, ForkChoiceError};
|
||||||
use log::{debug, trace};
|
use log::{debug, trace};
|
||||||
use operation_pool::DepositInsertStatus;
|
use operation_pool::DepositInsertStatus;
|
||||||
@ -12,20 +15,19 @@ use state_processing::per_block_processing::errors::{
|
|||||||
};
|
};
|
||||||
use state_processing::{
|
use state_processing::{
|
||||||
per_block_processing, per_block_processing_without_verifying_block_signature,
|
per_block_processing, per_block_processing_without_verifying_block_signature,
|
||||||
per_slot_processing, BlockProcessingError, SlotProcessingError,
|
per_slot_processing, BlockProcessingError,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use store::{Error as DBError, Store};
|
use store::{Error as DBError, Store};
|
||||||
|
use tree_hash::TreeHash;
|
||||||
use types::*;
|
use types::*;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum ValidBlock {
|
pub enum BlockProcessingOutcome {
|
||||||
/// The block was successfully processed.
|
/// Block was valid and imported into the block graph.
|
||||||
Processed,
|
Processed,
|
||||||
}
|
/// The blocks parent_root is unknown.
|
||||||
|
ParentUnknown { parent: Hash256 },
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum InvalidBlock {
|
|
||||||
/// The block slot is greater than the present slot.
|
/// The block slot is greater than the present slot.
|
||||||
FutureSlot {
|
FutureSlot {
|
||||||
present_slot: Slot,
|
present_slot: Slot,
|
||||||
@ -33,68 +35,47 @@ pub enum InvalidBlock {
|
|||||||
},
|
},
|
||||||
/// The block state_root does not match the generated state.
|
/// The block state_root does not match the generated state.
|
||||||
StateRootMismatch,
|
StateRootMismatch,
|
||||||
/// The blocks parent_root is unknown.
|
/// The block was a genesis block, these blocks cannot be re-imported.
|
||||||
ParentUnknown,
|
GenesisBlock,
|
||||||
/// There was an error whilst advancing the parent state to the present slot. This condition
|
/// The slot is finalized, no need to import.
|
||||||
/// should not occur, it likely represents an internal error.
|
FinalizedSlot,
|
||||||
SlotProcessingError(SlotProcessingError),
|
/// Block is already known, no need to re-import.
|
||||||
|
BlockIsAlreadyKnown,
|
||||||
/// The block could not be applied to the state, it is invalid.
|
/// The block could not be applied to the state, it is invalid.
|
||||||
PerBlockProcessingError(BlockProcessingError),
|
PerBlockProcessingError(BlockProcessingError),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum BlockProcessingOutcome {
|
|
||||||
/// The block was successfully validated.
|
|
||||||
ValidBlock(ValidBlock),
|
|
||||||
/// The block was not successfully validated.
|
|
||||||
InvalidBlock(InvalidBlock),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BlockProcessingOutcome {
|
|
||||||
/// Returns `true` if the block was objectively invalid and we should disregard the peer who
|
|
||||||
/// sent it.
|
|
||||||
pub fn is_invalid(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
BlockProcessingOutcome::ValidBlock(_) => false,
|
|
||||||
BlockProcessingOutcome::InvalidBlock(r) => match r {
|
|
||||||
InvalidBlock::FutureSlot { .. } => true,
|
|
||||||
InvalidBlock::StateRootMismatch => true,
|
|
||||||
InvalidBlock::ParentUnknown => false,
|
|
||||||
InvalidBlock::SlotProcessingError(_) => false,
|
|
||||||
InvalidBlock::PerBlockProcessingError(e) => match e {
|
|
||||||
BlockProcessingError::Invalid(_) => true,
|
|
||||||
BlockProcessingError::BeaconStateError(_) => false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if the block was successfully processed and can be removed from any import
|
|
||||||
/// queues or temporary storage.
|
|
||||||
pub fn sucessfully_processed(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
BlockProcessingOutcome::ValidBlock(_) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait BeaconChainTypes {
|
pub trait BeaconChainTypes {
|
||||||
type Store: store::Store;
|
type Store: store::Store;
|
||||||
type SlotClock: slot_clock::SlotClock;
|
type SlotClock: slot_clock::SlotClock;
|
||||||
type ForkChoice: fork_choice::ForkChoice;
|
type ForkChoice: fork_choice::ForkChoice<Self::Store>;
|
||||||
type EthSpec: types::EthSpec;
|
type EthSpec: types::EthSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents the "Beacon Chain" component of Ethereum 2.0. Allows import of blocks and block
|
||||||
|
/// operations and chooses a canonical head.
|
||||||
pub struct BeaconChain<T: BeaconChainTypes> {
|
pub struct BeaconChain<T: BeaconChainTypes> {
|
||||||
pub store: Arc<T::Store>,
|
|
||||||
pub slot_clock: T::SlotClock,
|
|
||||||
pub op_pool: OperationPool<T::EthSpec>,
|
|
||||||
canonical_head: RwLock<CheckPoint<T::EthSpec>>,
|
|
||||||
finalized_head: RwLock<CheckPoint<T::EthSpec>>,
|
|
||||||
pub state: RwLock<BeaconState<T::EthSpec>>,
|
|
||||||
pub spec: ChainSpec,
|
pub spec: ChainSpec,
|
||||||
|
/// Persistent storage for blocks, states, etc. Typically an on-disk store, such as LevelDB.
|
||||||
|
pub store: Arc<T::Store>,
|
||||||
|
/// Reports the current slot, typically based upon the system clock.
|
||||||
|
pub slot_clock: T::SlotClock,
|
||||||
|
/// Stores all operations (e.g., `Attestation`, `Deposit`, etc) that are candidates for
|
||||||
|
/// inclusion in a block.
|
||||||
|
pub op_pool: OperationPool<T::EthSpec>,
|
||||||
|
/// Stores a "snapshot" of the chain at the time the head-of-the-chain block was recieved.
|
||||||
|
canonical_head: RwLock<CheckPoint<T::EthSpec>>,
|
||||||
|
/// The same state from `self.canonical_head`, but updated at the start of each slot with a
|
||||||
|
/// skip slot if no block is recieved. This is effectively a cache that avoids repeating calls
|
||||||
|
/// to `per_slot_processing`.
|
||||||
|
state: RwLock<BeaconState<T::EthSpec>>,
|
||||||
|
/// The root of the genesis block.
|
||||||
|
genesis_block_root: Hash256,
|
||||||
|
/// A state-machine that is updated with information from the network and chooses a canonical
|
||||||
|
/// head block.
|
||||||
pub fork_choice: RwLock<T::ForkChoice>,
|
pub fork_choice: RwLock<T::ForkChoice>,
|
||||||
|
/// Stores metrics about this `BeaconChain`.
|
||||||
|
pub metrics: Metrics,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: BeaconChainTypes> BeaconChain<T> {
|
impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||||
@ -110,18 +91,16 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
let state_root = genesis_state.canonical_root();
|
let state_root = genesis_state.canonical_root();
|
||||||
store.put(&state_root, &genesis_state)?;
|
store.put(&state_root, &genesis_state)?;
|
||||||
|
|
||||||
let block_root = genesis_block.block_header().canonical_root();
|
let genesis_block_root = genesis_block.block_header().canonical_root();
|
||||||
store.put(&block_root, &genesis_block)?;
|
store.put(&genesis_block_root, &genesis_block)?;
|
||||||
|
|
||||||
|
// Also store the genesis block under the `ZERO_HASH` key.
|
||||||
|
let genesis_block_root = genesis_block.block_header().canonical_root();
|
||||||
|
store.put(&spec.zero_hash, &genesis_block)?;
|
||||||
|
|
||||||
let finalized_head = RwLock::new(CheckPoint::new(
|
|
||||||
genesis_block.clone(),
|
|
||||||
block_root,
|
|
||||||
genesis_state.clone(),
|
|
||||||
state_root,
|
|
||||||
));
|
|
||||||
let canonical_head = RwLock::new(CheckPoint::new(
|
let canonical_head = RwLock::new(CheckPoint::new(
|
||||||
genesis_block.clone(),
|
genesis_block.clone(),
|
||||||
block_root,
|
genesis_block_root,
|
||||||
genesis_state.clone(),
|
genesis_state.clone(),
|
||||||
state_root,
|
state_root,
|
||||||
));
|
));
|
||||||
@ -129,17 +108,65 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
genesis_state.build_all_caches(&spec)?;
|
genesis_state.build_all_caches(&spec)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
spec,
|
||||||
store,
|
store,
|
||||||
slot_clock,
|
slot_clock,
|
||||||
op_pool: OperationPool::new(),
|
op_pool: OperationPool::new(),
|
||||||
state: RwLock::new(genesis_state),
|
state: RwLock::new(genesis_state),
|
||||||
finalized_head,
|
|
||||||
canonical_head,
|
canonical_head,
|
||||||
spec,
|
genesis_block_root,
|
||||||
fork_choice: RwLock::new(fork_choice),
|
fork_choice: RwLock::new(fork_choice),
|
||||||
|
metrics: Metrics::new()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempt to load an existing instance from the given `store`.
|
||||||
|
pub fn from_store(
|
||||||
|
store: Arc<T::Store>,
|
||||||
|
spec: ChainSpec,
|
||||||
|
) -> Result<Option<BeaconChain<T>>, Error> {
|
||||||
|
let key = Hash256::from_slice(&BEACON_CHAIN_DB_KEY.as_bytes());
|
||||||
|
let p: PersistedBeaconChain<T> = match store.get(&key) {
|
||||||
|
Err(e) => return Err(e.into()),
|
||||||
|
Ok(None) => return Ok(None),
|
||||||
|
Ok(Some(p)) => p,
|
||||||
|
};
|
||||||
|
|
||||||
|
let slot_clock = T::SlotClock::new(
|
||||||
|
spec.genesis_slot,
|
||||||
|
p.state.genesis_time,
|
||||||
|
spec.seconds_per_slot,
|
||||||
|
);
|
||||||
|
|
||||||
|
let fork_choice = T::ForkChoice::new(store.clone());
|
||||||
|
|
||||||
|
Ok(Some(BeaconChain {
|
||||||
|
spec,
|
||||||
|
store,
|
||||||
|
slot_clock,
|
||||||
|
op_pool: OperationPool::default(),
|
||||||
|
canonical_head: RwLock::new(p.canonical_head),
|
||||||
|
state: RwLock::new(p.state),
|
||||||
|
fork_choice: RwLock::new(fork_choice),
|
||||||
|
genesis_block_root: p.genesis_block_root,
|
||||||
|
metrics: Metrics::new()?,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to save this instance to `self.store`.
|
||||||
|
pub fn persist(&self) -> Result<(), Error> {
|
||||||
|
let p: PersistedBeaconChain<T> = PersistedBeaconChain {
|
||||||
|
canonical_head: self.canonical_head.read().clone(),
|
||||||
|
genesis_block_root: self.genesis_block_root,
|
||||||
|
state: self.state.read().clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let key = Hash256::from_slice(&BEACON_CHAIN_DB_KEY.as_bytes());
|
||||||
|
self.store.put(&key, &p)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the beacon block body for each beacon block root in `roots`.
|
/// Returns the beacon block body for each beacon block root in `roots`.
|
||||||
///
|
///
|
||||||
/// Fails if any root in `roots` does not have a corresponding block.
|
/// Fails if any root in `roots` does not have a corresponding block.
|
||||||
@ -148,7 +175,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|root| match self.get_block(root)? {
|
.map(|root| match self.get_block(root)? {
|
||||||
Some(block) => Ok(block.body),
|
Some(block) => Ok(block.body),
|
||||||
None => Err(Error::DBInconsistent("Missing block".into())),
|
None => Err(Error::DBInconsistent(format!("Missing block: {}", root))),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@ -169,85 +196,24 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
|
|
||||||
Ok(headers?)
|
Ok(headers?)
|
||||||
}
|
}
|
||||||
|
/// Iterate in reverse (highest to lowest slot) through all blocks from the block at `slot`
|
||||||
/// Returns `count `beacon block roots, starting from `start_slot` with an
|
/// through to the genesis block.
|
||||||
/// interval of `skip` slots between each root.
|
|
||||||
///
|
///
|
||||||
/// ## Errors:
|
/// Returns `None` for headers prior to genesis or when there is an error reading from `Store`.
|
||||||
///
|
///
|
||||||
/// - `SlotOutOfBounds`: Unable to return the full specified range.
|
/// Contains duplicate headers when skip slots are encountered.
|
||||||
/// - `SlotOutOfBounds`: Unable to load a state from the DB.
|
pub fn rev_iter_blocks(&self, slot: Slot) -> BlockIterator<T::EthSpec, T::Store> {
|
||||||
/// - `SlotOutOfBounds`: Start slot is higher than the first slot.
|
BlockIterator::new(self.store.clone(), self.state.read().clone(), slot)
|
||||||
/// - Other: BeaconState` is inconsistent.
|
|
||||||
pub fn get_block_roots(
|
|
||||||
&self,
|
|
||||||
earliest_slot: Slot,
|
|
||||||
count: usize,
|
|
||||||
skip: usize,
|
|
||||||
) -> Result<Vec<Hash256>, Error> {
|
|
||||||
let step_by = Slot::from(skip + 1);
|
|
||||||
|
|
||||||
let mut roots: Vec<Hash256> = vec![];
|
|
||||||
|
|
||||||
// The state for reading block roots. Will be updated with an older state if slots go too
|
|
||||||
// far back in history.
|
|
||||||
let mut state = self.state.read().clone();
|
|
||||||
|
|
||||||
// The final slot in this series, will be reduced by `skip` each loop iteration.
|
|
||||||
let mut slot = earliest_slot + Slot::from(count * (skip + 1)) - 1;
|
|
||||||
|
|
||||||
// If the highest slot requested is that of the current state insert the root of the
|
|
||||||
// head block, unless the head block's slot is not matching.
|
|
||||||
if slot == state.slot && self.head().beacon_block.slot == slot {
|
|
||||||
roots.push(self.head().beacon_block_root);
|
|
||||||
|
|
||||||
slot -= step_by;
|
|
||||||
} else if slot >= state.slot {
|
|
||||||
return Err(BeaconStateError::SlotOutOfBounds.into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
/// Iterates in reverse (highest to lowest slot) through all block roots from `slot` through to
|
||||||
// If the slot is within the range of the current state's block roots, append the root
|
/// genesis.
|
||||||
// to the output vec.
|
///
|
||||||
//
|
/// Returns `None` for roots prior to genesis or when there is an error reading from `Store`.
|
||||||
// If we get `SlotOutOfBounds` error, load the oldest available historic
|
///
|
||||||
// state from the DB.
|
/// Contains duplicate roots when skip slots are encountered.
|
||||||
match state.get_block_root(slot) {
|
pub fn rev_iter_block_roots(&self, slot: Slot) -> BlockRootsIterator<T::EthSpec, T::Store> {
|
||||||
Ok(root) => {
|
BlockRootsIterator::new(self.store.clone(), self.state.read().clone(), slot)
|
||||||
if slot < earliest_slot {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
roots.push(*root);
|
|
||||||
slot -= step_by;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(BeaconStateError::SlotOutOfBounds) => {
|
|
||||||
// Read the earliest historic state in the current slot.
|
|
||||||
let earliest_historic_slot =
|
|
||||||
state.slot - Slot::from(T::EthSpec::slots_per_historical_root());
|
|
||||||
// Load the earlier state from disk.
|
|
||||||
let new_state_root = state.get_state_root(earliest_historic_slot)?;
|
|
||||||
|
|
||||||
// Break if the DB is unable to load the state.
|
|
||||||
state = match self.store.get(&new_state_root) {
|
|
||||||
Ok(Some(state)) => state,
|
|
||||||
_ => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => return Err(e.into()),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the results if they pass a sanity check.
|
|
||||||
if (slot <= earliest_slot) && (roots.len() == count) {
|
|
||||||
// Reverse the ordering of the roots. We extracted them in reverse order to make it
|
|
||||||
// simpler to lookup historic states.
|
|
||||||
//
|
|
||||||
// This is a potential optimisation target.
|
|
||||||
Ok(roots.iter().rev().cloned().collect())
|
|
||||||
} else {
|
|
||||||
Err(BeaconStateError::SlotOutOfBounds.into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the block at the given root, if any.
|
/// Returns the block at the given root, if any.
|
||||||
@ -259,44 +225,16 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
Ok(self.store.get(block_root)?)
|
Ok(self.store.get(block_root)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the canonical head to some new values.
|
/// Update the canonical head to `new_head`.
|
||||||
pub fn update_canonical_head(
|
fn update_canonical_head(&self, new_head: CheckPoint<T::EthSpec>) -> Result<(), Error> {
|
||||||
&self,
|
// Update the checkpoint that stores the head of the chain at the time it received the
|
||||||
new_beacon_block: BeaconBlock,
|
// block.
|
||||||
new_beacon_block_root: Hash256,
|
*self.canonical_head.write() = new_head;
|
||||||
new_beacon_state: BeaconState<T::EthSpec>,
|
|
||||||
new_beacon_state_root: Hash256,
|
|
||||||
) {
|
|
||||||
debug!(
|
|
||||||
"Updating canonical head with block at slot: {}",
|
|
||||||
new_beacon_block.slot
|
|
||||||
);
|
|
||||||
let mut head = self.canonical_head.write();
|
|
||||||
head.update(
|
|
||||||
new_beacon_block,
|
|
||||||
new_beacon_block_root,
|
|
||||||
new_beacon_state,
|
|
||||||
new_beacon_state_root,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a read-lock guarded `CheckPoint` struct for reading the head (as chosen by the
|
// Update the always-at-the-present-slot state we keep around for performance gains.
|
||||||
/// fork-choice rule).
|
*self.state.write() = {
|
||||||
///
|
let mut state = self.canonical_head.read().beacon_state.clone();
|
||||||
/// It is important to note that the `beacon_state` returned may not match the present slot. It
|
|
||||||
/// is the state as it was when the head block was received, which could be some slots prior to
|
|
||||||
/// now.
|
|
||||||
pub fn head(&self) -> RwLockReadGuard<CheckPoint<T::EthSpec>> {
|
|
||||||
self.canonical_head.read()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the canonical `BeaconState` with the supplied state.
|
|
||||||
///
|
|
||||||
/// Advances the chain forward to the present slot. This method is better than just setting
|
|
||||||
/// state and calling `catchup_state` as it will not result in an old state being installed and
|
|
||||||
/// then having it iteratively updated -- in such a case it's possible for another thread to
|
|
||||||
/// find the state at an old slot.
|
|
||||||
pub fn update_state(&self, mut state: BeaconState<T::EthSpec>) -> Result<(), Error> {
|
|
||||||
let present_slot = match self.slot_clock.present_slot() {
|
let present_slot = match self.slot_clock.present_slot() {
|
||||||
Ok(Some(slot)) => slot,
|
Ok(Some(slot)) => slot,
|
||||||
_ => return Err(Error::UnableToReadSlot),
|
_ => return Err(Error::UnableToReadSlot),
|
||||||
@ -309,13 +247,40 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
|
|
||||||
state.build_all_caches(&self.spec)?;
|
state.build_all_caches(&self.spec)?;
|
||||||
|
|
||||||
*self.state.write() = state;
|
state
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save `self` to `self.store`.
|
||||||
|
self.persist()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a read-lock guarded `BeaconState` which is the `canonical_head` that has been
|
||||||
|
/// updated to match the current slot clock.
|
||||||
|
pub fn current_state(&self) -> RwLockReadGuard<BeaconState<T::EthSpec>> {
|
||||||
|
self.state.read()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a read-lock guarded `CheckPoint` struct for reading the head (as chosen by the
|
||||||
|
/// fork-choice rule).
|
||||||
|
///
|
||||||
|
/// It is important to note that the `beacon_state` returned may not match the present slot. It
|
||||||
|
/// is the state as it was when the head block was received, which could be some slots prior to
|
||||||
|
/// now.
|
||||||
|
pub fn head(&self) -> RwLockReadGuard<CheckPoint<T::EthSpec>> {
|
||||||
|
self.canonical_head.read()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the slot of the highest block in the canonical chain.
|
||||||
|
pub fn best_slot(&self) -> Slot {
|
||||||
|
self.canonical_head.read().beacon_block.slot
|
||||||
|
}
|
||||||
|
|
||||||
/// Ensures the current canonical `BeaconState` has been transitioned to match the `slot_clock`.
|
/// Ensures the current canonical `BeaconState` has been transitioned to match the `slot_clock`.
|
||||||
pub fn catchup_state(&self) -> Result<(), Error> {
|
pub fn catchup_state(&self) -> Result<(), Error> {
|
||||||
|
let spec = &self.spec;
|
||||||
|
|
||||||
let present_slot = match self.slot_clock.present_slot() {
|
let present_slot = match self.slot_clock.present_slot() {
|
||||||
Ok(Some(slot)) => slot,
|
Ok(Some(slot)) => slot,
|
||||||
_ => return Err(Error::UnableToReadSlot),
|
_ => return Err(Error::UnableToReadSlot),
|
||||||
@ -326,13 +291,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
// If required, transition the new state to the present slot.
|
// If required, transition the new state to the present slot.
|
||||||
for _ in state.slot.as_u64()..present_slot.as_u64() {
|
for _ in state.slot.as_u64()..present_slot.as_u64() {
|
||||||
// Ensure the next epoch state caches are built in case of an epoch transition.
|
// Ensure the next epoch state caches are built in case of an epoch transition.
|
||||||
state.build_epoch_cache(RelativeEpoch::NextWithoutRegistryChange, &self.spec)?;
|
state.build_committee_cache(RelativeEpoch::Next, spec)?;
|
||||||
state.build_epoch_cache(RelativeEpoch::NextWithRegistryChange, &self.spec)?;
|
|
||||||
|
|
||||||
per_slot_processing(&mut *state, &self.spec)?;
|
per_slot_processing(&mut *state, spec)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.build_all_caches(&self.spec)?;
|
state.build_all_caches(spec)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -346,29 +310,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the justified head to some new values.
|
|
||||||
pub fn update_finalized_head(
|
|
||||||
&self,
|
|
||||||
new_beacon_block: BeaconBlock,
|
|
||||||
new_beacon_block_root: Hash256,
|
|
||||||
new_beacon_state: BeaconState<T::EthSpec>,
|
|
||||||
new_beacon_state_root: Hash256,
|
|
||||||
) {
|
|
||||||
let mut finalized_head = self.finalized_head.write();
|
|
||||||
finalized_head.update(
|
|
||||||
new_beacon_block,
|
|
||||||
new_beacon_block_root,
|
|
||||||
new_beacon_state,
|
|
||||||
new_beacon_state_root,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a read-lock guarded `CheckPoint` struct for reading the justified head (as chosen,
|
|
||||||
/// indirectly, by the fork-choice rule).
|
|
||||||
pub fn finalized_head(&self) -> RwLockReadGuard<CheckPoint<T::EthSpec>> {
|
|
||||||
self.finalized_head.read()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the validator index (if any) for the given public key.
|
/// Returns the validator index (if any) for the given public key.
|
||||||
///
|
///
|
||||||
/// Information is retrieved from the present `beacon_state.validator_registry`.
|
/// Information is retrieved from the present `beacon_state.validator_registry`.
|
||||||
@ -407,13 +348,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
/// genesis.
|
/// genesis.
|
||||||
pub fn slots_since_genesis(&self) -> Option<SlotHeight> {
|
pub fn slots_since_genesis(&self) -> Option<SlotHeight> {
|
||||||
let now = self.read_slot_clock()?;
|
let now = self.read_slot_clock()?;
|
||||||
|
let genesis_slot = self.spec.genesis_slot;
|
||||||
|
|
||||||
if now < self.spec.genesis_slot {
|
if now < genesis_slot {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(SlotHeight::from(
|
Some(SlotHeight::from(now.as_u64() - genesis_slot.as_u64()))
|
||||||
now.as_u64() - self.spec.genesis_slot.as_u64(),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -433,7 +373,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
pub fn block_proposer(&self, slot: Slot) -> Result<usize, BeaconStateError> {
|
pub fn block_proposer(&self, slot: Slot) -> Result<usize, BeaconStateError> {
|
||||||
self.state
|
self.state
|
||||||
.write()
|
.write()
|
||||||
.build_epoch_cache(RelativeEpoch::Current, &self.spec)?;
|
.build_committee_cache(RelativeEpoch::Current, &self.spec)?;
|
||||||
|
|
||||||
let index = self.state.read().get_beacon_proposer_index(
|
let index = self.state.read().get_beacon_proposer_index(
|
||||||
slot,
|
slot,
|
||||||
@ -459,7 +399,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
if let Some(attestation_duty) = self
|
if let Some(attestation_duty) = self
|
||||||
.state
|
.state
|
||||||
.read()
|
.read()
|
||||||
.get_attestation_duties(validator_index, &self.spec)?
|
.get_attestation_duties(validator_index, RelativeEpoch::Current)?
|
||||||
{
|
{
|
||||||
Ok(Some((attestation_duty.slot, attestation_duty.shard)))
|
Ok(Some((attestation_duty.slot, attestation_duty.shard)))
|
||||||
} else {
|
} else {
|
||||||
@ -469,15 +409,19 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
|
|
||||||
/// Produce an `AttestationData` that is valid for the present `slot` and given `shard`.
|
/// Produce an `AttestationData` that is valid for the present `slot` and given `shard`.
|
||||||
pub fn produce_attestation_data(&self, shard: u64) -> Result<AttestationData, Error> {
|
pub fn produce_attestation_data(&self, shard: u64) -> Result<AttestationData, Error> {
|
||||||
trace!("BeaconChain::produce_attestation: shard: {}", shard);
|
let slots_per_epoch = T::EthSpec::slots_per_epoch();
|
||||||
|
|
||||||
|
self.metrics.attestation_production_requests.inc();
|
||||||
|
let timer = self.metrics.attestation_production_times.start_timer();
|
||||||
|
|
||||||
let state = self.state.read();
|
let state = self.state.read();
|
||||||
|
|
||||||
let current_epoch_start_slot = self
|
let current_epoch_start_slot = self
|
||||||
.state
|
.state
|
||||||
.read()
|
.read()
|
||||||
.slot
|
.slot
|
||||||
.epoch(self.spec.slots_per_epoch)
|
.epoch(slots_per_epoch)
|
||||||
.start_slot(self.spec.slots_per_epoch);
|
.start_slot(slots_per_epoch);
|
||||||
|
|
||||||
let target_root = if state.slot == current_epoch_start_slot {
|
let target_root = if state.slot == current_epoch_start_slot {
|
||||||
// If we're on the first slot of the state's epoch.
|
// If we're on the first slot of the state's epoch.
|
||||||
@ -490,22 +434,28 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
*self
|
*self
|
||||||
.state
|
.state
|
||||||
.read()
|
.read()
|
||||||
.get_block_root(current_epoch_start_slot - self.spec.slots_per_epoch)?
|
.get_block_root(current_epoch_start_slot - slots_per_epoch)?
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If we're not on the first slot of the epoch.
|
// If we're not on the first slot of the epoch.
|
||||||
*self.state.read().get_block_root(current_epoch_start_slot)?
|
*self.state.read().get_block_root(current_epoch_start_slot)?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let previous_crosslink_root =
|
||||||
|
Hash256::from_slice(&state.get_current_crosslink(shard)?.tree_hash_root());
|
||||||
|
|
||||||
|
self.metrics.attestation_production_successes.inc();
|
||||||
|
timer.observe_duration();
|
||||||
|
|
||||||
Ok(AttestationData {
|
Ok(AttestationData {
|
||||||
slot: self.state.read().slot,
|
|
||||||
shard,
|
|
||||||
beacon_block_root: self.head().beacon_block_root,
|
beacon_block_root: self.head().beacon_block_root,
|
||||||
target_root,
|
|
||||||
crosslink_data_root: Hash256::zero(),
|
|
||||||
previous_crosslink: state.latest_crosslinks[shard as usize].clone(),
|
|
||||||
source_epoch: state.current_justified_epoch,
|
source_epoch: state.current_justified_epoch,
|
||||||
source_root: state.current_justified_root,
|
source_root: state.current_justified_root,
|
||||||
|
target_epoch: state.current_epoch(),
|
||||||
|
target_root,
|
||||||
|
shard,
|
||||||
|
previous_crosslink_root,
|
||||||
|
crosslink_data_root: Hash256::zero(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -517,8 +467,20 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
&self,
|
&self,
|
||||||
attestation: Attestation,
|
attestation: Attestation,
|
||||||
) -> Result<(), AttestationValidationError> {
|
) -> Result<(), AttestationValidationError> {
|
||||||
self.op_pool
|
self.metrics.attestation_processing_requests.inc();
|
||||||
.insert_attestation(attestation, &*self.state.read(), &self.spec)
|
let timer = self.metrics.attestation_processing_times.start_timer();
|
||||||
|
|
||||||
|
let result = self
|
||||||
|
.op_pool
|
||||||
|
.insert_attestation(attestation, &*self.state.read(), &self.spec);
|
||||||
|
|
||||||
|
if result.is_ok() {
|
||||||
|
self.metrics.attestation_processing_successes.inc();
|
||||||
|
}
|
||||||
|
|
||||||
|
timer.observe_duration();
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Accept some deposit and queue it for inclusion in an appropriate block.
|
/// Accept some deposit and queue it for inclusion in an appropriate block.
|
||||||
@ -564,19 +526,39 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
///
|
///
|
||||||
/// Will accept blocks from prior slots, however it will reject any block from a future slot.
|
/// Will accept blocks from prior slots, however it will reject any block from a future slot.
|
||||||
pub fn process_block(&self, block: BeaconBlock) -> Result<BlockProcessingOutcome, Error> {
|
pub fn process_block(&self, block: BeaconBlock) -> Result<BlockProcessingOutcome, Error> {
|
||||||
debug!("Processing block with slot {}...", block.slot);
|
self.metrics.block_processing_requests.inc();
|
||||||
|
let timer = self.metrics.block_processing_times.start_timer();
|
||||||
|
|
||||||
|
let finalized_slot = self
|
||||||
|
.state
|
||||||
|
.read()
|
||||||
|
.finalized_epoch
|
||||||
|
.start_slot(T::EthSpec::slots_per_epoch());
|
||||||
|
if block.slot <= finalized_slot {
|
||||||
|
return Ok(BlockProcessingOutcome::FinalizedSlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
if block.slot == 0 {
|
||||||
|
return Ok(BlockProcessingOutcome::GenesisBlock);
|
||||||
|
}
|
||||||
|
|
||||||
let block_root = block.block_header().canonical_root();
|
let block_root = block.block_header().canonical_root();
|
||||||
|
|
||||||
|
if block_root == self.genesis_block_root {
|
||||||
|
return Ok(BlockProcessingOutcome::GenesisBlock);
|
||||||
|
}
|
||||||
|
|
||||||
let present_slot = self.present_slot();
|
let present_slot = self.present_slot();
|
||||||
|
|
||||||
if block.slot > present_slot {
|
if block.slot > present_slot {
|
||||||
return Ok(BlockProcessingOutcome::InvalidBlock(
|
return Ok(BlockProcessingOutcome::FutureSlot {
|
||||||
InvalidBlock::FutureSlot {
|
|
||||||
present_slot,
|
present_slot,
|
||||||
block_slot: block.slot,
|
block_slot: block.slot,
|
||||||
},
|
});
|
||||||
));
|
}
|
||||||
|
|
||||||
|
if self.store.exists::<BeaconBlock>(&block_root)? {
|
||||||
|
return Ok(BlockProcessingOutcome::BlockIsAlreadyKnown);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the blocks parent block from the database, returning invalid if that block is not
|
// Load the blocks parent block from the database, returning invalid if that block is not
|
||||||
@ -585,9 +567,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
let parent_block: BeaconBlock = match self.store.get(&parent_block_root)? {
|
let parent_block: BeaconBlock = match self.store.get(&parent_block_root)? {
|
||||||
Some(previous_block_root) => previous_block_root,
|
Some(previous_block_root) => previous_block_root,
|
||||||
None => {
|
None => {
|
||||||
return Ok(BlockProcessingOutcome::InvalidBlock(
|
return Ok(BlockProcessingOutcome::ParentUnknown {
|
||||||
InvalidBlock::ParentUnknown,
|
parent: parent_block_root,
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -605,50 +587,49 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
// Transition the parent state to the block slot.
|
// Transition the parent state to the block slot.
|
||||||
let mut state: BeaconState<T::EthSpec> = parent_state;
|
let mut state: BeaconState<T::EthSpec> = parent_state;
|
||||||
for _ in state.slot.as_u64()..block.slot.as_u64() {
|
for _ in state.slot.as_u64()..block.slot.as_u64() {
|
||||||
if let Err(e) = per_slot_processing(&mut state, &self.spec) {
|
per_slot_processing(&mut state, &self.spec)?;
|
||||||
return Ok(BlockProcessingOutcome::InvalidBlock(
|
|
||||||
InvalidBlock::SlotProcessingError(e),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.build_committee_cache(RelativeEpoch::Current, &self.spec)?;
|
||||||
|
|
||||||
// Apply the received block to its parent state (which has been transitioned into this
|
// Apply the received block to its parent state (which has been transitioned into this
|
||||||
// slot).
|
// slot).
|
||||||
if let Err(e) = per_block_processing(&mut state, &block, &self.spec) {
|
match per_block_processing(&mut state, &block, &self.spec) {
|
||||||
return Ok(BlockProcessingOutcome::InvalidBlock(
|
Err(BlockProcessingError::BeaconStateError(e)) => {
|
||||||
InvalidBlock::PerBlockProcessingError(e),
|
return Err(Error::BeaconStateError(e))
|
||||||
));
|
}
|
||||||
|
Err(e) => return Ok(BlockProcessingOutcome::PerBlockProcessingError(e)),
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let state_root = state.canonical_root();
|
let state_root = state.canonical_root();
|
||||||
|
|
||||||
if block.state_root != state_root {
|
if block.state_root != state_root {
|
||||||
return Ok(BlockProcessingOutcome::InvalidBlock(
|
return Ok(BlockProcessingOutcome::StateRootMismatch);
|
||||||
InvalidBlock::StateRootMismatch,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the block and state.
|
// Store the block and state.
|
||||||
self.store.put(&block_root, &block)?;
|
self.store.put(&block_root, &block)?;
|
||||||
self.store.put(&state_root, &state)?;
|
self.store.put(&state_root, &state)?;
|
||||||
|
|
||||||
// run the fork_choice add_block logic
|
// Register the new block with the fork choice service.
|
||||||
self.fork_choice
|
self.fork_choice
|
||||||
.write()
|
.write()
|
||||||
.add_block(&block, &block_root, &self.spec)?;
|
.add_block(&block, &block_root, &self.spec)?;
|
||||||
|
|
||||||
// If the parent block was the parent_block, automatically update the canonical head.
|
// Execute the fork choice algorithm, enthroning a new head if discovered.
|
||||||
//
|
//
|
||||||
// TODO: this is a first-in-best-dressed scenario that is not ideal; fork_choice should be
|
// Note: in the future we may choose to run fork-choice less often, potentially based upon
|
||||||
// run instead.
|
// some heuristic around number of attestations seen for the block.
|
||||||
if self.head().beacon_block_root == parent_block_root {
|
self.fork_choice()?;
|
||||||
self.update_canonical_head(block.clone(), block_root, state.clone(), state_root);
|
|
||||||
|
|
||||||
// Update the canonical `BeaconState`.
|
self.metrics.block_processing_successes.inc();
|
||||||
self.update_state(state)?;
|
self.metrics
|
||||||
}
|
.operations_per_block_attestation
|
||||||
|
.observe(block.body.attestations.len() as f64);
|
||||||
|
timer.observe_duration();
|
||||||
|
|
||||||
Ok(BlockProcessingOutcome::ValidBlock(ValidBlock::Processed))
|
Ok(BlockProcessingOutcome::Processed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Produce a new block at the present slot.
|
/// Produce a new block at the present slot.
|
||||||
@ -660,16 +641,22 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
randao_reveal: Signature,
|
randao_reveal: Signature,
|
||||||
) -> Result<(BeaconBlock, BeaconState<T::EthSpec>), BlockProductionError> {
|
) -> Result<(BeaconBlock, BeaconState<T::EthSpec>), BlockProductionError> {
|
||||||
debug!("Producing block at slot {}...", self.state.read().slot);
|
debug!("Producing block at slot {}...", self.state.read().slot);
|
||||||
|
self.metrics.block_production_requests.inc();
|
||||||
|
let timer = self.metrics.block_production_times.start_timer();
|
||||||
|
|
||||||
let mut state = self.state.read().clone();
|
let mut state = self.state.read().clone();
|
||||||
|
|
||||||
state.build_epoch_cache(RelativeEpoch::Current, &self.spec)?;
|
state.build_committee_cache(RelativeEpoch::Current, &self.spec)?;
|
||||||
|
|
||||||
trace!("Finding attestations for new block...");
|
trace!("Finding attestations for new block...");
|
||||||
|
|
||||||
let previous_block_root = *state
|
let previous_block_root = if state.slot > 0 {
|
||||||
|
*state
|
||||||
.get_block_root(state.slot - 1)
|
.get_block_root(state.slot - 1)
|
||||||
.map_err(|_| BlockProductionError::UnableToGetBlockRootFromState)?;
|
.map_err(|_| BlockProductionError::UnableToGetBlockRootFromState)?
|
||||||
|
} else {
|
||||||
|
state.latest_block_header.canonical_root()
|
||||||
|
};
|
||||||
|
|
||||||
let (proposer_slashings, attester_slashings) =
|
let (proposer_slashings, attester_slashings) =
|
||||||
self.op_pool.get_slashings(&*self.state.read(), &self.spec);
|
self.op_pool.get_slashings(&*self.state.read(), &self.spec);
|
||||||
@ -678,14 +665,17 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
slot: state.slot,
|
slot: state.slot,
|
||||||
previous_block_root,
|
previous_block_root,
|
||||||
state_root: Hash256::zero(), // Updated after the state is calculated.
|
state_root: Hash256::zero(), // Updated after the state is calculated.
|
||||||
signature: self.spec.empty_signature.clone(), // To be completed by a validator.
|
signature: Signature::empty_signature(), // To be completed by a validator.
|
||||||
body: BeaconBlockBody {
|
body: BeaconBlockBody {
|
||||||
randao_reveal,
|
randao_reveal,
|
||||||
eth1_data: Eth1Data {
|
eth1_data: Eth1Data {
|
||||||
// TODO: replace with real data
|
// TODO: replace with real data
|
||||||
|
deposit_count: 0,
|
||||||
deposit_root: Hash256::zero(),
|
deposit_root: Hash256::zero(),
|
||||||
block_hash: Hash256::zero(),
|
block_hash: Hash256::zero(),
|
||||||
},
|
},
|
||||||
|
// TODO: badass Lighthouse graffiti
|
||||||
|
graffiti: [0; 32],
|
||||||
proposer_slashings,
|
proposer_slashings,
|
||||||
attester_slashings,
|
attester_slashings,
|
||||||
attestations: self
|
attestations: self
|
||||||
@ -710,35 +700,63 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
|||||||
|
|
||||||
block.state_root = state_root;
|
block.state_root = state_root;
|
||||||
|
|
||||||
|
self.metrics.block_production_successes.inc();
|
||||||
|
timer.observe_duration();
|
||||||
|
|
||||||
Ok((block, state))
|
Ok((block, state))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Left this as is, modify later
|
/// Execute the fork choice algorithm and enthrone the result as the canonical head.
|
||||||
pub fn fork_choice(&self) -> Result<(), Error> {
|
pub fn fork_choice(&self) -> Result<(), Error> {
|
||||||
let present_head = self.finalized_head().beacon_block_root;
|
self.metrics.fork_choice_requests.inc();
|
||||||
|
|
||||||
let new_head = self
|
// Start fork choice metrics timer.
|
||||||
|
let timer = self.metrics.fork_choice_times.start_timer();
|
||||||
|
|
||||||
|
let justified_root = {
|
||||||
|
let root = self.head().beacon_state.current_justified_root;
|
||||||
|
if root == self.spec.zero_hash {
|
||||||
|
self.genesis_block_root
|
||||||
|
} else {
|
||||||
|
root
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Determine the root of the block that is the head of the chain.
|
||||||
|
let beacon_block_root = self
|
||||||
.fork_choice
|
.fork_choice
|
||||||
.write()
|
.write()
|
||||||
.find_head(&present_head, &self.spec)?;
|
.find_head(&justified_root, &self.spec)?;
|
||||||
|
|
||||||
if new_head != present_head {
|
// End fork choice metrics timer.
|
||||||
let block: BeaconBlock = self
|
timer.observe_duration();
|
||||||
|
|
||||||
|
// If a new head was chosen.
|
||||||
|
if beacon_block_root != self.head().beacon_block_root {
|
||||||
|
self.metrics.fork_choice_changed_head.inc();
|
||||||
|
|
||||||
|
let beacon_block: BeaconBlock = self
|
||||||
.store
|
.store
|
||||||
.get(&new_head)?
|
.get(&beacon_block_root)?
|
||||||
.ok_or_else(|| Error::MissingBeaconBlock(new_head))?;
|
.ok_or_else(|| Error::MissingBeaconBlock(beacon_block_root))?;
|
||||||
let block_root = block.canonical_root();
|
|
||||||
|
|
||||||
let state: BeaconState<T::EthSpec> = self
|
let beacon_state_root = beacon_block.state_root;
|
||||||
|
let beacon_state: BeaconState<T::EthSpec> = self
|
||||||
.store
|
.store
|
||||||
.get(&block.state_root)?
|
.get(&beacon_state_root)?
|
||||||
.ok_or_else(|| Error::MissingBeaconState(block.state_root))?;
|
.ok_or_else(|| Error::MissingBeaconState(beacon_state_root))?;
|
||||||
let state_root = state.canonical_root();
|
|
||||||
|
|
||||||
self.update_canonical_head(block, block_root, state.clone(), state_root);
|
// If we switched to a new chain (instead of building atop the present chain).
|
||||||
|
if self.head().beacon_block_root != beacon_block.previous_block_root {
|
||||||
|
self.metrics.fork_choice_reorg_count.inc();
|
||||||
|
};
|
||||||
|
|
||||||
// Update the canonical `BeaconState`.
|
self.update_canonical_head(CheckPoint {
|
||||||
self.update_state(state)?;
|
beacon_block,
|
||||||
|
beacon_block_root,
|
||||||
|
beacon_state,
|
||||||
|
beacon_state_root,
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use serde_derive::Serialize;
|
use serde_derive::Serialize;
|
||||||
|
use ssz_derive::{Decode, Encode};
|
||||||
use types::{BeaconBlock, BeaconState, EthSpec, Hash256};
|
use types::{BeaconBlock, BeaconState, EthSpec, Hash256};
|
||||||
|
|
||||||
/// Represents some block and it's associated state. Generally, this will be used for tracking the
|
/// Represents some block and it's associated state. Generally, this will be used for tracking the
|
||||||
/// head, justified head and finalized head.
|
/// head, justified head and finalized head.
|
||||||
#[derive(Clone, Serialize, PartialEq, Debug)]
|
#[derive(Clone, Serialize, PartialEq, Debug, Encode, Decode)]
|
||||||
pub struct CheckPoint<E: EthSpec> {
|
pub struct CheckPoint<E: EthSpec> {
|
||||||
pub beacon_block: BeaconBlock,
|
pub beacon_block: BeaconBlock,
|
||||||
pub beacon_block_root: Hash256,
|
pub beacon_block_root: Hash256,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use crate::metrics::Error as MetricsError;
|
||||||
use fork_choice::ForkChoiceError;
|
use fork_choice::ForkChoiceError;
|
||||||
use state_processing::BlockProcessingError;
|
use state_processing::BlockProcessingError;
|
||||||
use state_processing::SlotProcessingError;
|
use state_processing::SlotProcessingError;
|
||||||
@ -25,10 +26,17 @@ pub enum BeaconChainError {
|
|||||||
MissingBeaconBlock(Hash256),
|
MissingBeaconBlock(Hash256),
|
||||||
MissingBeaconState(Hash256),
|
MissingBeaconState(Hash256),
|
||||||
SlotProcessingError(SlotProcessingError),
|
SlotProcessingError(SlotProcessingError),
|
||||||
|
MetricsError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
easy_from_to!(SlotProcessingError, BeaconChainError);
|
easy_from_to!(SlotProcessingError, BeaconChainError);
|
||||||
|
|
||||||
|
impl From<MetricsError> for BeaconChainError {
|
||||||
|
fn from(e: MetricsError) -> BeaconChainError {
|
||||||
|
BeaconChainError::MetricsError(format!("{:?}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum BlockProductionError {
|
pub enum BlockProductionError {
|
||||||
UnableToGetBlockRootFromState,
|
UnableToGetBlockRootFromState,
|
||||||
|
133
beacon_node/beacon_chain/src/iter.rs
Normal file
133
beacon_node/beacon_chain/src/iter.rs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use store::Store;
|
||||||
|
use types::{BeaconBlock, BeaconState, BeaconStateError, EthSpec, Hash256, Slot};
|
||||||
|
|
||||||
|
/// Extends `BlockRootsIterator`, returning `BeaconBlock` instances, instead of their roots.
|
||||||
|
pub struct BlockIterator<T: EthSpec, U> {
|
||||||
|
roots: BlockRootsIterator<T, U>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: EthSpec, U: Store> BlockIterator<T, U> {
|
||||||
|
/// Create a new iterator over all blocks in the given `beacon_state` and prior states.
|
||||||
|
pub fn new(store: Arc<U>, beacon_state: BeaconState<T>, start_slot: Slot) -> Self {
|
||||||
|
Self {
|
||||||
|
roots: BlockRootsIterator::new(store, beacon_state, start_slot),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: EthSpec, U: Store> Iterator for BlockIterator<T, U> {
|
||||||
|
type Item = BeaconBlock;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let root = self.roots.next()?;
|
||||||
|
self.roots.store.get(&root).ok()?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterates backwards through block roots.
|
||||||
|
///
|
||||||
|
/// Uses the `latest_block_roots` field of `BeaconState` to as the source of block roots and will
|
||||||
|
/// perform a lookup on the `Store` for a prior `BeaconState` if `latest_block_roots` has been
|
||||||
|
/// exhausted.
|
||||||
|
///
|
||||||
|
/// Returns `None` for roots prior to genesis or when there is an error reading from `Store`.
|
||||||
|
pub struct BlockRootsIterator<T: EthSpec, U> {
|
||||||
|
store: Arc<U>,
|
||||||
|
beacon_state: BeaconState<T>,
|
||||||
|
slot: Slot,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: EthSpec, U: Store> BlockRootsIterator<T, U> {
|
||||||
|
/// Create a new iterator over all block roots in the given `beacon_state` and prior states.
|
||||||
|
pub fn new(store: Arc<U>, beacon_state: BeaconState<T>, start_slot: Slot) -> Self {
|
||||||
|
Self {
|
||||||
|
slot: start_slot,
|
||||||
|
beacon_state,
|
||||||
|
store,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: EthSpec, U: Store> Iterator for BlockRootsIterator<T, U> {
|
||||||
|
type Item = Hash256;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if (self.slot == 0) || (self.slot > self.beacon_state.slot) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.slot -= 1;
|
||||||
|
|
||||||
|
match self.beacon_state.get_block_root(self.slot) {
|
||||||
|
Ok(root) => Some(*root),
|
||||||
|
Err(BeaconStateError::SlotOutOfBounds) => {
|
||||||
|
// Read a `BeaconState` from the store that has access to prior historical root.
|
||||||
|
self.beacon_state = {
|
||||||
|
// Load the earlier state from disk. Skip forward one slot, because a state
|
||||||
|
// doesn't return it's own state root.
|
||||||
|
let new_state_root = self.beacon_state.get_state_root(self.slot + 1).ok()?;
|
||||||
|
|
||||||
|
self.store.get(&new_state_root).ok()?
|
||||||
|
}?;
|
||||||
|
|
||||||
|
self.beacon_state.get_block_root(self.slot).ok().cloned()
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use store::MemoryStore;
|
||||||
|
use types::{test_utils::TestingBeaconStateBuilder, Keypair, MainnetEthSpec};
|
||||||
|
|
||||||
|
fn get_state<T: EthSpec>() -> BeaconState<T> {
|
||||||
|
let builder = TestingBeaconStateBuilder::from_single_keypair(
|
||||||
|
0,
|
||||||
|
&Keypair::random(),
|
||||||
|
&T::default_spec(),
|
||||||
|
);
|
||||||
|
let (state, _keypairs) = builder.build();
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn root_iter() {
|
||||||
|
let store = Arc::new(MemoryStore::open());
|
||||||
|
let slots_per_historical_root = MainnetEthSpec::slots_per_historical_root();
|
||||||
|
|
||||||
|
let mut state_a: BeaconState<MainnetEthSpec> = get_state();
|
||||||
|
let mut state_b: BeaconState<MainnetEthSpec> = get_state();
|
||||||
|
|
||||||
|
state_a.slot = Slot::from(slots_per_historical_root);
|
||||||
|
state_b.slot = Slot::from(slots_per_historical_root * 2);
|
||||||
|
|
||||||
|
let mut hashes = (0..).into_iter().map(|i| Hash256::from(i));
|
||||||
|
|
||||||
|
for root in &mut state_a.latest_block_roots[..] {
|
||||||
|
*root = hashes.next().unwrap()
|
||||||
|
}
|
||||||
|
for root in &mut state_b.latest_block_roots[..] {
|
||||||
|
*root = hashes.next().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
let state_a_root = hashes.next().unwrap();
|
||||||
|
state_b.latest_state_roots[0] = state_a_root;
|
||||||
|
store.put(&state_a_root, &state_a).unwrap();
|
||||||
|
|
||||||
|
let iter = BlockRootsIterator::new(store.clone(), state_b.clone(), state_b.slot - 1);
|
||||||
|
let mut collected: Vec<Hash256> = iter.collect();
|
||||||
|
collected.reverse();
|
||||||
|
|
||||||
|
let expected_len = 2 * MainnetEthSpec::slots_per_historical_root() - 1;
|
||||||
|
|
||||||
|
assert_eq!(collected.len(), expected_len);
|
||||||
|
|
||||||
|
for i in 0..expected_len {
|
||||||
|
assert_eq!(collected[i], Hash256::from(i as u64));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,11 @@
|
|||||||
mod beacon_chain;
|
mod beacon_chain;
|
||||||
mod checkpoint;
|
mod checkpoint;
|
||||||
mod errors;
|
mod errors;
|
||||||
|
pub mod iter;
|
||||||
|
mod metrics;
|
||||||
|
mod persisted_beacon_chain;
|
||||||
|
|
||||||
pub use self::beacon_chain::{
|
pub use self::beacon_chain::{BeaconChain, BeaconChainTypes, BlockProcessingOutcome};
|
||||||
BeaconChain, BeaconChainTypes, BlockProcessingOutcome, InvalidBlock, ValidBlock,
|
|
||||||
};
|
|
||||||
pub use self::checkpoint::CheckPoint;
|
pub use self::checkpoint::CheckPoint;
|
||||||
pub use self::errors::{BeaconChainError, BlockProductionError};
|
pub use self::errors::{BeaconChainError, BlockProductionError};
|
||||||
pub use fork_choice;
|
pub use fork_choice;
|
||||||
|
143
beacon_node/beacon_chain/src/metrics.rs
Normal file
143
beacon_node/beacon_chain/src/metrics.rs
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
pub use prometheus::Error;
|
||||||
|
use prometheus::{Histogram, HistogramOpts, IntCounter, Opts, Registry};
|
||||||
|
|
||||||
|
pub struct Metrics {
|
||||||
|
pub block_processing_requests: IntCounter,
|
||||||
|
pub block_processing_successes: IntCounter,
|
||||||
|
pub block_processing_times: Histogram,
|
||||||
|
pub block_production_requests: IntCounter,
|
||||||
|
pub block_production_successes: IntCounter,
|
||||||
|
pub block_production_times: Histogram,
|
||||||
|
pub attestation_production_requests: IntCounter,
|
||||||
|
pub attestation_production_successes: IntCounter,
|
||||||
|
pub attestation_production_times: Histogram,
|
||||||
|
pub attestation_processing_requests: IntCounter,
|
||||||
|
pub attestation_processing_successes: IntCounter,
|
||||||
|
pub attestation_processing_times: Histogram,
|
||||||
|
pub fork_choice_requests: IntCounter,
|
||||||
|
pub fork_choice_changed_head: IntCounter,
|
||||||
|
pub fork_choice_reorg_count: IntCounter,
|
||||||
|
pub fork_choice_times: Histogram,
|
||||||
|
pub operations_per_block_attestation: Histogram,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Metrics {
|
||||||
|
pub fn new() -> Result<Self, Error> {
|
||||||
|
Ok(Self {
|
||||||
|
block_processing_requests: {
|
||||||
|
let opts = Opts::new("block_processing_requests", "total_blocks_processed");
|
||||||
|
IntCounter::with_opts(opts)?
|
||||||
|
},
|
||||||
|
block_processing_successes: {
|
||||||
|
let opts = Opts::new("block_processing_successes", "total_valid_blocks_processed");
|
||||||
|
IntCounter::with_opts(opts)?
|
||||||
|
},
|
||||||
|
block_processing_times: {
|
||||||
|
let opts = HistogramOpts::new("block_processing_times", "block_processing_time");
|
||||||
|
Histogram::with_opts(opts)?
|
||||||
|
},
|
||||||
|
block_production_requests: {
|
||||||
|
let opts = Opts::new("block_production_requests", "attempts_to_produce_new_block");
|
||||||
|
IntCounter::with_opts(opts)?
|
||||||
|
},
|
||||||
|
block_production_successes: {
|
||||||
|
let opts = Opts::new("block_production_successes", "blocks_successfully_produced");
|
||||||
|
IntCounter::with_opts(opts)?
|
||||||
|
},
|
||||||
|
block_production_times: {
|
||||||
|
let opts = HistogramOpts::new("block_production_times", "block_production_time");
|
||||||
|
Histogram::with_opts(opts)?
|
||||||
|
},
|
||||||
|
attestation_production_requests: {
|
||||||
|
let opts = Opts::new(
|
||||||
|
"attestation_production_requests",
|
||||||
|
"total_attestation_production_requests",
|
||||||
|
);
|
||||||
|
IntCounter::with_opts(opts)?
|
||||||
|
},
|
||||||
|
attestation_production_successes: {
|
||||||
|
let opts = Opts::new(
|
||||||
|
"attestation_production_successes",
|
||||||
|
"total_attestation_production_successes",
|
||||||
|
);
|
||||||
|
IntCounter::with_opts(opts)?
|
||||||
|
},
|
||||||
|
attestation_production_times: {
|
||||||
|
let opts = HistogramOpts::new(
|
||||||
|
"attestation_production_times",
|
||||||
|
"attestation_production_time",
|
||||||
|
);
|
||||||
|
Histogram::with_opts(opts)?
|
||||||
|
},
|
||||||
|
attestation_processing_requests: {
|
||||||
|
let opts = Opts::new(
|
||||||
|
"attestation_processing_requests",
|
||||||
|
"total_attestation_processing_requests",
|
||||||
|
);
|
||||||
|
IntCounter::with_opts(opts)?
|
||||||
|
},
|
||||||
|
attestation_processing_successes: {
|
||||||
|
let opts = Opts::new(
|
||||||
|
"attestation_processing_successes",
|
||||||
|
"total_attestation_processing_successes",
|
||||||
|
);
|
||||||
|
IntCounter::with_opts(opts)?
|
||||||
|
},
|
||||||
|
attestation_processing_times: {
|
||||||
|
let opts = HistogramOpts::new(
|
||||||
|
"attestation_processing_times",
|
||||||
|
"attestation_processing_time",
|
||||||
|
);
|
||||||
|
Histogram::with_opts(opts)?
|
||||||
|
},
|
||||||
|
fork_choice_requests: {
|
||||||
|
let opts = Opts::new("fork_choice_requests", "total_times_fork_choice_called");
|
||||||
|
IntCounter::with_opts(opts)?
|
||||||
|
},
|
||||||
|
fork_choice_changed_head: {
|
||||||
|
let opts = Opts::new(
|
||||||
|
"fork_choice_changed_head",
|
||||||
|
"total_times_fork_choice_chose_a_new_head",
|
||||||
|
);
|
||||||
|
IntCounter::with_opts(opts)?
|
||||||
|
},
|
||||||
|
fork_choice_reorg_count: {
|
||||||
|
let opts = Opts::new("fork_choice_reorg_count", "number_of_reorgs");
|
||||||
|
IntCounter::with_opts(opts)?
|
||||||
|
},
|
||||||
|
fork_choice_times: {
|
||||||
|
let opts = HistogramOpts::new("fork_choice_time", "total_time_to_run_fork_choice");
|
||||||
|
Histogram::with_opts(opts)?
|
||||||
|
},
|
||||||
|
operations_per_block_attestation: {
|
||||||
|
let opts = HistogramOpts::new(
|
||||||
|
"operations_per_block_attestation",
|
||||||
|
"count_of_attestations_per_block",
|
||||||
|
);
|
||||||
|
Histogram::with_opts(opts)?
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(&self, registry: &Registry) -> Result<(), Error> {
|
||||||
|
registry.register(Box::new(self.block_processing_requests.clone()))?;
|
||||||
|
registry.register(Box::new(self.block_processing_successes.clone()))?;
|
||||||
|
registry.register(Box::new(self.block_processing_times.clone()))?;
|
||||||
|
registry.register(Box::new(self.block_production_requests.clone()))?;
|
||||||
|
registry.register(Box::new(self.block_production_successes.clone()))?;
|
||||||
|
registry.register(Box::new(self.block_production_times.clone()))?;
|
||||||
|
registry.register(Box::new(self.attestation_production_requests.clone()))?;
|
||||||
|
registry.register(Box::new(self.attestation_production_successes.clone()))?;
|
||||||
|
registry.register(Box::new(self.attestation_production_times.clone()))?;
|
||||||
|
registry.register(Box::new(self.attestation_processing_requests.clone()))?;
|
||||||
|
registry.register(Box::new(self.attestation_processing_successes.clone()))?;
|
||||||
|
registry.register(Box::new(self.attestation_processing_times.clone()))?;
|
||||||
|
registry.register(Box::new(self.fork_choice_requests.clone()))?;
|
||||||
|
registry.register(Box::new(self.fork_choice_changed_head.clone()))?;
|
||||||
|
registry.register(Box::new(self.fork_choice_reorg_count.clone()))?;
|
||||||
|
registry.register(Box::new(self.fork_choice_times.clone()))?;
|
||||||
|
registry.register(Box::new(self.operations_per_block_attestation.clone()))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
30
beacon_node/beacon_chain/src/persisted_beacon_chain.rs
Normal file
30
beacon_node/beacon_chain/src/persisted_beacon_chain.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use crate::{BeaconChainTypes, CheckPoint};
|
||||||
|
use ssz::{Decode, Encode};
|
||||||
|
use ssz_derive::{Decode, Encode};
|
||||||
|
use store::{DBColumn, Error as StoreError, StoreItem};
|
||||||
|
use types::{BeaconState, Hash256};
|
||||||
|
|
||||||
|
/// 32-byte key for accessing the `PersistedBeaconChain`.
|
||||||
|
pub const BEACON_CHAIN_DB_KEY: &str = "PERSISTEDBEACONCHAINPERSISTEDBEA";
|
||||||
|
|
||||||
|
#[derive(Encode, Decode)]
|
||||||
|
pub struct PersistedBeaconChain<T: BeaconChainTypes> {
|
||||||
|
pub canonical_head: CheckPoint<T::EthSpec>,
|
||||||
|
// TODO: operations pool.
|
||||||
|
pub genesis_block_root: Hash256,
|
||||||
|
pub state: BeaconState<T::EthSpec>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: BeaconChainTypes> StoreItem for PersistedBeaconChain<T> {
|
||||||
|
fn db_column() -> DBColumn {
|
||||||
|
DBColumn::BeaconChain
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_store_bytes(&self) -> Vec<u8> {
|
||||||
|
self.as_ssz_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_store_bytes(bytes: &mut [u8]) -> Result<Self, StoreError> {
|
||||||
|
Self::from_ssz_bytes(bytes).map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
@ -11,9 +11,13 @@ store = { path = "../store" }
|
|||||||
http_server = { path = "../http_server" }
|
http_server = { path = "../http_server" }
|
||||||
rpc = { path = "../rpc" }
|
rpc = { path = "../rpc" }
|
||||||
fork_choice = { path = "../../eth2/fork_choice" }
|
fork_choice = { path = "../../eth2/fork_choice" }
|
||||||
|
prometheus = "^0.6"
|
||||||
types = { path = "../../eth2/types" }
|
types = { path = "../../eth2/types" }
|
||||||
tree_hash = { path = "../../eth2/utils/tree_hash" }
|
tree_hash = { path = "../../eth2/utils/tree_hash" }
|
||||||
|
eth2_config = { path = "../../eth2/utils/eth2_config" }
|
||||||
slot_clock = { path = "../../eth2/utils/slot_clock" }
|
slot_clock = { path = "../../eth2/utils/slot_clock" }
|
||||||
|
serde = "1.0"
|
||||||
|
serde_derive = "1.0"
|
||||||
error-chain = "0.12.0"
|
error-chain = "0.12.0"
|
||||||
slog = "^2.2.3"
|
slog = "^2.2.3"
|
||||||
ssz = { path = "../../eth2/utils/ssz" }
|
ssz = { path = "../../eth2/utils/ssz" }
|
||||||
|
@ -1,99 +1,81 @@
|
|||||||
use crate::ClientConfig;
|
|
||||||
use beacon_chain::{
|
use beacon_chain::{
|
||||||
fork_choice::BitwiseLMDGhost,
|
fork_choice::OptimizedLMDGhost, slot_clock::SystemTimeSlotClock, store::Store, BeaconChain,
|
||||||
slot_clock::SystemTimeSlotClock,
|
BeaconChainTypes,
|
||||||
store::{DiskStore, MemoryStore, Store},
|
|
||||||
BeaconChain, BeaconChainTypes,
|
|
||||||
};
|
};
|
||||||
|
use fork_choice::ForkChoice;
|
||||||
|
use slog::{info, Logger};
|
||||||
|
use slot_clock::SlotClock;
|
||||||
|
use std::marker::PhantomData;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tree_hash::TreeHash;
|
use tree_hash::TreeHash;
|
||||||
use types::{
|
use types::{test_utils::TestingBeaconStateBuilder, BeaconBlock, ChainSpec, EthSpec, Hash256};
|
||||||
test_utils::TestingBeaconStateBuilder, BeaconBlock, EthSpec, FewValidatorsEthSpec, Hash256,
|
|
||||||
};
|
/// The number initial validators when starting the `Minimal`.
|
||||||
|
const TESTNET_VALIDATOR_COUNT: usize = 16;
|
||||||
|
|
||||||
/// Provides a new, initialized `BeaconChain`
|
/// Provides a new, initialized `BeaconChain`
|
||||||
pub trait InitialiseBeaconChain<T: BeaconChainTypes> {
|
pub trait InitialiseBeaconChain<T: BeaconChainTypes> {
|
||||||
fn initialise_beacon_chain(config: &ClientConfig) -> BeaconChain<T>;
|
fn initialise_beacon_chain(
|
||||||
|
store: Arc<T::Store>,
|
||||||
|
spec: ChainSpec,
|
||||||
|
log: Logger,
|
||||||
|
) -> BeaconChain<T> {
|
||||||
|
maybe_load_from_store_for_testnet::<_, T::Store, T::EthSpec>(store, spec, log)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A testnet-suitable BeaconChainType, using `MemoryStore`.
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TestnetMemoryBeaconChainTypes;
|
pub struct ClientType<S: Store, E: EthSpec> {
|
||||||
|
_phantom_t: PhantomData<S>,
|
||||||
|
_phantom_u: PhantomData<E>,
|
||||||
|
}
|
||||||
|
|
||||||
impl BeaconChainTypes for TestnetMemoryBeaconChainTypes {
|
impl<S: Store, E: EthSpec + Clone> BeaconChainTypes for ClientType<S, E> {
|
||||||
type Store = MemoryStore;
|
type Store = S;
|
||||||
type SlotClock = SystemTimeSlotClock;
|
type SlotClock = SystemTimeSlotClock;
|
||||||
type ForkChoice = BitwiseLMDGhost<Self::Store, Self::EthSpec>;
|
type ForkChoice = OptimizedLMDGhost<S, E>;
|
||||||
type EthSpec = FewValidatorsEthSpec;
|
type EthSpec = E;
|
||||||
}
|
}
|
||||||
|
impl<T: Store, E: EthSpec, X: BeaconChainTypes> InitialiseBeaconChain<X> for ClientType<T, E> {}
|
||||||
|
|
||||||
impl<T> InitialiseBeaconChain<T> for TestnetMemoryBeaconChainTypes
|
/// Loads a `BeaconChain` from `store`, if it exists. Otherwise, create a new chain from genesis.
|
||||||
|
fn maybe_load_from_store_for_testnet<T, U: Store, V: EthSpec>(
|
||||||
|
store: Arc<U>,
|
||||||
|
spec: ChainSpec,
|
||||||
|
log: Logger,
|
||||||
|
) -> BeaconChain<T>
|
||||||
where
|
where
|
||||||
T: BeaconChainTypes<
|
T: BeaconChainTypes<Store = U>,
|
||||||
Store = MemoryStore,
|
T::ForkChoice: ForkChoice<U>,
|
||||||
SlotClock = SystemTimeSlotClock,
|
|
||||||
ForkChoice = BitwiseLMDGhost<MemoryStore, FewValidatorsEthSpec>,
|
|
||||||
>,
|
|
||||||
{
|
{
|
||||||
fn initialise_beacon_chain(_config: &ClientConfig) -> BeaconChain<T> {
|
if let Ok(Some(beacon_chain)) = BeaconChain::from_store(store.clone(), spec.clone()) {
|
||||||
initialize_chain(MemoryStore::open())
|
info!(
|
||||||
}
|
log,
|
||||||
}
|
"Loaded BeaconChain from store";
|
||||||
|
"slot" => beacon_chain.head().beacon_state.slot,
|
||||||
|
"best_slot" => beacon_chain.best_slot(),
|
||||||
|
);
|
||||||
|
|
||||||
/// A testnet-suitable BeaconChainType, using `DiskStore`.
|
beacon_chain
|
||||||
#[derive(Clone)]
|
} else {
|
||||||
pub struct TestnetDiskBeaconChainTypes;
|
info!(log, "Initializing new BeaconChain from genesis");
|
||||||
|
let state_builder = TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(
|
||||||
impl BeaconChainTypes for TestnetDiskBeaconChainTypes {
|
TESTNET_VALIDATOR_COUNT,
|
||||||
type Store = DiskStore;
|
&spec,
|
||||||
type SlotClock = SystemTimeSlotClock;
|
);
|
||||||
type ForkChoice = BitwiseLMDGhost<Self::Store, Self::EthSpec>;
|
|
||||||
type EthSpec = FewValidatorsEthSpec;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> InitialiseBeaconChain<T> for TestnetDiskBeaconChainTypes
|
|
||||||
where
|
|
||||||
T: BeaconChainTypes<
|
|
||||||
Store = DiskStore,
|
|
||||||
SlotClock = SystemTimeSlotClock,
|
|
||||||
ForkChoice = BitwiseLMDGhost<DiskStore, FewValidatorsEthSpec>,
|
|
||||||
>,
|
|
||||||
{
|
|
||||||
fn initialise_beacon_chain(config: &ClientConfig) -> BeaconChain<T> {
|
|
||||||
let store = DiskStore::open(&config.db_name).expect("Unable to open DB.");
|
|
||||||
|
|
||||||
initialize_chain(store)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Produces a `BeaconChain` given some pre-initialized `Store`.
|
|
||||||
fn initialize_chain<T, U: Store, V: EthSpec>(store: U) -> BeaconChain<T>
|
|
||||||
where
|
|
||||||
T: BeaconChainTypes<
|
|
||||||
Store = U,
|
|
||||||
SlotClock = SystemTimeSlotClock,
|
|
||||||
ForkChoice = BitwiseLMDGhost<U, V>,
|
|
||||||
>,
|
|
||||||
{
|
|
||||||
let spec = T::EthSpec::spec();
|
|
||||||
|
|
||||||
let store = Arc::new(store);
|
|
||||||
|
|
||||||
let state_builder = TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(8, &spec);
|
|
||||||
let (genesis_state, _keypairs) = state_builder.build();
|
let (genesis_state, _keypairs) = state_builder.build();
|
||||||
|
|
||||||
let mut genesis_block = BeaconBlock::empty(&spec);
|
let mut genesis_block = BeaconBlock::empty(&spec);
|
||||||
genesis_block.state_root = Hash256::from_slice(&genesis_state.tree_hash_root());
|
genesis_block.state_root = Hash256::from_slice(&genesis_state.tree_hash_root());
|
||||||
|
|
||||||
// Slot clock
|
// Slot clock
|
||||||
let slot_clock = SystemTimeSlotClock::new(
|
let slot_clock = T::SlotClock::new(
|
||||||
spec.genesis_slot,
|
spec.genesis_slot,
|
||||||
genesis_state.genesis_time,
|
genesis_state.genesis_time,
|
||||||
spec.seconds_per_slot,
|
spec.seconds_per_slot,
|
||||||
)
|
);
|
||||||
.expect("Unable to load SystemTimeSlotClock");
|
|
||||||
// Choose the fork choice
|
// Choose the fork choice
|
||||||
let fork_choice = BitwiseLMDGhost::new(store.clone());
|
let fork_choice = T::ForkChoice::new(store.clone());
|
||||||
|
|
||||||
// Genesis chain
|
// Genesis chain
|
||||||
//TODO: Handle error correctly
|
//TODO: Handle error correctly
|
||||||
@ -102,8 +84,9 @@ where
|
|||||||
slot_clock,
|
slot_clock,
|
||||||
genesis_state,
|
genesis_state,
|
||||||
genesis_block,
|
genesis_block,
|
||||||
spec.clone(),
|
spec,
|
||||||
fork_choice,
|
fork_choice,
|
||||||
)
|
)
|
||||||
.expect("Terminate if beacon chain generation fails")
|
.expect("Terminate if beacon chain generation fails")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -1,151 +1,67 @@
|
|||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use fork_choice::ForkChoiceAlgorithm;
|
|
||||||
use http_server::HttpServerConfig;
|
use http_server::HttpServerConfig;
|
||||||
use network::NetworkConfig;
|
use network::NetworkConfig;
|
||||||
use slog::error;
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::net::SocketAddr;
|
|
||||||
use std::net::{IpAddr, Ipv4Addr};
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use types::multiaddr::Protocol;
|
|
||||||
use types::multiaddr::ToMultiaddr;
|
|
||||||
use types::Multiaddr;
|
|
||||||
use types::{ChainSpec, EthSpec, LighthouseTestnetEthSpec};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
/// The core configuration of a Lighthouse beacon node.
|
||||||
pub enum DBType {
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
Memory,
|
|
||||||
Disk,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores the client configuration for this Lighthouse instance.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ClientConfig {
|
pub struct ClientConfig {
|
||||||
pub data_dir: PathBuf,
|
pub data_dir: PathBuf,
|
||||||
pub spec: ChainSpec,
|
pub db_type: String,
|
||||||
pub net_conf: network::NetworkConfig,
|
db_name: String,
|
||||||
pub fork_choice: ForkChoiceAlgorithm,
|
pub network: network::NetworkConfig,
|
||||||
pub db_type: DBType,
|
pub rpc: rpc::RPCConfig,
|
||||||
pub db_name: PathBuf,
|
pub http: HttpServerConfig,
|
||||||
pub rpc_conf: rpc::RPCConfig,
|
|
||||||
pub http_conf: HttpServerConfig, //pub ipc_conf:
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ClientConfig {
|
impl Default for ClientConfig {
|
||||||
/// Build a new lighthouse configuration from defaults.
|
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let data_dir = {
|
|
||||||
let home = dirs::home_dir().expect("Unable to determine home dir.");
|
|
||||||
home.join(".lighthouse/")
|
|
||||||
};
|
|
||||||
fs::create_dir_all(&data_dir)
|
|
||||||
.unwrap_or_else(|_| panic!("Unable to create {:?}", &data_dir));
|
|
||||||
|
|
||||||
let default_spec = LighthouseTestnetEthSpec::spec();
|
|
||||||
let default_net_conf = NetworkConfig::new(default_spec.boot_nodes.clone());
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
data_dir: data_dir.clone(),
|
data_dir: PathBuf::from(".lighthouse"),
|
||||||
// default to foundation for chain specs
|
db_type: "disk".to_string(),
|
||||||
spec: default_spec,
|
db_name: "chain_db".to_string(),
|
||||||
net_conf: default_net_conf,
|
// Note: there are no default bootnodes specified.
|
||||||
// default to bitwise LMD Ghost
|
// Once bootnodes are established, add them here.
|
||||||
fork_choice: ForkChoiceAlgorithm::BitwiseLMDGhost,
|
network: NetworkConfig::new(vec![]),
|
||||||
// default to memory db for now
|
rpc: rpc::RPCConfig::default(),
|
||||||
db_type: DBType::Memory,
|
http: HttpServerConfig::default(),
|
||||||
// default db name for disk-based dbs
|
|
||||||
db_name: data_dir.join("chain_db"),
|
|
||||||
rpc_conf: rpc::RPCConfig::default(),
|
|
||||||
http_conf: HttpServerConfig::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientConfig {
|
impl ClientConfig {
|
||||||
/// Parses the CLI arguments into a `Config` struct.
|
/// Returns the path to which the client may initialize an on-disk database.
|
||||||
pub fn parse_args(args: ArgMatches, log: &slog::Logger) -> Result<Self, &'static str> {
|
pub fn db_path(&self) -> Option<PathBuf> {
|
||||||
let mut config = ClientConfig::default();
|
self.data_dir()
|
||||||
|
.and_then(|path| Some(path.join(&self.db_name)))
|
||||||
/* Network related arguments */
|
|
||||||
|
|
||||||
// Custom p2p listen port
|
|
||||||
if let Some(port_str) = args.value_of("port") {
|
|
||||||
if let Ok(port) = port_str.parse::<u16>() {
|
|
||||||
config.net_conf.listen_port = port;
|
|
||||||
// update the listening multiaddrs
|
|
||||||
for address in &mut config.net_conf.listen_addresses {
|
|
||||||
address.pop();
|
|
||||||
address.append(Protocol::Tcp(port));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error!(log, "Invalid port"; "port" => port_str);
|
|
||||||
return Err("Invalid port");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Custom listening address ipv4/ipv6
|
|
||||||
// TODO: Handle list of addresses
|
|
||||||
if let Some(listen_address_str) = args.value_of("listen-address") {
|
|
||||||
if let Ok(listen_address) = listen_address_str.parse::<IpAddr>() {
|
|
||||||
let multiaddr = SocketAddr::new(listen_address, config.net_conf.listen_port)
|
|
||||||
.to_multiaddr()
|
|
||||||
.expect("Invalid listen address format");
|
|
||||||
config.net_conf.listen_addresses = vec![multiaddr];
|
|
||||||
} else {
|
|
||||||
error!(log, "Invalid IP Address"; "Address" => listen_address_str);
|
|
||||||
return Err("Invalid IP Address");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom bootnodes
|
/// Returns the core path for the client.
|
||||||
if let Some(boot_addresses_str) = args.value_of("boot-nodes") {
|
pub fn data_dir(&self) -> Option<PathBuf> {
|
||||||
let mut boot_addresses_split = boot_addresses_str.split(",");
|
let path = dirs::home_dir()?.join(&self.data_dir);
|
||||||
for boot_address in boot_addresses_split {
|
fs::create_dir_all(&path).ok()?;
|
||||||
if let Ok(boot_address) = boot_address.parse::<Multiaddr>() {
|
Some(path)
|
||||||
config.net_conf.boot_nodes.append(&mut vec![boot_address]);
|
|
||||||
} else {
|
|
||||||
error!(log, "Invalid Bootnode multiaddress"; "Multiaddr" => boot_addresses_str);
|
|
||||||
return Err("Invalid IP Address");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Filesystem related arguments */
|
/// Apply the following arguments to `self`, replacing values if they are specified in `args`.
|
||||||
|
///
|
||||||
// Custom datadir
|
/// Returns an error if arguments are obviously invalid. May succeed even if some values are
|
||||||
|
/// invalid.
|
||||||
|
pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), &'static str> {
|
||||||
if let Some(dir) = args.value_of("datadir") {
|
if let Some(dir) = args.value_of("datadir") {
|
||||||
config.data_dir = PathBuf::from(dir.to_string());
|
self.data_dir = PathBuf::from(dir);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* RPC related arguments */
|
if let Some(dir) = args.value_of("db") {
|
||||||
|
self.db_type = dir.to_string();
|
||||||
if args.is_present("rpc") {
|
|
||||||
config.rpc_conf.enabled = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(rpc_address) = args.value_of("rpc-address") {
|
self.network.apply_cli_args(args)?;
|
||||||
if let Ok(listen_address) = rpc_address.parse::<Ipv4Addr>() {
|
self.rpc.apply_cli_args(args)?;
|
||||||
config.rpc_conf.listen_address = listen_address;
|
self.http.apply_cli_args(args)?;
|
||||||
} else {
|
|
||||||
error!(log, "Invalid RPC listen address"; "Address" => rpc_address);
|
|
||||||
return Err("Invalid RPC listen address");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(rpc_port) = args.value_of("rpc-port") {
|
Ok(())
|
||||||
if let Ok(port) = rpc_port.parse::<u16>() {
|
|
||||||
config.rpc_conf.port = port;
|
|
||||||
} else {
|
|
||||||
error!(log, "Invalid RPC port"; "port" => rpc_port);
|
|
||||||
return Err("Invalid RPC port");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match args.value_of("db") {
|
|
||||||
Some("disk") => config.db_type = DBType::Disk,
|
|
||||||
Some("memory") => config.db_type = DBType::Memory,
|
|
||||||
_ => unreachable!(), // clap prevents this.
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(config)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
// generates error types
|
|
||||||
use network;
|
use network;
|
||||||
|
|
||||||
use error_chain::{
|
use error_chain::error_chain;
|
||||||
error_chain, error_chain_processing, impl_error_chain_kind, impl_error_chain_processed,
|
|
||||||
impl_extract_backtrace,
|
|
||||||
};
|
|
||||||
|
|
||||||
error_chain! {
|
error_chain! {
|
||||||
links {
|
links {
|
||||||
Network(network::error::Error, network::error::ErrorKind);
|
Network(network::error::Error, network::error::ErrorKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,10 @@ pub mod error;
|
|||||||
pub mod notifier;
|
pub mod notifier;
|
||||||
|
|
||||||
use beacon_chain::BeaconChain;
|
use beacon_chain::BeaconChain;
|
||||||
use beacon_chain_types::InitialiseBeaconChain;
|
|
||||||
use exit_future::Signal;
|
use exit_future::Signal;
|
||||||
use futures::{future::Future, Stream};
|
use futures::{future::Future, Stream};
|
||||||
use network::Service as NetworkService;
|
use network::Service as NetworkService;
|
||||||
|
use prometheus::Registry;
|
||||||
use slog::{error, info, o};
|
use slog::{error, info, o};
|
||||||
use slot_clock::SlotClock;
|
use slot_clock::SlotClock;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
@ -19,16 +19,18 @@ use tokio::runtime::TaskExecutor;
|
|||||||
use tokio::timer::Interval;
|
use tokio::timer::Interval;
|
||||||
|
|
||||||
pub use beacon_chain::BeaconChainTypes;
|
pub use beacon_chain::BeaconChainTypes;
|
||||||
pub use beacon_chain_types::{TestnetDiskBeaconChainTypes, TestnetMemoryBeaconChainTypes};
|
pub use beacon_chain_types::ClientType;
|
||||||
pub use client_config::{ClientConfig, DBType};
|
pub use beacon_chain_types::InitialiseBeaconChain;
|
||||||
|
pub use client_config::ClientConfig;
|
||||||
|
pub use eth2_config::Eth2Config;
|
||||||
|
|
||||||
/// Main beacon node client service. This provides the connection and initialisation of the clients
|
/// Main beacon node client service. This provides the connection and initialisation of the clients
|
||||||
/// sub-services in multiple threads.
|
/// sub-services in multiple threads.
|
||||||
pub struct Client<T: BeaconChainTypes> {
|
pub struct Client<T: BeaconChainTypes> {
|
||||||
/// Configuration for the lighthouse client.
|
/// Configuration for the lighthouse client.
|
||||||
_config: ClientConfig,
|
_client_config: ClientConfig,
|
||||||
/// The beacon chain for the running client.
|
/// The beacon chain for the running client.
|
||||||
_beacon_chain: Arc<BeaconChain<T>>,
|
beacon_chain: Arc<BeaconChain<T>>,
|
||||||
/// Reference to the network service.
|
/// Reference to the network service.
|
||||||
pub network: Arc<NetworkService<T>>,
|
pub network: Arc<NetworkService<T>>,
|
||||||
/// Signal to terminate the RPC server.
|
/// Signal to terminate the RPC server.
|
||||||
@ -49,12 +51,27 @@ where
|
|||||||
{
|
{
|
||||||
/// Generate an instance of the client. Spawn and link all internal sub-processes.
|
/// Generate an instance of the client. Spawn and link all internal sub-processes.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
config: ClientConfig,
|
client_config: ClientConfig,
|
||||||
|
eth2_config: Eth2Config,
|
||||||
|
store: T::Store,
|
||||||
log: slog::Logger,
|
log: slog::Logger,
|
||||||
executor: &TaskExecutor,
|
executor: &TaskExecutor,
|
||||||
) -> error::Result<Self> {
|
) -> error::Result<Self> {
|
||||||
// generate a beacon chain
|
let metrics_registry = Registry::new();
|
||||||
let beacon_chain = Arc::new(T::initialise_beacon_chain(&config));
|
let store = Arc::new(store);
|
||||||
|
let seconds_per_slot = eth2_config.spec.seconds_per_slot;
|
||||||
|
|
||||||
|
// Load a `BeaconChain` from the store, or create a new one if it does not exist.
|
||||||
|
let beacon_chain = Arc::new(T::initialise_beacon_chain(
|
||||||
|
store,
|
||||||
|
eth2_config.spec.clone(),
|
||||||
|
log.clone(),
|
||||||
|
));
|
||||||
|
// Registry all beacon chain metrics with the global registry.
|
||||||
|
beacon_chain
|
||||||
|
.metrics
|
||||||
|
.register(&metrics_registry)
|
||||||
|
.expect("Failed to registry metrics");
|
||||||
|
|
||||||
if beacon_chain.read_slot_clock().is_none() {
|
if beacon_chain.read_slot_clock().is_none() {
|
||||||
panic!("Cannot start client before genesis!")
|
panic!("Cannot start client before genesis!")
|
||||||
@ -65,7 +82,7 @@ where
|
|||||||
// If we don't block here we create an initial scenario where we're unable to process any
|
// If we don't block here we create an initial scenario where we're unable to process any
|
||||||
// blocks and we're basically useless.
|
// blocks and we're basically useless.
|
||||||
{
|
{
|
||||||
let state_slot = beacon_chain.state.read().slot;
|
let state_slot = beacon_chain.head().beacon_state.slot;
|
||||||
let wall_clock_slot = beacon_chain.read_slot_clock().unwrap();
|
let wall_clock_slot = beacon_chain.read_slot_clock().unwrap();
|
||||||
let slots_since_genesis = beacon_chain.slots_since_genesis().unwrap();
|
let slots_since_genesis = beacon_chain.slots_since_genesis().unwrap();
|
||||||
info!(
|
info!(
|
||||||
@ -81,13 +98,13 @@ where
|
|||||||
info!(
|
info!(
|
||||||
log,
|
log,
|
||||||
"State initialized";
|
"State initialized";
|
||||||
"state_slot" => beacon_chain.state.read().slot,
|
"state_slot" => beacon_chain.head().beacon_state.slot,
|
||||||
"wall_clock_slot" => beacon_chain.read_slot_clock().unwrap(),
|
"wall_clock_slot" => beacon_chain.read_slot_clock().unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Start the network service, libp2p and syncing threads
|
// Start the network service, libp2p and syncing threads
|
||||||
// TODO: Add beacon_chain reference to network parameters
|
// TODO: Add beacon_chain reference to network parameters
|
||||||
let network_config = &config.net_conf;
|
let network_config = &client_config.network;
|
||||||
let network_logger = log.new(o!("Service" => "Network"));
|
let network_logger = log.new(o!("Service" => "Network"));
|
||||||
let (network, network_send) = NetworkService::new(
|
let (network, network_send) = NetworkService::new(
|
||||||
beacon_chain.clone(),
|
beacon_chain.clone(),
|
||||||
@ -97,9 +114,9 @@ where
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
// spawn the RPC server
|
// spawn the RPC server
|
||||||
let rpc_exit_signal = if config.rpc_conf.enabled {
|
let rpc_exit_signal = if client_config.rpc.enabled {
|
||||||
Some(rpc::start_server(
|
Some(rpc::start_server(
|
||||||
&config.rpc_conf,
|
&client_config.rpc,
|
||||||
executor,
|
executor,
|
||||||
network_send.clone(),
|
network_send.clone(),
|
||||||
beacon_chain.clone(),
|
beacon_chain.clone(),
|
||||||
@ -112,20 +129,26 @@ where
|
|||||||
// Start the `http_server` service.
|
// Start the `http_server` service.
|
||||||
//
|
//
|
||||||
// Note: presently we are ignoring the config and _always_ starting a HTTP server.
|
// Note: presently we are ignoring the config and _always_ starting a HTTP server.
|
||||||
let http_exit_signal = Some(http_server::start_service(
|
let http_exit_signal = if client_config.http.enabled {
|
||||||
&config.http_conf,
|
Some(http_server::start_service(
|
||||||
|
&client_config.http,
|
||||||
executor,
|
executor,
|
||||||
network_send,
|
network_send,
|
||||||
beacon_chain.clone(),
|
beacon_chain.clone(),
|
||||||
|
client_config.db_path().expect("unable to read datadir"),
|
||||||
|
metrics_registry,
|
||||||
&log,
|
&log,
|
||||||
));
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let (slot_timer_exit_signal, exit) = exit_future::signal();
|
let (slot_timer_exit_signal, exit) = exit_future::signal();
|
||||||
if let Ok(Some(duration_to_next_slot)) = beacon_chain.slot_clock.duration_to_next_slot() {
|
if let Ok(Some(duration_to_next_slot)) = beacon_chain.slot_clock.duration_to_next_slot() {
|
||||||
// set up the validator work interval - start at next slot and proceed every slot
|
// set up the validator work interval - start at next slot and proceed every slot
|
||||||
let interval = {
|
let interval = {
|
||||||
// Set the interval to start at the next slot, and every slot after
|
// Set the interval to start at the next slot, and every slot after
|
||||||
let slot_duration = Duration::from_secs(config.spec.seconds_per_slot);
|
let slot_duration = Duration::from_secs(seconds_per_slot);
|
||||||
//TODO: Handle checked add correctly
|
//TODO: Handle checked add correctly
|
||||||
Interval::new(Instant::now() + duration_to_next_slot, slot_duration)
|
Interval::new(Instant::now() + duration_to_next_slot, slot_duration)
|
||||||
};
|
};
|
||||||
@ -147,8 +170,8 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(Client {
|
Ok(Client {
|
||||||
_config: config,
|
_client_config: client_config,
|
||||||
_beacon_chain: beacon_chain,
|
beacon_chain,
|
||||||
http_exit_signal,
|
http_exit_signal,
|
||||||
rpc_exit_signal,
|
rpc_exit_signal,
|
||||||
slot_timer_exit_signal: Some(slot_timer_exit_signal),
|
slot_timer_exit_signal: Some(slot_timer_exit_signal),
|
||||||
@ -159,6 +182,14 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: BeaconChainTypes> Drop for Client<T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Save the beacon chain to it's store before dropping.
|
||||||
|
let _result = self.beacon_chain.persist();
|
||||||
|
dbg!("Saved BeaconChain to store");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn do_state_catchup<T: BeaconChainTypes>(chain: &Arc<BeaconChain<T>>, log: &slog::Logger) {
|
fn do_state_catchup<T: BeaconChainTypes>(chain: &Arc<BeaconChain<T>>, log: &slog::Logger) {
|
||||||
if let Some(genesis_height) = chain.slots_since_genesis() {
|
if let Some(genesis_height) = chain.slots_since_genesis() {
|
||||||
let result = chain.catchup_state();
|
let result = chain.catchup_state();
|
||||||
@ -167,7 +198,7 @@ fn do_state_catchup<T: BeaconChainTypes>(chain: &Arc<BeaconChain<T>>, log: &slog
|
|||||||
"best_slot" => chain.head().beacon_block.slot,
|
"best_slot" => chain.head().beacon_block.slot,
|
||||||
"latest_block_root" => format!("{}", chain.head().beacon_block_root),
|
"latest_block_root" => format!("{}", chain.head().beacon_block_root),
|
||||||
"wall_clock_slot" => chain.read_slot_clock().unwrap(),
|
"wall_clock_slot" => chain.read_slot_clock().unwrap(),
|
||||||
"state_slot" => chain.state.read().slot,
|
"state_slot" => chain.head().beacon_state.slot,
|
||||||
"slots_since_genesis" => genesis_height,
|
"slots_since_genesis" => genesis_height,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -6,9 +6,12 @@ edition = "2018"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
beacon_chain = { path = "../beacon_chain" }
|
beacon_chain = { path = "../beacon_chain" }
|
||||||
|
clap = "2.32.0"
|
||||||
# SigP repository until PR is merged
|
# SigP repository until PR is merged
|
||||||
libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "b3c32d9a821ae6cc89079499cc6e8a6bab0bffc3" }
|
libp2p = { git = "https://github.com/SigP/rust-libp2p", rev = "b3c32d9a821ae6cc89079499cc6e8a6bab0bffc3" }
|
||||||
types = { path = "../../eth2/types" }
|
types = { path = "../../eth2/types" }
|
||||||
|
serde = "1.0"
|
||||||
|
serde_derive = "1.0"
|
||||||
ssz = { path = "../../eth2/utils/ssz" }
|
ssz = { path = "../../eth2/utils/ssz" }
|
||||||
ssz_derive = { path = "../../eth2/utils/ssz_derive" }
|
ssz_derive = { path = "../../eth2/utils/ssz_derive" }
|
||||||
slog = "2.4.1"
|
slog = "2.4.1"
|
||||||
|
@ -261,7 +261,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ssz_encoding() {
|
fn ssz_encoding() {
|
||||||
let original = PubsubMessage::Block(BeaconBlock::empty(&FoundationEthSpec::spec()));
|
let original = PubsubMessage::Block(BeaconBlock::empty(&MainnetEthSpec::default_spec()));
|
||||||
|
|
||||||
let encoded = ssz_encode(&original);
|
let encoded = ssz_encode(&original);
|
||||||
|
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
use crate::Multiaddr;
|
use clap::ArgMatches;
|
||||||
use libp2p::gossipsub::{GossipsubConfig, GossipsubConfigBuilder};
|
use libp2p::gossipsub::{GossipsubConfig, GossipsubConfigBuilder};
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use types::multiaddr::{Error as MultiaddrError, Multiaddr};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
/// Network configuration for lighthouse.
|
/// Network configuration for lighthouse.
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
//TODO: stubbing networking initial params, change in the future
|
|
||||||
/// IP address to listen on.
|
/// IP address to listen on.
|
||||||
pub listen_addresses: Vec<Multiaddr>,
|
listen_addresses: Vec<String>,
|
||||||
/// Listen port UDP/TCP.
|
|
||||||
pub listen_port: u16,
|
|
||||||
/// Gossipsub configuration parameters.
|
/// Gossipsub configuration parameters.
|
||||||
|
#[serde(skip)]
|
||||||
pub gs_config: GossipsubConfig,
|
pub gs_config: GossipsubConfig,
|
||||||
/// Configuration parameters for node identification protocol.
|
/// Configuration parameters for node identification protocol.
|
||||||
|
#[serde(skip)]
|
||||||
pub identify_config: IdentifyConfig,
|
pub identify_config: IdentifyConfig,
|
||||||
/// List of nodes to initially connect to.
|
/// List of nodes to initially connect to.
|
||||||
pub boot_nodes: Vec<Multiaddr>,
|
boot_nodes: Vec<String>,
|
||||||
/// Client version
|
/// Client version
|
||||||
pub client_version: String,
|
pub client_version: String,
|
||||||
/// List of topics to subscribe to as strings
|
/// List of topics to subscribe to as strings
|
||||||
@ -25,15 +27,12 @@ impl Default for Config {
|
|||||||
/// Generate a default network configuration.
|
/// Generate a default network configuration.
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Config {
|
Config {
|
||||||
listen_addresses: vec!["/ip4/127.0.0.1/tcp/9000"
|
listen_addresses: vec!["/ip4/127.0.0.1/tcp/9000".to_string()],
|
||||||
.parse()
|
|
||||||
.expect("is a correct multi-address")],
|
|
||||||
listen_port: 9000,
|
|
||||||
gs_config: GossipsubConfigBuilder::new()
|
gs_config: GossipsubConfigBuilder::new()
|
||||||
.max_gossip_size(4_000_000)
|
.max_gossip_size(4_000_000)
|
||||||
.build(),
|
.build(),
|
||||||
identify_config: IdentifyConfig::default(),
|
identify_config: IdentifyConfig::default(),
|
||||||
boot_nodes: Vec::new(),
|
boot_nodes: vec![],
|
||||||
client_version: version::version(),
|
client_version: version::version(),
|
||||||
topics: vec![String::from("beacon_chain")],
|
topics: vec![String::from("beacon_chain")],
|
||||||
}
|
}
|
||||||
@ -41,12 +40,34 @@ impl Default for Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn new(boot_nodes: Vec<Multiaddr>) -> Self {
|
pub fn new(boot_nodes: Vec<String>) -> Self {
|
||||||
let mut conf = Config::default();
|
let mut conf = Config::default();
|
||||||
conf.boot_nodes = boot_nodes;
|
conf.boot_nodes = boot_nodes;
|
||||||
|
|
||||||
conf
|
conf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn listen_addresses(&self) -> Result<Vec<Multiaddr>, MultiaddrError> {
|
||||||
|
self.listen_addresses.iter().map(|s| s.parse()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn boot_nodes(&self) -> Result<Vec<Multiaddr>, MultiaddrError> {
|
||||||
|
self.boot_nodes.iter().map(|s| s.parse()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), &'static str> {
|
||||||
|
if let Some(listen_address_str) = args.value_of("listen-address") {
|
||||||
|
let listen_addresses = listen_address_str.split(',').map(Into::into).collect();
|
||||||
|
self.listen_addresses = listen_addresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(boot_addresses_str) = args.value_of("boot-nodes") {
|
||||||
|
let boot_addresses = boot_addresses_str.split(',').map(Into::into).collect();
|
||||||
|
self.boot_nodes = boot_addresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The configuration parameters for the Identify protocol
|
/// The configuration parameters for the Identify protocol
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
// generates error types
|
// generates error types
|
||||||
|
|
||||||
use error_chain::{
|
use error_chain::error_chain;
|
||||||
error_chain, error_chain_processing, impl_error_chain_kind, impl_error_chain_processed,
|
|
||||||
impl_extract_backtrace,
|
|
||||||
};
|
|
||||||
|
|
||||||
error_chain! {}
|
error_chain! {}
|
||||||
|
@ -172,8 +172,8 @@ pub struct BeaconBlockRootsResponse {
|
|||||||
impl BeaconBlockRootsResponse {
|
impl BeaconBlockRootsResponse {
|
||||||
/// Returns `true` if each `self.roots.slot[i]` is higher than the preceeding `i`.
|
/// Returns `true` if each `self.roots.slot[i]` is higher than the preceeding `i`.
|
||||||
pub fn slots_are_ascending(&self) -> bool {
|
pub fn slots_are_ascending(&self) -> bool {
|
||||||
for i in 1..self.roots.len() {
|
for window in self.roots.windows(2) {
|
||||||
if self.roots[i - 1].slot >= self.roots[i].slot {
|
if window[0].slot >= window[1].slot {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ impl RequestId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return the previous id.
|
/// Return the previous id.
|
||||||
pub fn previous(&self) -> Self {
|
pub fn previous(self) -> Self {
|
||||||
Self(self.0 - 1)
|
Self(self.0 - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,10 +130,6 @@ struct SszContainer {
|
|||||||
bytes: Vec<u8>,
|
bytes: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE!
|
|
||||||
//
|
|
||||||
// This code has not been tested, it is a placeholder until we can update to the new libp2p
|
|
||||||
// spec.
|
|
||||||
fn decode(packet: Vec<u8>) -> Result<RPCEvent, DecodeError> {
|
fn decode(packet: Vec<u8>) -> Result<RPCEvent, DecodeError> {
|
||||||
let msg = SszContainer::from_ssz_bytes(&packet)?;
|
let msg = SszContainer::from_ssz_bytes(&packet)?;
|
||||||
|
|
||||||
@ -220,7 +216,7 @@ impl Encode for RPCEvent {
|
|||||||
} => SszContainer {
|
} => SszContainer {
|
||||||
is_request: true,
|
is_request: true,
|
||||||
id: (*id).into(),
|
id: (*id).into(),
|
||||||
other: (*method_id).into(),
|
other: *method_id,
|
||||||
bytes: match body {
|
bytes: match body {
|
||||||
RPCRequest::Hello(body) => body.as_ssz_bytes(),
|
RPCRequest::Hello(body) => body.as_ssz_bytes(),
|
||||||
RPCRequest::Goodbye(body) => body.as_ssz_bytes(),
|
RPCRequest::Goodbye(body) => body.as_ssz_bytes(),
|
||||||
@ -237,7 +233,7 @@ impl Encode for RPCEvent {
|
|||||||
} => SszContainer {
|
} => SszContainer {
|
||||||
is_request: false,
|
is_request: false,
|
||||||
id: (*id).into(),
|
id: (*id).into(),
|
||||||
other: (*method_id).into(),
|
other: *method_id,
|
||||||
bytes: match result {
|
bytes: match result {
|
||||||
RPCResponse::Hello(response) => response.as_ssz_bytes(),
|
RPCResponse::Hello(response) => response.as_ssz_bytes(),
|
||||||
RPCResponse::BeaconBlockRoots(response) => response.as_ssz_bytes(),
|
RPCResponse::BeaconBlockRoots(response) => response.as_ssz_bytes(),
|
||||||
|
@ -57,7 +57,10 @@ impl Service {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// listen on all addresses
|
// listen on all addresses
|
||||||
for address in &config.listen_addresses {
|
for address in config
|
||||||
|
.listen_addresses()
|
||||||
|
.map_err(|e| format!("Invalid listen multiaddr: {}", e))?
|
||||||
|
{
|
||||||
match Swarm::listen_on(&mut swarm, address.clone()) {
|
match Swarm::listen_on(&mut swarm, address.clone()) {
|
||||||
Ok(mut listen_addr) => {
|
Ok(mut listen_addr) => {
|
||||||
listen_addr.append(Protocol::P2p(local_peer_id.clone().into()));
|
listen_addr.append(Protocol::P2p(local_peer_id.clone().into()));
|
||||||
@ -68,7 +71,10 @@ impl Service {
|
|||||||
}
|
}
|
||||||
// connect to boot nodes - these are currently stored as multiaddrs
|
// connect to boot nodes - these are currently stored as multiaddrs
|
||||||
// Once we have discovery, can set to peerId
|
// Once we have discovery, can set to peerId
|
||||||
for bootnode in config.boot_nodes {
|
for bootnode in config
|
||||||
|
.boot_nodes()
|
||||||
|
.map_err(|e| format!("Invalid boot node multiaddr: {:?}", e))?
|
||||||
|
{
|
||||||
match Swarm::dial_addr(&mut swarm, bootnode.clone()) {
|
match Swarm::dial_addr(&mut swarm, bootnode.clone()) {
|
||||||
Ok(()) => debug!(log, "Dialing bootnode: {}", bootnode),
|
Ok(()) => debug!(log, "Dialing bootnode: {}", bootnode),
|
||||||
Err(err) => debug!(
|
Err(err) => debug!(
|
||||||
|
@ -20,7 +20,7 @@ fork_choice = { path = "../../eth2/fork_choice" }
|
|||||||
grpcio = { version = "0.4", default-features = false, features = ["protobuf-codec"] }
|
grpcio = { version = "0.4", default-features = false, features = ["protobuf-codec"] }
|
||||||
persistent = "^0.4"
|
persistent = "^0.4"
|
||||||
protobuf = "2.0.2"
|
protobuf = "2.0.2"
|
||||||
prometheus = "^0.6"
|
prometheus = { version = "^0.6", features = ["process"] }
|
||||||
clap = "2.32.0"
|
clap = "2.32.0"
|
||||||
store = { path = "../store" }
|
store = { path = "../store" }
|
||||||
dirs = "1.0.3"
|
dirs = "1.0.3"
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
|
use crate::metrics::LocalMetrics;
|
||||||
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||||
use iron::typemap::Key;
|
use iron::typemap::Key;
|
||||||
|
use prometheus::Registry;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct BeaconChainKey<T> {
|
pub struct BeaconChainKey<T> {
|
||||||
@ -10,3 +13,21 @@ pub struct BeaconChainKey<T> {
|
|||||||
impl<T: BeaconChainTypes + 'static> Key for BeaconChainKey<T> {
|
impl<T: BeaconChainTypes + 'static> Key for BeaconChainKey<T> {
|
||||||
type Value = Arc<BeaconChain<T>>;
|
type Value = Arc<BeaconChain<T>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct MetricsRegistryKey;
|
||||||
|
|
||||||
|
impl Key for MetricsRegistryKey {
|
||||||
|
type Value = Registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LocalMetricsKey;
|
||||||
|
|
||||||
|
impl Key for LocalMetricsKey {
|
||||||
|
type Value = LocalMetrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DBPathKey;
|
||||||
|
|
||||||
|
impl Key for DBPathKey {
|
||||||
|
type Value = PathBuf;
|
||||||
|
}
|
||||||
|
@ -3,39 +3,65 @@ mod key;
|
|||||||
mod metrics;
|
mod metrics;
|
||||||
|
|
||||||
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||||
|
use clap::ArgMatches;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use iron::prelude::*;
|
use iron::prelude::*;
|
||||||
use network::NetworkMessage;
|
use network::NetworkMessage;
|
||||||
|
use prometheus::Registry;
|
||||||
use router::Router;
|
use router::Router;
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use slog::{info, o, warn};
|
use slog::{info, o, warn};
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::runtime::TaskExecutor;
|
use tokio::runtime::TaskExecutor;
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct HttpServerConfig {
|
pub struct HttpServerConfig {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub listen_address: String,
|
pub listen_address: String,
|
||||||
|
pub listen_port: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for HttpServerConfig {
|
impl Default for HttpServerConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
listen_address: "127.0.0.1:5051".to_string(),
|
listen_address: "127.0.0.1".to_string(),
|
||||||
|
listen_port: "5052".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HttpServerConfig {
|
||||||
|
pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), &'static str> {
|
||||||
|
if args.is_present("http") {
|
||||||
|
self.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(listen_address) = args.value_of("http-address") {
|
||||||
|
self.listen_address = listen_address.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(listen_port) = args.value_of("http-port") {
|
||||||
|
self.listen_port = listen_port.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Build the `iron` HTTP server, defining the core routes.
|
/// Build the `iron` HTTP server, defining the core routes.
|
||||||
pub fn create_iron_http_server<T: BeaconChainTypes + 'static>(
|
pub fn create_iron_http_server<T: BeaconChainTypes + 'static>(
|
||||||
beacon_chain: Arc<BeaconChain<T>>,
|
beacon_chain: Arc<BeaconChain<T>>,
|
||||||
|
db_path: PathBuf,
|
||||||
|
metrics_registry: Registry,
|
||||||
) -> Iron<Router> {
|
) -> Iron<Router> {
|
||||||
let mut router = Router::new();
|
let mut router = Router::new();
|
||||||
|
|
||||||
// A `GET` request to `/metrics` is handled by the `metrics` module.
|
// A `GET` request to `/metrics` is handled by the `metrics` module.
|
||||||
router.get(
|
router.get(
|
||||||
"/metrics",
|
"/metrics",
|
||||||
metrics::build_handler(beacon_chain.clone()),
|
metrics::build_handler(beacon_chain.clone(), db_path, metrics_registry),
|
||||||
"metrics",
|
"metrics",
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -51,6 +77,8 @@ pub fn start_service<T: BeaconChainTypes + 'static>(
|
|||||||
executor: &TaskExecutor,
|
executor: &TaskExecutor,
|
||||||
_network_chan: crossbeam_channel::Sender<NetworkMessage>,
|
_network_chan: crossbeam_channel::Sender<NetworkMessage>,
|
||||||
beacon_chain: Arc<BeaconChain<T>>,
|
beacon_chain: Arc<BeaconChain<T>>,
|
||||||
|
db_path: PathBuf,
|
||||||
|
metrics_registry: Registry,
|
||||||
log: &slog::Logger,
|
log: &slog::Logger,
|
||||||
) -> exit_future::Signal {
|
) -> exit_future::Signal {
|
||||||
let log = log.new(o!("Service"=>"HTTP"));
|
let log = log.new(o!("Service"=>"HTTP"));
|
||||||
@ -61,7 +89,7 @@ pub fn start_service<T: BeaconChainTypes + 'static>(
|
|||||||
let (shutdown_trigger, wait_for_shutdown) = exit_future::signal();
|
let (shutdown_trigger, wait_for_shutdown) = exit_future::signal();
|
||||||
|
|
||||||
// Create an `iron` http, without starting it yet.
|
// Create an `iron` http, without starting it yet.
|
||||||
let iron = create_iron_http_server(beacon_chain);
|
let iron = create_iron_http_server(beacon_chain, db_path, metrics_registry);
|
||||||
|
|
||||||
// Create a HTTP server future.
|
// Create a HTTP server future.
|
||||||
//
|
//
|
||||||
@ -69,16 +97,14 @@ pub fn start_service<T: BeaconChainTypes + 'static>(
|
|||||||
// 2. Build an exit future that will shutdown the server when requested.
|
// 2. Build an exit future that will shutdown the server when requested.
|
||||||
// 3. Return the exit future, so the caller may shutdown the service when desired.
|
// 3. Return the exit future, so the caller may shutdown the service when desired.
|
||||||
let http_service = {
|
let http_service = {
|
||||||
|
let listen_address = format!("{}:{}", config.listen_address, config.listen_port);
|
||||||
// Start the HTTP server
|
// Start the HTTP server
|
||||||
let server_start_result = iron.http(config.listen_address.clone());
|
let server_start_result = iron.http(listen_address.clone());
|
||||||
|
|
||||||
if server_start_result.is_ok() {
|
if server_start_result.is_ok() {
|
||||||
info!(log, "HTTP server running on {}", config.listen_address);
|
info!(log, "HTTP server running on {}", listen_address);
|
||||||
} else {
|
} else {
|
||||||
warn!(
|
warn!(log, "HTTP server failed to start on {}", listen_address);
|
||||||
log,
|
|
||||||
"HTTP server failed to start on {}", config.listen_address
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a future that will shutdown the HTTP server when the `shutdown_trigger` is
|
// Build a future that will shutdown the HTTP server when the `shutdown_trigger` is
|
||||||
|
@ -1,20 +1,34 @@
|
|||||||
use crate::{key::BeaconChainKey, map_persistent_err_to_500};
|
use crate::{
|
||||||
|
key::{BeaconChainKey, DBPathKey, LocalMetricsKey, MetricsRegistryKey},
|
||||||
|
map_persistent_err_to_500,
|
||||||
|
};
|
||||||
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||||
use iron::prelude::*;
|
use iron::prelude::*;
|
||||||
use iron::{status::Status, Handler, IronResult, Request, Response};
|
use iron::{status::Status, Handler, IronResult, Request, Response};
|
||||||
use persistent::Read;
|
use persistent::Read;
|
||||||
use prometheus::{Encoder, IntCounter, Opts, Registry, TextEncoder};
|
use prometheus::{Encoder, Registry, TextEncoder};
|
||||||
use slot_clock::SlotClock;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use types::Slot;
|
|
||||||
|
pub use local_metrics::LocalMetrics;
|
||||||
|
|
||||||
|
mod local_metrics;
|
||||||
|
|
||||||
/// Yields a handler for the metrics endpoint.
|
/// Yields a handler for the metrics endpoint.
|
||||||
pub fn build_handler<T: BeaconChainTypes + 'static>(
|
pub fn build_handler<T: BeaconChainTypes + 'static>(
|
||||||
beacon_chain: Arc<BeaconChain<T>>,
|
beacon_chain: Arc<BeaconChain<T>>,
|
||||||
|
db_path: PathBuf,
|
||||||
|
metrics_registry: Registry,
|
||||||
) -> impl Handler {
|
) -> impl Handler {
|
||||||
let mut chain = Chain::new(handle_metrics::<T>);
|
let mut chain = Chain::new(handle_metrics::<T>);
|
||||||
|
|
||||||
|
let local_metrics = LocalMetrics::new().unwrap();
|
||||||
|
local_metrics.register(&metrics_registry).unwrap();
|
||||||
|
|
||||||
chain.link(Read::<BeaconChainKey<T>>::both(beacon_chain));
|
chain.link(Read::<BeaconChainKey<T>>::both(beacon_chain));
|
||||||
|
chain.link(Read::<MetricsRegistryKey>::both(metrics_registry));
|
||||||
|
chain.link(Read::<LocalMetricsKey>::both(local_metrics));
|
||||||
|
chain.link(Read::<DBPathKey>::both(db_path));
|
||||||
|
|
||||||
chain
|
chain
|
||||||
}
|
}
|
||||||
@ -27,23 +41,28 @@ fn handle_metrics<T: BeaconChainTypes + 'static>(req: &mut Request) -> IronResul
|
|||||||
.get::<Read<BeaconChainKey<T>>>()
|
.get::<Read<BeaconChainKey<T>>>()
|
||||||
.map_err(map_persistent_err_to_500)?;
|
.map_err(map_persistent_err_to_500)?;
|
||||||
|
|
||||||
let r = Registry::new();
|
let r = req
|
||||||
|
.get::<Read<MetricsRegistryKey>>()
|
||||||
|
.map_err(map_persistent_err_to_500)?;
|
||||||
|
|
||||||
let present_slot = if let Ok(Some(slot)) = beacon_chain.slot_clock.present_slot() {
|
let local_metrics = req
|
||||||
slot
|
.get::<Read<LocalMetricsKey>>()
|
||||||
} else {
|
.map_err(map_persistent_err_to_500)?;
|
||||||
Slot::new(0)
|
|
||||||
};
|
let db_path = req
|
||||||
register_and_set_slot(
|
.get::<Read<DBPathKey>>()
|
||||||
&r,
|
.map_err(map_persistent_err_to_500)?;
|
||||||
"present_slot",
|
|
||||||
"direct_slock_clock_reading",
|
// Update metrics that are calculated on each scrape.
|
||||||
present_slot,
|
local_metrics.update(&beacon_chain, &db_path);
|
||||||
);
|
|
||||||
|
|
||||||
// Gather the metrics.
|
|
||||||
let mut buffer = vec![];
|
let mut buffer = vec![];
|
||||||
let encoder = TextEncoder::new();
|
let encoder = TextEncoder::new();
|
||||||
|
|
||||||
|
// Gather `DEFAULT_REGISTRY` metrics.
|
||||||
|
encoder.encode(&prometheus::gather(), &mut buffer).unwrap();
|
||||||
|
|
||||||
|
// Gather metrics from our registry.
|
||||||
let metric_families = r.gather();
|
let metric_families = r.gather();
|
||||||
encoder.encode(&metric_families, &mut buffer).unwrap();
|
encoder.encode(&metric_families, &mut buffer).unwrap();
|
||||||
|
|
||||||
@ -51,10 +70,3 @@ fn handle_metrics<T: BeaconChainTypes + 'static>(req: &mut Request) -> IronResul
|
|||||||
|
|
||||||
Ok(Response::with((Status::Ok, prom_string)))
|
Ok(Response::with((Status::Ok, prom_string)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_and_set_slot(registry: &Registry, name: &str, help: &str, slot: Slot) {
|
|
||||||
let counter_opts = Opts::new(name, help);
|
|
||||||
let counter = IntCounter::with_opts(counter_opts).unwrap();
|
|
||||||
registry.register(Box::new(counter.clone())).unwrap();
|
|
||||||
counter.inc_by(slot.as_u64() as i64);
|
|
||||||
}
|
|
||||||
|
106
beacon_node/http_server/src/metrics/local_metrics.rs
Normal file
106
beacon_node/http_server/src/metrics/local_metrics.rs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||||
|
use prometheus::{IntGauge, Opts, Registry};
|
||||||
|
use slot_clock::SlotClock;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use types::{EthSpec, Slot};
|
||||||
|
|
||||||
|
// If set to `true` will iterate and sum the balances of all validators in the state for each
|
||||||
|
// scrape.
|
||||||
|
const SHOULD_SUM_VALIDATOR_BALANCES: bool = true;
|
||||||
|
|
||||||
|
pub struct LocalMetrics {
|
||||||
|
present_slot: IntGauge,
|
||||||
|
present_epoch: IntGauge,
|
||||||
|
best_slot: IntGauge,
|
||||||
|
validator_count: IntGauge,
|
||||||
|
justified_epoch: IntGauge,
|
||||||
|
finalized_epoch: IntGauge,
|
||||||
|
validator_balances_sum: IntGauge,
|
||||||
|
database_size: IntGauge,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocalMetrics {
|
||||||
|
/// Create a new instance.
|
||||||
|
pub fn new() -> Result<Self, prometheus::Error> {
|
||||||
|
Ok(Self {
|
||||||
|
present_slot: {
|
||||||
|
let opts = Opts::new("present_slot", "slot_at_time_of_scrape");
|
||||||
|
IntGauge::with_opts(opts)?
|
||||||
|
},
|
||||||
|
present_epoch: {
|
||||||
|
let opts = Opts::new("present_epoch", "epoch_at_time_of_scrape");
|
||||||
|
IntGauge::with_opts(opts)?
|
||||||
|
},
|
||||||
|
best_slot: {
|
||||||
|
let opts = Opts::new("best_slot", "slot_of_block_at_chain_head");
|
||||||
|
IntGauge::with_opts(opts)?
|
||||||
|
},
|
||||||
|
validator_count: {
|
||||||
|
let opts = Opts::new("validator_count", "number_of_validators");
|
||||||
|
IntGauge::with_opts(opts)?
|
||||||
|
},
|
||||||
|
justified_epoch: {
|
||||||
|
let opts = Opts::new("justified_epoch", "state_justified_epoch");
|
||||||
|
IntGauge::with_opts(opts)?
|
||||||
|
},
|
||||||
|
finalized_epoch: {
|
||||||
|
let opts = Opts::new("finalized_epoch", "state_finalized_epoch");
|
||||||
|
IntGauge::with_opts(opts)?
|
||||||
|
},
|
||||||
|
validator_balances_sum: {
|
||||||
|
let opts = Opts::new("validator_balances_sum", "sum_of_all_validator_balances");
|
||||||
|
IntGauge::with_opts(opts)?
|
||||||
|
},
|
||||||
|
database_size: {
|
||||||
|
let opts = Opts::new("database_size", "size_of_on_disk_db_in_mb");
|
||||||
|
IntGauge::with_opts(opts)?
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registry this instance with the `registry`.
|
||||||
|
pub fn register(&self, registry: &Registry) -> Result<(), prometheus::Error> {
|
||||||
|
registry.register(Box::new(self.present_slot.clone()))?;
|
||||||
|
registry.register(Box::new(self.present_epoch.clone()))?;
|
||||||
|
registry.register(Box::new(self.best_slot.clone()))?;
|
||||||
|
registry.register(Box::new(self.validator_count.clone()))?;
|
||||||
|
registry.register(Box::new(self.finalized_epoch.clone()))?;
|
||||||
|
registry.register(Box::new(self.justified_epoch.clone()))?;
|
||||||
|
registry.register(Box::new(self.validator_balances_sum.clone()))?;
|
||||||
|
registry.register(Box::new(self.database_size.clone()))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the metrics in `self` to the latest values.
|
||||||
|
pub fn update<T: BeaconChainTypes>(&self, beacon_chain: &BeaconChain<T>, db_path: &PathBuf) {
|
||||||
|
let state = &beacon_chain.head().beacon_state;
|
||||||
|
|
||||||
|
let present_slot = beacon_chain
|
||||||
|
.slot_clock
|
||||||
|
.present_slot()
|
||||||
|
.unwrap_or_else(|_| None)
|
||||||
|
.unwrap_or_else(|| Slot::new(0));
|
||||||
|
self.present_slot.set(present_slot.as_u64() as i64);
|
||||||
|
self.present_epoch
|
||||||
|
.set(present_slot.epoch(T::EthSpec::slots_per_epoch()).as_u64() as i64);
|
||||||
|
|
||||||
|
self.best_slot.set(state.slot.as_u64() as i64);
|
||||||
|
self.validator_count
|
||||||
|
.set(state.validator_registry.len() as i64);
|
||||||
|
self.justified_epoch
|
||||||
|
.set(state.current_justified_epoch.as_u64() as i64);
|
||||||
|
self.finalized_epoch
|
||||||
|
.set(state.finalized_epoch.as_u64() as i64);
|
||||||
|
if SHOULD_SUM_VALIDATOR_BALANCES {
|
||||||
|
self.validator_balances_sum
|
||||||
|
.set(state.balances.iter().sum::<u64>() as i64);
|
||||||
|
}
|
||||||
|
let db_size = File::open(db_path)
|
||||||
|
.and_then(|f| f.metadata())
|
||||||
|
.and_then(|m| Ok(m.len()))
|
||||||
|
.unwrap_or(0);
|
||||||
|
self.database_size.set(db_size as i64);
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ sloggers = "0.3.2"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
beacon_chain = { path = "../beacon_chain" }
|
beacon_chain = { path = "../beacon_chain" }
|
||||||
|
store = { path = "../store" }
|
||||||
eth2-libp2p = { path = "../eth2-libp2p" }
|
eth2-libp2p = { path = "../eth2-libp2p" }
|
||||||
version = { path = "../version" }
|
version = { path = "../version" }
|
||||||
types = { path = "../../eth2/types" }
|
types = { path = "../../eth2/types" }
|
||||||
|
@ -1,157 +0,0 @@
|
|||||||
use beacon_chain::BeaconChain as RawBeaconChain;
|
|
||||||
use beacon_chain::{
|
|
||||||
parking_lot::RwLockReadGuard,
|
|
||||||
types::{BeaconState, ChainSpec},
|
|
||||||
AttestationValidationError, CheckPoint,
|
|
||||||
};
|
|
||||||
use eth2_libp2p::rpc::HelloMessage;
|
|
||||||
use types::{
|
|
||||||
Attestation, BeaconBlock, BeaconBlockBody, BeaconBlockHeader, Epoch, EthSpec, Hash256, Slot,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use beacon_chain::{BeaconChainError, BeaconChainTypes, BlockProcessingOutcome, InvalidBlock};
|
|
||||||
|
|
||||||
/// The network's API to the beacon chain.
|
|
||||||
pub trait BeaconChain<T: BeaconChainTypes>: Send + Sync {
|
|
||||||
fn get_spec(&self) -> &ChainSpec;
|
|
||||||
|
|
||||||
fn get_state(&self) -> RwLockReadGuard<BeaconState<T::EthSpec>>;
|
|
||||||
|
|
||||||
fn slot(&self) -> Slot;
|
|
||||||
|
|
||||||
fn head(&self) -> RwLockReadGuard<CheckPoint<T::EthSpec>>;
|
|
||||||
|
|
||||||
fn get_block(&self, block_root: &Hash256) -> Result<Option<BeaconBlock>, BeaconChainError>;
|
|
||||||
|
|
||||||
fn best_slot(&self) -> Slot;
|
|
||||||
|
|
||||||
fn best_block_root(&self) -> Hash256;
|
|
||||||
|
|
||||||
fn finalized_head(&self) -> RwLockReadGuard<CheckPoint<T::EthSpec>>;
|
|
||||||
|
|
||||||
fn finalized_epoch(&self) -> Epoch;
|
|
||||||
|
|
||||||
fn hello_message(&self) -> HelloMessage;
|
|
||||||
|
|
||||||
fn process_block(&self, block: BeaconBlock)
|
|
||||||
-> Result<BlockProcessingOutcome, BeaconChainError>;
|
|
||||||
|
|
||||||
fn process_attestation(
|
|
||||||
&self,
|
|
||||||
attestation: Attestation,
|
|
||||||
) -> Result<(), AttestationValidationError>;
|
|
||||||
|
|
||||||
fn get_block_roots(
|
|
||||||
&self,
|
|
||||||
start_slot: Slot,
|
|
||||||
count: usize,
|
|
||||||
skip: usize,
|
|
||||||
) -> Result<Vec<Hash256>, BeaconChainError>;
|
|
||||||
|
|
||||||
fn get_block_headers(
|
|
||||||
&self,
|
|
||||||
start_slot: Slot,
|
|
||||||
count: usize,
|
|
||||||
skip: usize,
|
|
||||||
) -> Result<Vec<BeaconBlockHeader>, BeaconChainError>;
|
|
||||||
|
|
||||||
fn get_block_bodies(&self, roots: &[Hash256])
|
|
||||||
-> Result<Vec<BeaconBlockBody>, BeaconChainError>;
|
|
||||||
|
|
||||||
fn is_new_block_root(&self, beacon_block_root: &Hash256) -> Result<bool, BeaconChainError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: BeaconChainTypes> BeaconChain<T> for RawBeaconChain<T> {
|
|
||||||
fn get_spec(&self) -> &ChainSpec {
|
|
||||||
&self.spec
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_state(&self) -> RwLockReadGuard<BeaconState<T::EthSpec>> {
|
|
||||||
self.state.read()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn slot(&self) -> Slot {
|
|
||||||
self.get_state().slot
|
|
||||||
}
|
|
||||||
|
|
||||||
fn head(&self) -> RwLockReadGuard<CheckPoint<T::EthSpec>> {
|
|
||||||
self.head()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_block(&self, block_root: &Hash256) -> Result<Option<BeaconBlock>, BeaconChainError> {
|
|
||||||
self.get_block(block_root)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finalized_epoch(&self) -> Epoch {
|
|
||||||
self.get_state().finalized_epoch
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finalized_head(&self) -> RwLockReadGuard<CheckPoint<T::EthSpec>> {
|
|
||||||
self.finalized_head()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn best_slot(&self) -> Slot {
|
|
||||||
self.head().beacon_block.slot
|
|
||||||
}
|
|
||||||
|
|
||||||
fn best_block_root(&self) -> Hash256 {
|
|
||||||
self.head().beacon_block_root
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hello_message(&self) -> HelloMessage {
|
|
||||||
let spec = self.get_spec();
|
|
||||||
let state = self.get_state();
|
|
||||||
|
|
||||||
HelloMessage {
|
|
||||||
network_id: spec.chain_id,
|
|
||||||
latest_finalized_root: state.finalized_root,
|
|
||||||
latest_finalized_epoch: state.finalized_epoch,
|
|
||||||
best_root: self.best_block_root(),
|
|
||||||
best_slot: self.best_slot(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_block(
|
|
||||||
&self,
|
|
||||||
block: BeaconBlock,
|
|
||||||
) -> Result<BlockProcessingOutcome, BeaconChainError> {
|
|
||||||
self.process_block(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_attestation(
|
|
||||||
&self,
|
|
||||||
attestation: Attestation,
|
|
||||||
) -> Result<(), AttestationValidationError> {
|
|
||||||
self.process_attestation(attestation)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_block_roots(
|
|
||||||
&self,
|
|
||||||
start_slot: Slot,
|
|
||||||
count: usize,
|
|
||||||
skip: usize,
|
|
||||||
) -> Result<Vec<Hash256>, BeaconChainError> {
|
|
||||||
self.get_block_roots(start_slot, count, skip)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_block_headers(
|
|
||||||
&self,
|
|
||||||
start_slot: Slot,
|
|
||||||
count: usize,
|
|
||||||
skip: usize,
|
|
||||||
) -> Result<Vec<BeaconBlockHeader>, BeaconChainError> {
|
|
||||||
let roots = self.get_block_roots(start_slot, count, skip)?;
|
|
||||||
self.get_block_headers(&roots)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_block_bodies(
|
|
||||||
&self,
|
|
||||||
roots: &[Hash256],
|
|
||||||
) -> Result<Vec<BeaconBlockBody>, BeaconChainError> {
|
|
||||||
self.get_block_bodies(roots)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_new_block_root(&self, beacon_block_root: &Hash256) -> Result<bool, BeaconChainError> {
|
|
||||||
self.is_new_block_root(beacon_block_root)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +1,7 @@
|
|||||||
// generates error types
|
// generates error types
|
||||||
use eth2_libp2p;
|
use eth2_libp2p;
|
||||||
|
|
||||||
use error_chain::{
|
use error_chain::error_chain;
|
||||||
error_chain, error_chain_processing, impl_error_chain_kind, impl_error_chain_processed,
|
|
||||||
impl_extract_backtrace,
|
|
||||||
};
|
|
||||||
|
|
||||||
error_chain! {
|
error_chain! {
|
||||||
links {
|
links {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
/// This crate provides the network server for Lighthouse.
|
/// This crate provides the network server for Lighthouse.
|
||||||
pub mod beacon_chain;
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod message_handler;
|
pub mod message_handler;
|
||||||
pub mod service;
|
pub mod service;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::beacon_chain::{BeaconChain, BeaconChainTypes};
|
|
||||||
use crate::error;
|
use crate::error;
|
||||||
use crate::service::{NetworkMessage, OutgoingMessage};
|
use crate::service::{NetworkMessage, OutgoingMessage};
|
||||||
use crate::sync::SimpleSync;
|
use crate::sync::SimpleSync;
|
||||||
|
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||||
use crossbeam_channel::{unbounded as channel, Sender};
|
use crossbeam_channel::{unbounded as channel, Sender};
|
||||||
use eth2_libp2p::{
|
use eth2_libp2p::{
|
||||||
behaviour::PubsubMessage,
|
behaviour::PubsubMessage,
|
||||||
@ -155,7 +155,7 @@ impl<T: BeaconChainTypes + 'static> MessageHandler<T> {
|
|||||||
if self
|
if self
|
||||||
.network_context
|
.network_context
|
||||||
.outstanding_outgoing_request_ids
|
.outstanding_outgoing_request_ids
|
||||||
.remove(&(peer_id.clone(), id.clone()))
|
.remove(&(peer_id.clone(), id))
|
||||||
.is_none()
|
.is_none()
|
||||||
{
|
{
|
||||||
warn!(
|
warn!(
|
||||||
@ -250,7 +250,7 @@ impl NetworkContext {
|
|||||||
let id = self.generate_request_id(&peer_id);
|
let id = self.generate_request_id(&peer_id);
|
||||||
|
|
||||||
self.outstanding_outgoing_request_ids
|
self.outstanding_outgoing_request_ids
|
||||||
.insert((peer_id.clone(), id.clone()), Instant::now());
|
.insert((peer_id.clone(), id), Instant::now());
|
||||||
|
|
||||||
self.send_rpc_event(
|
self.send_rpc_event(
|
||||||
peer_id,
|
peer_id,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::beacon_chain::{BeaconChain, BeaconChainTypes};
|
|
||||||
use crate::error;
|
use crate::error;
|
||||||
use crate::message_handler::{HandlerMessage, MessageHandler};
|
use crate::message_handler::{HandlerMessage, MessageHandler};
|
||||||
use crate::NetworkConfig;
|
use crate::NetworkConfig;
|
||||||
|
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||||
use crossbeam_channel::{unbounded as channel, Sender, TryRecvError};
|
use crossbeam_channel::{unbounded as channel, Sender, TryRecvError};
|
||||||
use eth2_libp2p::Service as LibP2PService;
|
use eth2_libp2p::Service as LibP2PService;
|
||||||
use eth2_libp2p::{Libp2pEvent, PeerId};
|
use eth2_libp2p::{Libp2pEvent, PeerId};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::beacon_chain::{BeaconChain, BeaconChainTypes};
|
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||||
use eth2_libp2p::rpc::methods::*;
|
use eth2_libp2p::rpc::methods::*;
|
||||||
use eth2_libp2p::PeerId;
|
use eth2_libp2p::PeerId;
|
||||||
use slog::{debug, error};
|
use slog::{debug, error};
|
||||||
@ -166,7 +166,7 @@ impl<T: BeaconChainTypes> ImportQueue<T> {
|
|||||||
let mut required_bodies: Vec<Hash256> = vec![];
|
let mut required_bodies: Vec<Hash256> = vec![];
|
||||||
|
|
||||||
for header in headers {
|
for header in headers {
|
||||||
let block_root = Hash256::from_slice(&header.tree_hash_root()[..]);
|
let block_root = Hash256::from_slice(&header.canonical_root()[..]);
|
||||||
|
|
||||||
if self.chain_has_not_seen_block(&block_root) {
|
if self.chain_has_not_seen_block(&block_root) {
|
||||||
self.insert_header(block_root, header, sender.clone());
|
self.insert_header(block_root, header, sender.clone());
|
||||||
@ -212,7 +212,7 @@ impl<T: BeaconChainTypes> ImportQueue<T> {
|
|||||||
// Case 2: there was no partial with a matching block root.
|
// Case 2: there was no partial with a matching block root.
|
||||||
//
|
//
|
||||||
// A new partial is added. This case permits adding a header without already known the
|
// A new partial is added. This case permits adding a header without already known the
|
||||||
// root -- this is not possible in the wire protocol however we support it anyway.
|
// root.
|
||||||
self.partials.push(PartialBeaconBlock {
|
self.partials.push(PartialBeaconBlock {
|
||||||
slot: header.slot,
|
slot: header.slot,
|
||||||
block_root,
|
block_root,
|
||||||
@ -250,7 +250,7 @@ impl<T: BeaconChainTypes> ImportQueue<T> {
|
|||||||
///
|
///
|
||||||
/// If the partial already existed, the `inserted` time is set to `now`.
|
/// If the partial already existed, the `inserted` time is set to `now`.
|
||||||
fn insert_full_block(&mut self, block: BeaconBlock, sender: PeerId) {
|
fn insert_full_block(&mut self, block: BeaconBlock, sender: PeerId) {
|
||||||
let block_root = Hash256::from_slice(&block.tree_hash_root()[..]);
|
let block_root = Hash256::from_slice(&block.canonical_root()[..]);
|
||||||
|
|
||||||
let partial = PartialBeaconBlock {
|
let partial = PartialBeaconBlock {
|
||||||
slot: block.slot,
|
slot: block.slot,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use super::import_queue::ImportQueue;
|
use super::import_queue::ImportQueue;
|
||||||
use crate::beacon_chain::{BeaconChain, BeaconChainTypes, BlockProcessingOutcome, InvalidBlock};
|
|
||||||
use crate::message_handler::NetworkContext;
|
use crate::message_handler::NetworkContext;
|
||||||
|
use beacon_chain::{BeaconChain, BeaconChainTypes, BlockProcessingOutcome};
|
||||||
use eth2_libp2p::rpc::methods::*;
|
use eth2_libp2p::rpc::methods::*;
|
||||||
use eth2_libp2p::rpc::{RPCRequest, RPCResponse, RequestId};
|
use eth2_libp2p::rpc::{RPCRequest, RPCResponse, RequestId};
|
||||||
use eth2_libp2p::PeerId;
|
use eth2_libp2p::PeerId;
|
||||||
@ -8,8 +8,10 @@ use slog::{debug, error, info, o, warn};
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tree_hash::TreeHash;
|
use store::Store;
|
||||||
use types::{Attestation, BeaconBlock, Epoch, Hash256, Slot};
|
use types::{
|
||||||
|
Attestation, BeaconBlock, BeaconBlockBody, BeaconBlockHeader, Epoch, EthSpec, Hash256, Slot,
|
||||||
|
};
|
||||||
|
|
||||||
/// The number of slots that we can import blocks ahead of us, before going into full Sync mode.
|
/// The number of slots that we can import blocks ahead of us, before going into full Sync mode.
|
||||||
const SLOT_IMPORT_TOLERANCE: u64 = 100;
|
const SLOT_IMPORT_TOLERANCE: u64 = 100;
|
||||||
@ -21,6 +23,9 @@ const QUEUE_STALE_SECS: u64 = 600;
|
|||||||
/// Otherwise we queue it.
|
/// Otherwise we queue it.
|
||||||
const FUTURE_SLOT_TOLERANCE: u64 = 1;
|
const FUTURE_SLOT_TOLERANCE: u64 = 1;
|
||||||
|
|
||||||
|
const SHOULD_FORWARD_GOSSIP_BLOCK: bool = true;
|
||||||
|
const SHOULD_NOT_FORWARD_GOSSIP_BLOCK: bool = false;
|
||||||
|
|
||||||
/// Keeps track of syncing information for known connected peers.
|
/// Keeps track of syncing information for known connected peers.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct PeerSyncInfo {
|
pub struct PeerSyncInfo {
|
||||||
@ -31,51 +36,6 @@ pub struct PeerSyncInfo {
|
|||||||
best_slot: Slot,
|
best_slot: Slot,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PeerSyncInfo {
|
|
||||||
/// Returns `true` if the has a different network ID to `other`.
|
|
||||||
fn has_different_network_id_to(&self, other: Self) -> bool {
|
|
||||||
self.network_id != other.network_id
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if the peer has a higher finalized epoch than `other`.
|
|
||||||
fn has_higher_finalized_epoch_than(&self, other: Self) -> bool {
|
|
||||||
self.latest_finalized_epoch > other.latest_finalized_epoch
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns `true` if the peer has a higher best slot than `other`.
|
|
||||||
fn has_higher_best_slot_than(&self, other: Self) -> bool {
|
|
||||||
self.best_slot > other.best_slot
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The status of a peers view on the chain, relative to some other view of the chain (presumably
|
|
||||||
/// our view).
|
|
||||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
|
||||||
pub enum PeerStatus {
|
|
||||||
/// The peer is on a completely different chain.
|
|
||||||
DifferentNetworkId,
|
|
||||||
/// The peer lists a finalized epoch for which we have a different root.
|
|
||||||
FinalizedEpochNotInChain,
|
|
||||||
/// The peer has a higher finalized epoch.
|
|
||||||
HigherFinalizedEpoch,
|
|
||||||
/// The peer has a higher best slot.
|
|
||||||
HigherBestSlot,
|
|
||||||
/// The peer has the same or lesser view of the chain. We have nothing to request of them.
|
|
||||||
NotInteresting,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PeerStatus {
|
|
||||||
pub fn should_handshake(self) -> bool {
|
|
||||||
match self {
|
|
||||||
PeerStatus::DifferentNetworkId => false,
|
|
||||||
PeerStatus::FinalizedEpochNotInChain => false,
|
|
||||||
PeerStatus::HigherFinalizedEpoch => true,
|
|
||||||
PeerStatus::HigherBestSlot => true,
|
|
||||||
PeerStatus::NotInteresting => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<HelloMessage> for PeerSyncInfo {
|
impl From<HelloMessage> for PeerSyncInfo {
|
||||||
fn from(hello: HelloMessage) -> PeerSyncInfo {
|
fn from(hello: HelloMessage) -> PeerSyncInfo {
|
||||||
PeerSyncInfo {
|
PeerSyncInfo {
|
||||||
@ -90,7 +50,7 @@ impl From<HelloMessage> for PeerSyncInfo {
|
|||||||
|
|
||||||
impl<T: BeaconChainTypes> From<&Arc<BeaconChain<T>>> for PeerSyncInfo {
|
impl<T: BeaconChainTypes> From<&Arc<BeaconChain<T>>> for PeerSyncInfo {
|
||||||
fn from(chain: &Arc<BeaconChain<T>>) -> PeerSyncInfo {
|
fn from(chain: &Arc<BeaconChain<T>>) -> PeerSyncInfo {
|
||||||
Self::from(chain.hello_message())
|
Self::from(hello_message(chain))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,9 +111,9 @@ impl<T: BeaconChainTypes> SimpleSync<T> {
|
|||||||
///
|
///
|
||||||
/// Sends a `Hello` message to the peer.
|
/// Sends a `Hello` message to the peer.
|
||||||
pub fn on_connect(&self, peer_id: PeerId, network: &mut NetworkContext) {
|
pub fn on_connect(&self, peer_id: PeerId, network: &mut NetworkContext) {
|
||||||
info!(self.log, "PeerConnect"; "peer" => format!("{:?}", peer_id));
|
info!(self.log, "PeerConnected"; "peer" => format!("{:?}", peer_id));
|
||||||
|
|
||||||
network.send_rpc_request(peer_id, RPCRequest::Hello(self.chain.hello_message()));
|
network.send_rpc_request(peer_id, RPCRequest::Hello(hello_message(&self.chain)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle a `Hello` request.
|
/// Handle a `Hello` request.
|
||||||
@ -172,7 +132,7 @@ impl<T: BeaconChainTypes> SimpleSync<T> {
|
|||||||
network.send_rpc_response(
|
network.send_rpc_response(
|
||||||
peer_id.clone(),
|
peer_id.clone(),
|
||||||
request_id,
|
request_id,
|
||||||
RPCResponse::Hello(self.chain.hello_message()),
|
RPCResponse::Hello(hello_message(&self.chain)),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.process_hello(peer_id, hello, network);
|
self.process_hello(peer_id, hello, network);
|
||||||
@ -191,51 +151,6 @@ impl<T: BeaconChainTypes> SimpleSync<T> {
|
|||||||
self.process_hello(peer_id, hello, network);
|
self.process_hello(peer_id, hello, network);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a `PeerStatus` for some peer.
|
|
||||||
fn peer_status(&self, peer: PeerSyncInfo) -> PeerStatus {
|
|
||||||
let local = PeerSyncInfo::from(&self.chain);
|
|
||||||
|
|
||||||
if peer.has_different_network_id_to(local) {
|
|
||||||
return PeerStatus::DifferentNetworkId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if local.has_higher_finalized_epoch_than(peer) {
|
|
||||||
let peer_finalized_slot = peer
|
|
||||||
.latest_finalized_epoch
|
|
||||||
.start_slot(self.chain.get_spec().slots_per_epoch);
|
|
||||||
|
|
||||||
let local_roots = self.chain.get_block_roots(peer_finalized_slot, 1, 0);
|
|
||||||
|
|
||||||
if let Ok(local_roots) = local_roots {
|
|
||||||
if let Some(local_root) = local_roots.get(0) {
|
|
||||||
if *local_root != peer.latest_finalized_root {
|
|
||||||
return PeerStatus::FinalizedEpochNotInChain;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error!(
|
|
||||||
self.log,
|
|
||||||
"Cannot get root for peer finalized slot.";
|
|
||||||
"error" => "empty roots"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error!(
|
|
||||||
self.log,
|
|
||||||
"Cannot get root for peer finalized slot.";
|
|
||||||
"error" => format!("{:?}", local_roots)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if peer.has_higher_finalized_epoch_than(local) {
|
|
||||||
PeerStatus::HigherFinalizedEpoch
|
|
||||||
} else if peer.has_higher_best_slot_than(local) {
|
|
||||||
PeerStatus::HigherBestSlot
|
|
||||||
} else {
|
|
||||||
PeerStatus::NotInteresting
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Process a `Hello` message, requesting new blocks if appropriate.
|
/// Process a `Hello` message, requesting new blocks if appropriate.
|
||||||
///
|
///
|
||||||
/// Disconnects the peer if required.
|
/// Disconnects the peer if required.
|
||||||
@ -245,31 +160,64 @@ impl<T: BeaconChainTypes> SimpleSync<T> {
|
|||||||
hello: HelloMessage,
|
hello: HelloMessage,
|
||||||
network: &mut NetworkContext,
|
network: &mut NetworkContext,
|
||||||
) {
|
) {
|
||||||
let spec = self.chain.get_spec();
|
let spec = &self.chain.spec;
|
||||||
|
|
||||||
let remote = PeerSyncInfo::from(hello);
|
let remote = PeerSyncInfo::from(hello);
|
||||||
let local = PeerSyncInfo::from(&self.chain);
|
let local = PeerSyncInfo::from(&self.chain);
|
||||||
let remote_status = self.peer_status(remote);
|
|
||||||
|
|
||||||
if remote_status.should_handshake() {
|
// Disconnect nodes who are on a different network.
|
||||||
info!(self.log, "HandshakeSuccess"; "peer" => format!("{:?}", peer_id));
|
if local.network_id != remote.network_id {
|
||||||
self.known_peers.insert(peer_id.clone(), remote);
|
|
||||||
} else {
|
|
||||||
info!(
|
info!(
|
||||||
self.log, "HandshakeFailure";
|
self.log, "HandshakeFailure";
|
||||||
"peer" => format!("{:?}", peer_id),
|
"peer" => format!("{:?}", peer_id),
|
||||||
"reason" => "network_id"
|
"reason" => "network_id"
|
||||||
);
|
);
|
||||||
network.disconnect(peer_id.clone(), GoodbyeReason::IrreleventNetwork);
|
network.disconnect(peer_id.clone(), GoodbyeReason::IrreleventNetwork);
|
||||||
|
// Disconnect nodes if our finalized epoch is greater than thieirs, and their finalized
|
||||||
|
// epoch is not in our chain. Viz., they are on another chain.
|
||||||
|
//
|
||||||
|
// If the local or remote have a `latest_finalized_root == ZERO_HASH`, skips checks about
|
||||||
|
// the finalized_root. The logic is akward and I think we're better without it.
|
||||||
|
} else if (local.latest_finalized_epoch >= remote.latest_finalized_epoch)
|
||||||
|
&& (!self
|
||||||
|
.chain
|
||||||
|
.rev_iter_block_roots(local.best_slot)
|
||||||
|
.any(|root| root == remote.latest_finalized_root))
|
||||||
|
&& (local.latest_finalized_root != spec.zero_hash)
|
||||||
|
&& (remote.latest_finalized_root != spec.zero_hash)
|
||||||
|
{
|
||||||
|
info!(
|
||||||
|
self.log, "HandshakeFailure";
|
||||||
|
"peer" => format!("{:?}", peer_id),
|
||||||
|
"reason" => "wrong_finalized_chain"
|
||||||
|
);
|
||||||
|
network.disconnect(peer_id.clone(), GoodbyeReason::IrreleventNetwork);
|
||||||
|
// Process handshakes from peers that seem to be on our chain.
|
||||||
|
} else {
|
||||||
|
info!(self.log, "HandshakeSuccess"; "peer" => format!("{:?}", peer_id));
|
||||||
|
self.known_peers.insert(peer_id.clone(), remote);
|
||||||
|
|
||||||
|
// If we have equal or better finalized epochs and best slots, we require nothing else from
|
||||||
|
// this peer.
|
||||||
|
//
|
||||||
|
// We make an exception when our best slot is 0. Best slot does not indicate wether or
|
||||||
|
// not there is a block at slot zero.
|
||||||
|
if (remote.latest_finalized_epoch <= local.latest_finalized_epoch)
|
||||||
|
&& (remote.best_slot <= local.best_slot)
|
||||||
|
&& (local.best_slot > 0)
|
||||||
|
{
|
||||||
|
debug!(self.log, "Peer is naive"; "peer" => format!("{:?}", peer_id));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If required, send additional requests.
|
// If the remote has a higher finalized epoch, request all block roots from our finalized
|
||||||
match remote_status {
|
// epoch through to its best slot.
|
||||||
PeerStatus::HigherFinalizedEpoch => {
|
if remote.latest_finalized_epoch > local.latest_finalized_epoch {
|
||||||
let start_slot = remote
|
debug!(self.log, "Peer has high finalized epoch"; "peer" => format!("{:?}", peer_id));
|
||||||
|
let start_slot = local
|
||||||
.latest_finalized_epoch
|
.latest_finalized_epoch
|
||||||
.start_slot(spec.slots_per_epoch);
|
.start_slot(T::EthSpec::slots_per_epoch());
|
||||||
let required_slots = start_slot - local.best_slot;
|
let required_slots = remote.best_slot - start_slot;
|
||||||
|
|
||||||
self.request_block_roots(
|
self.request_block_roots(
|
||||||
peer_id,
|
peer_id,
|
||||||
@ -279,22 +227,26 @@ impl<T: BeaconChainTypes> SimpleSync<T> {
|
|||||||
},
|
},
|
||||||
network,
|
network,
|
||||||
);
|
);
|
||||||
}
|
// If the remote has a greater best slot, request the roots between our best slot and their
|
||||||
PeerStatus::HigherBestSlot => {
|
// best slot.
|
||||||
let required_slots = remote.best_slot - local.best_slot;
|
} else if remote.best_slot > local.best_slot {
|
||||||
|
debug!(self.log, "Peer has higher best slot"; "peer" => format!("{:?}", peer_id));
|
||||||
|
let start_slot = local
|
||||||
|
.latest_finalized_epoch
|
||||||
|
.start_slot(T::EthSpec::slots_per_epoch());
|
||||||
|
let required_slots = remote.best_slot - start_slot;
|
||||||
|
|
||||||
self.request_block_roots(
|
self.request_block_roots(
|
||||||
peer_id,
|
peer_id,
|
||||||
BeaconBlockRootsRequest {
|
BeaconBlockRootsRequest {
|
||||||
start_slot: local.best_slot + 1,
|
start_slot,
|
||||||
count: required_slots.into(),
|
count: required_slots.into(),
|
||||||
},
|
},
|
||||||
network,
|
network,
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
debug!(self.log, "Nothing to request from peer"; "peer" => format!("{:?}", peer_id));
|
||||||
}
|
}
|
||||||
PeerStatus::FinalizedEpochNotInChain => {}
|
|
||||||
PeerStatus::DifferentNetworkId => {}
|
|
||||||
PeerStatus::NotInteresting => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,34 +263,40 @@ impl<T: BeaconChainTypes> SimpleSync<T> {
|
|||||||
"BlockRootsRequest";
|
"BlockRootsRequest";
|
||||||
"peer" => format!("{:?}", peer_id),
|
"peer" => format!("{:?}", peer_id),
|
||||||
"count" => req.count,
|
"count" => req.count,
|
||||||
|
"start_slot" => req.start_slot,
|
||||||
);
|
);
|
||||||
|
|
||||||
let roots = match self
|
let mut roots: Vec<Hash256> = self
|
||||||
.chain
|
.chain
|
||||||
.get_block_roots(req.start_slot, req.count as usize, 0)
|
.rev_iter_block_roots(req.start_slot + req.count)
|
||||||
{
|
.skip(1)
|
||||||
Ok(roots) => roots,
|
.take(req.count as usize)
|
||||||
Err(e) => {
|
.collect();
|
||||||
// TODO: return RPC error.
|
|
||||||
warn!(
|
|
||||||
self.log,
|
|
||||||
"RPCRequest"; "peer" => format!("{:?}", peer_id),
|
|
||||||
"req" => "BeaconBlockRoots",
|
|
||||||
"error" => format!("{:?}", e)
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let roots = roots
|
if roots.len() as u64 != req.count {
|
||||||
|
debug!(
|
||||||
|
self.log,
|
||||||
|
"BlockRootsRequest";
|
||||||
|
"peer" => format!("{:?}", peer_id),
|
||||||
|
"msg" => "Failed to return all requested hashes",
|
||||||
|
"requested" => req.count,
|
||||||
|
"returned" => roots.len(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
roots.reverse();
|
||||||
|
|
||||||
|
let mut roots: Vec<BlockRootSlot> = roots
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, &block_root)| BlockRootSlot {
|
.map(|(i, block_root)| BlockRootSlot {
|
||||||
slot: req.start_slot + Slot::from(i),
|
slot: req.start_slot + Slot::from(i),
|
||||||
block_root,
|
block_root: *block_root,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
roots.dedup_by_key(|brs| brs.block_root);
|
||||||
|
|
||||||
network.send_rpc_response(
|
network.send_rpc_response(
|
||||||
peer_id,
|
peer_id,
|
||||||
request_id,
|
request_id,
|
||||||
@ -424,23 +382,29 @@ impl<T: BeaconChainTypes> SimpleSync<T> {
|
|||||||
"count" => req.max_headers,
|
"count" => req.max_headers,
|
||||||
);
|
);
|
||||||
|
|
||||||
let headers = match self.chain.get_block_headers(
|
let count = req.max_headers;
|
||||||
req.start_slot,
|
|
||||||
req.max_headers as usize,
|
// Collect the block roots.
|
||||||
req.skip_slots as usize,
|
//
|
||||||
) {
|
// Instead of using `chain.rev_iter_blocks` we collect the roots first. This avoids
|
||||||
Ok(headers) => headers,
|
// unnecessary block deserialization when `req.skip_slots > 0`.
|
||||||
Err(e) => {
|
let mut roots: Vec<Hash256> = self
|
||||||
// TODO: return RPC error.
|
.chain
|
||||||
warn!(
|
.rev_iter_block_roots(req.start_slot + (count - 1))
|
||||||
self.log,
|
.take(count as usize)
|
||||||
"RPCRequest"; "peer" => format!("{:?}", peer_id),
|
.collect();
|
||||||
"req" => "BeaconBlockHeaders",
|
|
||||||
"error" => format!("{:?}", e)
|
roots.reverse();
|
||||||
);
|
roots.dedup();
|
||||||
return;
|
|
||||||
}
|
let headers: Vec<BeaconBlockHeader> = roots
|
||||||
};
|
.into_iter()
|
||||||
|
.step_by(req.skip_slots as usize + 1)
|
||||||
|
.filter_map(|root| {
|
||||||
|
let block = self.chain.store.get::<BeaconBlock>(&root).ok()?;
|
||||||
|
Some(block?.block_header())
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
network.send_rpc_response(
|
network.send_rpc_response(
|
||||||
peer_id,
|
peer_id,
|
||||||
@ -488,27 +452,33 @@ impl<T: BeaconChainTypes> SimpleSync<T> {
|
|||||||
req: BeaconBlockBodiesRequest,
|
req: BeaconBlockBodiesRequest,
|
||||||
network: &mut NetworkContext,
|
network: &mut NetworkContext,
|
||||||
) {
|
) {
|
||||||
|
let block_bodies: Vec<BeaconBlockBody> = req
|
||||||
|
.block_roots
|
||||||
|
.iter()
|
||||||
|
.filter_map(|root| {
|
||||||
|
if let Ok(Some(block)) = self.chain.store.get::<BeaconBlock>(root) {
|
||||||
|
Some(block.body)
|
||||||
|
} else {
|
||||||
|
debug!(
|
||||||
|
self.log,
|
||||||
|
"Peer requested unknown block";
|
||||||
|
"peer" => format!("{:?}", peer_id),
|
||||||
|
"request_root" => format!("{:}", root),
|
||||||
|
);
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
self.log,
|
self.log,
|
||||||
"BlockBodiesRequest";
|
"BlockBodiesRequest";
|
||||||
"peer" => format!("{:?}", peer_id),
|
"peer" => format!("{:?}", peer_id),
|
||||||
"count" => req.block_roots.len(),
|
"requested" => req.block_roots.len(),
|
||||||
|
"returned" => block_bodies.len(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let block_bodies = match self.chain.get_block_bodies(&req.block_roots) {
|
|
||||||
Ok(bodies) => bodies,
|
|
||||||
Err(e) => {
|
|
||||||
// TODO: return RPC error.
|
|
||||||
warn!(
|
|
||||||
self.log,
|
|
||||||
"RPCRequest"; "peer" => format!("{:?}", peer_id),
|
|
||||||
"req" => "BeaconBlockBodies",
|
|
||||||
"error" => format!("{:?}", e)
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
network.send_rpc_response(
|
network.send_rpc_response(
|
||||||
peer_id,
|
peer_id,
|
||||||
request_id,
|
request_id,
|
||||||
@ -542,6 +512,8 @@ impl<T: BeaconChainTypes> SimpleSync<T> {
|
|||||||
|
|
||||||
/// Process a gossip message declaring a new block.
|
/// Process a gossip message declaring a new block.
|
||||||
///
|
///
|
||||||
|
/// Attempts to apply to block to the beacon chain. May queue the block for later processing.
|
||||||
|
///
|
||||||
/// Returns a `bool` which, if `true`, indicates we should forward the block to our peers.
|
/// Returns a `bool` which, if `true`, indicates we should forward the block to our peers.
|
||||||
pub fn on_block_gossip(
|
pub fn on_block_gossip(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -549,140 +521,35 @@ impl<T: BeaconChainTypes> SimpleSync<T> {
|
|||||||
block: BeaconBlock,
|
block: BeaconBlock,
|
||||||
network: &mut NetworkContext,
|
network: &mut NetworkContext,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
info!(
|
if let Some(outcome) =
|
||||||
self.log,
|
self.process_block(peer_id.clone(), block.clone(), network, &"gossip")
|
||||||
"NewGossipBlock";
|
{
|
||||||
"peer" => format!("{:?}", peer_id),
|
match outcome {
|
||||||
);
|
BlockProcessingOutcome::Processed => SHOULD_FORWARD_GOSSIP_BLOCK,
|
||||||
|
BlockProcessingOutcome::ParentUnknown { .. } => {
|
||||||
// Ignore any block from a finalized slot.
|
|
||||||
if self.slot_is_finalized(block.slot) {
|
|
||||||
warn!(
|
|
||||||
self.log, "NewGossipBlock";
|
|
||||||
"msg" => "new block slot is finalized.",
|
|
||||||
"block_slot" => block.slot,
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let block_root = Hash256::from_slice(&block.tree_hash_root());
|
|
||||||
|
|
||||||
// Ignore any block that the chain already knows about.
|
|
||||||
if self.chain_has_seen_block(&block_root) {
|
|
||||||
println!("this happened");
|
|
||||||
// TODO: Age confirm that we shouldn't forward a block if we already know of it.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!(
|
|
||||||
self.log,
|
|
||||||
"NewGossipBlock";
|
|
||||||
"peer" => format!("{:?}", peer_id),
|
|
||||||
"msg" => "processing block",
|
|
||||||
);
|
|
||||||
match self.chain.process_block(block.clone()) {
|
|
||||||
Ok(BlockProcessingOutcome::InvalidBlock(InvalidBlock::ParentUnknown)) => {
|
|
||||||
// The block was valid and we processed it successfully.
|
|
||||||
debug!(
|
|
||||||
self.log, "NewGossipBlock";
|
|
||||||
"msg" => "parent block unknown",
|
|
||||||
"parent_root" => format!("{}", block.previous_block_root),
|
|
||||||
"peer" => format!("{:?}", peer_id),
|
|
||||||
);
|
|
||||||
// Queue the block for later processing.
|
|
||||||
self.import_queue
|
self.import_queue
|
||||||
.enqueue_full_blocks(vec![block], peer_id.clone());
|
.enqueue_full_blocks(vec![block], peer_id.clone());
|
||||||
// Send a hello to learn of the clients best slot so we can then sync the require
|
|
||||||
// parent(s).
|
SHOULD_FORWARD_GOSSIP_BLOCK
|
||||||
network.send_rpc_request(
|
|
||||||
peer_id.clone(),
|
|
||||||
RPCRequest::Hello(self.chain.hello_message()),
|
|
||||||
);
|
|
||||||
// Forward the block onto our peers.
|
|
||||||
//
|
|
||||||
// Note: this may need to be changed if we decide to only forward blocks if we have
|
|
||||||
// all required info.
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
Ok(BlockProcessingOutcome::InvalidBlock(InvalidBlock::FutureSlot {
|
BlockProcessingOutcome::FutureSlot {
|
||||||
present_slot,
|
present_slot,
|
||||||
block_slot,
|
block_slot,
|
||||||
})) => {
|
} if present_slot + FUTURE_SLOT_TOLERANCE >= block_slot => {
|
||||||
if block_slot - present_slot > FUTURE_SLOT_TOLERANCE {
|
self.import_queue
|
||||||
// The block is too far in the future, drop it.
|
.enqueue_full_blocks(vec![block], peer_id.clone());
|
||||||
warn!(
|
|
||||||
self.log, "NewGossipBlock";
|
SHOULD_FORWARD_GOSSIP_BLOCK
|
||||||
"msg" => "future block rejected",
|
|
||||||
"present_slot" => present_slot,
|
|
||||||
"block_slot" => block_slot,
|
|
||||||
"FUTURE_SLOT_TOLERANCE" => FUTURE_SLOT_TOLERANCE,
|
|
||||||
"peer" => format!("{:?}", peer_id),
|
|
||||||
);
|
|
||||||
// Do not forward the block around to peers.
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
// The block is in the future, but not too far.
|
|
||||||
warn!(
|
|
||||||
self.log, "NewGossipBlock";
|
|
||||||
"msg" => "queuing future block",
|
|
||||||
"present_slot" => present_slot,
|
|
||||||
"block_slot" => block_slot,
|
|
||||||
"FUTURE_SLOT_TOLERANCE" => FUTURE_SLOT_TOLERANCE,
|
|
||||||
"peer" => format!("{:?}", peer_id),
|
|
||||||
);
|
|
||||||
// Queue the block for later processing.
|
|
||||||
self.import_queue.enqueue_full_blocks(vec![block], peer_id);
|
|
||||||
// Forward the block around to peers.
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
}
|
// Note: known blocks are forwarded on the gossip network.
|
||||||
Ok(outcome) => {
|
|
||||||
if outcome.is_invalid() {
|
|
||||||
// The peer has sent a block which is fundamentally invalid.
|
|
||||||
warn!(
|
|
||||||
self.log, "NewGossipBlock";
|
|
||||||
"msg" => "invalid block from peer",
|
|
||||||
"outcome" => format!("{:?}", outcome),
|
|
||||||
"peer" => format!("{:?}", peer_id),
|
|
||||||
);
|
|
||||||
// Disconnect the peer
|
|
||||||
network.disconnect(peer_id, GoodbyeReason::Fault);
|
|
||||||
// Do not forward the block to peers.
|
|
||||||
false
|
|
||||||
} else if outcome.sucessfully_processed() {
|
|
||||||
// The block was valid and we processed it successfully.
|
|
||||||
info!(
|
|
||||||
self.log, "NewGossipBlock";
|
|
||||||
"msg" => "block import successful",
|
|
||||||
"peer" => format!("{:?}", peer_id),
|
|
||||||
);
|
|
||||||
// Forward the block to peers
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
// The block wasn't necessarily invalid but we didn't process it successfully.
|
|
||||||
// This condition shouldn't be reached.
|
|
||||||
error!(
|
|
||||||
self.log, "NewGossipBlock";
|
|
||||||
"msg" => "unexpected condition in processing block.",
|
|
||||||
"outcome" => format!("{:?}", outcome),
|
|
||||||
);
|
|
||||||
// Do not forward the block on.
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
// We encountered an error whilst processing the block.
|
|
||||||
//
|
//
|
||||||
// Blocks should not be able to trigger errors, instead they should be flagged as
|
// We rely upon the lower layers (libp2p) to stop loops occuring from re-gossiped
|
||||||
// invalid.
|
// blocks.
|
||||||
error!(
|
BlockProcessingOutcome::BlockIsAlreadyKnown => SHOULD_FORWARD_GOSSIP_BLOCK,
|
||||||
self.log, "NewGossipBlock";
|
_ => SHOULD_NOT_FORWARD_GOSSIP_BLOCK,
|
||||||
"msg" => "internal error in processing block.",
|
|
||||||
"error" => format!("{:?}", e),
|
|
||||||
);
|
|
||||||
// Do not forward the block to peers.
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
SHOULD_NOT_FORWARD_GOSSIP_BLOCK
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -691,19 +558,15 @@ impl<T: BeaconChainTypes> SimpleSync<T> {
|
|||||||
/// Not currently implemented.
|
/// Not currently implemented.
|
||||||
pub fn on_attestation_gossip(
|
pub fn on_attestation_gossip(
|
||||||
&mut self,
|
&mut self,
|
||||||
peer_id: PeerId,
|
_peer_id: PeerId,
|
||||||
msg: Attestation,
|
msg: Attestation,
|
||||||
_network: &mut NetworkContext,
|
_network: &mut NetworkContext,
|
||||||
) {
|
) {
|
||||||
info!(
|
|
||||||
self.log,
|
|
||||||
"NewAttestationGossip";
|
|
||||||
"peer" => format!("{:?}", peer_id),
|
|
||||||
);
|
|
||||||
|
|
||||||
match self.chain.process_attestation(msg) {
|
match self.chain.process_attestation(msg) {
|
||||||
Ok(()) => info!(self.log, "ImportedAttestation"),
|
Ok(()) => info!(self.log, "ImportedAttestation"; "source" => "gossip"),
|
||||||
Err(e) => warn!(self.log, "InvalidAttestation"; "error" => format!("{:?}", e)),
|
Err(e) => {
|
||||||
|
warn!(self.log, "InvalidAttestation"; "source" => "gossip", "error" => format!("{:?}", e))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -713,55 +576,32 @@ impl<T: BeaconChainTypes> SimpleSync<T> {
|
|||||||
/// the queue.
|
/// the queue.
|
||||||
pub fn process_import_queue(&mut self, network: &mut NetworkContext) {
|
pub fn process_import_queue(&mut self, network: &mut NetworkContext) {
|
||||||
let mut successful = 0;
|
let mut successful = 0;
|
||||||
let mut invalid = 0;
|
|
||||||
let mut errored = 0;
|
|
||||||
|
|
||||||
// Loop through all of the complete blocks in the queue.
|
// Loop through all of the complete blocks in the queue.
|
||||||
for (block_root, block, sender) in self.import_queue.complete_blocks() {
|
for (block_root, block, sender) in self.import_queue.complete_blocks() {
|
||||||
match self.chain.process_block(block) {
|
let processing_result = self.process_block(sender, block.clone(), network, &"gossip");
|
||||||
Ok(outcome) => {
|
|
||||||
if outcome.is_invalid() {
|
let should_dequeue = match processing_result {
|
||||||
invalid += 1;
|
Some(BlockProcessingOutcome::ParentUnknown { .. }) => false,
|
||||||
warn!(
|
Some(BlockProcessingOutcome::FutureSlot {
|
||||||
self.log,
|
present_slot,
|
||||||
"InvalidBlock";
|
block_slot,
|
||||||
"sender_peer_id" => format!("{:?}", sender),
|
}) if present_slot + FUTURE_SLOT_TOLERANCE >= block_slot => false,
|
||||||
"reason" => format!("{:?}", outcome),
|
_ => true,
|
||||||
);
|
};
|
||||||
network.disconnect(sender, GoodbyeReason::Fault);
|
|
||||||
break;
|
if processing_result == Some(BlockProcessingOutcome::Processed) {
|
||||||
|
successful += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this results to true, the item will be removed from the queue.
|
if should_dequeue {
|
||||||
if outcome.sucessfully_processed() {
|
|
||||||
successful += 1;
|
|
||||||
self.import_queue.remove(block_root);
|
self.import_queue.remove(block_root);
|
||||||
} else {
|
|
||||||
debug!(
|
|
||||||
self.log,
|
|
||||||
"ProcessImportQueue";
|
|
||||||
"msg" => "Block not imported",
|
|
||||||
"outcome" => format!("{:?}", outcome),
|
|
||||||
"peer" => format!("{:?}", sender),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
errored += 1;
|
|
||||||
error!(self.log, "BlockProcessingError"; "error" => format!("{:?}", e));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if successful > 0 {
|
if successful > 0 {
|
||||||
info!(self.log, "Imported {} blocks", successful)
|
info!(self.log, "Imported {} blocks", successful)
|
||||||
}
|
}
|
||||||
if invalid > 0 {
|
|
||||||
warn!(self.log, "Rejected {} invalid blocks", invalid)
|
|
||||||
}
|
|
||||||
if errored > 0 {
|
|
||||||
warn!(self.log, "Failed to process {} blocks", errored)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request some `BeaconBlockRoots` from the remote peer.
|
/// Request some `BeaconBlockRoots` from the remote peer.
|
||||||
@ -833,17 +673,140 @@ impl<T: BeaconChainTypes> SimpleSync<T> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the given slot is finalized in our chain.
|
|
||||||
fn slot_is_finalized(&self, slot: Slot) -> bool {
|
|
||||||
slot <= self
|
|
||||||
.chain
|
|
||||||
.hello_message()
|
|
||||||
.latest_finalized_epoch
|
|
||||||
.start_slot(self.chain.get_spec().slots_per_epoch)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generates our current state in the form of a HELLO RPC message.
|
/// Generates our current state in the form of a HELLO RPC message.
|
||||||
pub fn generate_hello(&self) -> HelloMessage {
|
pub fn generate_hello(&self) -> HelloMessage {
|
||||||
self.chain.hello_message()
|
hello_message(&self.chain)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Processes the `block` that was received from `peer_id`.
|
||||||
|
///
|
||||||
|
/// If the block was submitted to the beacon chain without internal error, `Some(outcome)` is
|
||||||
|
/// returned, otherwise `None` is returned. Note: `Some(_)` does not necessarily indicate that
|
||||||
|
/// the block was successfully processed or valid.
|
||||||
|
///
|
||||||
|
/// This function performs the following duties:
|
||||||
|
///
|
||||||
|
/// - Attempting to import the block into the beacon chain.
|
||||||
|
/// - Logging
|
||||||
|
/// - Requesting unavailable blocks (e.g., if parent is unknown).
|
||||||
|
/// - Disconnecting faulty nodes.
|
||||||
|
///
|
||||||
|
/// This function does not remove processed blocks from the import queue.
|
||||||
|
fn process_block(
|
||||||
|
&mut self,
|
||||||
|
peer_id: PeerId,
|
||||||
|
block: BeaconBlock,
|
||||||
|
network: &mut NetworkContext,
|
||||||
|
source: &str,
|
||||||
|
) -> Option<BlockProcessingOutcome> {
|
||||||
|
let processing_result = self.chain.process_block(block.clone());
|
||||||
|
|
||||||
|
if let Ok(outcome) = processing_result {
|
||||||
|
match outcome {
|
||||||
|
BlockProcessingOutcome::Processed => {
|
||||||
|
info!(
|
||||||
|
self.log, "Imported block from network";
|
||||||
|
"source" => source,
|
||||||
|
"slot" => block.slot,
|
||||||
|
"peer" => format!("{:?}", peer_id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
BlockProcessingOutcome::ParentUnknown { parent } => {
|
||||||
|
// The block was valid and we processed it successfully.
|
||||||
|
debug!(
|
||||||
|
self.log, "ParentBlockUnknown";
|
||||||
|
"source" => source,
|
||||||
|
"parent_root" => format!("{}", parent),
|
||||||
|
"peer" => format!("{:?}", peer_id),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Send a hello to learn of the clients best slot so we can then sync the require
|
||||||
|
// parent(s).
|
||||||
|
network.send_rpc_request(
|
||||||
|
peer_id.clone(),
|
||||||
|
RPCRequest::Hello(hello_message(&self.chain)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Explicitly request the parent block from the peer.
|
||||||
|
//
|
||||||
|
// It is likely that this is duplicate work, given we already send a hello
|
||||||
|
// request. However, I believe there are some edge-cases where the hello
|
||||||
|
// message doesn't suffice, so we perform this request as well.
|
||||||
|
self.request_block_headers(
|
||||||
|
peer_id,
|
||||||
|
BeaconBlockHeadersRequest {
|
||||||
|
start_root: parent,
|
||||||
|
start_slot: block.slot - 1,
|
||||||
|
max_headers: 1,
|
||||||
|
skip_slots: 0,
|
||||||
|
},
|
||||||
|
network,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
BlockProcessingOutcome::FutureSlot {
|
||||||
|
present_slot,
|
||||||
|
block_slot,
|
||||||
|
} => {
|
||||||
|
if present_slot + FUTURE_SLOT_TOLERANCE >= block_slot {
|
||||||
|
// The block is too far in the future, drop it.
|
||||||
|
warn!(
|
||||||
|
self.log, "FutureBlock";
|
||||||
|
"source" => source,
|
||||||
|
"msg" => "block for future slot rejected, check your time",
|
||||||
|
"present_slot" => present_slot,
|
||||||
|
"block_slot" => block_slot,
|
||||||
|
"FUTURE_SLOT_TOLERANCE" => FUTURE_SLOT_TOLERANCE,
|
||||||
|
"peer" => format!("{:?}", peer_id),
|
||||||
|
);
|
||||||
|
network.disconnect(peer_id, GoodbyeReason::Fault);
|
||||||
|
} else {
|
||||||
|
// The block is in the future, but not too far.
|
||||||
|
debug!(
|
||||||
|
self.log, "QueuedFutureBlock";
|
||||||
|
"source" => source,
|
||||||
|
"msg" => "queuing future block, check your time",
|
||||||
|
"present_slot" => present_slot,
|
||||||
|
"block_slot" => block_slot,
|
||||||
|
"FUTURE_SLOT_TOLERANCE" => FUTURE_SLOT_TOLERANCE,
|
||||||
|
"peer" => format!("{:?}", peer_id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
debug!(
|
||||||
|
self.log, "InvalidBlock";
|
||||||
|
"source" => source,
|
||||||
|
"msg" => "peer sent invalid block",
|
||||||
|
"outcome" => format!("{:?}", outcome),
|
||||||
|
"peer" => format!("{:?}", peer_id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(outcome)
|
||||||
|
} else {
|
||||||
|
error!(
|
||||||
|
self.log, "BlockProcessingFailure";
|
||||||
|
"source" => source,
|
||||||
|
"msg" => "unexpected condition in processing block.",
|
||||||
|
"outcome" => format!("{:?}", processing_result)
|
||||||
|
);
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a `HelloMessage` representing the state of the given `beacon_chain`.
|
||||||
|
fn hello_message<T: BeaconChainTypes>(beacon_chain: &BeaconChain<T>) -> HelloMessage {
|
||||||
|
let spec = &beacon_chain.spec;
|
||||||
|
let state = &beacon_chain.head().beacon_state;
|
||||||
|
|
||||||
|
HelloMessage {
|
||||||
|
network_id: spec.chain_id,
|
||||||
|
latest_finalized_root: state.finalized_root,
|
||||||
|
latest_finalized_epoch: state.finalized_epoch,
|
||||||
|
best_root: beacon_chain.head().beacon_block_root,
|
||||||
|
best_slot: state.slot,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ clap = "2.32.0"
|
|||||||
store = { path = "../store" }
|
store = { path = "../store" }
|
||||||
dirs = "1.0.3"
|
dirs = "1.0.3"
|
||||||
futures = "0.1.23"
|
futures = "0.1.23"
|
||||||
|
serde = "1.0"
|
||||||
|
serde_derive = "1.0"
|
||||||
slog = "^2.2.3"
|
slog = "^2.2.3"
|
||||||
slog-term = "^2.4.0"
|
slog-term = "^2.4.0"
|
||||||
slog-async = "^2.3.0"
|
slog-async = "^2.3.0"
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use crate::beacon_chain::{BeaconChain, BeaconChainTypes};
|
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||||
|
use eth2_libp2p::PubsubMessage;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use grpcio::{RpcContext, RpcStatus, RpcStatusCode, UnarySink};
|
use grpcio::{RpcContext, RpcStatus, RpcStatusCode, UnarySink};
|
||||||
|
use network::NetworkMessage;
|
||||||
use protos::services::{
|
use protos::services::{
|
||||||
AttestationData as AttestationDataProto, ProduceAttestationDataRequest,
|
AttestationData as AttestationDataProto, ProduceAttestationDataRequest,
|
||||||
ProduceAttestationDataResponse, PublishAttestationRequest, PublishAttestationResponse,
|
ProduceAttestationDataResponse, PublishAttestationRequest, PublishAttestationResponse,
|
||||||
@ -14,6 +16,7 @@ use types::Attestation;
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AttestationServiceInstance<T: BeaconChainTypes> {
|
pub struct AttestationServiceInstance<T: BeaconChainTypes> {
|
||||||
pub chain: Arc<BeaconChain<T>>,
|
pub chain: Arc<BeaconChain<T>>,
|
||||||
|
pub network_chan: crossbeam_channel::Sender<NetworkMessage>,
|
||||||
pub log: slog::Logger,
|
pub log: slog::Logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,7 +37,7 @@ impl<T: BeaconChainTypes> AttestationService for AttestationServiceInstance<T> {
|
|||||||
// verify the slot, drop lock on state afterwards
|
// verify the slot, drop lock on state afterwards
|
||||||
{
|
{
|
||||||
let slot_requested = req.get_slot();
|
let slot_requested = req.get_slot();
|
||||||
let state = self.chain.get_state();
|
let state = &self.chain.current_state();
|
||||||
|
|
||||||
// Start by performing some checks
|
// Start by performing some checks
|
||||||
// Check that the AttestionData is for the current slot (otherwise it will not be valid)
|
// Check that the AttestionData is for the current slot (otherwise it will not be valid)
|
||||||
@ -124,7 +127,7 @@ impl<T: BeaconChainTypes> AttestationService for AttestationServiceInstance<T> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match self.chain.process_attestation(attestation) {
|
match self.chain.process_attestation(attestation.clone()) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
// Attestation was successfully processed.
|
// Attestation was successfully processed.
|
||||||
info!(
|
info!(
|
||||||
@ -133,6 +136,25 @@ impl<T: BeaconChainTypes> AttestationService for AttestationServiceInstance<T> {
|
|||||||
"type" => "valid_attestation",
|
"type" => "valid_attestation",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TODO: Obtain topics from the network service properly.
|
||||||
|
let topic = types::TopicBuilder::new("beacon_chain".to_string()).build();
|
||||||
|
let message = PubsubMessage::Attestation(attestation);
|
||||||
|
|
||||||
|
// Publish the attestation to the p2p network via gossipsub.
|
||||||
|
self.network_chan
|
||||||
|
.send(NetworkMessage::Publish {
|
||||||
|
topics: vec![topic],
|
||||||
|
message: Box::new(message),
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
error!(
|
||||||
|
self.log,
|
||||||
|
"PublishAttestation";
|
||||||
|
"type" => "failed to publish to gossipsub",
|
||||||
|
"error" => format!("{:?}", e)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
resp.set_success(true);
|
resp.set_success(true);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::beacon_chain::{BeaconChain, BeaconChainTypes};
|
use beacon_chain::{BeaconChain, BeaconChainTypes, BlockProcessingOutcome};
|
||||||
use crossbeam_channel;
|
use crossbeam_channel;
|
||||||
use eth2_libp2p::PubsubMessage;
|
use eth2_libp2p::PubsubMessage;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
@ -95,14 +95,12 @@ impl<T: BeaconChainTypes> BeaconBlockService for BeaconBlockServiceInstance<T> {
|
|||||||
Ok(block) => {
|
Ok(block) => {
|
||||||
match self.chain.process_block(block.clone()) {
|
match self.chain.process_block(block.clone()) {
|
||||||
Ok(outcome) => {
|
Ok(outcome) => {
|
||||||
if outcome.sucessfully_processed() {
|
if outcome == BlockProcessingOutcome::Processed {
|
||||||
// Block was successfully processed.
|
// Block was successfully processed.
|
||||||
info!(
|
info!(
|
||||||
self.log,
|
self.log,
|
||||||
"PublishBeaconBlock";
|
"Valid block from RPC";
|
||||||
"type" => "valid_block",
|
|
||||||
"block_slot" => block.slot,
|
"block_slot" => block.slot,
|
||||||
"outcome" => format!("{:?}", outcome)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Obtain topics from the network service properly.
|
// TODO: Obtain topics from the network service properly.
|
||||||
@ -126,12 +124,11 @@ impl<T: BeaconChainTypes> BeaconBlockService for BeaconBlockServiceInstance<T> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
resp.set_success(true);
|
resp.set_success(true);
|
||||||
} else if outcome.is_invalid() {
|
} else {
|
||||||
// Block was invalid.
|
// Block was not successfully processed.
|
||||||
warn!(
|
warn!(
|
||||||
self.log,
|
self.log,
|
||||||
"PublishBeaconBlock";
|
"Invalid block from RPC";
|
||||||
"type" => "invalid_block",
|
|
||||||
"outcome" => format!("{:?}", outcome)
|
"outcome" => format!("{:?}", outcome)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -139,17 +136,6 @@ impl<T: BeaconChainTypes> BeaconBlockService for BeaconBlockServiceInstance<T> {
|
|||||||
resp.set_msg(
|
resp.set_msg(
|
||||||
format!("InvalidBlock: {:?}", outcome).as_bytes().to_vec(),
|
format!("InvalidBlock: {:?}", outcome).as_bytes().to_vec(),
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
// Some failure during processing.
|
|
||||||
warn!(
|
|
||||||
self.log,
|
|
||||||
"PublishBeaconBlock";
|
|
||||||
"type" => "unable_to_import",
|
|
||||||
"outcome" => format!("{:?}", outcome)
|
|
||||||
);
|
|
||||||
|
|
||||||
resp.set_success(false);
|
|
||||||
resp.set_msg(format!("other: {:?}", outcome).as_bytes().to_vec());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
use beacon_chain::BeaconChain as RawBeaconChain;
|
|
||||||
use beacon_chain::{
|
|
||||||
parking_lot::{RwLockReadGuard, RwLockWriteGuard},
|
|
||||||
types::{BeaconState, ChainSpec, Signature},
|
|
||||||
AttestationValidationError, BlockProductionError,
|
|
||||||
};
|
|
||||||
pub use beacon_chain::{BeaconChainError, BeaconChainTypes, BlockProcessingOutcome};
|
|
||||||
use types::{Attestation, AttestationData, BeaconBlock, EthSpec};
|
|
||||||
|
|
||||||
/// The RPC's API to the beacon chain.
|
|
||||||
pub trait BeaconChain<T: BeaconChainTypes>: Send + Sync {
|
|
||||||
fn get_spec(&self) -> &ChainSpec;
|
|
||||||
|
|
||||||
fn get_state(&self) -> RwLockReadGuard<BeaconState<T::EthSpec>>;
|
|
||||||
|
|
||||||
fn get_mut_state(&self) -> RwLockWriteGuard<BeaconState<T::EthSpec>>;
|
|
||||||
|
|
||||||
fn process_block(&self, block: BeaconBlock)
|
|
||||||
-> Result<BlockProcessingOutcome, BeaconChainError>;
|
|
||||||
|
|
||||||
fn produce_block(
|
|
||||||
&self,
|
|
||||||
randao_reveal: Signature,
|
|
||||||
) -> Result<(BeaconBlock, BeaconState<T::EthSpec>), BlockProductionError>;
|
|
||||||
|
|
||||||
fn produce_attestation_data(&self, shard: u64) -> Result<AttestationData, BeaconChainError>;
|
|
||||||
|
|
||||||
fn process_attestation(
|
|
||||||
&self,
|
|
||||||
attestation: Attestation,
|
|
||||||
) -> Result<(), AttestationValidationError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: BeaconChainTypes> BeaconChain<T> for RawBeaconChain<T> {
|
|
||||||
fn get_spec(&self) -> &ChainSpec {
|
|
||||||
&self.spec
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_state(&self) -> RwLockReadGuard<BeaconState<T::EthSpec>> {
|
|
||||||
self.state.read()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_mut_state(&self) -> RwLockWriteGuard<BeaconState<T::EthSpec>> {
|
|
||||||
self.state.write()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_block(
|
|
||||||
&self,
|
|
||||||
block: BeaconBlock,
|
|
||||||
) -> Result<BlockProcessingOutcome, BeaconChainError> {
|
|
||||||
self.process_block(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn produce_block(
|
|
||||||
&self,
|
|
||||||
randao_reveal: Signature,
|
|
||||||
) -> Result<(BeaconBlock, BeaconState<T::EthSpec>), BlockProductionError> {
|
|
||||||
self.produce_block(randao_reveal)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn produce_attestation_data(&self, shard: u64) -> Result<AttestationData, BeaconChainError> {
|
|
||||||
self.produce_attestation_data(shard)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_attestation(
|
|
||||||
&self,
|
|
||||||
attestation: Attestation,
|
|
||||||
) -> Result<(), AttestationValidationError> {
|
|
||||||
self.process_attestation(attestation)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
use crate::beacon_chain::{BeaconChain, BeaconChainTypes};
|
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use grpcio::{RpcContext, UnarySink};
|
use grpcio::{RpcContext, UnarySink};
|
||||||
use protos::services::{Empty, Fork, NodeInfoResponse};
|
use protos::services::{Empty, Fork, NodeInfoResponse};
|
||||||
@ -22,7 +22,7 @@ impl<T: BeaconChainTypes> BeaconNodeService for BeaconNodeServiceInstance<T> {
|
|||||||
node_info.set_version(version::version());
|
node_info.set_version(version::version());
|
||||||
|
|
||||||
// get the chain state
|
// get the chain state
|
||||||
let state = self.chain.get_state();
|
let state = &self.chain.head().beacon_state;
|
||||||
let state_fork = state.fork.clone();
|
let state_fork = state.fork.clone();
|
||||||
let genesis_time = state.genesis_time;
|
let genesis_time = state.genesis_time;
|
||||||
|
|
||||||
@ -32,10 +32,12 @@ impl<T: BeaconChainTypes> BeaconNodeService for BeaconNodeServiceInstance<T> {
|
|||||||
fork.set_current_version(state_fork.current_version.to_vec());
|
fork.set_current_version(state_fork.current_version.to_vec());
|
||||||
fork.set_epoch(state_fork.epoch.into());
|
fork.set_epoch(state_fork.epoch.into());
|
||||||
|
|
||||||
|
let spec = &self.chain.spec;
|
||||||
|
|
||||||
node_info.set_fork(fork);
|
node_info.set_fork(fork);
|
||||||
node_info.set_genesis_time(genesis_time);
|
node_info.set_genesis_time(genesis_time);
|
||||||
node_info.set_genesis_slot(self.chain.get_spec().genesis_slot.as_u64());
|
node_info.set_genesis_slot(spec.genesis_slot.as_u64());
|
||||||
node_info.set_chain_id(u32::from(self.chain.get_spec().chain_id));
|
node_info.set_chain_id(u32::from(spec.chain_id));
|
||||||
|
|
||||||
// send the node_info the requester
|
// send the node_info the requester
|
||||||
let error_log = self.log.clone();
|
let error_log = self.log.clone();
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
use clap::ArgMatches;
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
/// RPC Configuration
|
/// RPC Configuration
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// Enable the RPC server.
|
/// Enable the RPC server.
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
@ -20,3 +22,23 @@ impl Default for Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn apply_cli_args(&mut self, args: &ArgMatches) -> Result<(), &'static str> {
|
||||||
|
if args.is_present("rpc") {
|
||||||
|
self.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(rpc_address) = args.value_of("rpc-address") {
|
||||||
|
self.listen_address = rpc_address
|
||||||
|
.parse::<Ipv4Addr>()
|
||||||
|
.map_err(|_| "rpc-address is not IPv4 address")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(rpc_port) = args.value_of("rpc-port") {
|
||||||
|
self.port = rpc_port.parse::<u16>().map_err(|_| "rpc-port is not u16")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
mod attestation;
|
mod attestation;
|
||||||
mod beacon_block;
|
mod beacon_block;
|
||||||
pub mod beacon_chain;
|
|
||||||
mod beacon_node;
|
mod beacon_node;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
mod validator;
|
mod validator;
|
||||||
|
|
||||||
use self::attestation::AttestationServiceInstance;
|
use self::attestation::AttestationServiceInstance;
|
||||||
use self::beacon_block::BeaconBlockServiceInstance;
|
use self::beacon_block::BeaconBlockServiceInstance;
|
||||||
use self::beacon_chain::{BeaconChain, BeaconChainTypes};
|
|
||||||
use self::beacon_node::BeaconNodeServiceInstance;
|
use self::beacon_node::BeaconNodeServiceInstance;
|
||||||
use self::validator::ValidatorServiceInstance;
|
use self::validator::ValidatorServiceInstance;
|
||||||
|
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||||
pub use config::Config as RPCConfig;
|
pub use config::Config as RPCConfig;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use grpcio::{Environment, ServerBuilder};
|
use grpcio::{Environment, ServerBuilder};
|
||||||
@ -47,7 +46,7 @@ pub fn start_server<T: BeaconChainTypes + Clone + 'static>(
|
|||||||
let beacon_block_service = {
|
let beacon_block_service = {
|
||||||
let instance = BeaconBlockServiceInstance {
|
let instance = BeaconBlockServiceInstance {
|
||||||
chain: beacon_chain.clone(),
|
chain: beacon_chain.clone(),
|
||||||
network_chan,
|
network_chan: network_chan.clone(),
|
||||||
log: log.clone(),
|
log: log.clone(),
|
||||||
};
|
};
|
||||||
create_beacon_block_service(instance)
|
create_beacon_block_service(instance)
|
||||||
@ -62,6 +61,7 @@ pub fn start_server<T: BeaconChainTypes + Clone + 'static>(
|
|||||||
let attestation_service = {
|
let attestation_service = {
|
||||||
let instance = AttestationServiceInstance {
|
let instance = AttestationServiceInstance {
|
||||||
chain: beacon_chain.clone(),
|
chain: beacon_chain.clone(),
|
||||||
|
network_chan,
|
||||||
log: log.clone(),
|
log: log.clone(),
|
||||||
};
|
};
|
||||||
create_attestation_service(instance)
|
create_attestation_service(instance)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::beacon_chain::{BeaconChain, BeaconChainTypes};
|
use beacon_chain::{BeaconChain, BeaconChainTypes};
|
||||||
use bls::PublicKey;
|
use bls::PublicKey;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use grpcio::{RpcContext, RpcStatus, RpcStatusCode, UnarySink};
|
use grpcio::{RpcContext, RpcStatus, RpcStatusCode, UnarySink};
|
||||||
@ -7,14 +7,13 @@ use protos::services_grpc::ValidatorService;
|
|||||||
use slog::{trace, warn};
|
use slog::{trace, warn};
|
||||||
use ssz::Decode;
|
use ssz::Decode;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use types::{Epoch, RelativeEpoch};
|
use types::{Epoch, EthSpec, RelativeEpoch};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ValidatorServiceInstance<T: BeaconChainTypes> {
|
pub struct ValidatorServiceInstance<T: BeaconChainTypes> {
|
||||||
pub chain: Arc<BeaconChain<T>>,
|
pub chain: Arc<BeaconChain<T>>,
|
||||||
pub log: slog::Logger,
|
pub log: slog::Logger,
|
||||||
}
|
}
|
||||||
//TODO: Refactor Errors
|
|
||||||
|
|
||||||
impl<T: BeaconChainTypes> ValidatorService for ValidatorServiceInstance<T> {
|
impl<T: BeaconChainTypes> ValidatorService for ValidatorServiceInstance<T> {
|
||||||
/// For a list of validator public keys, this function returns the slot at which each
|
/// For a list of validator public keys, this function returns the slot at which each
|
||||||
@ -29,14 +28,15 @@ impl<T: BeaconChainTypes> ValidatorService for ValidatorServiceInstance<T> {
|
|||||||
let validators = req.get_validators();
|
let validators = req.get_validators();
|
||||||
trace!(self.log, "RPC request"; "endpoint" => "GetValidatorDuties", "epoch" => req.get_epoch());
|
trace!(self.log, "RPC request"; "endpoint" => "GetValidatorDuties", "epoch" => req.get_epoch());
|
||||||
|
|
||||||
let spec = self.chain.get_spec();
|
let spec = &self.chain.spec;
|
||||||
let state = self.chain.get_state();
|
let state = &self.chain.current_state();
|
||||||
let epoch = Epoch::from(req.get_epoch());
|
let epoch = Epoch::from(req.get_epoch());
|
||||||
let mut resp = GetDutiesResponse::new();
|
let mut resp = GetDutiesResponse::new();
|
||||||
let resp_validators = resp.mut_active_validators();
|
let resp_validators = resp.mut_active_validators();
|
||||||
|
|
||||||
let relative_epoch =
|
let relative_epoch =
|
||||||
match RelativeEpoch::from_epoch(state.slot.epoch(spec.slots_per_epoch), epoch) {
|
match RelativeEpoch::from_epoch(state.slot.epoch(T::EthSpec::slots_per_epoch()), epoch)
|
||||||
|
{
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// incorrect epoch
|
// incorrect epoch
|
||||||
@ -52,7 +52,7 @@ impl<T: BeaconChainTypes> ValidatorService for ValidatorServiceInstance<T> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let validator_proposers: Result<Vec<usize>, _> = epoch
|
let validator_proposers: Result<Vec<usize>, _> = epoch
|
||||||
.slot_iter(spec.slots_per_epoch)
|
.slot_iter(T::EthSpec::slots_per_epoch())
|
||||||
.map(|slot| state.get_beacon_proposer_index(slot, relative_epoch, &spec))
|
.map(|slot| state.get_beacon_proposer_index(slot, relative_epoch, &spec))
|
||||||
.collect();
|
.collect();
|
||||||
let validator_proposers = match validator_proposers {
|
let validator_proposers = match validator_proposers {
|
||||||
@ -115,7 +115,9 @@ impl<T: BeaconChainTypes> ValidatorService for ValidatorServiceInstance<T> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// get attestation duties and check if validator is active
|
// get attestation duties and check if validator is active
|
||||||
let attestation_duties = match state.get_attestation_duties(val_index, &spec) {
|
let attestation_duties = match state
|
||||||
|
.get_attestation_duties(val_index, RelativeEpoch::Current)
|
||||||
|
{
|
||||||
Ok(Some(v)) => v,
|
Ok(Some(v)) => v,
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
// validator is inactive, go to the next validator
|
// validator is inactive, go to the next validator
|
||||||
@ -146,7 +148,7 @@ impl<T: BeaconChainTypes> ValidatorService for ValidatorServiceInstance<T> {
|
|||||||
// check if the validator needs to propose a block
|
// check if the validator needs to propose a block
|
||||||
if let Some(slot) = validator_proposers.iter().position(|&v| val_index == v) {
|
if let Some(slot) = validator_proposers.iter().position(|&v| val_index == v) {
|
||||||
duty.set_block_production_slot(
|
duty.set_block_production_slot(
|
||||||
epoch.start_slot(spec.slots_per_epoch).as_u64() + slot as u64,
|
epoch.start_slot(T::EthSpec::slots_per_epoch()).as_u64() + slot as u64,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// no blocks to propose this epoch
|
// no blocks to propose this epoch
|
||||||
|
@ -3,8 +3,15 @@ extern crate slog;
|
|||||||
mod run;
|
mod run;
|
||||||
|
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
use client::ClientConfig;
|
use client::{ClientConfig, Eth2Config};
|
||||||
use slog::{error, o, Drain};
|
use eth2_config::{get_data_dir, read_from_file, write_to_file};
|
||||||
|
use slog::{crit, o, Drain};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub const DEFAULT_DATA_DIR: &str = ".lighthouse";
|
||||||
|
|
||||||
|
pub const CLIENT_CONFIG_FILENAME: &str = "beacon-node.toml";
|
||||||
|
pub const ETH2_CONFIG_FILENAME: &str = "eth2-spec.toml";
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let decorator = slog_term::TermDecorator::new().build();
|
let decorator = slog_term::TermDecorator::new().build();
|
||||||
@ -22,28 +29,22 @@ fn main() {
|
|||||||
.long("datadir")
|
.long("datadir")
|
||||||
.value_name("DIR")
|
.value_name("DIR")
|
||||||
.help("Data directory for keys and databases.")
|
.help("Data directory for keys and databases.")
|
||||||
.takes_value(true),
|
.takes_value(true)
|
||||||
|
.default_value(DEFAULT_DATA_DIR),
|
||||||
)
|
)
|
||||||
// network related arguments
|
// network related arguments
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("listen-address")
|
Arg::with_name("listen-address")
|
||||||
.long("listen-address")
|
.long("listen-address")
|
||||||
.value_name("Listen Address")
|
.value_name("Listen Address")
|
||||||
.help("The Network address to listen for p2p connections.")
|
.help("One or more comma-delimited multi-addresses to listen for p2p connections.")
|
||||||
.takes_value(true),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("port")
|
|
||||||
.long("port")
|
|
||||||
.value_name("PORT")
|
|
||||||
.help("Network listen port for p2p connections.")
|
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("boot-nodes")
|
Arg::with_name("boot-nodes")
|
||||||
.long("boot-nodes")
|
.long("boot-nodes")
|
||||||
.value_name("BOOTNODES")
|
.value_name("BOOTNODES")
|
||||||
.help("A list of comma separated multi addresses representing bootnodes to connect to.")
|
.help("One or more comma-delimited multi-addresses to bootstrap the p2p network.")
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
// rpc related arguments
|
// rpc related arguments
|
||||||
@ -68,6 +69,28 @@ fn main() {
|
|||||||
.help("Listen port for RPC endpoint.")
|
.help("Listen port for RPC endpoint.")
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
|
// HTTP related arguments
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("http")
|
||||||
|
.long("http")
|
||||||
|
.value_name("HTTP")
|
||||||
|
.help("Enable the HTTP server.")
|
||||||
|
.takes_value(false),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("http-address")
|
||||||
|
.long("http-address")
|
||||||
|
.value_name("HTTPADDRESS")
|
||||||
|
.help("Listen address for the HTTP server.")
|
||||||
|
.takes_value(true),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("http-port")
|
||||||
|
.long("http-port")
|
||||||
|
.value_name("HTTPPORT")
|
||||||
|
.help("Listen port for the HTTP server.")
|
||||||
|
.takes_value(true),
|
||||||
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("db")
|
Arg::with_name("db")
|
||||||
.long("db")
|
.long("db")
|
||||||
@ -77,13 +100,101 @@ fn main() {
|
|||||||
.possible_values(&["disk", "memory"])
|
.possible_values(&["disk", "memory"])
|
||||||
.default_value("memory"),
|
.default_value("memory"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("spec-constants")
|
||||||
|
.long("spec-constants")
|
||||||
|
.value_name("TITLE")
|
||||||
|
.short("s")
|
||||||
|
.help("The title of the spec constants for chain config.")
|
||||||
|
.takes_value(true)
|
||||||
|
.possible_values(&["mainnet", "minimal"])
|
||||||
|
.default_value("minimal"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("recent-genesis")
|
||||||
|
.long("recent-genesis")
|
||||||
|
.short("r")
|
||||||
|
.help("When present, genesis will be within 30 minutes prior. Only for testing"),
|
||||||
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
// invalid arguments, panic
|
let data_dir = match get_data_dir(&matches, PathBuf::from(DEFAULT_DATA_DIR)) {
|
||||||
let config = ClientConfig::parse_args(matches, &logger).unwrap();
|
Ok(dir) => dir,
|
||||||
|
Err(e) => {
|
||||||
|
crit!(logger, "Failed to initialize data dir"; "error" => format!("{:?}", e));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
match run::run_beacon_node(config, &logger) {
|
let client_config_path = data_dir.join(CLIENT_CONFIG_FILENAME);
|
||||||
|
|
||||||
|
// Attempt to lead the `ClientConfig` from disk.
|
||||||
|
//
|
||||||
|
// If file doesn't exist, create a new, default one.
|
||||||
|
let mut client_config = match read_from_file::<ClientConfig>(client_config_path.clone()) {
|
||||||
|
Ok(Some(c)) => c,
|
||||||
|
Ok(None) => {
|
||||||
|
let default = ClientConfig::default();
|
||||||
|
if let Err(e) = write_to_file(client_config_path, &default) {
|
||||||
|
crit!(logger, "Failed to write default ClientConfig to file"; "error" => format!("{:?}", e));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
crit!(logger, "Failed to load a ChainConfig file"; "error" => format!("{:?}", e));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ensure the `data_dir` in the config matches that supplied to the CLI.
|
||||||
|
client_config.data_dir = data_dir.clone();
|
||||||
|
|
||||||
|
// Update the client config with any CLI args.
|
||||||
|
match client_config.apply_cli_args(&matches) {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(s) => {
|
||||||
|
crit!(logger, "Failed to parse ClientConfig CLI arguments"; "error" => s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let eth2_config_path = data_dir.join(ETH2_CONFIG_FILENAME);
|
||||||
|
|
||||||
|
// Attempt to load the `Eth2Config` from file.
|
||||||
|
//
|
||||||
|
// If the file doesn't exist, create a default one depending on the CLI flags.
|
||||||
|
let mut eth2_config = match read_from_file::<Eth2Config>(eth2_config_path.clone()) {
|
||||||
|
Ok(Some(c)) => c,
|
||||||
|
Ok(None) => {
|
||||||
|
let default = match matches.value_of("spec-constants") {
|
||||||
|
Some("mainnet") => Eth2Config::mainnet(),
|
||||||
|
Some("minimal") => Eth2Config::minimal(),
|
||||||
|
_ => unreachable!(), // Guarded by slog.
|
||||||
|
};
|
||||||
|
if let Err(e) = write_to_file(eth2_config_path, &default) {
|
||||||
|
crit!(logger, "Failed to write default Eth2Config to file"; "error" => format!("{:?}", e));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
crit!(logger, "Failed to load/generate an Eth2Config"; "error" => format!("{:?}", e));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update the eth2 config with any CLI flags.
|
||||||
|
match eth2_config.apply_cli_args(&matches) {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(s) => {
|
||||||
|
crit!(logger, "Failed to parse Eth2Config CLI arguments"; "error" => s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match run::run_beacon_node(client_config, eth2_config, &logger) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) => error!(logger, "Beacon node failed because {:?}", e),
|
Err(e) => crit!(logger, "Beacon node failed to start"; "reason" => format!("{:}", e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,62 +1,115 @@
|
|||||||
use client::{
|
use client::{
|
||||||
error, notifier, BeaconChainTypes, Client, ClientConfig, DBType, TestnetDiskBeaconChainTypes,
|
error, notifier, BeaconChainTypes, Client, ClientConfig, ClientType, Eth2Config,
|
||||||
TestnetMemoryBeaconChainTypes,
|
InitialiseBeaconChain,
|
||||||
};
|
};
|
||||||
use futures::sync::oneshot;
|
use futures::sync::oneshot;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use slog::info;
|
use slog::{error, info, warn};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use store::{DiskStore, MemoryStore};
|
||||||
use tokio::runtime::Builder;
|
use tokio::runtime::Builder;
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
use tokio::runtime::TaskExecutor;
|
use tokio::runtime::TaskExecutor;
|
||||||
use tokio_timer::clock::Clock;
|
use tokio_timer::clock::Clock;
|
||||||
|
use types::{MainnetEthSpec, MinimalEthSpec};
|
||||||
|
|
||||||
pub fn run_beacon_node(config: ClientConfig, log: &slog::Logger) -> error::Result<()> {
|
pub fn run_beacon_node(
|
||||||
|
client_config: ClientConfig,
|
||||||
|
eth2_config: Eth2Config,
|
||||||
|
log: &slog::Logger,
|
||||||
|
) -> error::Result<()> {
|
||||||
let runtime = Builder::new()
|
let runtime = Builder::new()
|
||||||
.name_prefix("main-")
|
.name_prefix("main-")
|
||||||
.clock(Clock::system())
|
.clock(Clock::system())
|
||||||
.build()
|
.build()
|
||||||
.map_err(|e| format!("{:?}", e))?;
|
.map_err(|e| format!("{:?}", e))?;
|
||||||
|
|
||||||
// Log configuration
|
|
||||||
info!(log, "Listening on {:?}", &config.net_conf.listen_addresses;
|
|
||||||
"data_dir" => &config.data_dir.to_str(),
|
|
||||||
"port" => &config.net_conf.listen_port);
|
|
||||||
|
|
||||||
let executor = runtime.executor();
|
let executor = runtime.executor();
|
||||||
|
|
||||||
match config.db_type {
|
let db_path: PathBuf = client_config
|
||||||
DBType::Disk => {
|
.db_path()
|
||||||
|
.ok_or_else::<error::Error, _>(|| "Unable to access database path".into())?;
|
||||||
|
let db_type = &client_config.db_type;
|
||||||
|
let spec_constants = eth2_config.spec_constants.clone();
|
||||||
|
|
||||||
|
let other_client_config = client_config.clone();
|
||||||
|
|
||||||
|
warn!(
|
||||||
|
log,
|
||||||
|
"This software is EXPERIMENTAL and provides no guarantees or warranties."
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = match (db_type.as_str(), spec_constants.as_str()) {
|
||||||
|
("disk", "minimal") => run::<ClientType<DiskStore, MinimalEthSpec>>(
|
||||||
|
&db_path,
|
||||||
|
client_config,
|
||||||
|
eth2_config,
|
||||||
|
executor,
|
||||||
|
runtime,
|
||||||
|
log,
|
||||||
|
),
|
||||||
|
("memory", "minimal") => run::<ClientType<MemoryStore, MinimalEthSpec>>(
|
||||||
|
&db_path,
|
||||||
|
client_config,
|
||||||
|
eth2_config,
|
||||||
|
executor,
|
||||||
|
runtime,
|
||||||
|
log,
|
||||||
|
),
|
||||||
|
("disk", "mainnet") => run::<ClientType<DiskStore, MainnetEthSpec>>(
|
||||||
|
&db_path,
|
||||||
|
client_config,
|
||||||
|
eth2_config,
|
||||||
|
executor,
|
||||||
|
runtime,
|
||||||
|
log,
|
||||||
|
),
|
||||||
|
("memory", "mainnet") => run::<ClientType<MemoryStore, MainnetEthSpec>>(
|
||||||
|
&db_path,
|
||||||
|
client_config,
|
||||||
|
eth2_config,
|
||||||
|
executor,
|
||||||
|
runtime,
|
||||||
|
log,
|
||||||
|
),
|
||||||
|
(db_type, spec) => {
|
||||||
|
error!(log, "Unknown runtime configuration"; "spec_constants" => spec, "db_type" => db_type);
|
||||||
|
Err("Unknown specification and/or db_type.".into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if result.is_ok() {
|
||||||
info!(
|
info!(
|
||||||
log,
|
log,
|
||||||
"BeaconNode starting";
|
"Started beacon node";
|
||||||
"type" => "TestnetDiskBeaconChainTypes"
|
"p2p_listen_addresses" => format!("{:?}", &other_client_config.network.listen_addresses()),
|
||||||
|
"data_dir" => format!("{:?}", other_client_config.data_dir()),
|
||||||
|
"spec_constants" => &spec_constants,
|
||||||
|
"db_type" => &other_client_config.db_type,
|
||||||
);
|
);
|
||||||
let client: Client<TestnetDiskBeaconChainTypes> =
|
|
||||||
Client::new(config, log.clone(), &executor)?;
|
|
||||||
|
|
||||||
run(client, executor, runtime, log)
|
|
||||||
}
|
|
||||||
DBType::Memory => {
|
|
||||||
info!(
|
|
||||||
log,
|
|
||||||
"BeaconNode starting";
|
|
||||||
"type" => "TestnetMemoryBeaconChainTypes"
|
|
||||||
);
|
|
||||||
let client: Client<TestnetMemoryBeaconChainTypes> =
|
|
||||||
Client::new(config, log.clone(), &executor)?;
|
|
||||||
|
|
||||||
run(client, executor, runtime, log)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run<T: BeaconChainTypes + Send + Sync + 'static>(
|
result
|
||||||
client: Client<T>,
|
}
|
||||||
|
|
||||||
|
pub fn run<T>(
|
||||||
|
db_path: &Path,
|
||||||
|
client_config: ClientConfig,
|
||||||
|
eth2_config: Eth2Config,
|
||||||
executor: TaskExecutor,
|
executor: TaskExecutor,
|
||||||
mut runtime: Runtime,
|
mut runtime: Runtime,
|
||||||
log: &slog::Logger,
|
log: &slog::Logger,
|
||||||
) -> error::Result<()> {
|
) -> error::Result<()>
|
||||||
|
where
|
||||||
|
T: BeaconChainTypes + InitialiseBeaconChain<T> + Clone + Send + Sync + 'static,
|
||||||
|
T::Store: OpenDatabase,
|
||||||
|
{
|
||||||
|
let store = T::Store::open_database(&db_path)?;
|
||||||
|
|
||||||
|
let client: Client<T> = Client::new(client_config, eth2_config, store, log.clone(), &executor)?;
|
||||||
|
|
||||||
// run service until ctrl-c
|
// run service until ctrl-c
|
||||||
let (ctrlc_send, ctrlc_oneshot) = oneshot::channel();
|
let (ctrlc_send, ctrlc_oneshot) = oneshot::channel();
|
||||||
let ctrlc_send_c = RefCell::new(Some(ctrlc_send));
|
let ctrlc_send_c = RefCell::new(Some(ctrlc_send));
|
||||||
@ -84,3 +137,22 @@ pub fn run<T: BeaconChainTypes + Send + Sync + 'static>(
|
|||||||
runtime.shutdown_on_idle().wait().unwrap();
|
runtime.shutdown_on_idle().wait().unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A convenience trait, providing a method to open a database.
|
||||||
|
///
|
||||||
|
/// Panics if unable to open the database.
|
||||||
|
pub trait OpenDatabase: Sized {
|
||||||
|
fn open_database(path: &Path) -> error::Result<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpenDatabase for MemoryStore {
|
||||||
|
fn open_database(_path: &Path) -> error::Result<Self> {
|
||||||
|
Ok(MemoryStore::open())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpenDatabase for DiskStore {
|
||||||
|
fn open_database(path: &Path) -> error::Result<Self> {
|
||||||
|
DiskStore::open(path).map_err(|e| format!("Unable to open database: {:?}", e).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -25,15 +25,23 @@ pub fn get_block_at_preceeding_slot<T: Store>(
|
|||||||
slot: Slot,
|
slot: Slot,
|
||||||
start_root: Hash256,
|
start_root: Hash256,
|
||||||
) -> Result<Option<(Hash256, BeaconBlock)>, Error> {
|
) -> Result<Option<(Hash256, BeaconBlock)>, Error> {
|
||||||
let mut root = start_root;
|
Ok(match get_at_preceeding_slot(store, slot, start_root)? {
|
||||||
|
Some((hash, bytes)) => Some((hash, BeaconBlock::from_ssz_bytes(&bytes)?)),
|
||||||
|
None => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_at_preceeding_slot<T: Store>(
|
||||||
|
store: &T,
|
||||||
|
slot: Slot,
|
||||||
|
mut root: Hash256,
|
||||||
|
) -> Result<Option<(Hash256, Vec<u8>)>, Error> {
|
||||||
loop {
|
loop {
|
||||||
if let Some(bytes) = get_block_bytes(store, root)? {
|
if let Some(bytes) = get_block_bytes(store, root)? {
|
||||||
let this_slot = read_slot_from_block_bytes(&bytes)?;
|
let this_slot = read_slot_from_block_bytes(&bytes)?;
|
||||||
|
|
||||||
if this_slot == slot {
|
if this_slot == slot {
|
||||||
let block = BeaconBlock::from_ssz_bytes(&bytes)?;
|
break Ok(Some((root, bytes)));
|
||||||
break Ok(Some((root, block)));
|
|
||||||
} else if this_slot < slot {
|
} else if this_slot < slot {
|
||||||
break Ok(None);
|
break Ok(None);
|
||||||
} else {
|
} else {
|
||||||
@ -53,7 +61,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read_slot() {
|
fn read_slot() {
|
||||||
let spec = FewValidatorsEthSpec::spec();
|
let spec = MinimalEthSpec::default_spec();
|
||||||
|
|
||||||
let test_slot = |slot: Slot| {
|
let test_slot = |slot: Slot| {
|
||||||
let mut block = BeaconBlock::empty(&spec);
|
let mut block = BeaconBlock::empty(&spec);
|
||||||
@ -77,7 +85,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn read_previous_block_root() {
|
fn read_previous_block_root() {
|
||||||
let spec = FewValidatorsEthSpec::spec();
|
let spec = MinimalEthSpec::default_spec();
|
||||||
|
|
||||||
let test_root = |root: Hash256| {
|
let test_root = |root: Hash256| {
|
||||||
let mut block = BeaconBlock::empty(&spec);
|
let mut block = BeaconBlock::empty(&spec);
|
||||||
@ -122,7 +130,7 @@ mod tests {
|
|||||||
fn chain_without_skips() {
|
fn chain_without_skips() {
|
||||||
let n: usize = 10;
|
let n: usize = 10;
|
||||||
let store = MemoryStore::open();
|
let store = MemoryStore::open();
|
||||||
let spec = FewValidatorsEthSpec::spec();
|
let spec = MinimalEthSpec::default_spec();
|
||||||
|
|
||||||
let slots: Vec<usize> = (0..n).collect();
|
let slots: Vec<usize> = (0..n).collect();
|
||||||
let blocks_and_roots = build_chain(&store, &slots, &spec);
|
let blocks_and_roots = build_chain(&store, &slots, &spec);
|
||||||
@ -146,7 +154,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn chain_with_skips() {
|
fn chain_with_skips() {
|
||||||
let store = MemoryStore::open();
|
let store = MemoryStore::open();
|
||||||
let spec = FewValidatorsEthSpec::spec();
|
let spec = MinimalEthSpec::default_spec();
|
||||||
|
|
||||||
let slots = vec![0, 1, 2, 5];
|
let slots = vec![0, 1, 2, 5];
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
extern crate rocksdb;
|
extern crate rocksdb;
|
||||||
|
|
||||||
// use super::stores::COLUMNS;
|
|
||||||
use super::{ClientDB, DBError, DBValue};
|
use super::{ClientDB, DBError, DBValue};
|
||||||
use rocksdb::Error as RocksError;
|
use rocksdb::Error as RocksError;
|
||||||
use rocksdb::{Options, DB};
|
use rocksdb::{Options, DB};
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use crate::*;
|
use crate::*;
|
||||||
use ssz::{Decode, Encode};
|
use ssz::{Decode, Encode};
|
||||||
|
|
||||||
|
mod beacon_state;
|
||||||
|
|
||||||
impl StoreItem for BeaconBlock {
|
impl StoreItem for BeaconBlock {
|
||||||
fn db_column() -> DBColumn {
|
fn db_column() -> DBColumn {
|
||||||
DBColumn::BeaconBlock
|
DBColumn::BeaconBlock
|
||||||
@ -14,17 +16,3 @@ impl StoreItem for BeaconBlock {
|
|||||||
Self::from_ssz_bytes(bytes).map_err(Into::into)
|
Self::from_ssz_bytes(bytes).map_err(Into::into)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: EthSpec> StoreItem for BeaconState<T> {
|
|
||||||
fn db_column() -> DBColumn {
|
|
||||||
DBColumn::BeaconState
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_store_bytes(&self) -> Vec<u8> {
|
|
||||||
self.as_ssz_bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_store_bytes(bytes: &mut [u8]) -> Result<Self, Error> {
|
|
||||||
Self::from_ssz_bytes(bytes).map_err(Into::into)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
64
beacon_node/store/src/impls/beacon_state.rs
Normal file
64
beacon_node/store/src/impls/beacon_state.rs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
use crate::*;
|
||||||
|
use ssz::{Decode, DecodeError, Encode};
|
||||||
|
use ssz_derive::{Decode, Encode};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use types::beacon_state::{CommitteeCache, CACHED_EPOCHS};
|
||||||
|
|
||||||
|
/// A container for storing `BeaconState` components.
|
||||||
|
#[derive(Encode, Decode)]
|
||||||
|
struct StorageContainer {
|
||||||
|
state_bytes: Vec<u8>,
|
||||||
|
committee_caches_bytes: Vec<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StorageContainer {
|
||||||
|
/// Create a new instance for storing a `BeaconState`.
|
||||||
|
pub fn new<T: EthSpec>(state: &BeaconState<T>) -> Self {
|
||||||
|
let mut committee_caches_bytes = vec![];
|
||||||
|
|
||||||
|
for cache in state.committee_caches[..].iter() {
|
||||||
|
committee_caches_bytes.push(cache.as_ssz_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
state_bytes: state.as_ssz_bytes(),
|
||||||
|
committee_caches_bytes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: EthSpec> TryInto<BeaconState<T>> for StorageContainer {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<BeaconState<T>, Error> {
|
||||||
|
let mut state: BeaconState<T> = BeaconState::from_ssz_bytes(&self.state_bytes)?;
|
||||||
|
|
||||||
|
for i in 0..CACHED_EPOCHS {
|
||||||
|
let bytes = &self.committee_caches_bytes.get(i).ok_or_else(|| {
|
||||||
|
Error::SszDecodeError(DecodeError::BytesInvalid(
|
||||||
|
"Insufficient committees for BeaconState".to_string(),
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
state.committee_caches[i] = CommitteeCache::from_ssz_bytes(bytes)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: EthSpec> StoreItem for BeaconState<T> {
|
||||||
|
fn db_column() -> DBColumn {
|
||||||
|
DBColumn::BeaconState
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_store_bytes(&self) -> Vec<u8> {
|
||||||
|
let container = StorageContainer::new(self);
|
||||||
|
container.as_ssz_bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_store_bytes(bytes: &mut [u8]) -> Result<Self, Error> {
|
||||||
|
let container = StorageContainer::from_ssz_bytes(bytes)?;
|
||||||
|
container.try_into()
|
||||||
|
}
|
||||||
|
}
|
@ -5,10 +5,14 @@ use leveldb::database::Database;
|
|||||||
use leveldb::error::Error as LevelDBError;
|
use leveldb::error::Error as LevelDBError;
|
||||||
use leveldb::options::{Options, ReadOptions, WriteOptions};
|
use leveldb::options::{Options, ReadOptions, WriteOptions};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// A wrapped leveldb database.
|
/// A wrapped leveldb database.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct LevelDB {
|
pub struct LevelDB {
|
||||||
db: Database<BytesKey>,
|
// Note: this `Arc` is only included because of an artificial constraint by gRPC. Hopefully we
|
||||||
|
// can remove this one day.
|
||||||
|
db: Arc<Database<BytesKey>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LevelDB {
|
impl LevelDB {
|
||||||
@ -18,7 +22,7 @@ impl LevelDB {
|
|||||||
|
|
||||||
options.create_if_missing = true;
|
options.create_if_missing = true;
|
||||||
|
|
||||||
let db = Database::open(path, options)?;
|
let db = Arc::new(Database::open(path, options)?);
|
||||||
|
|
||||||
Ok(Self { db })
|
Ok(Self { db })
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,23 @@
|
|||||||
use super::{Error, Store};
|
use super::{Error, Store};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
type DBHashMap = HashMap<Vec<u8>, Vec<u8>>;
|
type DBHashMap = HashMap<Vec<u8>, Vec<u8>>;
|
||||||
|
|
||||||
/// A thread-safe `HashMap` wrapper.
|
/// A thread-safe `HashMap` wrapper.
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct MemoryStore {
|
pub struct MemoryStore {
|
||||||
db: RwLock<DBHashMap>,
|
// Note: this `Arc` is only included because of an artificial constraint by gRPC. Hopefully we
|
||||||
|
// can remove this one day.
|
||||||
|
db: Arc<RwLock<DBHashMap>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemoryStore {
|
impl MemoryStore {
|
||||||
/// Create a new, empty database.
|
/// Create a new, empty database.
|
||||||
pub fn open() -> Self {
|
pub fn open() -> Self {
|
||||||
Self {
|
Self {
|
||||||
db: RwLock::new(HashMap::new()),
|
db: Arc::new(RwLock::new(HashMap::new())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,10 @@ version = "0.1.0"
|
|||||||
authors = ["Age Manning <Age@AgeManning.com>"]
|
authors = ["Age Manning <Age@AgeManning.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "benches"
|
||||||
|
harness = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
store = { path = "../../beacon_node/store" }
|
store = { path = "../../beacon_node/store" }
|
||||||
ssz = { path = "../utils/ssz" }
|
ssz = { path = "../utils/ssz" }
|
||||||
@ -12,6 +16,7 @@ log = "0.4.6"
|
|||||||
bit-vec = "0.5.0"
|
bit-vec = "0.5.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
criterion = "0.2"
|
||||||
hex = "0.3.2"
|
hex = "0.3.2"
|
||||||
yaml-rust = "0.4.2"
|
yaml-rust = "0.4.2"
|
||||||
bls = { path = "../utils/bls" }
|
bls = { path = "../utils/bls" }
|
||||||
|
75
eth2/fork_choice/benches/benches.rs
Normal file
75
eth2/fork_choice/benches/benches.rs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
use criterion::Criterion;
|
||||||
|
use criterion::{criterion_group, criterion_main, Benchmark};
|
||||||
|
use fork_choice::{test_utils::TestingForkChoiceBuilder, ForkChoice, OptimizedLMDGhost};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use store::MemoryStore;
|
||||||
|
use types::{ChainSpec, EthSpec, MainnetEthSpec};
|
||||||
|
|
||||||
|
pub type TestedForkChoice<T, U> = OptimizedLMDGhost<T, U>;
|
||||||
|
pub type TestedEthSpec = MainnetEthSpec;
|
||||||
|
|
||||||
|
/// Helper function to setup a builder and spec.
|
||||||
|
fn setup(
|
||||||
|
validator_count: usize,
|
||||||
|
chain_length: usize,
|
||||||
|
) -> (
|
||||||
|
TestingForkChoiceBuilder<MemoryStore, TestedEthSpec>,
|
||||||
|
ChainSpec,
|
||||||
|
) {
|
||||||
|
let store = MemoryStore::open();
|
||||||
|
let builder: TestingForkChoiceBuilder<MemoryStore, TestedEthSpec> =
|
||||||
|
TestingForkChoiceBuilder::new(validator_count, chain_length, Arc::new(store));
|
||||||
|
let spec = TestedEthSpec::default_spec();
|
||||||
|
|
||||||
|
(builder, spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Benches adding blocks to fork_choice.
|
||||||
|
fn add_block(c: &mut Criterion) {
|
||||||
|
let validator_count = 16;
|
||||||
|
let chain_length = 100;
|
||||||
|
|
||||||
|
let (builder, spec) = setup(validator_count, chain_length);
|
||||||
|
|
||||||
|
c.bench(
|
||||||
|
&format!("{}_blocks", chain_length),
|
||||||
|
Benchmark::new("add_blocks", move |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
let mut fc = builder.build::<TestedForkChoice<MemoryStore, TestedEthSpec>>();
|
||||||
|
for (root, block) in builder.chain.iter().skip(1) {
|
||||||
|
fc.add_block(block, root, &spec).unwrap();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.sample_size(10),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Benches fork choice head finding.
|
||||||
|
fn find_head(c: &mut Criterion) {
|
||||||
|
let validator_count = 16;
|
||||||
|
let chain_length = 64 * 2;
|
||||||
|
|
||||||
|
let (builder, spec) = setup(validator_count, chain_length);
|
||||||
|
|
||||||
|
let mut fc = builder.build::<TestedForkChoice<MemoryStore, TestedEthSpec>>();
|
||||||
|
for (root, block) in builder.chain.iter().skip(1) {
|
||||||
|
fc.add_block(block, root, &spec).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let head_root = builder.chain.last().unwrap().0;
|
||||||
|
for i in 0..validator_count {
|
||||||
|
fc.add_attestation(i as u64, &head_root, &spec).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
c.bench(
|
||||||
|
&format!("{}_blocks", chain_length),
|
||||||
|
Benchmark::new("find_head", move |b| {
|
||||||
|
b.iter(|| fc.find_head(&builder.genesis_root(), &spec).unwrap())
|
||||||
|
})
|
||||||
|
.sample_size(10),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, add_block, find_head);
|
||||||
|
criterion_main!(benches);
|
40
eth2/fork_choice/examples/example.rs
Normal file
40
eth2/fork_choice/examples/example.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
use fork_choice::{test_utils::TestingForkChoiceBuilder, ForkChoice, OptimizedLMDGhost};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use store::{MemoryStore, Store};
|
||||||
|
use types::{BeaconBlock, ChainSpec, EthSpec, Hash256, MainnetEthSpec};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let validator_count = 16;
|
||||||
|
let chain_length = 100;
|
||||||
|
let repetitions = 50;
|
||||||
|
|
||||||
|
let store = MemoryStore::open();
|
||||||
|
let builder: TestingForkChoiceBuilder<MemoryStore, MainnetEthSpec> =
|
||||||
|
TestingForkChoiceBuilder::new(validator_count, chain_length, Arc::new(store));
|
||||||
|
|
||||||
|
let fork_choosers: Vec<OptimizedLMDGhost<MemoryStore, MainnetEthSpec>> = (0..repetitions)
|
||||||
|
.into_iter()
|
||||||
|
.map(|_| builder.build())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let spec = &MainnetEthSpec::default_spec();
|
||||||
|
|
||||||
|
println!("Running {} times...", repetitions);
|
||||||
|
for fc in fork_choosers {
|
||||||
|
do_thing(fc, &builder.chain, builder.genesis_root(), spec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
fn do_thing<F: ForkChoice<S>, S: Store>(
|
||||||
|
mut fc: F,
|
||||||
|
chain: &[(Hash256, BeaconBlock)],
|
||||||
|
genesis_root: Hash256,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) {
|
||||||
|
for (root, block) in chain.iter().skip(1) {
|
||||||
|
fc.add_block(block, root, spec).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let _head = fc.find_head(&genesis_root, spec).unwrap();
|
||||||
|
}
|
@ -48,18 +48,6 @@ pub struct BitwiseLMDGhost<T, E> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Store, E: EthSpec> BitwiseLMDGhost<T, E> {
|
impl<T: Store, E: EthSpec> BitwiseLMDGhost<T, E> {
|
||||||
pub fn new(store: Arc<T>) -> Self {
|
|
||||||
BitwiseLMDGhost {
|
|
||||||
cache: HashMap::new(),
|
|
||||||
ancestors: vec![HashMap::new(); 16],
|
|
||||||
latest_attestation_targets: HashMap::new(),
|
|
||||||
children: HashMap::new(),
|
|
||||||
max_known_height: SlotHeight::new(0),
|
|
||||||
store,
|
|
||||||
_phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finds the latest votes weighted by validator balance. Returns a hashmap of block_hash to
|
/// Finds the latest votes weighted by validator balance. Returns a hashmap of block_hash to
|
||||||
/// weighted votes.
|
/// weighted votes.
|
||||||
pub fn get_latest_votes(
|
pub fn get_latest_votes(
|
||||||
@ -80,13 +68,11 @@ impl<T: Store, E: EthSpec> BitwiseLMDGhost<T, E> {
|
|||||||
.ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?;
|
.ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?;
|
||||||
|
|
||||||
let active_validator_indices =
|
let active_validator_indices =
|
||||||
current_state.get_active_validator_indices(block_slot.epoch(spec.slots_per_epoch));
|
current_state.get_active_validator_indices(block_slot.epoch(E::slots_per_epoch()));
|
||||||
|
|
||||||
for index in active_validator_indices {
|
for index in active_validator_indices {
|
||||||
let balance = std::cmp::min(
|
let balance = std::cmp::min(current_state.balances[index], spec.max_effective_balance)
|
||||||
current_state.validator_balances[index],
|
/ spec.effective_balance_increment;
|
||||||
spec.max_deposit_amount,
|
|
||||||
) / spec.fork_choice_balance_increment;
|
|
||||||
if balance > 0 {
|
if balance > 0 {
|
||||||
if let Some(target) = self.latest_attestation_targets.get(&(index as u64)) {
|
if let Some(target) = self.latest_attestation_targets.get(&(index as u64)) {
|
||||||
*latest_votes.entry(*target).or_insert_with(|| 0) += balance;
|
*latest_votes.entry(*target).or_insert_with(|| 0) += balance;
|
||||||
@ -132,12 +118,12 @@ impl<T: Store, E: EthSpec> BitwiseLMDGhost<T, E> {
|
|||||||
|
|
||||||
// not in the cache recursively search for ancestors using a log-lookup
|
// not in the cache recursively search for ancestors using a log-lookup
|
||||||
if let Some(ancestor) = {
|
if let Some(ancestor) = {
|
||||||
let ancestor_lookup = self.ancestors
|
let ancestor_lookup = *self.ancestors
|
||||||
[log2_int((block_height - target_height - 1u64).as_u64()) as usize]
|
[log2_int((block_height - target_height - 1u64).as_u64()) as usize]
|
||||||
.get(&block_hash)
|
.get(&block_hash)
|
||||||
//TODO: Panic if we can't lookup and fork choice fails
|
//TODO: Panic if we can't lookup and fork choice fails
|
||||||
.expect("All blocks should be added to the ancestor log lookup table");
|
.expect("All blocks should be added to the ancestor log lookup table");
|
||||||
self.get_ancestor(*ancestor_lookup, target_height, &spec)
|
self.get_ancestor(ancestor_lookup, target_height, &spec)
|
||||||
} {
|
} {
|
||||||
// add the result to the cache
|
// add the result to the cache
|
||||||
self.cache.insert(cache_key, ancestor);
|
self.cache.insert(cache_key, ancestor);
|
||||||
@ -163,7 +149,7 @@ impl<T: Store, E: EthSpec> BitwiseLMDGhost<T, E> {
|
|||||||
// these have already been weighted by balance
|
// these have already been weighted by balance
|
||||||
for (hash, votes) in latest_votes.iter() {
|
for (hash, votes) in latest_votes.iter() {
|
||||||
if let Some(ancestor) = self.get_ancestor(*hash, block_height, spec) {
|
if let Some(ancestor) = self.get_ancestor(*hash, block_height, spec) {
|
||||||
let current_vote_value = current_votes.get(&ancestor).unwrap_or_else(|| &0);
|
let current_vote_value = *current_votes.get(&ancestor).unwrap_or_else(|| &0);
|
||||||
current_votes.insert(ancestor, current_vote_value + *votes);
|
current_votes.insert(ancestor, current_vote_value + *votes);
|
||||||
total_vote_count += votes;
|
total_vote_count += votes;
|
||||||
}
|
}
|
||||||
@ -229,7 +215,19 @@ impl<T: Store, E: EthSpec> BitwiseLMDGhost<T, E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Store, E: EthSpec> ForkChoice for BitwiseLMDGhost<T, E> {
|
impl<T: Store, E: EthSpec> ForkChoice<T> for BitwiseLMDGhost<T, E> {
|
||||||
|
fn new(store: Arc<T>) -> Self {
|
||||||
|
BitwiseLMDGhost {
|
||||||
|
cache: HashMap::new(),
|
||||||
|
ancestors: vec![HashMap::new(); 16],
|
||||||
|
latest_attestation_targets: HashMap::new(),
|
||||||
|
children: HashMap::new(),
|
||||||
|
max_known_height: SlotHeight::new(0),
|
||||||
|
store,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn add_block(
|
fn add_block(
|
||||||
&mut self,
|
&mut self,
|
||||||
block: &BeaconBlock,
|
block: &BeaconBlock,
|
||||||
|
@ -20,9 +20,9 @@ pub mod bitwise_lmd_ghost;
|
|||||||
pub mod longest_chain;
|
pub mod longest_chain;
|
||||||
pub mod optimized_lmd_ghost;
|
pub mod optimized_lmd_ghost;
|
||||||
pub mod slow_lmd_ghost;
|
pub mod slow_lmd_ghost;
|
||||||
|
pub mod test_utils;
|
||||||
|
|
||||||
// use store::stores::BeaconBlockAtSlotError;
|
use std::sync::Arc;
|
||||||
// use store::DBError;
|
|
||||||
use store::Error as DBError;
|
use store::Error as DBError;
|
||||||
use types::{BeaconBlock, ChainSpec, Hash256};
|
use types::{BeaconBlock, ChainSpec, Hash256};
|
||||||
|
|
||||||
@ -34,7 +34,10 @@ pub use slow_lmd_ghost::SlowLMDGhost;
|
|||||||
/// Defines the interface for Fork Choices. Each Fork choice will define their own data structures
|
/// 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.
|
/// which can be built in block processing through the `add_block` and `add_attestation` functions.
|
||||||
/// The main fork choice algorithm is specified in `find_head
|
/// The main fork choice algorithm is specified in `find_head
|
||||||
pub trait ForkChoice: Send + Sync {
|
pub trait ForkChoice<T>: Send + Sync {
|
||||||
|
/// Create a new `ForkChoice` which reads from `store`.
|
||||||
|
fn new(store: Arc<T>) -> Self;
|
||||||
|
|
||||||
/// Called when a block has been added. Allows generic block-level data structures to be
|
/// Called when a block has been added. Allows generic block-level data structures to be
|
||||||
/// built for a given fork-choice.
|
/// built for a given fork-choice.
|
||||||
fn add_block(
|
fn add_block(
|
||||||
@ -78,22 +81,6 @@ impl From<DBError> for ForkChoiceError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
impl From<BeaconBlockAtSlotError> for ForkChoiceError {
|
|
||||||
fn from(e: BeaconBlockAtSlotError) -> ForkChoiceError {
|
|
||||||
match e {
|
|
||||||
BeaconBlockAtSlotError::UnknownBeaconBlock(hash) => {
|
|
||||||
ForkChoiceError::MissingBeaconBlock(hash)
|
|
||||||
}
|
|
||||||
BeaconBlockAtSlotError::InvalidBeaconBlock(hash) => {
|
|
||||||
ForkChoiceError::MissingBeaconBlock(hash)
|
|
||||||
}
|
|
||||||
BeaconBlockAtSlotError::DBError(string) => ForkChoiceError::StorageError(string),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// Fork choice options that are currently implemented.
|
/// Fork choice options that are currently implemented.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ForkChoiceAlgorithm {
|
pub enum ForkChoiceAlgorithm {
|
||||||
|
@ -10,16 +10,14 @@ pub struct LongestChain<T> {
|
|||||||
store: Arc<T>,
|
store: Arc<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Store> LongestChain<T> {
|
impl<T: Store> ForkChoice<T> for LongestChain<T> {
|
||||||
pub fn new(store: Arc<T>) -> Self {
|
fn new(store: Arc<T>) -> Self {
|
||||||
LongestChain {
|
LongestChain {
|
||||||
head_block_hashes: Vec::new(),
|
head_block_hashes: Vec::new(),
|
||||||
store,
|
store,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Store> ForkChoice for LongestChain<T> {
|
|
||||||
fn add_block(
|
fn add_block(
|
||||||
&mut self,
|
&mut self,
|
||||||
block: &BeaconBlock,
|
block: &BeaconBlock,
|
||||||
|
@ -48,18 +48,6 @@ pub struct OptimizedLMDGhost<T, E> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Store, E: EthSpec> OptimizedLMDGhost<T, E> {
|
impl<T: Store, E: EthSpec> OptimizedLMDGhost<T, E> {
|
||||||
pub fn new(store: Arc<T>) -> Self {
|
|
||||||
OptimizedLMDGhost {
|
|
||||||
cache: HashMap::new(),
|
|
||||||
ancestors: vec![HashMap::new(); 16],
|
|
||||||
latest_attestation_targets: HashMap::new(),
|
|
||||||
children: HashMap::new(),
|
|
||||||
max_known_height: SlotHeight::new(0),
|
|
||||||
store,
|
|
||||||
_phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finds the latest votes weighted by validator balance. Returns a hashmap of block_hash to
|
/// Finds the latest votes weighted by validator balance. Returns a hashmap of block_hash to
|
||||||
/// weighted votes.
|
/// weighted votes.
|
||||||
pub fn get_latest_votes(
|
pub fn get_latest_votes(
|
||||||
@ -80,13 +68,11 @@ impl<T: Store, E: EthSpec> OptimizedLMDGhost<T, E> {
|
|||||||
.ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?;
|
.ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?;
|
||||||
|
|
||||||
let active_validator_indices =
|
let active_validator_indices =
|
||||||
current_state.get_active_validator_indices(block_slot.epoch(spec.slots_per_epoch));
|
current_state.get_active_validator_indices(block_slot.epoch(E::slots_per_epoch()));
|
||||||
|
|
||||||
for index in active_validator_indices {
|
for index in active_validator_indices {
|
||||||
let balance = std::cmp::min(
|
let balance = std::cmp::min(current_state.balances[index], spec.max_effective_balance)
|
||||||
current_state.validator_balances[index],
|
/ spec.effective_balance_increment;
|
||||||
spec.max_deposit_amount,
|
|
||||||
) / spec.fork_choice_balance_increment;
|
|
||||||
if balance > 0 {
|
if balance > 0 {
|
||||||
if let Some(target) = self.latest_attestation_targets.get(&(index as u64)) {
|
if let Some(target) = self.latest_attestation_targets.get(&(index as u64)) {
|
||||||
*latest_votes.entry(*target).or_insert_with(|| 0) += balance;
|
*latest_votes.entry(*target).or_insert_with(|| 0) += balance;
|
||||||
@ -132,12 +118,12 @@ impl<T: Store, E: EthSpec> OptimizedLMDGhost<T, E> {
|
|||||||
|
|
||||||
// not in the cache recursively search for ancestors using a log-lookup
|
// not in the cache recursively search for ancestors using a log-lookup
|
||||||
if let Some(ancestor) = {
|
if let Some(ancestor) = {
|
||||||
let ancestor_lookup = self.ancestors
|
let ancestor_lookup = *self.ancestors
|
||||||
[log2_int((block_height - target_height - 1u64).as_u64()) as usize]
|
[log2_int((block_height - target_height - 1u64).as_u64()) as usize]
|
||||||
.get(&block_hash)
|
.get(&block_hash)
|
||||||
//TODO: Panic if we can't lookup and fork choice fails
|
//TODO: Panic if we can't lookup and fork choice fails
|
||||||
.expect("All blocks should be added to the ancestor log lookup table");
|
.expect("All blocks should be added to the ancestor log lookup table");
|
||||||
self.get_ancestor(*ancestor_lookup, target_height, &spec)
|
self.get_ancestor(ancestor_lookup, target_height, &spec)
|
||||||
} {
|
} {
|
||||||
// add the result to the cache
|
// add the result to the cache
|
||||||
self.cache.insert(cache_key, ancestor);
|
self.cache.insert(cache_key, ancestor);
|
||||||
@ -163,7 +149,7 @@ impl<T: Store, E: EthSpec> OptimizedLMDGhost<T, E> {
|
|||||||
// these have already been weighted by balance
|
// these have already been weighted by balance
|
||||||
for (hash, votes) in latest_votes.iter() {
|
for (hash, votes) in latest_votes.iter() {
|
||||||
if let Some(ancestor) = self.get_ancestor(*hash, block_height, spec) {
|
if let Some(ancestor) = self.get_ancestor(*hash, block_height, spec) {
|
||||||
let current_vote_value = current_votes.get(&ancestor).unwrap_or_else(|| &0);
|
let current_vote_value = *current_votes.get(&ancestor).unwrap_or_else(|| &0);
|
||||||
current_votes.insert(ancestor, current_vote_value + *votes);
|
current_votes.insert(ancestor, current_vote_value + *votes);
|
||||||
total_vote_count += votes;
|
total_vote_count += votes;
|
||||||
}
|
}
|
||||||
@ -200,7 +186,19 @@ impl<T: Store, E: EthSpec> OptimizedLMDGhost<T, E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Store, E: EthSpec> ForkChoice for OptimizedLMDGhost<T, E> {
|
impl<T: Store, E: EthSpec> ForkChoice<T> for OptimizedLMDGhost<T, E> {
|
||||||
|
fn new(store: Arc<T>) -> Self {
|
||||||
|
OptimizedLMDGhost {
|
||||||
|
cache: HashMap::new(),
|
||||||
|
ancestors: vec![HashMap::new(); 16],
|
||||||
|
latest_attestation_targets: HashMap::new(),
|
||||||
|
children: HashMap::new(),
|
||||||
|
max_known_height: SlotHeight::new(0),
|
||||||
|
store,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn add_block(
|
fn add_block(
|
||||||
&mut self,
|
&mut self,
|
||||||
block: &BeaconBlock,
|
block: &BeaconBlock,
|
||||||
|
@ -20,15 +20,6 @@ pub struct SlowLMDGhost<T, E> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Store, E: EthSpec> SlowLMDGhost<T, E> {
|
impl<T: Store, E: EthSpec> SlowLMDGhost<T, E> {
|
||||||
pub fn new(store: Arc<T>) -> Self {
|
|
||||||
SlowLMDGhost {
|
|
||||||
latest_attestation_targets: HashMap::new(),
|
|
||||||
children: HashMap::new(),
|
|
||||||
store,
|
|
||||||
_phantom: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finds the latest votes weighted by validator balance. Returns a hashmap of block_hash to
|
/// Finds the latest votes weighted by validator balance. Returns a hashmap of block_hash to
|
||||||
/// weighted votes.
|
/// weighted votes.
|
||||||
pub fn get_latest_votes(
|
pub fn get_latest_votes(
|
||||||
@ -49,13 +40,11 @@ impl<T: Store, E: EthSpec> SlowLMDGhost<T, E> {
|
|||||||
.ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?;
|
.ok_or_else(|| ForkChoiceError::MissingBeaconState(*state_root))?;
|
||||||
|
|
||||||
let active_validator_indices =
|
let active_validator_indices =
|
||||||
current_state.get_active_validator_indices(block_slot.epoch(spec.slots_per_epoch));
|
current_state.get_active_validator_indices(block_slot.epoch(E::slots_per_epoch()));
|
||||||
|
|
||||||
for index in active_validator_indices {
|
for index in active_validator_indices {
|
||||||
let balance = std::cmp::min(
|
let balance = std::cmp::min(current_state.balances[index], spec.max_effective_balance)
|
||||||
current_state.validator_balances[index],
|
/ spec.effective_balance_increment;
|
||||||
spec.max_deposit_amount,
|
|
||||||
) / spec.fork_choice_balance_increment;
|
|
||||||
if balance > 0 {
|
if balance > 0 {
|
||||||
if let Some(target) = self.latest_attestation_targets.get(&(index as u64)) {
|
if let Some(target) = self.latest_attestation_targets.get(&(index as u64)) {
|
||||||
*latest_votes.entry(*target).or_insert_with(|| 0) += balance;
|
*latest_votes.entry(*target).or_insert_with(|| 0) += balance;
|
||||||
@ -94,7 +83,16 @@ impl<T: Store, E: EthSpec> SlowLMDGhost<T, E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Store, E: EthSpec> ForkChoice for SlowLMDGhost<T, E> {
|
impl<T: Store, E: EthSpec> ForkChoice<T> for SlowLMDGhost<T, E> {
|
||||||
|
fn new(store: Arc<T>) -> Self {
|
||||||
|
SlowLMDGhost {
|
||||||
|
latest_attestation_targets: HashMap::new(),
|
||||||
|
children: HashMap::new(),
|
||||||
|
store,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Process when a block is added
|
/// Process when a block is added
|
||||||
fn add_block(
|
fn add_block(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
91
eth2/fork_choice/src/test_utils.rs
Normal file
91
eth2/fork_choice/src/test_utils.rs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
use crate::ForkChoice;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use store::Store;
|
||||||
|
use types::{
|
||||||
|
test_utils::{SeedableRng, TestRandom, TestingBeaconStateBuilder, XorShiftRng},
|
||||||
|
BeaconBlock, BeaconState, EthSpec, Hash256, Keypair, MainnetEthSpec,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Creates a chain of blocks and produces `ForkChoice` instances with pre-filled stores.
|
||||||
|
pub struct TestingForkChoiceBuilder<S, E> {
|
||||||
|
store: Arc<S>,
|
||||||
|
pub chain: Vec<(Hash256, BeaconBlock)>,
|
||||||
|
_phantom: PhantomData<E>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Store, E: EthSpec> TestingForkChoiceBuilder<S, E> {
|
||||||
|
pub fn new(validator_count: usize, chain_length: usize, store: Arc<S>) -> Self {
|
||||||
|
let chain =
|
||||||
|
get_chain_of_blocks::<MainnetEthSpec, S>(chain_length, validator_count, store.clone());
|
||||||
|
|
||||||
|
Self {
|
||||||
|
store,
|
||||||
|
chain,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn genesis_root(&self) -> Hash256 {
|
||||||
|
self.chain[0].0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a new `ForkChoice` instance with a chain stored in it's `Store`.
|
||||||
|
pub fn build<F: ForkChoice<S>>(&self) -> F {
|
||||||
|
F::new(self.store.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_state<T: EthSpec>(validator_count: usize) -> BeaconState<T> {
|
||||||
|
let spec = T::default_spec();
|
||||||
|
|
||||||
|
let builder: TestingBeaconStateBuilder<T> =
|
||||||
|
TestingBeaconStateBuilder::from_single_keypair(validator_count, &Keypair::random(), &spec);
|
||||||
|
let (state, _keypairs) = builder.build();
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates a chain of blocks of length `len`.
|
||||||
|
///
|
||||||
|
/// Creates a `BeaconState` for the block and stores it in `Store`, along with the block.
|
||||||
|
///
|
||||||
|
/// Returns the chain of blocks.
|
||||||
|
fn get_chain_of_blocks<T: EthSpec, U: Store>(
|
||||||
|
len: usize,
|
||||||
|
validator_count: usize,
|
||||||
|
store: Arc<U>,
|
||||||
|
) -> Vec<(Hash256, BeaconBlock)> {
|
||||||
|
let spec = T::default_spec();
|
||||||
|
let mut blocks_and_roots: Vec<(Hash256, BeaconBlock)> = vec![];
|
||||||
|
let mut unique_hashes = (0..).map(Hash256::from);
|
||||||
|
let mut random_block = BeaconBlock::random_for_test(&mut XorShiftRng::from_seed([42; 16]));
|
||||||
|
random_block.previous_block_root = Hash256::zero();
|
||||||
|
let beacon_state = get_state::<T>(validator_count);
|
||||||
|
|
||||||
|
for i in 0..len {
|
||||||
|
let slot = spec.genesis_slot + i as u64;
|
||||||
|
|
||||||
|
// Generate and store the state.
|
||||||
|
let mut state = beacon_state.clone();
|
||||||
|
state.slot = slot;
|
||||||
|
let state_root = unique_hashes.next().unwrap();
|
||||||
|
store.put(&state_root, &state).unwrap();
|
||||||
|
|
||||||
|
// Generate the block.
|
||||||
|
let mut block = random_block.clone();
|
||||||
|
block.slot = slot;
|
||||||
|
block.state_root = state_root;
|
||||||
|
|
||||||
|
// Chain all the blocks to their parents.
|
||||||
|
if i > 0 {
|
||||||
|
block.previous_block_root = blocks_and_roots[i - 1].0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the block.
|
||||||
|
let block_root = unique_hashes.next().unwrap();
|
||||||
|
store.put(&block_root, &block).unwrap();
|
||||||
|
blocks_and_roots.push((block_root, block));
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks_and_roots
|
||||||
|
}
|
@ -1,20 +1,17 @@
|
|||||||
#![cfg(not(debug_assertions))]
|
#![cfg(not(debug_assertions))]
|
||||||
// Tests the available fork-choice algorithms
|
/// Tests the available fork-choice algorithms
|
||||||
|
|
||||||
pub use beacon_chain::BeaconChain;
|
pub use beacon_chain::BeaconChain;
|
||||||
use bls::Signature;
|
use bls::Signature;
|
||||||
use store::MemoryStore;
|
use store::MemoryStore;
|
||||||
use store::Store;
|
use store::Store;
|
||||||
// use env_logger::{Builder, Env};
|
// use env_logger::{Builder, Env};
|
||||||
use fork_choice::{
|
use fork_choice::{BitwiseLMDGhost, ForkChoice, LongestChain, OptimizedLMDGhost, SlowLMDGhost};
|
||||||
BitwiseLMDGhost, ForkChoice, ForkChoiceAlgorithm, LongestChain, OptimizedLMDGhost, SlowLMDGhost,
|
|
||||||
};
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{fs::File, io::prelude::*, path::PathBuf};
|
use std::{fs::File, io::prelude::*, path::PathBuf};
|
||||||
use types::test_utils::TestingBeaconStateBuilder;
|
use types::test_utils::TestingBeaconStateBuilder;
|
||||||
use types::{
|
use types::{
|
||||||
BeaconBlock, BeaconBlockBody, Eth1Data, EthSpec, FoundationEthSpec, Hash256, Keypair, Slot,
|
BeaconBlock, BeaconBlockBody, Eth1Data, EthSpec, Hash256, Keypair, MainnetEthSpec, Slot,
|
||||||
};
|
};
|
||||||
use yaml_rust::yaml;
|
use yaml_rust::yaml;
|
||||||
|
|
||||||
@ -25,8 +22,7 @@ fn test_optimized_lmd_ghost() {
|
|||||||
// set up logging
|
// set up logging
|
||||||
// Builder::from_env(Env::default().default_filter_or("trace")).init();
|
// Builder::from_env(Env::default().default_filter_or("trace")).init();
|
||||||
|
|
||||||
test_yaml_vectors(
|
test_yaml_vectors::<OptimizedLMDGhost<MemoryStore, MainnetEthSpec>>(
|
||||||
ForkChoiceAlgorithm::OptimizedLMDGhost,
|
|
||||||
"tests/lmd_ghost_test_vectors.yaml",
|
"tests/lmd_ghost_test_vectors.yaml",
|
||||||
100,
|
100,
|
||||||
);
|
);
|
||||||
@ -37,8 +33,7 @@ fn test_bitwise_lmd_ghost() {
|
|||||||
// set up logging
|
// set up logging
|
||||||
//Builder::from_env(Env::default().default_filter_or("trace")).init();
|
//Builder::from_env(Env::default().default_filter_or("trace")).init();
|
||||||
|
|
||||||
test_yaml_vectors(
|
test_yaml_vectors::<BitwiseLMDGhost<MemoryStore, MainnetEthSpec>>(
|
||||||
ForkChoiceAlgorithm::BitwiseLMDGhost,
|
|
||||||
"tests/bitwise_lmd_ghost_test_vectors.yaml",
|
"tests/bitwise_lmd_ghost_test_vectors.yaml",
|
||||||
100,
|
100,
|
||||||
);
|
);
|
||||||
@ -46,8 +41,7 @@ fn test_bitwise_lmd_ghost() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_slow_lmd_ghost() {
|
fn test_slow_lmd_ghost() {
|
||||||
test_yaml_vectors(
|
test_yaml_vectors::<SlowLMDGhost<MemoryStore, MainnetEthSpec>>(
|
||||||
ForkChoiceAlgorithm::SlowLMDGhost,
|
|
||||||
"tests/lmd_ghost_test_vectors.yaml",
|
"tests/lmd_ghost_test_vectors.yaml",
|
||||||
100,
|
100,
|
||||||
);
|
);
|
||||||
@ -55,16 +49,11 @@ fn test_slow_lmd_ghost() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_longest_chain() {
|
fn test_longest_chain() {
|
||||||
test_yaml_vectors(
|
test_yaml_vectors::<LongestChain<MemoryStore>>("tests/longest_chain_test_vectors.yaml", 100);
|
||||||
ForkChoiceAlgorithm::LongestChain,
|
|
||||||
"tests/longest_chain_test_vectors.yaml",
|
|
||||||
100,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// run a generic test over given YAML test vectors
|
// run a generic test over given YAML test vectors
|
||||||
fn test_yaml_vectors(
|
fn test_yaml_vectors<T: ForkChoice<MemoryStore>>(
|
||||||
fork_choice_algo: ForkChoiceAlgorithm,
|
|
||||||
yaml_file_path: &str,
|
yaml_file_path: &str,
|
||||||
emulated_validators: usize, // the number of validators used to give weights.
|
emulated_validators: usize, // the number of validators used to give weights.
|
||||||
) {
|
) {
|
||||||
@ -72,9 +61,10 @@ fn test_yaml_vectors(
|
|||||||
let test_cases = load_test_cases_from_yaml(yaml_file_path);
|
let test_cases = load_test_cases_from_yaml(yaml_file_path);
|
||||||
|
|
||||||
// default vars
|
// default vars
|
||||||
let spec = FoundationEthSpec::spec();
|
let spec = MainnetEthSpec::default_spec();
|
||||||
let zero_hash = Hash256::zero();
|
let zero_hash = Hash256::zero();
|
||||||
let eth1_data = Eth1Data {
|
let eth1_data = Eth1Data {
|
||||||
|
deposit_count: 0,
|
||||||
deposit_root: zero_hash.clone(),
|
deposit_root: zero_hash.clone(),
|
||||||
block_hash: zero_hash.clone(),
|
block_hash: zero_hash.clone(),
|
||||||
};
|
};
|
||||||
@ -83,6 +73,7 @@ fn test_yaml_vectors(
|
|||||||
let body = BeaconBlockBody {
|
let body = BeaconBlockBody {
|
||||||
eth1_data,
|
eth1_data,
|
||||||
randao_reveal,
|
randao_reveal,
|
||||||
|
graffiti: [0; 32],
|
||||||
proposer_slashings: vec![],
|
proposer_slashings: vec![],
|
||||||
attester_slashings: vec![],
|
attester_slashings: vec![],
|
||||||
attestations: vec![],
|
attestations: vec![],
|
||||||
@ -94,8 +85,7 @@ fn test_yaml_vectors(
|
|||||||
// process the tests
|
// process the tests
|
||||||
for test_case in test_cases {
|
for test_case in test_cases {
|
||||||
// setup a fresh test
|
// setup a fresh test
|
||||||
let (mut fork_choice, store, state_root) =
|
let (mut fork_choice, store, state_root) = setup_inital_state::<T>(emulated_validators);
|
||||||
setup_inital_state(&fork_choice_algo, emulated_validators);
|
|
||||||
|
|
||||||
// keep a hashmap of block_id's to block_hashes (random hashes to abstract block_id)
|
// keep a hashmap of block_id's to block_hashes (random hashes to abstract block_id)
|
||||||
//let mut block_id_map: HashMap<String, Hash256> = HashMap::new();
|
//let mut block_id_map: HashMap<String, Hash256> = HashMap::new();
|
||||||
@ -204,35 +194,19 @@ fn load_test_cases_from_yaml(file_path: &str) -> Vec<yaml_rust::Yaml> {
|
|||||||
doc["test_cases"].as_vec().unwrap().clone()
|
doc["test_cases"].as_vec().unwrap().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialise a single validator and state. All blocks will reference this state root.
|
fn setup_inital_state<T>(
|
||||||
fn setup_inital_state(
|
// fork_choice_algo: &ForkChoiceAlgorithm,
|
||||||
fork_choice_algo: &ForkChoiceAlgorithm,
|
|
||||||
num_validators: usize,
|
num_validators: usize,
|
||||||
) -> (Box<ForkChoice>, Arc<MemoryStore>, Hash256) {
|
) -> (T, Arc<MemoryStore>, Hash256)
|
||||||
|
where
|
||||||
|
T: ForkChoice<MemoryStore>,
|
||||||
|
{
|
||||||
let store = Arc::new(MemoryStore::open());
|
let store = Arc::new(MemoryStore::open());
|
||||||
|
|
||||||
// the fork choice instantiation
|
let fork_choice = ForkChoice::new(store.clone());
|
||||||
let fork_choice: Box<ForkChoice> = match fork_choice_algo {
|
let spec = MainnetEthSpec::default_spec();
|
||||||
ForkChoiceAlgorithm::OptimizedLMDGhost => {
|
|
||||||
let f: OptimizedLMDGhost<MemoryStore, FoundationEthSpec> =
|
|
||||||
OptimizedLMDGhost::new(store.clone());
|
|
||||||
Box::new(f)
|
|
||||||
}
|
|
||||||
ForkChoiceAlgorithm::BitwiseLMDGhost => {
|
|
||||||
let f: BitwiseLMDGhost<MemoryStore, FoundationEthSpec> =
|
|
||||||
BitwiseLMDGhost::new(store.clone());
|
|
||||||
Box::new(f)
|
|
||||||
}
|
|
||||||
ForkChoiceAlgorithm::SlowLMDGhost => {
|
|
||||||
let f: SlowLMDGhost<MemoryStore, FoundationEthSpec> = SlowLMDGhost::new(store.clone());
|
|
||||||
Box::new(f)
|
|
||||||
}
|
|
||||||
ForkChoiceAlgorithm::LongestChain => Box::new(LongestChain::new(store.clone())),
|
|
||||||
};
|
|
||||||
|
|
||||||
let spec = FoundationEthSpec::spec();
|
let mut state_builder: TestingBeaconStateBuilder<MainnetEthSpec> =
|
||||||
|
|
||||||
let mut state_builder: TestingBeaconStateBuilder<FoundationEthSpec> =
|
|
||||||
TestingBeaconStateBuilder::from_single_keypair(num_validators, &Keypair::random(), &spec);
|
TestingBeaconStateBuilder::from_single_keypair(num_validators, &Keypair::random(), &spec);
|
||||||
state_builder.build_caches(&spec).unwrap();
|
state_builder.build_caches(&spec).unwrap();
|
||||||
let (state, _keypairs) = state_builder.build();
|
let (state, _keypairs) = state_builder.build();
|
||||||
|
@ -6,10 +6,12 @@ use state_processing::per_block_processing::errors::{
|
|||||||
AttestationValidationError, AttesterSlashingValidationError, DepositValidationError,
|
AttestationValidationError, AttesterSlashingValidationError, DepositValidationError,
|
||||||
ExitValidationError, ProposerSlashingValidationError, TransferValidationError,
|
ExitValidationError, ProposerSlashingValidationError, TransferValidationError,
|
||||||
};
|
};
|
||||||
|
#[cfg(not(test))]
|
||||||
|
use state_processing::per_block_processing::verify_deposit_merkle_proof;
|
||||||
use state_processing::per_block_processing::{
|
use state_processing::per_block_processing::{
|
||||||
gather_attester_slashing_indices_modular, validate_attestation,
|
get_slashable_indices_modular, validate_attestation,
|
||||||
validate_attestation_time_independent_only, verify_attester_slashing, verify_deposit,
|
validate_attestation_time_independent_only, verify_attester_slashing, verify_exit,
|
||||||
verify_exit, verify_exit_time_independent_only, verify_proposer_slashing, verify_transfer,
|
verify_exit_time_independent_only, verify_proposer_slashing, verify_transfer,
|
||||||
verify_transfer_time_independent_only,
|
verify_transfer_time_independent_only,
|
||||||
};
|
};
|
||||||
use std::collections::{btree_map::Entry, hash_map, BTreeMap, HashMap, HashSet};
|
use std::collections::{btree_map::Entry, hash_map, BTreeMap, HashMap, HashSet};
|
||||||
@ -20,11 +22,6 @@ use types::{
|
|||||||
EthSpec, ProposerSlashing, Transfer, Validator, VoluntaryExit,
|
EthSpec, ProposerSlashing, Transfer, Validator, VoluntaryExit,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
const VERIFY_DEPOSIT_PROOFS: bool = false;
|
|
||||||
#[cfg(not(test))]
|
|
||||||
const VERIFY_DEPOSIT_PROOFS: bool = false; // TODO: enable this
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct OperationPool<T: EthSpec + Default> {
|
pub struct OperationPool<T: EthSpec + Default> {
|
||||||
/// Map from attestation ID (see below) to vectors of attestations.
|
/// Map from attestation ID (see below) to vectors of attestations.
|
||||||
@ -60,7 +57,7 @@ impl AttestationId {
|
|||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut bytes = ssz_encode(attestation);
|
let mut bytes = ssz_encode(attestation);
|
||||||
let epoch = attestation.slot.epoch(spec.slots_per_epoch);
|
let epoch = attestation.target_epoch;
|
||||||
bytes.extend_from_slice(&AttestationId::compute_domain_bytes(epoch, state, spec));
|
bytes.extend_from_slice(&AttestationId::compute_domain_bytes(epoch, state, spec));
|
||||||
AttestationId(bytes)
|
AttestationId(bytes)
|
||||||
}
|
}
|
||||||
@ -85,19 +82,13 @@ impl AttestationId {
|
|||||||
/// receive for including it in a block.
|
/// receive for including it in a block.
|
||||||
// TODO: this could be optimised with a map from validator index to whether that validator has
|
// TODO: this could be optimised with a map from validator index to whether that validator has
|
||||||
// attested in each of the current and previous epochs. Currently quadractic in number of validators.
|
// attested in each of the current and previous epochs. Currently quadractic in number of validators.
|
||||||
fn attestation_score<T: EthSpec>(
|
fn attestation_score<T: EthSpec>(attestation: &Attestation, state: &BeaconState<T>) -> usize {
|
||||||
attestation: &Attestation,
|
|
||||||
state: &BeaconState<T>,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> usize {
|
|
||||||
// Bitfield of validators whose attestations are new/fresh.
|
// Bitfield of validators whose attestations are new/fresh.
|
||||||
let mut new_validators = attestation.aggregation_bitfield.clone();
|
let mut new_validators = attestation.aggregation_bitfield.clone();
|
||||||
|
|
||||||
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
|
let state_attestations = if attestation.data.target_epoch == state.current_epoch() {
|
||||||
|
|
||||||
let state_attestations = if attestation_epoch == state.current_epoch(spec) {
|
|
||||||
&state.current_epoch_attestations
|
&state.current_epoch_attestations
|
||||||
} else if attestation_epoch == state.previous_epoch(spec) {
|
} else if attestation.data.target_epoch == state.previous_epoch() {
|
||||||
&state.previous_epoch_attestations
|
&state.previous_epoch_attestations
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
@ -181,8 +172,8 @@ impl<T: EthSpec> OperationPool<T> {
|
|||||||
/// Get a list of attestations for inclusion in a block.
|
/// Get a list of attestations for inclusion in a block.
|
||||||
pub fn get_attestations(&self, state: &BeaconState<T>, spec: &ChainSpec) -> Vec<Attestation> {
|
pub fn get_attestations(&self, state: &BeaconState<T>, spec: &ChainSpec) -> Vec<Attestation> {
|
||||||
// Attestations for the current fork, which may be from the current or previous epoch.
|
// Attestations for the current fork, which may be from the current or previous epoch.
|
||||||
let prev_epoch = state.previous_epoch(spec);
|
let prev_epoch = state.previous_epoch();
|
||||||
let current_epoch = state.current_epoch(spec);
|
let current_epoch = state.current_epoch();
|
||||||
let prev_domain_bytes = AttestationId::compute_domain_bytes(prev_epoch, state, spec);
|
let prev_domain_bytes = AttestationId::compute_domain_bytes(prev_epoch, state, spec);
|
||||||
let curr_domain_bytes = AttestationId::compute_domain_bytes(current_epoch, state, spec);
|
let curr_domain_bytes = AttestationId::compute_domain_bytes(current_epoch, state, spec);
|
||||||
self.attestations
|
self.attestations
|
||||||
@ -199,7 +190,7 @@ impl<T: EthSpec> OperationPool<T> {
|
|||||||
.filter(|attestation| validate_attestation(state, attestation, spec).is_ok())
|
.filter(|attestation| validate_attestation(state, attestation, spec).is_ok())
|
||||||
// Scored by the number of new attestations they introduce (descending)
|
// Scored by the number of new attestations they introduce (descending)
|
||||||
// TODO: need to consider attestations introduced in THIS block
|
// TODO: need to consider attestations introduced in THIS block
|
||||||
.map(|att| (att, attestation_score(att, state, spec)))
|
.map(|att| (att, attestation_score(att, state)))
|
||||||
// Don't include any useless attestations (score 0)
|
// Don't include any useless attestations (score 0)
|
||||||
.filter(|&(_, score)| score != 0)
|
.filter(|&(_, score)| score != 0)
|
||||||
.sorted_by_key(|&(_, score)| std::cmp::Reverse(score))
|
.sorted_by_key(|&(_, score)| std::cmp::Reverse(score))
|
||||||
@ -211,15 +202,16 @@ impl<T: EthSpec> OperationPool<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Remove attestations which are too old to be included in a block.
|
/// Remove attestations which are too old to be included in a block.
|
||||||
// TODO: we could probably prune other attestations here:
|
pub fn prune_attestations(&self, finalized_state: &BeaconState<T>) {
|
||||||
// - ones that are completely covered by attestations included in the state
|
// We know we can include an attestation if:
|
||||||
// - maybe ones invalidated by the confirmation of one fork over another
|
// state.slot <= attestation_slot + SLOTS_PER_EPOCH
|
||||||
pub fn prune_attestations(&self, finalized_state: &BeaconState<T>, spec: &ChainSpec) {
|
// We approximate this check using the attestation's epoch, to avoid computing
|
||||||
|
// the slot or relying on the committee cache of the finalized state.
|
||||||
self.attestations.write().retain(|_, attestations| {
|
self.attestations.write().retain(|_, attestations| {
|
||||||
// All the attestations in this bucket have the same data, so we only need to
|
// All the attestations in this bucket have the same data, so we only need to
|
||||||
// check the first one.
|
// check the first one.
|
||||||
attestations.first().map_or(false, |att| {
|
attestations.first().map_or(false, |att| {
|
||||||
finalized_state.slot < att.data.slot + spec.slots_per_epoch
|
finalized_state.current_epoch() <= att.data.target_epoch + 1
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -227,6 +219,7 @@ impl<T: EthSpec> OperationPool<T> {
|
|||||||
/// Add a deposit to the pool.
|
/// Add a deposit to the pool.
|
||||||
///
|
///
|
||||||
/// No two distinct deposits should be added with the same index.
|
/// No two distinct deposits should be added with the same index.
|
||||||
|
#[cfg_attr(test, allow(unused_variables))]
|
||||||
pub fn insert_deposit(
|
pub fn insert_deposit(
|
||||||
&self,
|
&self,
|
||||||
deposit: Deposit,
|
deposit: Deposit,
|
||||||
@ -237,7 +230,9 @@ impl<T: EthSpec> OperationPool<T> {
|
|||||||
|
|
||||||
match self.deposits.write().entry(deposit.index) {
|
match self.deposits.write().entry(deposit.index) {
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
verify_deposit(state, &deposit, VERIFY_DEPOSIT_PROOFS, spec)?;
|
// TODO: fix tests to generate valid merkle proofs
|
||||||
|
#[cfg(not(test))]
|
||||||
|
verify_deposit_merkle_proof(state, &deposit, spec)?;
|
||||||
entry.insert(deposit);
|
entry.insert(deposit);
|
||||||
Ok(Fresh)
|
Ok(Fresh)
|
||||||
}
|
}
|
||||||
@ -245,7 +240,9 @@ impl<T: EthSpec> OperationPool<T> {
|
|||||||
if entry.get() == &deposit {
|
if entry.get() == &deposit {
|
||||||
Ok(Duplicate)
|
Ok(Duplicate)
|
||||||
} else {
|
} else {
|
||||||
verify_deposit(state, &deposit, VERIFY_DEPOSIT_PROOFS, spec)?;
|
// TODO: fix tests to generate valid merkle proofs
|
||||||
|
#[cfg(not(test))]
|
||||||
|
verify_deposit_merkle_proof(state, &deposit, spec)?;
|
||||||
Ok(Replaced(Box::new(entry.insert(deposit))))
|
Ok(Replaced(Box::new(entry.insert(deposit))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -256,6 +253,7 @@ impl<T: EthSpec> OperationPool<T> {
|
|||||||
///
|
///
|
||||||
/// Take at most the maximum number of deposits, beginning from the current deposit index.
|
/// Take at most the maximum number of deposits, beginning from the current deposit index.
|
||||||
pub fn get_deposits(&self, state: &BeaconState<T>, spec: &ChainSpec) -> Vec<Deposit> {
|
pub fn get_deposits(&self, state: &BeaconState<T>, spec: &ChainSpec) -> Vec<Deposit> {
|
||||||
|
// TODO: might want to re-check the Merkle proof to account for Eth1 forking
|
||||||
let start_idx = state.deposit_index;
|
let start_idx = state.deposit_index;
|
||||||
(start_idx..start_idx + spec.max_deposits)
|
(start_idx..start_idx + spec.max_deposits)
|
||||||
.map(|idx| self.deposits.read().get(&idx).cloned())
|
.map(|idx| self.deposits.read().get(&idx).cloned())
|
||||||
@ -300,8 +298,8 @@ impl<T: EthSpec> OperationPool<T> {
|
|||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> (AttestationId, AttestationId) {
|
) -> (AttestationId, AttestationId) {
|
||||||
(
|
(
|
||||||
AttestationId::from_data(&slashing.slashable_attestation_1.data, state, spec),
|
AttestationId::from_data(&slashing.attestation_1.data, state, spec),
|
||||||
AttestationId::from_data(&slashing.slashable_attestation_2.data, state, spec),
|
AttestationId::from_data(&slashing.attestation_2.data, state, spec),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,12 +354,10 @@ impl<T: EthSpec> OperationPool<T> {
|
|||||||
})
|
})
|
||||||
.filter(|(_, slashing)| {
|
.filter(|(_, slashing)| {
|
||||||
// Take all slashings that will slash 1 or more validators.
|
// Take all slashings that will slash 1 or more validators.
|
||||||
let slashed_validators = gather_attester_slashing_indices_modular(
|
let slashed_validators =
|
||||||
state,
|
get_slashable_indices_modular(state, slashing, |index, validator| {
|
||||||
slashing,
|
validator.slashed || to_be_slashed.contains(&index)
|
||||||
|index, validator| validator.slashed || to_be_slashed.contains(&index),
|
});
|
||||||
spec,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Extend the `to_be_slashed` set so subsequent iterations don't try to include
|
// Extend the `to_be_slashed` set so subsequent iterations don't try to include
|
||||||
// useless slashings.
|
// useless slashings.
|
||||||
@ -380,12 +376,11 @@ impl<T: EthSpec> OperationPool<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Prune proposer slashings for all slashed or withdrawn validators.
|
/// Prune proposer slashings for all slashed or withdrawn validators.
|
||||||
pub fn prune_proposer_slashings(&self, finalized_state: &BeaconState<T>, spec: &ChainSpec) {
|
pub fn prune_proposer_slashings(&self, finalized_state: &BeaconState<T>) {
|
||||||
prune_validator_hash_map(
|
prune_validator_hash_map(
|
||||||
&mut self.proposer_slashings.write(),
|
&mut self.proposer_slashings.write(),
|
||||||
|validator| {
|
|validator| {
|
||||||
validator.slashed
|
validator.slashed || validator.is_withdrawable_at(finalized_state.current_epoch())
|
||||||
|| validator.is_withdrawable_at(finalized_state.current_epoch(spec))
|
|
||||||
},
|
},
|
||||||
finalized_state,
|
finalized_state,
|
||||||
);
|
);
|
||||||
@ -396,13 +391,11 @@ impl<T: EthSpec> OperationPool<T> {
|
|||||||
pub fn prune_attester_slashings(&self, finalized_state: &BeaconState<T>, spec: &ChainSpec) {
|
pub fn prune_attester_slashings(&self, finalized_state: &BeaconState<T>, spec: &ChainSpec) {
|
||||||
self.attester_slashings.write().retain(|id, slashing| {
|
self.attester_slashings.write().retain(|id, slashing| {
|
||||||
let fork_ok = &Self::attester_slashing_id(slashing, finalized_state, spec) == id;
|
let fork_ok = &Self::attester_slashing_id(slashing, finalized_state, spec) == id;
|
||||||
let curr_epoch = finalized_state.current_epoch(spec);
|
let curr_epoch = finalized_state.current_epoch();
|
||||||
let slashing_ok = gather_attester_slashing_indices_modular(
|
let slashing_ok =
|
||||||
finalized_state,
|
get_slashable_indices_modular(finalized_state, slashing, |_, validator| {
|
||||||
slashing,
|
validator.slashed || validator.is_withdrawable_at(curr_epoch)
|
||||||
|_, validator| validator.slashed || validator.is_withdrawable_at(curr_epoch),
|
})
|
||||||
spec,
|
|
||||||
)
|
|
||||||
.is_ok();
|
.is_ok();
|
||||||
fork_ok && slashing_ok
|
fork_ok && slashing_ok
|
||||||
});
|
});
|
||||||
@ -436,10 +429,10 @@ impl<T: EthSpec> OperationPool<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Prune if validator has already exited at the last finalized state.
|
/// Prune if validator has already exited at the last finalized state.
|
||||||
pub fn prune_voluntary_exits(&self, finalized_state: &BeaconState<T>, spec: &ChainSpec) {
|
pub fn prune_voluntary_exits(&self, finalized_state: &BeaconState<T>) {
|
||||||
prune_validator_hash_map(
|
prune_validator_hash_map(
|
||||||
&mut self.voluntary_exits.write(),
|
&mut self.voluntary_exits.write(),
|
||||||
|validator| validator.is_exited_at(finalized_state.current_epoch(spec)),
|
|validator| validator.is_exited_at(finalized_state.current_epoch()),
|
||||||
finalized_state,
|
finalized_state,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -482,11 +475,11 @@ impl<T: EthSpec> OperationPool<T> {
|
|||||||
|
|
||||||
/// Prune all types of transactions given the latest finalized state.
|
/// Prune all types of transactions given the latest finalized state.
|
||||||
pub fn prune_all(&self, finalized_state: &BeaconState<T>, spec: &ChainSpec) {
|
pub fn prune_all(&self, finalized_state: &BeaconState<T>, spec: &ChainSpec) {
|
||||||
self.prune_attestations(finalized_state, spec);
|
self.prune_attestations(finalized_state);
|
||||||
self.prune_deposits(finalized_state);
|
self.prune_deposits(finalized_state);
|
||||||
self.prune_proposer_slashings(finalized_state, spec);
|
self.prune_proposer_slashings(finalized_state);
|
||||||
self.prune_attester_slashings(finalized_state, spec);
|
self.prune_attester_slashings(finalized_state, spec);
|
||||||
self.prune_voluntary_exits(finalized_state, spec);
|
self.prune_voluntary_exits(finalized_state);
|
||||||
self.prune_transfers(finalized_state);
|
self.prune_transfers(finalized_state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -566,8 +559,8 @@ mod tests {
|
|||||||
let rng = &mut XorShiftRng::from_seed([42; 16]);
|
let rng = &mut XorShiftRng::from_seed([42; 16]);
|
||||||
let (ref spec, ref state) = test_state(rng);
|
let (ref spec, ref state) = test_state(rng);
|
||||||
let op_pool = OperationPool::new();
|
let op_pool = OperationPool::new();
|
||||||
let deposit1 = make_deposit(rng, state, spec);
|
let deposit1 = make_deposit(rng);
|
||||||
let mut deposit2 = make_deposit(rng, state, spec);
|
let mut deposit2 = make_deposit(rng);
|
||||||
deposit2.index = deposit1.index;
|
deposit2.index = deposit1.index;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -595,7 +588,7 @@ mod tests {
|
|||||||
let offset = 1;
|
let offset = 1;
|
||||||
assert!(offset <= extra);
|
assert!(offset <= extra);
|
||||||
|
|
||||||
let deposits = dummy_deposits(rng, &state, &spec, start, max_deposits + extra);
|
let deposits = dummy_deposits(rng, start, max_deposits + extra);
|
||||||
|
|
||||||
for deposit in &deposits {
|
for deposit in &deposits {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -626,8 +619,8 @@ mod tests {
|
|||||||
let gap = 25;
|
let gap = 25;
|
||||||
let start2 = start1 + count + gap;
|
let start2 = start1 + count + gap;
|
||||||
|
|
||||||
let deposits1 = dummy_deposits(rng, &state, &spec, start1, count);
|
let deposits1 = dummy_deposits(rng, start1, count);
|
||||||
let deposits2 = dummy_deposits(rng, &state, &spec, start2, count);
|
let deposits2 = dummy_deposits(rng, start2, count);
|
||||||
|
|
||||||
for d in deposits1.into_iter().chain(deposits2) {
|
for d in deposits1.into_iter().chain(deposits2) {
|
||||||
assert!(op_pool.insert_deposit(d, &state, &spec).is_ok());
|
assert!(op_pool.insert_deposit(d, &state, &spec).is_ok());
|
||||||
@ -665,38 +658,14 @@ mod tests {
|
|||||||
assert_eq!(op_pool.num_deposits(), 0);
|
assert_eq!(op_pool.num_deposits(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a random deposit (with a valid proof of posession)
|
// Create a random deposit
|
||||||
fn make_deposit<T: EthSpec>(
|
fn make_deposit(rng: &mut XorShiftRng) -> Deposit {
|
||||||
rng: &mut XorShiftRng,
|
Deposit::random_for_test(rng)
|
||||||
state: &BeaconState<T>,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Deposit {
|
|
||||||
let keypair = Keypair::random();
|
|
||||||
let mut deposit = Deposit::random_for_test(rng);
|
|
||||||
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,
|
|
||||||
state.slot.epoch(spec.slots_per_epoch),
|
|
||||||
&state.fork,
|
|
||||||
spec,
|
|
||||||
);
|
|
||||||
deposit.deposit_data.deposit_input = deposit_input;
|
|
||||||
deposit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create `count` dummy deposits with sequential deposit IDs beginning from `start`.
|
// Create `count` dummy deposits with sequential deposit IDs beginning from `start`.
|
||||||
fn dummy_deposits<T: EthSpec>(
|
fn dummy_deposits(rng: &mut XorShiftRng, start: u64, count: u64) -> Vec<Deposit> {
|
||||||
rng: &mut XorShiftRng,
|
let proto_deposit = make_deposit(rng);
|
||||||
state: &BeaconState<T>,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
start: u64,
|
|
||||||
count: u64,
|
|
||||||
) -> Vec<Deposit> {
|
|
||||||
let proto_deposit = make_deposit(rng, state, spec);
|
|
||||||
(start..start + count)
|
(start..start + count)
|
||||||
.map(|index| {
|
.map(|index| {
|
||||||
let mut deposit = proto_deposit.clone();
|
let mut deposit = proto_deposit.clone();
|
||||||
@ -706,12 +675,12 @@ mod tests {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_state(rng: &mut XorShiftRng) -> (ChainSpec, BeaconState<FoundationEthSpec>) {
|
fn test_state(rng: &mut XorShiftRng) -> (ChainSpec, BeaconState<MainnetEthSpec>) {
|
||||||
let spec = FoundationEthSpec::spec();
|
let spec = MainnetEthSpec::default_spec();
|
||||||
|
|
||||||
let mut state = BeaconState::random_for_test(rng);
|
let mut state = BeaconState::random_for_test(rng);
|
||||||
|
|
||||||
state.fork = Fork::genesis(&spec);
|
state.fork = Fork::genesis(MainnetEthSpec::genesis_epoch());
|
||||||
|
|
||||||
(spec, state)
|
(spec, state)
|
||||||
}
|
}
|
||||||
@ -723,7 +692,8 @@ mod tests {
|
|||||||
/// Create a signed attestation for use in tests.
|
/// Create a signed attestation for use in tests.
|
||||||
/// Signed by all validators in `committee[signing_range]` and `committee[extra_signer]`.
|
/// Signed by all validators in `committee[signing_range]` and `committee[extra_signer]`.
|
||||||
fn signed_attestation<R: std::slice::SliceIndex<[usize], Output = [usize]>, E: EthSpec>(
|
fn signed_attestation<R: std::slice::SliceIndex<[usize], Output = [usize]>, E: EthSpec>(
|
||||||
committee: &CrosslinkCommittee,
|
committee: &[usize],
|
||||||
|
shard: u64,
|
||||||
keypairs: &[Keypair],
|
keypairs: &[Keypair],
|
||||||
signing_range: R,
|
signing_range: R,
|
||||||
slot: Slot,
|
slot: Slot,
|
||||||
@ -731,18 +701,12 @@ mod tests {
|
|||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
extra_signer: Option<usize>,
|
extra_signer: Option<usize>,
|
||||||
) -> Attestation {
|
) -> Attestation {
|
||||||
let mut builder = TestingAttestationBuilder::new(
|
let mut builder = TestingAttestationBuilder::new(state, committee, slot, shard, spec);
|
||||||
state,
|
let signers = &committee[signing_range];
|
||||||
&committee.committee,
|
|
||||||
slot,
|
|
||||||
committee.shard,
|
|
||||||
spec,
|
|
||||||
);
|
|
||||||
let signers = &committee.committee[signing_range];
|
|
||||||
let committee_keys = signers.iter().map(|&i| &keypairs[i].sk).collect::<Vec<_>>();
|
let committee_keys = signers.iter().map(|&i| &keypairs[i].sk).collect::<Vec<_>>();
|
||||||
builder.sign(signers, &committee_keys, &state.fork, spec);
|
builder.sign(signers, &committee_keys, &state.fork, spec);
|
||||||
extra_signer.map(|c_idx| {
|
extra_signer.map(|c_idx| {
|
||||||
let validator_index = committee.committee[c_idx];
|
let validator_index = committee[c_idx];
|
||||||
builder.sign(
|
builder.sign(
|
||||||
&[validator_index],
|
&[validator_index],
|
||||||
&[&keypairs[validator_index].sk],
|
&[&keypairs[validator_index].sk],
|
||||||
@ -757,63 +721,71 @@ mod tests {
|
|||||||
fn attestation_test_state<E: EthSpec>(
|
fn attestation_test_state<E: EthSpec>(
|
||||||
num_committees: usize,
|
num_committees: usize,
|
||||||
) -> (BeaconState<E>, Vec<Keypair>, ChainSpec) {
|
) -> (BeaconState<E>, Vec<Keypair>, ChainSpec) {
|
||||||
let spec = E::spec();
|
let spec = E::default_spec();
|
||||||
|
|
||||||
let num_validators =
|
let num_validators =
|
||||||
num_committees * (spec.slots_per_epoch * spec.target_committee_size) as usize;
|
num_committees * E::slots_per_epoch() as usize * spec.target_committee_size;
|
||||||
let mut state_builder = TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(
|
let mut state_builder = TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(
|
||||||
num_validators,
|
num_validators,
|
||||||
&spec,
|
&spec,
|
||||||
);
|
);
|
||||||
let slot_offset = 1000 * spec.slots_per_epoch + spec.slots_per_epoch / 2;
|
let slot_offset = 1000 * E::slots_per_epoch() + E::slots_per_epoch() / 2;
|
||||||
let slot = spec.genesis_slot + slot_offset;
|
let slot = spec.genesis_slot + slot_offset;
|
||||||
state_builder.teleport_to_slot(slot, &spec);
|
state_builder.teleport_to_slot(slot);
|
||||||
state_builder.build_caches(&spec).unwrap();
|
state_builder.build_caches(&spec).unwrap();
|
||||||
let (state, keypairs) = state_builder.build();
|
let (state, keypairs) = state_builder.build();
|
||||||
|
|
||||||
(state, keypairs, FoundationEthSpec::spec())
|
(state, keypairs, MainnetEthSpec::default_spec())
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the latest crosslink in the state to match the attestation.
|
|
||||||
fn fake_latest_crosslink<E: EthSpec>(
|
|
||||||
att: &Attestation,
|
|
||||||
state: &mut BeaconState<E>,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) {
|
|
||||||
state.latest_crosslinks[att.data.shard as usize] = Crosslink {
|
|
||||||
crosslink_data_root: att.data.crosslink_data_root,
|
|
||||||
epoch: att.data.slot.epoch(spec.slots_per_epoch),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_attestation_score() {
|
fn test_attestation_score() {
|
||||||
let (ref mut state, ref keypairs, ref spec) =
|
let (ref mut state, ref keypairs, ref spec) =
|
||||||
attestation_test_state::<FoundationEthSpec>(1);
|
attestation_test_state::<MainnetEthSpec>(1);
|
||||||
|
|
||||||
let slot = state.slot - 1;
|
let slot = state.slot - 1;
|
||||||
let committees = state
|
let committees = state
|
||||||
.get_crosslink_committees_at_slot(slot, spec)
|
.get_crosslink_committees_at_slot(slot)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.clone();
|
.into_iter()
|
||||||
|
.map(CrosslinkCommittee::into_owned)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
for committee in committees {
|
for cc in committees {
|
||||||
let att1 = signed_attestation(&committee, keypairs, ..2, slot, state, spec, None);
|
let att1 = signed_attestation(
|
||||||
let att2 = signed_attestation(&committee, keypairs, .., slot, state, spec, None);
|
&cc.committee,
|
||||||
|
cc.shard,
|
||||||
|
keypairs,
|
||||||
|
..2,
|
||||||
|
slot,
|
||||||
|
state,
|
||||||
|
spec,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let att2 = signed_attestation(
|
||||||
|
&cc.committee,
|
||||||
|
cc.shard,
|
||||||
|
keypairs,
|
||||||
|
..,
|
||||||
|
slot,
|
||||||
|
state,
|
||||||
|
spec,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
att1.aggregation_bitfield.num_set_bits(),
|
att1.aggregation_bitfield.num_set_bits(),
|
||||||
attestation_score(&att1, state, spec)
|
attestation_score(&att1, state)
|
||||||
);
|
);
|
||||||
|
|
||||||
state
|
state.current_epoch_attestations.push(PendingAttestation {
|
||||||
.current_epoch_attestations
|
aggregation_bitfield: att1.aggregation_bitfield.clone(),
|
||||||
.push(PendingAttestation::from_attestation(&att1, state.slot));
|
data: att1.data.clone(),
|
||||||
|
inclusion_delay: 0,
|
||||||
|
proposer_index: 0,
|
||||||
|
});
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(cc.committee.len() - 2, attestation_score(&att2, state));
|
||||||
committee.committee.len() - 2,
|
|
||||||
attestation_score(&att2, state, spec)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -821,15 +793,17 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn attestation_aggregation_insert_get_prune() {
|
fn attestation_aggregation_insert_get_prune() {
|
||||||
let (ref mut state, ref keypairs, ref spec) =
|
let (ref mut state, ref keypairs, ref spec) =
|
||||||
attestation_test_state::<FoundationEthSpec>(1);
|
attestation_test_state::<MainnetEthSpec>(1);
|
||||||
|
|
||||||
let op_pool = OperationPool::new();
|
let op_pool = OperationPool::new();
|
||||||
|
|
||||||
let slot = state.slot - 1;
|
let slot = state.slot - 1;
|
||||||
let committees = state
|
let committees = state
|
||||||
.get_crosslink_committees_at_slot(slot, spec)
|
.get_crosslink_committees_at_slot(slot)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.clone();
|
.into_iter()
|
||||||
|
.map(CrosslinkCommittee::into_owned)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
committees.len(),
|
committees.len(),
|
||||||
@ -837,11 +811,12 @@ mod tests {
|
|||||||
"we expect just one committee with this many validators"
|
"we expect just one committee with this many validators"
|
||||||
);
|
);
|
||||||
|
|
||||||
for committee in &committees {
|
for cc in &committees {
|
||||||
let step_size = 2;
|
let step_size = 2;
|
||||||
for i in (0..committee.committee.len()).step_by(step_size) {
|
for i in (0..cc.committee.len()).step_by(step_size) {
|
||||||
let att = signed_attestation(
|
let att = signed_attestation(
|
||||||
committee,
|
&cc.committee,
|
||||||
|
cc.shard,
|
||||||
keypairs,
|
keypairs,
|
||||||
i..i + step_size,
|
i..i + step_size,
|
||||||
slot,
|
slot,
|
||||||
@ -849,7 +824,6 @@ mod tests {
|
|||||||
spec,
|
spec,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
fake_latest_crosslink(&att, state, spec);
|
|
||||||
op_pool.insert_attestation(att, state, spec).unwrap();
|
op_pool.insert_attestation(att, state, spec).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -873,13 +847,13 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Prune attestations shouldn't do anything at this point.
|
// Prune attestations shouldn't do anything at this point.
|
||||||
op_pool.prune_attestations(state, spec);
|
op_pool.prune_attestations(state);
|
||||||
assert_eq!(op_pool.num_attestations(), committees.len());
|
assert_eq!(op_pool.num_attestations(), committees.len());
|
||||||
|
|
||||||
// But once we advance to an epoch after the attestation, it should prune it out of
|
// But once we advance to more than an epoch after the attestation, it should prune it
|
||||||
// existence.
|
// out of existence.
|
||||||
state.slot = slot + spec.slots_per_epoch;
|
state.slot += 2 * MainnetEthSpec::slots_per_epoch();
|
||||||
op_pool.prune_attestations(state, spec);
|
op_pool.prune_attestations(state);
|
||||||
assert_eq!(op_pool.num_attestations(), 0);
|
assert_eq!(op_pool.num_attestations(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -887,19 +861,29 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn attestation_duplicate() {
|
fn attestation_duplicate() {
|
||||||
let (ref mut state, ref keypairs, ref spec) =
|
let (ref mut state, ref keypairs, ref spec) =
|
||||||
attestation_test_state::<FoundationEthSpec>(1);
|
attestation_test_state::<MainnetEthSpec>(1);
|
||||||
|
|
||||||
let op_pool = OperationPool::new();
|
let op_pool = OperationPool::new();
|
||||||
|
|
||||||
let slot = state.slot - 1;
|
let slot = state.slot - 1;
|
||||||
let committees = state
|
let committees = state
|
||||||
.get_crosslink_committees_at_slot(slot, spec)
|
.get_crosslink_committees_at_slot(slot)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.clone();
|
.into_iter()
|
||||||
|
.map(CrosslinkCommittee::into_owned)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
for committee in &committees {
|
for cc in &committees {
|
||||||
let att = signed_attestation(committee, keypairs, .., slot, state, spec, None);
|
let att = signed_attestation(
|
||||||
fake_latest_crosslink(&att, state, spec);
|
&cc.committee,
|
||||||
|
cc.shard,
|
||||||
|
keypairs,
|
||||||
|
..,
|
||||||
|
slot,
|
||||||
|
state,
|
||||||
|
spec,
|
||||||
|
None,
|
||||||
|
);
|
||||||
op_pool
|
op_pool
|
||||||
.insert_attestation(att.clone(), state, spec)
|
.insert_attestation(att.clone(), state, spec)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -914,23 +898,26 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn attestation_pairwise_overlapping() {
|
fn attestation_pairwise_overlapping() {
|
||||||
let (ref mut state, ref keypairs, ref spec) =
|
let (ref mut state, ref keypairs, ref spec) =
|
||||||
attestation_test_state::<FoundationEthSpec>(1);
|
attestation_test_state::<MainnetEthSpec>(1);
|
||||||
|
|
||||||
let op_pool = OperationPool::new();
|
let op_pool = OperationPool::new();
|
||||||
|
|
||||||
let slot = state.slot - 1;
|
let slot = state.slot - 1;
|
||||||
let committees = state
|
let committees = state
|
||||||
.get_crosslink_committees_at_slot(slot, spec)
|
.get_crosslink_committees_at_slot(slot)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.clone();
|
.into_iter()
|
||||||
|
.map(CrosslinkCommittee::into_owned)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let step_size = 2;
|
let step_size = 2;
|
||||||
for committee in &committees {
|
for cc in &committees {
|
||||||
// Create attestations that overlap on `step_size` validators, like:
|
// Create attestations that overlap on `step_size` validators, like:
|
||||||
// {0,1,2,3}, {2,3,4,5}, {4,5,6,7}, ...
|
// {0,1,2,3}, {2,3,4,5}, {4,5,6,7}, ...
|
||||||
for i in (0..committee.committee.len() - step_size).step_by(step_size) {
|
for i in (0..cc.committee.len() - step_size).step_by(step_size) {
|
||||||
let att = signed_attestation(
|
let att = signed_attestation(
|
||||||
committee,
|
&cc.committee,
|
||||||
|
cc.shard,
|
||||||
keypairs,
|
keypairs,
|
||||||
i..i + 2 * step_size,
|
i..i + 2 * step_size,
|
||||||
slot,
|
slot,
|
||||||
@ -938,7 +925,6 @@ mod tests {
|
|||||||
spec,
|
spec,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
fake_latest_crosslink(&att, state, spec);
|
|
||||||
op_pool.insert_attestation(att, state, spec).unwrap();
|
op_pool.insert_attestation(att, state, spec).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -960,23 +946,26 @@ mod tests {
|
|||||||
let big_step_size = 4;
|
let big_step_size = 4;
|
||||||
|
|
||||||
let (ref mut state, ref keypairs, ref spec) =
|
let (ref mut state, ref keypairs, ref spec) =
|
||||||
attestation_test_state::<FoundationEthSpec>(big_step_size);
|
attestation_test_state::<MainnetEthSpec>(big_step_size);
|
||||||
|
|
||||||
let op_pool = OperationPool::new();
|
let op_pool = OperationPool::new();
|
||||||
|
|
||||||
let slot = state.slot - 1;
|
let slot = state.slot - 1;
|
||||||
let committees = state
|
let committees = state
|
||||||
.get_crosslink_committees_at_slot(slot, spec)
|
.get_crosslink_committees_at_slot(slot)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.clone();
|
.into_iter()
|
||||||
|
.map(CrosslinkCommittee::into_owned)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let max_attestations = spec.max_attestations as usize;
|
let max_attestations = spec.max_attestations as usize;
|
||||||
let target_committee_size = spec.target_committee_size as usize;
|
let target_committee_size = spec.target_committee_size as usize;
|
||||||
|
|
||||||
let mut insert_attestations = |committee, step_size| {
|
let insert_attestations = |cc: &OwnedCrosslinkCommittee, step_size| {
|
||||||
for i in (0..target_committee_size).step_by(step_size) {
|
for i in (0..target_committee_size).step_by(step_size) {
|
||||||
let att = signed_attestation(
|
let att = signed_attestation(
|
||||||
committee,
|
&cc.committee,
|
||||||
|
cc.shard,
|
||||||
keypairs,
|
keypairs,
|
||||||
i..i + step_size,
|
i..i + step_size,
|
||||||
slot,
|
slot,
|
||||||
@ -984,7 +973,6 @@ mod tests {
|
|||||||
spec,
|
spec,
|
||||||
if i == 0 { None } else { Some(0) },
|
if i == 0 { None } else { Some(0) },
|
||||||
);
|
);
|
||||||
fake_latest_crosslink(&att, state, spec);
|
|
||||||
op_pool.insert_attestation(att, state, spec).unwrap();
|
op_pool.insert_attestation(att, state, spec).unwrap();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -21,6 +21,7 @@ fnv = "1.0"
|
|||||||
hashing = { path = "../utils/hashing" }
|
hashing = { path = "../utils/hashing" }
|
||||||
int_to_bytes = { path = "../utils/int_to_bytes" }
|
int_to_bytes = { path = "../utils/int_to_bytes" }
|
||||||
integer-sqrt = "0.1"
|
integer-sqrt = "0.1"
|
||||||
|
itertools = "0.8"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
merkle_proof = { path = "../utils/merkle_proof" }
|
merkle_proof = { path = "../utils/merkle_proof" }
|
||||||
ssz = { path = "../utils/ssz" }
|
ssz = { path = "../utils/ssz" }
|
||||||
|
@ -207,12 +207,12 @@ pub fn bench_block_processing(
|
|||||||
let spec = initial_spec.clone();
|
let spec = initial_spec.clone();
|
||||||
c.bench(
|
c.bench(
|
||||||
&format!("{}/block_processing", desc),
|
&format!("{}/block_processing", desc),
|
||||||
Benchmark::new("build_previous_state_epoch_cache", move |b| {
|
Benchmark::new("build_previous_state_committee_cache", move |b| {
|
||||||
b.iter_batched(
|
b.iter_batched(
|
||||||
|| state.clone(),
|
|| state.clone(),
|
||||||
|mut state| {
|
|mut state| {
|
||||||
state
|
state
|
||||||
.build_epoch_cache(RelativeEpoch::Previous, &spec)
|
.build_committee_cache(RelativeEpoch::Previous, &spec)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
state
|
state
|
||||||
},
|
},
|
||||||
@ -227,12 +227,12 @@ pub fn bench_block_processing(
|
|||||||
let spec = initial_spec.clone();
|
let spec = initial_spec.clone();
|
||||||
c.bench(
|
c.bench(
|
||||||
&format!("{}/block_processing", desc),
|
&format!("{}/block_processing", desc),
|
||||||
Benchmark::new("build_current_state_epoch_cache", move |b| {
|
Benchmark::new("build_current_state_committee_cache", move |b| {
|
||||||
b.iter_batched(
|
b.iter_batched(
|
||||||
|| state.clone(),
|
|| state.clone(),
|
||||||
|mut state| {
|
|mut state| {
|
||||||
state
|
state
|
||||||
.build_epoch_cache(RelativeEpoch::Current, &spec)
|
.build_committee_cache(RelativeEpoch::Current, &spec)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
state
|
state
|
||||||
},
|
},
|
||||||
|
@ -17,13 +17,13 @@ pub const SMALL_BENCHING_SAMPLE_SIZE: usize = 10;
|
|||||||
|
|
||||||
/// Run the benchmarking suite on a foundation spec with 16,384 validators.
|
/// 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) {
|
||||||
let spec = ChainSpec::foundation();
|
let spec = ChainSpec::mainnet();
|
||||||
|
|
||||||
let mut builder =
|
let mut builder =
|
||||||
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, &spec);
|
TestingBeaconStateBuilder::from_default_keypairs_file_if_exists(validator_count, &spec);
|
||||||
|
|
||||||
// Set the state to be just before an epoch transition.
|
// Set the state to be just before an epoch transition.
|
||||||
let target_slot = (spec.genesis_epoch + 4).end_slot(spec.slots_per_epoch);
|
let target_slot = (T::genesis_epoch() + 4).end_slot(T::slots_per_epoch());
|
||||||
builder.teleport_to_slot(target_slot, &spec);
|
builder.teleport_to_slot(target_slot, &spec);
|
||||||
|
|
||||||
// Builds all caches; benches will not contain shuffling/committee building times.
|
// Builds all caches; benches will not contain shuffling/committee building times.
|
||||||
@ -38,10 +38,10 @@ pub fn bench_epoch_processing_n_validators(c: &mut Criterion, validator_count: u
|
|||||||
// Assert that the state has an attestations for each committee that is able to include an
|
// Assert that the state has an attestations for each committee that is able to include an
|
||||||
// attestation in the state.
|
// attestation in the state.
|
||||||
let committees_per_epoch = spec.get_epoch_committee_count(validator_count);
|
let committees_per_epoch = spec.get_epoch_committee_count(validator_count);
|
||||||
let committees_per_slot = committees_per_epoch / spec.slots_per_epoch;
|
let committees_per_slot = committees_per_epoch / T::slots_per_epoch();
|
||||||
let previous_epoch_attestations = committees_per_epoch;
|
let previous_epoch_attestations = committees_per_epoch;
|
||||||
let current_epoch_attestations =
|
let current_epoch_attestations =
|
||||||
committees_per_slot * (spec.slots_per_epoch - spec.min_attestation_inclusion_delay);
|
committees_per_slot * (T::slots_per_epoch() - spec.min_attestation_inclusion_delay);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
state.latest_attestations.len() as u64,
|
state.latest_attestations.len() as u64,
|
||||||
previous_epoch_attestations + current_epoch_attestations,
|
previous_epoch_attestations + current_epoch_attestations,
|
||||||
|
@ -25,7 +25,7 @@ pub fn block_processing_worst_case(c: &mut Criterion) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Use the specifications from the Eth2.0 spec.
|
// Use the specifications from the Eth2.0 spec.
|
||||||
let spec = ChainSpec::foundation();
|
let spec = ChainSpec::mainnet();
|
||||||
|
|
||||||
// Create a builder for configuring the block and state for benching.
|
// Create a builder for configuring the block and state for benching.
|
||||||
let mut bench_builder = BlockBenchingBuilder::new(VALIDATOR_COUNT, &spec);
|
let mut bench_builder = BlockBenchingBuilder::new(VALIDATOR_COUNT, &spec);
|
||||||
@ -34,7 +34,7 @@ pub fn block_processing_worst_case(c: &mut Criterion) {
|
|||||||
bench_builder.maximize_block_operations(&spec);
|
bench_builder.maximize_block_operations(&spec);
|
||||||
|
|
||||||
// Set the state and block to be in the last slot of the 4th epoch.
|
// 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);
|
let last_slot_of_epoch = (T::genesis_epoch() + 4).end_slot(T::slots_per_epoch());
|
||||||
bench_builder.set_slot(last_slot_of_epoch, &spec);
|
bench_builder.set_slot(last_slot_of_epoch, &spec);
|
||||||
|
|
||||||
// Build all the state caches so the build times aren't included in the benches.
|
// Build all the state caches so the build times aren't included in the benches.
|
||||||
@ -59,7 +59,7 @@ pub fn block_processing_reasonable_case(c: &mut Criterion) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Use the specifications from the Eth2.0 spec.
|
// Use the specifications from the Eth2.0 spec.
|
||||||
let spec = ChainSpec::foundation();
|
let spec = ChainSpec::mainnet();
|
||||||
|
|
||||||
// Create a builder for configuring the block and state for benching.
|
// Create a builder for configuring the block and state for benching.
|
||||||
let mut bench_builder = BlockBenchingBuilder::new(VALIDATOR_COUNT, &spec);
|
let mut bench_builder = BlockBenchingBuilder::new(VALIDATOR_COUNT, &spec);
|
||||||
@ -67,13 +67,13 @@ pub fn block_processing_reasonable_case(c: &mut Criterion) {
|
|||||||
// Set the number of included operations to what we might expect normally.
|
// Set the number of included operations to what we might expect normally.
|
||||||
bench_builder.num_proposer_slashings = 0;
|
bench_builder.num_proposer_slashings = 0;
|
||||||
bench_builder.num_attester_slashings = 0;
|
bench_builder.num_attester_slashings = 0;
|
||||||
bench_builder.num_attestations = (spec.shard_count / spec.slots_per_epoch) as usize;
|
bench_builder.num_attestations = (spec.shard_count / T::slots_per_epoch()) as usize;
|
||||||
bench_builder.num_deposits = 2;
|
bench_builder.num_deposits = 2;
|
||||||
bench_builder.num_exits = 2;
|
bench_builder.num_exits = 2;
|
||||||
bench_builder.num_transfers = 2;
|
bench_builder.num_transfers = 2;
|
||||||
|
|
||||||
// Set the state and block to be in the last slot of the 4th epoch.
|
// 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);
|
let last_slot_of_epoch = (T::genesis_epoch() + 4).end_slot(T::slots_per_epoch());
|
||||||
bench_builder.set_slot(last_slot_of_epoch, &spec);
|
bench_builder.set_slot(last_slot_of_epoch, &spec);
|
||||||
|
|
||||||
// Build all the state caches so the build times aren't included in the benches.
|
// Build all the state caches so the build times aren't included in the benches.
|
||||||
|
33
eth2/state_processing/src/common/convert_to_indexed.rs
Normal file
33
eth2/state_processing/src/common/convert_to_indexed.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use super::{get_attesting_indices, get_attesting_indices_unsorted};
|
||||||
|
use itertools::{Either, Itertools};
|
||||||
|
use types::*;
|
||||||
|
|
||||||
|
/// Convert `attestation` to (almost) indexed-verifiable form.
|
||||||
|
///
|
||||||
|
/// Spec v0.6.3
|
||||||
|
pub fn convert_to_indexed<T: EthSpec>(
|
||||||
|
state: &BeaconState<T>,
|
||||||
|
attestation: &Attestation,
|
||||||
|
) -> Result<IndexedAttestation, BeaconStateError> {
|
||||||
|
let attesting_indices =
|
||||||
|
get_attesting_indices(state, &attestation.data, &attestation.aggregation_bitfield)?;
|
||||||
|
|
||||||
|
// We verify the custody bitfield by calling `get_attesting_indices_unsorted` and throwing
|
||||||
|
// away the result. This avoids double-sorting - the partition below takes care of the ordering.
|
||||||
|
get_attesting_indices_unsorted(state, &attestation.data, &attestation.custody_bitfield)?;
|
||||||
|
|
||||||
|
let (custody_bit_0_indices, custody_bit_1_indices) =
|
||||||
|
attesting_indices.into_iter().enumerate().partition_map(
|
||||||
|
|(committee_idx, validator_idx)| match attestation.custody_bitfield.get(committee_idx) {
|
||||||
|
Ok(true) => Either::Right(validator_idx as u64),
|
||||||
|
_ => Either::Left(validator_idx as u64),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(IndexedAttestation {
|
||||||
|
custody_bit_0_indices,
|
||||||
|
custody_bit_1_indices,
|
||||||
|
data: attestation.data.clone(),
|
||||||
|
signature: attestation.signature.clone(),
|
||||||
|
})
|
||||||
|
}
|
@ -1,22 +0,0 @@
|
|||||||
use types::{BeaconStateError as Error, *};
|
|
||||||
|
|
||||||
/// Exit the validator of the given `index`.
|
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
pub fn exit_validator<T: EthSpec>(
|
|
||||||
state: &mut BeaconState<T>,
|
|
||||||
validator_index: usize,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
if validator_index >= state.validator_registry.len() {
|
|
||||||
return Err(Error::UnknownValidator);
|
|
||||||
}
|
|
||||||
|
|
||||||
let delayed_epoch = state.get_delayed_activation_exit_epoch(state.current_epoch(spec), spec);
|
|
||||||
|
|
||||||
if state.validator_registry[validator_index].exit_epoch > delayed_epoch {
|
|
||||||
state.validator_registry[validator_index].exit_epoch = delayed_epoch;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
46
eth2/state_processing/src/common/get_attesting_indices.rs
Normal file
46
eth2/state_processing/src/common/get_attesting_indices.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use crate::common::verify_bitfield_length;
|
||||||
|
use types::*;
|
||||||
|
|
||||||
|
/// Returns validator indices which participated in the attestation, sorted by increasing index.
|
||||||
|
///
|
||||||
|
/// Spec v0.6.3
|
||||||
|
pub fn get_attesting_indices<T: EthSpec>(
|
||||||
|
state: &BeaconState<T>,
|
||||||
|
attestation_data: &AttestationData,
|
||||||
|
bitfield: &Bitfield,
|
||||||
|
) -> Result<Vec<usize>, BeaconStateError> {
|
||||||
|
get_attesting_indices_unsorted(state, attestation_data, bitfield).map(|mut indices| {
|
||||||
|
// Fast unstable sort is safe because validator indices are unique
|
||||||
|
indices.sort_unstable();
|
||||||
|
indices
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns validator indices which participated in the attestation, unsorted.
|
||||||
|
///
|
||||||
|
/// Spec v0.6.3
|
||||||
|
pub fn get_attesting_indices_unsorted<T: EthSpec>(
|
||||||
|
state: &BeaconState<T>,
|
||||||
|
attestation_data: &AttestationData,
|
||||||
|
bitfield: &Bitfield,
|
||||||
|
) -> Result<Vec<usize>, BeaconStateError> {
|
||||||
|
let target_relative_epoch =
|
||||||
|
RelativeEpoch::from_epoch(state.current_epoch(), attestation_data.target_epoch)?;
|
||||||
|
|
||||||
|
let committee =
|
||||||
|
state.get_crosslink_committee_for_shard(attestation_data.shard, target_relative_epoch)?;
|
||||||
|
|
||||||
|
if !verify_bitfield_length(&bitfield, committee.committee.len()) {
|
||||||
|
return Err(BeaconStateError::InvalidBitfield);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(committee
|
||||||
|
.committee
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, validator_index)| match bitfield.get(i) {
|
||||||
|
Ok(true) => Some(*validator_index),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
}
|
39
eth2/state_processing/src/common/initiate_validator_exit.rs
Normal file
39
eth2/state_processing/src/common/initiate_validator_exit.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use std::cmp::max;
|
||||||
|
use types::{BeaconStateError as Error, *};
|
||||||
|
|
||||||
|
/// Initiate the exit of the validator of the given `index`.
|
||||||
|
///
|
||||||
|
/// Spec v0.6.3
|
||||||
|
pub fn initiate_validator_exit<T: EthSpec>(
|
||||||
|
state: &mut BeaconState<T>,
|
||||||
|
index: usize,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if index >= state.validator_registry.len() {
|
||||||
|
return Err(Error::UnknownValidator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return if the validator already initiated exit
|
||||||
|
if state.validator_registry[index].exit_epoch != spec.far_future_epoch {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute exit queue epoch
|
||||||
|
let delayed_epoch = state.get_delayed_activation_exit_epoch(state.current_epoch(), spec);
|
||||||
|
let mut exit_queue_epoch = state
|
||||||
|
.exit_cache
|
||||||
|
.max_epoch()
|
||||||
|
.map_or(delayed_epoch, |epoch| max(epoch, delayed_epoch));
|
||||||
|
let exit_queue_churn = state.exit_cache.get_churn_at(exit_queue_epoch);
|
||||||
|
|
||||||
|
if exit_queue_churn >= state.get_churn_limit(spec)? {
|
||||||
|
exit_queue_epoch += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.exit_cache.record_validator_exit(exit_queue_epoch);
|
||||||
|
state.validator_registry[index].exit_epoch = exit_queue_epoch;
|
||||||
|
state.validator_registry[index].withdrawable_epoch =
|
||||||
|
exit_queue_epoch + spec.min_validator_withdrawability_delay;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,7 +1,11 @@
|
|||||||
mod exit_validator;
|
mod convert_to_indexed;
|
||||||
|
mod get_attesting_indices;
|
||||||
|
mod initiate_validator_exit;
|
||||||
mod slash_validator;
|
mod slash_validator;
|
||||||
mod verify_bitfield;
|
mod verify_bitfield;
|
||||||
|
|
||||||
pub use exit_validator::exit_validator;
|
pub use convert_to_indexed::convert_to_indexed;
|
||||||
|
pub use get_attesting_indices::{get_attesting_indices, get_attesting_indices_unsorted};
|
||||||
|
pub use initiate_validator_exit::initiate_validator_exit;
|
||||||
pub use slash_validator::slash_validator;
|
pub use slash_validator::slash_validator;
|
||||||
pub use verify_bitfield::verify_bitfield_length;
|
pub use verify_bitfield::verify_bitfield_length;
|
||||||
|
@ -1,61 +1,45 @@
|
|||||||
use crate::common::exit_validator;
|
use crate::common::initiate_validator_exit;
|
||||||
use types::{BeaconStateError as Error, *};
|
use types::{BeaconStateError as Error, *};
|
||||||
|
|
||||||
/// Slash the validator with index ``index``.
|
/// Slash the validator with index ``index``.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn slash_validator<T: EthSpec>(
|
pub fn slash_validator<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
validator_index: usize,
|
slashed_index: usize,
|
||||||
|
opt_whistleblower_index: Option<usize>,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let current_epoch = state.current_epoch(spec);
|
if slashed_index >= state.validator_registry.len() || slashed_index >= state.balances.len() {
|
||||||
|
|
||||||
if (validator_index >= state.validator_registry.len())
|
|
||||||
| (validator_index >= state.validator_balances.len())
|
|
||||||
{
|
|
||||||
return Err(BeaconStateError::UnknownValidator);
|
return Err(BeaconStateError::UnknownValidator);
|
||||||
}
|
}
|
||||||
|
|
||||||
let validator = &state.validator_registry[validator_index];
|
let current_epoch = state.current_epoch();
|
||||||
|
|
||||||
let effective_balance = state.get_effective_balance(validator_index, spec)?;
|
initiate_validator_exit(state, slashed_index, spec)?;
|
||||||
|
|
||||||
// A validator that is withdrawn cannot be slashed.
|
state.validator_registry[slashed_index].slashed = true;
|
||||||
//
|
state.validator_registry[slashed_index].withdrawable_epoch =
|
||||||
// This constraint will be lifted in Phase 0.
|
current_epoch + Epoch::from(T::latest_slashed_exit_length());
|
||||||
if state.slot
|
let slashed_balance = state.get_effective_balance(slashed_index, spec)?;
|
||||||
>= validator
|
|
||||||
.withdrawable_epoch
|
|
||||||
.start_slot(spec.slots_per_epoch)
|
|
||||||
{
|
|
||||||
return Err(Error::ValidatorIsWithdrawable);
|
|
||||||
}
|
|
||||||
|
|
||||||
exit_validator(state, validator_index, spec)?;
|
|
||||||
|
|
||||||
state.set_slashed_balance(
|
state.set_slashed_balance(
|
||||||
current_epoch,
|
current_epoch,
|
||||||
state.get_slashed_balance(current_epoch)? + effective_balance,
|
state.get_slashed_balance(current_epoch)? + slashed_balance,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let whistleblower_index =
|
let proposer_index =
|
||||||
state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)?;
|
state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)?;
|
||||||
let whistleblower_reward = effective_balance / spec.whistleblower_reward_quotient;
|
let whistleblower_index = opt_whistleblower_index.unwrap_or(proposer_index);
|
||||||
|
let whistleblowing_reward = slashed_balance / spec.whistleblowing_reward_quotient;
|
||||||
|
let proposer_reward = whistleblowing_reward / spec.proposer_reward_quotient;
|
||||||
|
|
||||||
|
safe_add_assign!(state.balances[proposer_index], proposer_reward);
|
||||||
safe_add_assign!(
|
safe_add_assign!(
|
||||||
state.validator_balances[whistleblower_index as usize],
|
state.balances[whistleblower_index],
|
||||||
whistleblower_reward
|
whistleblowing_reward.saturating_sub(proposer_reward)
|
||||||
);
|
);
|
||||||
safe_sub_assign!(
|
safe_sub_assign!(state.balances[slashed_index], whistleblowing_reward);
|
||||||
state.validator_balances[validator_index],
|
|
||||||
whistleblower_reward
|
|
||||||
);
|
|
||||||
|
|
||||||
state.validator_registry[validator_index].slashed = true;
|
|
||||||
|
|
||||||
state.validator_registry[validator_index].withdrawable_epoch =
|
|
||||||
current_epoch + Epoch::from(T::LatestSlashedExitLength::to_usize());
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use types::*;
|
|||||||
///
|
///
|
||||||
/// Is title `verify_bitfield` in spec.
|
/// Is title `verify_bitfield` in spec.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn verify_bitfield_length(bitfield: &Bitfield, committee_size: usize) -> bool {
|
pub fn verify_bitfield_length(bitfield: &Bitfield, committee_size: usize) -> bool {
|
||||||
if bitfield.num_bytes() != ((committee_size + 7) / 8) {
|
if bitfield.num_bytes() != ((committee_size + 7) / 8) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -9,8 +9,8 @@ pub enum GenesisError {
|
|||||||
|
|
||||||
/// Returns the genesis `BeaconState`
|
/// Returns the genesis `BeaconState`
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn get_genesis_state<T: EthSpec>(
|
pub fn get_genesis_beacon_state<T: EthSpec>(
|
||||||
genesis_validator_deposits: &[Deposit],
|
genesis_validator_deposits: &[Deposit],
|
||||||
genesis_time: u64,
|
genesis_time: u64,
|
||||||
genesis_eth1_data: Eth1Data,
|
genesis_eth1_data: Eth1Data,
|
||||||
@ -23,25 +23,23 @@ pub fn get_genesis_state<T: EthSpec>(
|
|||||||
process_deposits(&mut state, genesis_validator_deposits, spec)?;
|
process_deposits(&mut state, genesis_validator_deposits, spec)?;
|
||||||
|
|
||||||
// Process genesis activations.
|
// Process genesis activations.
|
||||||
for i in 0..state.validator_registry.len() {
|
for validator in &mut state.validator_registry {
|
||||||
if state.get_effective_balance(i, spec)? >= spec.max_deposit_amount {
|
if validator.effective_balance >= spec.max_effective_balance {
|
||||||
state.validator_registry[i].activation_epoch = spec.genesis_epoch;
|
validator.activation_eligibility_epoch = T::genesis_epoch();
|
||||||
|
validator.activation_epoch = T::genesis_epoch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the current epoch cache is built.
|
// Ensure the current epoch cache is built.
|
||||||
state.build_epoch_cache(RelativeEpoch::Current, spec)?;
|
state.build_committee_cache(RelativeEpoch::Current, spec)?;
|
||||||
|
|
||||||
// Set all the active index roots to be the genesis active index root.
|
// Set all the active index roots to be the genesis active index root.
|
||||||
let active_validator_indices = state
|
let active_validator_indices = state
|
||||||
.get_cached_active_validator_indices(RelativeEpoch::Current, spec)?
|
.get_cached_active_validator_indices(RelativeEpoch::Current)?
|
||||||
.to_vec();
|
.to_vec();
|
||||||
let genesis_active_index_root = Hash256::from_slice(&active_validator_indices.tree_hash_root());
|
let genesis_active_index_root = Hash256::from_slice(&active_validator_indices.tree_hash_root());
|
||||||
state.fill_active_index_roots_with(genesis_active_index_root);
|
state.fill_active_index_roots_with(genesis_active_index_root);
|
||||||
|
|
||||||
// Generate the current shuffling seed.
|
|
||||||
state.current_shuffling_seed = state.generate_seed(spec.genesis_epoch, spec)?;
|
|
||||||
|
|
||||||
Ok(state)
|
Ok(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ pub mod per_block_processing;
|
|||||||
pub mod per_epoch_processing;
|
pub mod per_epoch_processing;
|
||||||
pub mod per_slot_processing;
|
pub mod per_slot_processing;
|
||||||
|
|
||||||
pub use get_genesis_state::get_genesis_state;
|
pub use get_genesis_state::get_genesis_beacon_state;
|
||||||
pub use per_block_processing::{
|
pub use per_block_processing::{
|
||||||
errors::{BlockInvalid, BlockProcessingError},
|
errors::{BlockInvalid, BlockProcessingError},
|
||||||
per_block_processing, per_block_processing_without_verifying_block_signature,
|
per_block_processing, per_block_processing_without_verifying_block_signature,
|
||||||
|
@ -1,21 +1,25 @@
|
|||||||
use crate::common::slash_validator;
|
use crate::common::{initiate_validator_exit, slash_validator};
|
||||||
use errors::{BlockInvalid as Invalid, BlockProcessingError as Error, IntoWithIndex};
|
use errors::{BlockInvalid as Invalid, BlockProcessingError as Error, IntoWithIndex};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use tree_hash::{SignedRoot, TreeHash};
|
use tree_hash::{SignedRoot, TreeHash};
|
||||||
use types::*;
|
use types::*;
|
||||||
|
|
||||||
pub use self::verify_attester_slashing::{
|
pub use self::verify_attester_slashing::{
|
||||||
gather_attester_slashing_indices, gather_attester_slashing_indices_modular,
|
get_slashable_indices, get_slashable_indices_modular, verify_attester_slashing,
|
||||||
verify_attester_slashing,
|
|
||||||
};
|
};
|
||||||
pub use self::verify_proposer_slashing::verify_proposer_slashing;
|
pub use self::verify_proposer_slashing::verify_proposer_slashing;
|
||||||
pub use validate_attestation::{
|
pub use validate_attestation::{
|
||||||
validate_attestation, validate_attestation_time_independent_only,
|
validate_attestation, validate_attestation_time_independent_only,
|
||||||
validate_attestation_without_signature,
|
validate_attestation_without_signature,
|
||||||
};
|
};
|
||||||
pub use verify_deposit::{get_existing_validator_index, verify_deposit, verify_deposit_index};
|
pub use verify_deposit::{
|
||||||
|
get_existing_validator_index, verify_deposit_index, verify_deposit_merkle_proof,
|
||||||
|
verify_deposit_signature,
|
||||||
|
};
|
||||||
pub use verify_exit::{verify_exit, verify_exit_time_independent_only};
|
pub use verify_exit::{verify_exit, verify_exit_time_independent_only};
|
||||||
pub use verify_slashable_attestation::verify_slashable_attestation;
|
pub use verify_indexed_attestation::{
|
||||||
|
verify_indexed_attestation, verify_indexed_attestation_without_signature,
|
||||||
|
};
|
||||||
pub use verify_transfer::{
|
pub use verify_transfer::{
|
||||||
execute_transfer, verify_transfer, verify_transfer_time_independent_only,
|
execute_transfer, verify_transfer, verify_transfer_time_independent_only,
|
||||||
};
|
};
|
||||||
@ -27,21 +31,16 @@ mod validate_attestation;
|
|||||||
mod verify_attester_slashing;
|
mod verify_attester_slashing;
|
||||||
mod verify_deposit;
|
mod verify_deposit;
|
||||||
mod verify_exit;
|
mod verify_exit;
|
||||||
|
mod verify_indexed_attestation;
|
||||||
mod verify_proposer_slashing;
|
mod verify_proposer_slashing;
|
||||||
mod verify_slashable_attestation;
|
|
||||||
mod verify_transfer;
|
mod verify_transfer;
|
||||||
|
|
||||||
// Set to `true` to check the merkle proof that a deposit is in the eth1 deposit root.
|
|
||||||
//
|
|
||||||
// Presently disabled to make testing easier.
|
|
||||||
const VERIFY_DEPOSIT_MERKLE_PROOFS: bool = false;
|
|
||||||
|
|
||||||
/// Updates the state for a new block, whilst validating that the block is valid.
|
/// Updates the state for a new block, whilst validating that the block is valid.
|
||||||
///
|
///
|
||||||
/// Returns `Ok(())` if the block is valid and the state was successfully updated. Otherwise
|
/// 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.
|
/// returns an error describing why the block was invalid or how the function failed to execute.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn per_block_processing<T: EthSpec>(
|
pub fn per_block_processing<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
block: &BeaconBlock,
|
block: &BeaconBlock,
|
||||||
@ -56,7 +55,7 @@ pub fn per_block_processing<T: EthSpec>(
|
|||||||
/// Returns `Ok(())` if the block is valid and the state was successfully updated. Otherwise
|
/// 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.
|
/// returns an error describing why the block was invalid or how the function failed to execute.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn per_block_processing_without_verifying_block_signature<T: EthSpec>(
|
pub fn per_block_processing_without_verifying_block_signature<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
block: &BeaconBlock,
|
block: &BeaconBlock,
|
||||||
@ -71,24 +70,21 @@ pub fn per_block_processing_without_verifying_block_signature<T: EthSpec>(
|
|||||||
/// Returns `Ok(())` if the block is valid and the state was successfully updated. Otherwise
|
/// 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.
|
/// returns an error describing why the block was invalid or how the function failed to execute.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
fn per_block_processing_signature_optional<T: EthSpec>(
|
fn per_block_processing_signature_optional<T: EthSpec>(
|
||||||
mut state: &mut BeaconState<T>,
|
mut state: &mut BeaconState<T>,
|
||||||
block: &BeaconBlock,
|
block: &BeaconBlock,
|
||||||
should_verify_block_signature: bool,
|
should_verify_block_signature: bool,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
process_block_header(state, block, spec)?;
|
process_block_header(state, block, spec, should_verify_block_signature)?;
|
||||||
|
|
||||||
// Ensure the current and previous epoch cache is built.
|
// Ensure the current and previous epoch caches are built.
|
||||||
state.build_epoch_cache(RelativeEpoch::Previous, spec)?;
|
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
|
||||||
state.build_epoch_cache(RelativeEpoch::Current, spec)?;
|
state.build_committee_cache(RelativeEpoch::Current, spec)?;
|
||||||
|
|
||||||
if should_verify_block_signature {
|
|
||||||
verify_block_signature(&state, &block, &spec)?;
|
|
||||||
}
|
|
||||||
process_randao(&mut state, &block, &spec)?;
|
process_randao(&mut state, &block, &spec)?;
|
||||||
process_eth1_data(&mut state, &block.body.eth1_data)?;
|
process_eth1_data(&mut state, &block.body.eth1_data, spec)?;
|
||||||
process_proposer_slashings(&mut state, &block.body.proposer_slashings, spec)?;
|
process_proposer_slashings(&mut state, &block.body.proposer_slashings, spec)?;
|
||||||
process_attester_slashings(&mut state, &block.body.attester_slashings, spec)?;
|
process_attester_slashings(&mut state, &block.body.attester_slashings, spec)?;
|
||||||
process_attestations(&mut state, &block.body.attestations, spec)?;
|
process_attestations(&mut state, &block.body.attestations, spec)?;
|
||||||
@ -101,11 +97,12 @@ fn per_block_processing_signature_optional<T: EthSpec>(
|
|||||||
|
|
||||||
/// Processes the block header.
|
/// Processes the block header.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn process_block_header<T: EthSpec>(
|
pub fn process_block_header<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
block: &BeaconBlock,
|
block: &BeaconBlock,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
|
should_verify_block_signature: bool,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
verify!(block.slot == state.slot, Invalid::StateSlotMismatch);
|
verify!(block.slot == state.slot, Invalid::StateSlotMismatch);
|
||||||
|
|
||||||
@ -121,12 +118,21 @@ pub fn process_block_header<T: EthSpec>(
|
|||||||
|
|
||||||
state.latest_block_header = block.temporary_block_header(spec);
|
state.latest_block_header = block.temporary_block_header(spec);
|
||||||
|
|
||||||
|
// Verify proposer is not slashed
|
||||||
|
let proposer_idx = state.get_beacon_proposer_index(block.slot, RelativeEpoch::Current, spec)?;
|
||||||
|
let proposer = &state.validator_registry[proposer_idx];
|
||||||
|
verify!(!proposer.slashed, Invalid::ProposerSlashed(proposer_idx));
|
||||||
|
|
||||||
|
if should_verify_block_signature {
|
||||||
|
verify_block_signature(&state, &block, &spec)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verifies the signature of a block.
|
/// Verifies the signature of a block.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn verify_block_signature<T: EthSpec>(
|
pub fn verify_block_signature<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
block: &BeaconBlock,
|
block: &BeaconBlock,
|
||||||
@ -136,8 +142,8 @@ pub fn verify_block_signature<T: EthSpec>(
|
|||||||
[state.get_beacon_proposer_index(block.slot, RelativeEpoch::Current, spec)?];
|
[state.get_beacon_proposer_index(block.slot, RelativeEpoch::Current, spec)?];
|
||||||
|
|
||||||
let domain = spec.get_domain(
|
let domain = spec.get_domain(
|
||||||
block.slot.epoch(spec.slots_per_epoch),
|
block.slot.epoch(T::slots_per_epoch()),
|
||||||
Domain::BeaconBlock,
|
Domain::BeaconProposer,
|
||||||
&state.fork,
|
&state.fork,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -154,7 +160,7 @@ pub fn verify_block_signature<T: EthSpec>(
|
|||||||
/// Verifies the `randao_reveal` against the block's proposer pubkey and updates
|
/// Verifies the `randao_reveal` against the block's proposer pubkey and updates
|
||||||
/// `state.latest_randao_mixes`.
|
/// `state.latest_randao_mixes`.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn process_randao<T: EthSpec>(
|
pub fn process_randao<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
block: &BeaconBlock,
|
block: &BeaconBlock,
|
||||||
@ -166,9 +172,9 @@ pub fn process_randao<T: EthSpec>(
|
|||||||
// Verify the RANDAO is a valid signature of the proposer.
|
// Verify the RANDAO is a valid signature of the proposer.
|
||||||
verify!(
|
verify!(
|
||||||
block.body.randao_reveal.verify(
|
block.body.randao_reveal.verify(
|
||||||
&state.current_epoch(spec).tree_hash_root()[..],
|
&state.current_epoch().tree_hash_root()[..],
|
||||||
spec.get_domain(
|
spec.get_domain(
|
||||||
block.slot.epoch(spec.slots_per_epoch),
|
block.slot.epoch(T::slots_per_epoch()),
|
||||||
Domain::Randao,
|
Domain::Randao,
|
||||||
&state.fork
|
&state.fork
|
||||||
),
|
),
|
||||||
@ -178,32 +184,29 @@ pub fn process_randao<T: EthSpec>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Update the current epoch RANDAO mix.
|
// Update the current epoch RANDAO mix.
|
||||||
state.update_randao_mix(state.current_epoch(spec), &block.body.randao_reveal, spec)?;
|
state.update_randao_mix(state.current_epoch(), &block.body.randao_reveal)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the `state.eth1_data_votes` based upon the `eth1_data` provided.
|
/// Update the `state.eth1_data_votes` based upon the `eth1_data` provided.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn process_eth1_data<T: EthSpec>(
|
pub fn process_eth1_data<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
eth1_data: &Eth1Data,
|
eth1_data: &Eth1Data,
|
||||||
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Attempt to find a `Eth1DataVote` with matching `Eth1Data`.
|
state.eth1_data_votes.push(eth1_data.clone());
|
||||||
let matching_eth1_vote_index = state
|
|
||||||
|
let num_votes = state
|
||||||
.eth1_data_votes
|
.eth1_data_votes
|
||||||
.iter()
|
.iter()
|
||||||
.position(|vote| vote.eth1_data == *eth1_data);
|
.filter(|vote| *vote == eth1_data)
|
||||||
|
.count() as u64;
|
||||||
|
|
||||||
// If a vote exists, increment it's `vote_count`. Otherwise, create a new `Eth1DataVote`.
|
if num_votes * 2 > spec.slots_per_eth1_voting_period {
|
||||||
if let Some(index) = matching_eth1_vote_index {
|
state.latest_eth1_data = eth1_data.clone();
|
||||||
state.eth1_data_votes[index].vote_count += 1;
|
|
||||||
} else {
|
|
||||||
state.eth1_data_votes.push(Eth1DataVote {
|
|
||||||
eth1_data: eth1_data.clone(),
|
|
||||||
vote_count: 1,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -214,7 +217,7 @@ pub fn process_eth1_data<T: EthSpec>(
|
|||||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||||
/// an `Err` describing the invalid object or cause of failure.
|
/// an `Err` describing the invalid object or cause of failure.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn process_proposer_slashings<T: EthSpec>(
|
pub fn process_proposer_slashings<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
proposer_slashings: &[ProposerSlashing],
|
proposer_slashings: &[ProposerSlashing],
|
||||||
@ -236,18 +239,18 @@ pub fn process_proposer_slashings<T: EthSpec>(
|
|||||||
|
|
||||||
// Update the state.
|
// Update the state.
|
||||||
for proposer_slashing in proposer_slashings {
|
for proposer_slashing in proposer_slashings {
|
||||||
slash_validator(state, proposer_slashing.proposer_index as usize, spec)?;
|
slash_validator(state, proposer_slashing.proposer_index as usize, None, spec)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validates each `AttesterSlsashing` and updates the state, short-circuiting on an invalid object.
|
/// Validates each `AttesterSlashing` and updates the state, short-circuiting on an invalid object.
|
||||||
///
|
///
|
||||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||||
/// an `Err` describing the invalid object or cause of failure.
|
/// an `Err` describing the invalid object or cause of failure.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn process_attester_slashings<T: EthSpec>(
|
pub fn process_attester_slashings<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
attester_slashings: &[AttesterSlashing],
|
attester_slashings: &[AttesterSlashing],
|
||||||
@ -258,42 +261,42 @@ pub fn process_attester_slashings<T: EthSpec>(
|
|||||||
Invalid::MaxAttesterSlashingsExceed
|
Invalid::MaxAttesterSlashingsExceed
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify the `SlashableAttestation`s in parallel (these are the resource-consuming objects, not
|
// Verify the `IndexedAttestation`s in parallel (these are the resource-consuming objects, not
|
||||||
// the `AttesterSlashing`s themselves).
|
// the `AttesterSlashing`s themselves).
|
||||||
let mut slashable_attestations: Vec<&SlashableAttestation> =
|
let mut indexed_attestations: Vec<&IndexedAttestation> =
|
||||||
Vec::with_capacity(attester_slashings.len() * 2);
|
Vec::with_capacity(attester_slashings.len() * 2);
|
||||||
for attester_slashing in attester_slashings {
|
for attester_slashing in attester_slashings {
|
||||||
slashable_attestations.push(&attester_slashing.slashable_attestation_1);
|
indexed_attestations.push(&attester_slashing.attestation_1);
|
||||||
slashable_attestations.push(&attester_slashing.slashable_attestation_2);
|
indexed_attestations.push(&attester_slashing.attestation_2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify slashable attestations in parallel.
|
// Verify indexed attestations in parallel.
|
||||||
slashable_attestations
|
indexed_attestations
|
||||||
.par_iter()
|
.par_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.try_for_each(|(i, slashable_attestation)| {
|
.try_for_each(|(i, indexed_attestation)| {
|
||||||
verify_slashable_attestation(&state, slashable_attestation, spec)
|
verify_indexed_attestation(&state, indexed_attestation, spec)
|
||||||
.map_err(|e| e.into_with_index(i))
|
.map_err(|e| e.into_with_index(i))
|
||||||
})?;
|
})?;
|
||||||
let all_slashable_attestations_have_been_checked = true;
|
let all_indexed_attestations_have_been_checked = true;
|
||||||
|
|
||||||
// Gather the slashable indices and preform the final verification and update the state in series.
|
// Gather the indexed indices and preform the final verification and update the state in series.
|
||||||
for (i, attester_slashing) in attester_slashings.iter().enumerate() {
|
for (i, attester_slashing) in attester_slashings.iter().enumerate() {
|
||||||
let should_verify_slashable_attestations = !all_slashable_attestations_have_been_checked;
|
let should_verify_indexed_attestations = !all_indexed_attestations_have_been_checked;
|
||||||
|
|
||||||
verify_attester_slashing(
|
verify_attester_slashing(
|
||||||
&state,
|
&state,
|
||||||
&attester_slashing,
|
&attester_slashing,
|
||||||
should_verify_slashable_attestations,
|
should_verify_indexed_attestations,
|
||||||
spec,
|
spec,
|
||||||
)
|
)
|
||||||
.map_err(|e| e.into_with_index(i))?;
|
.map_err(|e| e.into_with_index(i))?;
|
||||||
|
|
||||||
let slashable_indices = gather_attester_slashing_indices(&state, &attester_slashing, spec)
|
let slashable_indices =
|
||||||
.map_err(|e| e.into_with_index(i))?;
|
get_slashable_indices(&state, &attester_slashing).map_err(|e| e.into_with_index(i))?;
|
||||||
|
|
||||||
for i in slashable_indices {
|
for i in slashable_indices {
|
||||||
slash_validator(state, i as usize, spec)?;
|
slash_validator(state, i as usize, None, spec)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,7 +308,7 @@ pub fn process_attester_slashings<T: EthSpec>(
|
|||||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||||
/// an `Err` describing the invalid object or cause of failure.
|
/// an `Err` describing the invalid object or cause of failure.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn process_attestations<T: EthSpec>(
|
pub fn process_attestations<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
attestations: &[Attestation],
|
attestations: &[Attestation],
|
||||||
@ -317,7 +320,7 @@ pub fn process_attestations<T: EthSpec>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Ensure the previous epoch cache exists.
|
// Ensure the previous epoch cache exists.
|
||||||
state.build_epoch_cache(RelativeEpoch::Previous, spec)?;
|
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
|
||||||
|
|
||||||
// Verify attestations in parallel.
|
// Verify attestations in parallel.
|
||||||
attestations
|
attestations
|
||||||
@ -328,13 +331,20 @@ pub fn process_attestations<T: EthSpec>(
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Update the state in series.
|
// Update the state in series.
|
||||||
|
let proposer_index =
|
||||||
|
state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)? as u64;
|
||||||
for attestation in attestations {
|
for attestation in attestations {
|
||||||
let pending_attestation = PendingAttestation::from_attestation(attestation, state.slot);
|
let attestation_slot = state.get_attestation_slot(&attestation.data)?;
|
||||||
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
|
let pending_attestation = PendingAttestation {
|
||||||
|
aggregation_bitfield: attestation.aggregation_bitfield.clone(),
|
||||||
|
data: attestation.data.clone(),
|
||||||
|
inclusion_delay: (state.slot - attestation_slot).as_u64(),
|
||||||
|
proposer_index,
|
||||||
|
};
|
||||||
|
|
||||||
if attestation_epoch == state.current_epoch(spec) {
|
if attestation.data.target_epoch == state.current_epoch() {
|
||||||
state.current_epoch_attestations.push(pending_attestation)
|
state.current_epoch_attestations.push(pending_attestation)
|
||||||
} else if attestation_epoch == state.previous_epoch(spec) {
|
} else {
|
||||||
state.previous_epoch_attestations.push(pending_attestation)
|
state.previous_epoch_attestations.push(pending_attestation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -347,15 +357,19 @@ pub fn process_attestations<T: EthSpec>(
|
|||||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||||
/// an `Err` describing the invalid object or cause of failure.
|
/// an `Err` describing the invalid object or cause of failure.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn process_deposits<T: EthSpec>(
|
pub fn process_deposits<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
deposits: &[Deposit],
|
deposits: &[Deposit],
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
verify!(
|
verify!(
|
||||||
deposits.len() as u64 <= spec.max_deposits,
|
deposits.len() as u64
|
||||||
Invalid::MaxDepositsExceeded
|
== std::cmp::min(
|
||||||
|
spec.max_deposits,
|
||||||
|
state.latest_eth1_data.deposit_count - state.deposit_index
|
||||||
|
),
|
||||||
|
Invalid::DepositCountInvalid
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify deposits in parallel.
|
// Verify deposits in parallel.
|
||||||
@ -363,50 +377,53 @@ pub fn process_deposits<T: EthSpec>(
|
|||||||
.par_iter()
|
.par_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.try_for_each(|(i, deposit)| {
|
.try_for_each(|(i, deposit)| {
|
||||||
verify_deposit(state, deposit, VERIFY_DEPOSIT_MERKLE_PROOFS, spec)
|
verify_deposit_merkle_proof(state, deposit, spec).map_err(|e| e.into_with_index(i))
|
||||||
.map_err(|e| e.into_with_index(i))
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Check `state.deposit_index` and update the state in series.
|
// Check `state.deposit_index` and update the state in series.
|
||||||
for (i, deposit) in deposits.iter().enumerate() {
|
for (i, deposit) in deposits.iter().enumerate() {
|
||||||
verify_deposit_index(state, deposit).map_err(|e| e.into_with_index(i))?;
|
verify_deposit_index(state, deposit).map_err(|e| e.into_with_index(i))?;
|
||||||
|
|
||||||
|
state.deposit_index += 1;
|
||||||
|
|
||||||
// Ensure the state's pubkey cache is fully up-to-date, it will be used to check to see if the
|
// 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.
|
// depositing validator already exists in the registry.
|
||||||
state.update_pubkey_cache()?;
|
state.update_pubkey_cache()?;
|
||||||
|
|
||||||
// Get an `Option<u64>` where `u64` is the validator index if this deposit public key
|
// Get an `Option<u64>` where `u64` is the validator index if this deposit public key
|
||||||
// already exists in the beacon_state.
|
// already exists in the beacon_state.
|
||||||
//
|
|
||||||
// This function also verifies the withdrawal credentials.
|
|
||||||
let validator_index =
|
let validator_index =
|
||||||
get_existing_validator_index(state, deposit).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 amount = deposit.data.amount;
|
||||||
let deposit_input = &deposit.deposit_data.deposit_input;
|
|
||||||
|
|
||||||
if let Some(index) = validator_index {
|
if let Some(index) = validator_index {
|
||||||
// Update the existing validator balance.
|
// Update the existing validator balance.
|
||||||
safe_add_assign!(
|
safe_add_assign!(state.balances[index as usize], amount);
|
||||||
state.validator_balances[index as usize],
|
|
||||||
deposit_data.amount
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
|
// The signature should be checked for new validators. Return early for a bad
|
||||||
|
// signature.
|
||||||
|
if verify_deposit_signature(state, deposit, spec).is_err() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new validator.
|
// Create a new validator.
|
||||||
let validator = Validator {
|
let validator = Validator {
|
||||||
pubkey: deposit_input.pubkey.clone(),
|
pubkey: deposit.data.pubkey.clone(),
|
||||||
withdrawal_credentials: deposit_input.withdrawal_credentials,
|
withdrawal_credentials: deposit.data.withdrawal_credentials,
|
||||||
|
activation_eligibility_epoch: spec.far_future_epoch,
|
||||||
activation_epoch: spec.far_future_epoch,
|
activation_epoch: spec.far_future_epoch,
|
||||||
exit_epoch: spec.far_future_epoch,
|
exit_epoch: spec.far_future_epoch,
|
||||||
withdrawable_epoch: spec.far_future_epoch,
|
withdrawable_epoch: spec.far_future_epoch,
|
||||||
initiated_exit: false,
|
effective_balance: std::cmp::min(
|
||||||
|
amount - amount % spec.effective_balance_increment,
|
||||||
|
spec.max_effective_balance,
|
||||||
|
),
|
||||||
slashed: false,
|
slashed: false,
|
||||||
};
|
};
|
||||||
state.validator_registry.push(validator);
|
state.validator_registry.push(validator);
|
||||||
state.validator_balances.push(deposit_data.amount);
|
state.balances.push(deposit.data.amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.deposit_index += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -417,7 +434,7 @@ pub fn process_deposits<T: EthSpec>(
|
|||||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||||
/// an `Err` describing the invalid object or cause of failure.
|
/// an `Err` describing the invalid object or cause of failure.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn process_exits<T: EthSpec>(
|
pub fn process_exits<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
voluntary_exits: &[VoluntaryExit],
|
voluntary_exits: &[VoluntaryExit],
|
||||||
@ -438,7 +455,7 @@ pub fn process_exits<T: EthSpec>(
|
|||||||
|
|
||||||
// Update the state in series.
|
// Update the state in series.
|
||||||
for exit in voluntary_exits {
|
for exit in voluntary_exits {
|
||||||
state.initiate_validator_exit(exit.validator_index as usize);
|
initiate_validator_exit(state, exit.validator_index as usize, spec)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -449,7 +466,7 @@ pub fn process_exits<T: EthSpec>(
|
|||||||
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
/// Returns `Ok(())` if the validation and state updates completed successfully, otherwise returns
|
||||||
/// an `Err` describing the invalid object or cause of failure.
|
/// an `Err` describing the invalid object or cause of failure.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn process_transfers<T: EthSpec>(
|
pub fn process_transfers<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
transfers: &[Transfer],
|
transfers: &[Transfer],
|
||||||
|
@ -22,8 +22,8 @@ impl<T: EthSpec> BlockProcessingBuilder<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_slot(&mut self, slot: Slot, spec: &ChainSpec) {
|
pub fn set_slot(&mut self, slot: Slot) {
|
||||||
self.state_builder.teleport_to_slot(slot, &spec);
|
self.state_builder.teleport_to_slot(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_caches(&mut self, spec: &ChainSpec) {
|
pub fn build_caches(&mut self, spec: &ChainSpec) {
|
||||||
@ -55,11 +55,13 @@ impl<T: EthSpec> BlockProcessingBuilder<T> {
|
|||||||
let keypair = &keypairs[proposer_index];
|
let keypair = &keypairs[proposer_index];
|
||||||
|
|
||||||
match randao_sk {
|
match randao_sk {
|
||||||
Some(sk) => builder.set_randao_reveal(&sk, &state.fork, spec),
|
Some(sk) => builder.set_randao_reveal::<T>(&sk, &state.fork, spec),
|
||||||
None => builder.set_randao_reveal(&keypair.sk, &state.fork, spec),
|
None => builder.set_randao_reveal::<T>(&keypair.sk, &state.fork, spec),
|
||||||
}
|
}
|
||||||
|
|
||||||
let block = self.block_builder.build(&keypair.sk, &state.fork, spec);
|
let block = self
|
||||||
|
.block_builder
|
||||||
|
.build::<T>(&keypair.sk, &state.fork, spec);
|
||||||
|
|
||||||
(block, state)
|
(block, state)
|
||||||
}
|
}
|
||||||
|
@ -71,19 +71,20 @@ pub enum BlockInvalid {
|
|||||||
state: Hash256,
|
state: Hash256,
|
||||||
block: Hash256,
|
block: Hash256,
|
||||||
},
|
},
|
||||||
|
ProposerSlashed(usize),
|
||||||
BadSignature,
|
BadSignature,
|
||||||
BadRandaoSignature,
|
BadRandaoSignature,
|
||||||
MaxAttestationsExceeded,
|
MaxAttestationsExceeded,
|
||||||
MaxAttesterSlashingsExceed,
|
MaxAttesterSlashingsExceed,
|
||||||
MaxProposerSlashingsExceeded,
|
MaxProposerSlashingsExceeded,
|
||||||
MaxDepositsExceeded,
|
DepositCountInvalid,
|
||||||
MaxExitsExceeded,
|
MaxExitsExceeded,
|
||||||
MaxTransfersExceed,
|
MaxTransfersExceed,
|
||||||
AttestationInvalid(usize, AttestationInvalid),
|
AttestationInvalid(usize, AttestationInvalid),
|
||||||
/// A `SlashableAttestation` inside an `AttesterSlashing` was invalid.
|
/// A `IndexedAttestation` inside an `AttesterSlashing` was invalid.
|
||||||
///
|
///
|
||||||
/// To determine the offending `AttesterSlashing` index, divide the error message `usize` by two.
|
/// To determine the offending `AttesterSlashing` index, divide the error message `usize` by two.
|
||||||
SlashableAttestationInvalid(usize, SlashableAttestationInvalid),
|
IndexedAttestationInvalid(usize, IndexedAttestationInvalid),
|
||||||
AttesterSlashingInvalid(usize, AttesterSlashingInvalid),
|
AttesterSlashingInvalid(usize, AttesterSlashingInvalid),
|
||||||
ProposerSlashingInvalid(usize, ProposerSlashingInvalid),
|
ProposerSlashingInvalid(usize, ProposerSlashingInvalid),
|
||||||
DepositInvalid(usize, DepositInvalid),
|
DepositInvalid(usize, DepositInvalid),
|
||||||
@ -125,6 +126,8 @@ pub enum AttestationInvalid {
|
|||||||
},
|
},
|
||||||
/// Attestation slot is too far in the past to be included in a block.
|
/// Attestation slot is too far in the past to be included in a block.
|
||||||
IncludedTooLate { state: Slot, attestation: Slot },
|
IncludedTooLate { state: Slot, attestation: Slot },
|
||||||
|
/// Attestation target epoch does not match the current or previous epoch.
|
||||||
|
BadTargetEpoch,
|
||||||
/// Attestation justified epoch does not match the states current or previous justified epoch.
|
/// Attestation justified epoch does not match the states current or previous justified epoch.
|
||||||
///
|
///
|
||||||
/// `is_current` is `true` if the attestation was compared to the
|
/// `is_current` is `true` if the attestation was compared to the
|
||||||
@ -169,11 +172,20 @@ pub enum AttestationInvalid {
|
|||||||
BadSignature,
|
BadSignature,
|
||||||
/// The shard block root was not set to zero. This is a phase 0 requirement.
|
/// The shard block root was not set to zero. This is a phase 0 requirement.
|
||||||
ShardBlockRootNotZero,
|
ShardBlockRootNotZero,
|
||||||
|
/// The indexed attestation created from this attestation was found to be invalid.
|
||||||
|
BadIndexedAttestation(IndexedAttestationInvalid),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_from_beacon_state_error!(AttestationValidationError);
|
impl_from_beacon_state_error!(AttestationValidationError);
|
||||||
impl_into_with_index_with_beacon_error!(AttestationValidationError, AttestationInvalid);
|
impl_into_with_index_with_beacon_error!(AttestationValidationError, AttestationInvalid);
|
||||||
|
|
||||||
|
impl From<IndexedAttestationValidationError> for AttestationValidationError {
|
||||||
|
fn from(err: IndexedAttestationValidationError) -> Self {
|
||||||
|
let IndexedAttestationValidationError::Invalid(e) = err;
|
||||||
|
AttestationValidationError::Invalid(AttestationInvalid::BadIndexedAttestation(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* `AttesterSlashing` Validation
|
* `AttesterSlashing` Validation
|
||||||
*/
|
*/
|
||||||
@ -194,10 +206,10 @@ pub enum AttesterSlashingInvalid {
|
|||||||
AttestationDataIdentical,
|
AttestationDataIdentical,
|
||||||
/// The attestations were not in conflict.
|
/// The attestations were not in conflict.
|
||||||
NotSlashable,
|
NotSlashable,
|
||||||
/// The first `SlashableAttestation` was invalid.
|
/// The first `IndexedAttestation` was invalid.
|
||||||
SlashableAttestation1Invalid(SlashableAttestationInvalid),
|
IndexedAttestation1Invalid(IndexedAttestationInvalid),
|
||||||
/// The second `SlashableAttestation` was invalid.
|
/// The second `IndexedAttestation` was invalid.
|
||||||
SlashableAttestation2Invalid(SlashableAttestationInvalid),
|
IndexedAttestation2Invalid(IndexedAttestationInvalid),
|
||||||
/// The validator index is unknown. One cannot slash one who does not exist.
|
/// The validator index is unknown. One cannot slash one who does not exist.
|
||||||
UnknownValidator(u64),
|
UnknownValidator(u64),
|
||||||
/// The specified validator has already been withdrawn.
|
/// The specified validator has already been withdrawn.
|
||||||
@ -210,52 +222,50 @@ impl_from_beacon_state_error!(AttesterSlashingValidationError);
|
|||||||
impl_into_with_index_with_beacon_error!(AttesterSlashingValidationError, AttesterSlashingInvalid);
|
impl_into_with_index_with_beacon_error!(AttesterSlashingValidationError, AttesterSlashingInvalid);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* `SlashableAttestation` Validation
|
* `IndexedAttestation` Validation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/// The object is invalid or validation failed.
|
/// The object is invalid or validation failed.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum SlashableAttestationValidationError {
|
pub enum IndexedAttestationValidationError {
|
||||||
/// Validation completed successfully and the object is invalid.
|
/// Validation completed successfully and the object is invalid.
|
||||||
Invalid(SlashableAttestationInvalid),
|
Invalid(IndexedAttestationInvalid),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes why an object is invalid.
|
/// Describes why an object is invalid.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum SlashableAttestationInvalid {
|
pub enum IndexedAttestationInvalid {
|
||||||
|
/// The custody bit 0 validators intersect with the bit 1 validators.
|
||||||
|
CustodyBitValidatorsIntersect,
|
||||||
/// The custody bitfield has some bits set `true`. This is not allowed in phase 0.
|
/// The custody bitfield has some bits set `true`. This is not allowed in phase 0.
|
||||||
CustodyBitfieldHasSetBits,
|
CustodyBitfieldHasSetBits,
|
||||||
/// No validator indices were specified.
|
/// No validator indices were specified.
|
||||||
NoValidatorIndices,
|
NoValidatorIndices,
|
||||||
|
/// The number of indices exceeds the global maximum.
|
||||||
|
///
|
||||||
|
/// (max_indices, indices_given)
|
||||||
|
MaxIndicesExceed(u64, usize),
|
||||||
/// The validator indices were not in increasing order.
|
/// The validator indices were not in increasing order.
|
||||||
///
|
///
|
||||||
/// The error occured between the given `index` and `index + 1`
|
/// The error occured between the given `index` and `index + 1`
|
||||||
BadValidatorIndicesOrdering(usize),
|
BadValidatorIndicesOrdering(usize),
|
||||||
/// The custody bitfield length is not the smallest possible size to represent the validators.
|
|
||||||
///
|
|
||||||
/// (validators_len, bitfield_len)
|
|
||||||
BadCustodyBitfieldLength(usize, usize),
|
|
||||||
/// The number of slashable indices exceed the global maximum.
|
|
||||||
///
|
|
||||||
/// (max_indices, indices_given)
|
|
||||||
MaxIndicesExceed(usize, usize),
|
|
||||||
/// The validator index is unknown. One cannot slash one who does not exist.
|
/// The validator index is unknown. One cannot slash one who does not exist.
|
||||||
UnknownValidator(u64),
|
UnknownValidator(u64),
|
||||||
/// The slashable attestation aggregate signature was not valid.
|
/// The indexed attestation aggregate signature was not valid.
|
||||||
BadSignature,
|
BadSignature,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<SlashableAttestationInvalid> for SlashableAttestationValidationError {
|
impl Into<IndexedAttestationInvalid> for IndexedAttestationValidationError {
|
||||||
fn into(self) -> SlashableAttestationInvalid {
|
fn into(self) -> IndexedAttestationInvalid {
|
||||||
match self {
|
match self {
|
||||||
SlashableAttestationValidationError::Invalid(e) => e,
|
IndexedAttestationValidationError::Invalid(e) => e,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_into_with_index_without_beacon_error!(
|
impl_into_with_index_without_beacon_error!(
|
||||||
SlashableAttestationValidationError,
|
IndexedAttestationValidationError,
|
||||||
SlashableAttestationInvalid
|
IndexedAttestationInvalid
|
||||||
);
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -280,10 +290,8 @@ pub enum ProposerSlashingInvalid {
|
|||||||
ProposalEpochMismatch(Slot, Slot),
|
ProposalEpochMismatch(Slot, Slot),
|
||||||
/// The proposals are identical and therefore not slashable.
|
/// The proposals are identical and therefore not slashable.
|
||||||
ProposalsIdentical,
|
ProposalsIdentical,
|
||||||
/// The specified proposer has already been slashed.
|
/// The specified proposer cannot be slashed because they are already slashed, or not active.
|
||||||
ProposerAlreadySlashed,
|
ProposerNotSlashable(u64),
|
||||||
/// The specified proposer has already been withdrawn.
|
|
||||||
ProposerAlreadyWithdrawn(u64),
|
|
||||||
/// The first proposal signature was invalid.
|
/// The first proposal signature was invalid.
|
||||||
BadProposal1Signature,
|
BadProposal1Signature,
|
||||||
/// The second proposal signature was invalid.
|
/// The second proposal signature was invalid.
|
||||||
@ -313,11 +321,8 @@ pub enum DepositValidationError {
|
|||||||
pub enum DepositInvalid {
|
pub enum DepositInvalid {
|
||||||
/// The deposit index does not match the state index.
|
/// The deposit index does not match the state index.
|
||||||
BadIndex { state: u64, deposit: u64 },
|
BadIndex { state: u64, deposit: u64 },
|
||||||
/// The proof-of-possession does not match the given pubkey.
|
/// The signature (proof-of-possession) does not match the given pubkey.
|
||||||
BadProofOfPossession,
|
BadSignature,
|
||||||
/// 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
|
/// The specified `branch` and `index` did not form a valid proof that the deposit is included
|
||||||
/// in the eth1 deposit root.
|
/// in the eth1 deposit root.
|
||||||
BadMerkleProof,
|
BadMerkleProof,
|
||||||
@ -340,6 +345,8 @@ pub enum ExitValidationError {
|
|||||||
/// Describes why an object is invalid.
|
/// Describes why an object is invalid.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum ExitInvalid {
|
pub enum ExitInvalid {
|
||||||
|
/// The specified validator is not active.
|
||||||
|
NotActive(u64),
|
||||||
/// The specified validator is not in the state's validator registry.
|
/// The specified validator is not in the state's validator registry.
|
||||||
ValidatorUnknown(u64),
|
ValidatorUnknown(u64),
|
||||||
/// The specified validator has a non-maximum exit epoch.
|
/// The specified validator has a non-maximum exit epoch.
|
||||||
@ -388,7 +395,12 @@ pub enum TransferInvalid {
|
|||||||
/// min_deposit_amount`
|
/// min_deposit_amount`
|
||||||
///
|
///
|
||||||
/// (resulting_amount, min_deposit_amount)
|
/// (resulting_amount, min_deposit_amount)
|
||||||
InvalidResultingFromBalance(u64, u64),
|
SenderDust(u64, u64),
|
||||||
|
/// This transfer would result in the `transfer.to` account to have `0 < balance <
|
||||||
|
/// min_deposit_amount`
|
||||||
|
///
|
||||||
|
/// (resulting_amount, min_deposit_amount)
|
||||||
|
RecipientDust(u64, u64),
|
||||||
/// The state slot does not match `transfer.slot`.
|
/// The state slot does not match `transfer.slot`.
|
||||||
///
|
///
|
||||||
/// (state_slot, transfer_slot)
|
/// (state_slot, transfer_slot)
|
||||||
|
@ -9,7 +9,7 @@ pub const VALIDATOR_COUNT: usize = 10;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn valid_block_ok() {
|
fn valid_block_ok() {
|
||||||
let spec = FoundationEthSpec::spec();
|
let spec = MainnetEthSpec::default_spec();
|
||||||
let builder = get_builder(&spec);
|
let builder = get_builder(&spec);
|
||||||
let (block, mut state) = builder.build(None, None, &spec);
|
let (block, mut state) = builder.build(None, None, &spec);
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ fn valid_block_ok() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_block_header_state_slot() {
|
fn invalid_block_header_state_slot() {
|
||||||
let spec = FoundationEthSpec::spec();
|
let spec = MainnetEthSpec::default_spec();
|
||||||
let builder = get_builder(&spec);
|
let builder = get_builder(&spec);
|
||||||
let (mut block, mut state) = builder.build(None, None, &spec);
|
let (mut block, mut state) = builder.build(None, None, &spec);
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ fn invalid_block_header_state_slot() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_parent_block_root() {
|
fn invalid_parent_block_root() {
|
||||||
let spec = FoundationEthSpec::spec();
|
let spec = MainnetEthSpec::default_spec();
|
||||||
let builder = get_builder(&spec);
|
let builder = get_builder(&spec);
|
||||||
let invalid_parent_root = Hash256::from([0xAA; 32]);
|
let invalid_parent_root = Hash256::from([0xAA; 32]);
|
||||||
let (block, mut state) = builder.build(None, Some(invalid_parent_root), &spec);
|
let (block, mut state) = builder.build(None, Some(invalid_parent_root), &spec);
|
||||||
@ -59,15 +59,15 @@ fn invalid_parent_block_root() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_block_signature() {
|
fn invalid_block_signature() {
|
||||||
let spec = FoundationEthSpec::spec();
|
let spec = MainnetEthSpec::default_spec();
|
||||||
let builder = get_builder(&spec);
|
let builder = get_builder(&spec);
|
||||||
let (mut block, mut state) = builder.build(None, None, &spec);
|
let (mut block, mut state) = builder.build(None, None, &spec);
|
||||||
|
|
||||||
// sign the block with a keypair that is not the expected proposer
|
// sign the block with a keypair that is not the expected proposer
|
||||||
let keypair = Keypair::random();
|
let keypair = Keypair::random();
|
||||||
let message = block.signed_root();
|
let message = block.signed_root();
|
||||||
let epoch = block.slot.epoch(spec.slots_per_epoch);
|
let epoch = block.slot.epoch(MainnetEthSpec::slots_per_epoch());
|
||||||
let domain = spec.get_domain(epoch, Domain::BeaconBlock, &state.fork);
|
let domain = spec.get_domain(epoch, Domain::BeaconProposer, &state.fork);
|
||||||
block.signature = Signature::new(&message, domain, &keypair.sk);
|
block.signature = Signature::new(&message, domain, &keypair.sk);
|
||||||
|
|
||||||
// process block with invalid block signature
|
// process block with invalid block signature
|
||||||
@ -82,7 +82,7 @@ fn invalid_block_signature() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_randao_reveal_signature() {
|
fn invalid_randao_reveal_signature() {
|
||||||
let spec = FoundationEthSpec::spec();
|
let spec = MainnetEthSpec::default_spec();
|
||||||
let builder = get_builder(&spec);
|
let builder = get_builder(&spec);
|
||||||
|
|
||||||
// sign randao reveal with random keypair
|
// sign randao reveal with random keypair
|
||||||
@ -100,12 +100,13 @@ fn invalid_randao_reveal_signature() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_builder(spec: &ChainSpec) -> (BlockProcessingBuilder<FoundationEthSpec>) {
|
fn get_builder(spec: &ChainSpec) -> (BlockProcessingBuilder<MainnetEthSpec>) {
|
||||||
let mut builder = BlockProcessingBuilder::new(VALIDATOR_COUNT, &spec);
|
let mut builder = BlockProcessingBuilder::new(VALIDATOR_COUNT, &spec);
|
||||||
|
|
||||||
// Set the state and block to be in the last slot of the 4th epoch.
|
// 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);
|
let last_slot_of_epoch =
|
||||||
builder.set_slot(last_slot_of_epoch, &spec);
|
(MainnetEthSpec::genesis_epoch() + 4).end_slot(MainnetEthSpec::slots_per_epoch());
|
||||||
|
builder.set_slot(last_slot_of_epoch);
|
||||||
builder.build_caches(&spec);
|
builder.build_caches(&spec);
|
||||||
|
|
||||||
(builder)
|
(builder)
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
use super::errors::{AttestationInvalid as Invalid, AttestationValidationError as Error};
|
use super::errors::{AttestationInvalid as Invalid, AttestationValidationError as Error};
|
||||||
use crate::common::verify_bitfield_length;
|
use crate::common::convert_to_indexed;
|
||||||
|
use crate::per_block_processing::{
|
||||||
|
verify_indexed_attestation, verify_indexed_attestation_without_signature,
|
||||||
|
};
|
||||||
use tree_hash::TreeHash;
|
use tree_hash::TreeHash;
|
||||||
use types::*;
|
use types::*;
|
||||||
|
|
||||||
@ -8,7 +11,7 @@ use types::*;
|
|||||||
///
|
///
|
||||||
/// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity.
|
/// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn validate_attestation<T: EthSpec>(
|
pub fn validate_attestation<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
attestation: &Attestation,
|
attestation: &Attestation,
|
||||||
@ -31,7 +34,7 @@ pub fn validate_attestation_time_independent_only<T: EthSpec>(
|
|||||||
///
|
///
|
||||||
/// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity.
|
/// Returns `Ok(())` if the `Attestation` is valid, otherwise indicates the reason for invalidity.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn validate_attestation_without_signature<T: EthSpec>(
|
pub fn validate_attestation_without_signature<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
attestation: &Attestation,
|
attestation: &Attestation,
|
||||||
@ -44,7 +47,7 @@ pub fn validate_attestation_without_signature<T: EthSpec>(
|
|||||||
/// given state, optionally validating the aggregate signature.
|
/// given state, optionally validating the aggregate signature.
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
fn validate_attestation_parametric<T: EthSpec>(
|
fn validate_attestation_parametric<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
attestation: &Attestation,
|
attestation: &Attestation,
|
||||||
@ -52,107 +55,29 @@ fn validate_attestation_parametric<T: EthSpec>(
|
|||||||
verify_signature: bool,
|
verify_signature: bool,
|
||||||
time_independent_only: bool,
|
time_independent_only: bool,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Can't submit pre-historic attestations.
|
let attestation_slot = state.get_attestation_slot(&attestation.data)?;
|
||||||
verify!(
|
|
||||||
attestation.data.slot >= spec.genesis_slot,
|
|
||||||
Invalid::PreGenesis {
|
|
||||||
genesis: spec.genesis_slot,
|
|
||||||
attestation: attestation.data.slot
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Can't submit attestations too far in history.
|
// Check attestation slot.
|
||||||
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!(
|
verify!(
|
||||||
time_independent_only
|
time_independent_only
|
||||||
|| attestation.data.slot + spec.min_attestation_inclusion_delay <= state.slot,
|
|| attestation_slot + spec.min_attestation_inclusion_delay <= state.slot,
|
||||||
Invalid::IncludedTooEarly {
|
Invalid::IncludedTooEarly {
|
||||||
state: state.slot,
|
state: state.slot,
|
||||||
delay: spec.min_attestation_inclusion_delay,
|
delay: spec.min_attestation_inclusion_delay,
|
||||||
attestation: attestation.data.slot
|
attestation: attestation_slot
|
||||||
|
}
|
||||||
|
);
|
||||||
|
verify!(
|
||||||
|
state.slot <= attestation_slot + T::slots_per_epoch(),
|
||||||
|
Invalid::IncludedTooLate {
|
||||||
|
state: state.slot,
|
||||||
|
attestation: attestation_slot
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Verify the justified epoch and root is correct.
|
// Verify the Casper FFG vote.
|
||||||
if !time_independent_only {
|
if !time_independent_only {
|
||||||
verify_justified_epoch_and_root(attestation, state, spec)?;
|
verify_casper_ffg_vote(attestation, state)?;
|
||||||
}
|
|
||||||
|
|
||||||
// Check that the crosslink data is valid.
|
|
||||||
//
|
|
||||||
// Verify that either:
|
|
||||||
//
|
|
||||||
// (i)`state.latest_crosslinks[attestation.data.shard] == attestation.data.latest_crosslink`,
|
|
||||||
//
|
|
||||||
// (ii) `state.latest_crosslinks[attestation.data.shard] ==
|
|
||||||
// Crosslink(crosslink_data_root=attestation.data.crosslink_data_root,
|
|
||||||
// epoch=slot_to_epoch(attestation.data.slot))`.
|
|
||||||
let potential_crosslink = Crosslink {
|
|
||||||
crosslink_data_root: attestation.data.crosslink_data_root,
|
|
||||||
epoch: attestation.data.slot.epoch(spec.slots_per_epoch),
|
|
||||||
};
|
|
||||||
verify!(
|
|
||||||
(attestation.data.previous_crosslink
|
|
||||||
== state.latest_crosslinks[attestation.data.shard as usize])
|
|
||||||
| (state.latest_crosslinks[attestation.data.shard as usize] == potential_crosslink),
|
|
||||||
Invalid::BadPreviousCrosslink
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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, 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: committee.len(),
|
|
||||||
bitfield_len: attestation.custody_bitfield.len()
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if verify_signature {
|
|
||||||
verify_attestation_signature(state, committee, attestation, spec)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crosslink data root is zero (to be removed in phase 1).
|
// Crosslink data root is zero (to be removed in phase 1).
|
||||||
@ -161,145 +86,71 @@ fn validate_attestation_parametric<T: EthSpec>(
|
|||||||
Invalid::ShardBlockRootNotZero
|
Invalid::ShardBlockRootNotZero
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Check signature and bitfields
|
||||||
|
let indexed_attestation = convert_to_indexed(state, attestation)?;
|
||||||
|
if verify_signature {
|
||||||
|
verify_indexed_attestation(state, &indexed_attestation, spec)?;
|
||||||
|
} else {
|
||||||
|
verify_indexed_attestation_without_signature(state, &indexed_attestation, spec)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify that the `source_epoch` and `source_root` of an `Attestation` correctly
|
/// Check target epoch, source epoch, source root, and source crosslink.
|
||||||
/// match the current (or previous) justified epoch and root from the state.
|
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
fn verify_justified_epoch_and_root<T: EthSpec>(
|
fn verify_casper_ffg_vote<T: EthSpec>(
|
||||||
attestation: &Attestation,
|
attestation: &Attestation,
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let state_epoch = state.slot.epoch(spec.slots_per_epoch);
|
let data = &attestation.data;
|
||||||
let attestation_epoch = attestation.data.slot.epoch(spec.slots_per_epoch);
|
if data.target_epoch == state.current_epoch() {
|
||||||
|
|
||||||
if attestation_epoch >= state_epoch {
|
|
||||||
verify!(
|
verify!(
|
||||||
attestation.data.source_epoch == state.current_justified_epoch,
|
data.source_epoch == state.current_justified_epoch,
|
||||||
Invalid::WrongJustifiedEpoch {
|
Invalid::WrongJustifiedEpoch {
|
||||||
state: state.current_justified_epoch,
|
state: state.current_justified_epoch,
|
||||||
attestation: attestation.data.source_epoch,
|
attestation: data.source_epoch,
|
||||||
is_current: true,
|
is_current: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
verify!(
|
verify!(
|
||||||
attestation.data.source_root == state.current_justified_root,
|
data.source_root == state.current_justified_root,
|
||||||
Invalid::WrongJustifiedRoot {
|
Invalid::WrongJustifiedRoot {
|
||||||
state: state.current_justified_root,
|
state: state.current_justified_root,
|
||||||
attestation: attestation.data.source_root,
|
attestation: data.source_root,
|
||||||
is_current: true,
|
is_current: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
verify!(
|
verify!(
|
||||||
attestation.data.source_epoch == state.previous_justified_epoch,
|
data.previous_crosslink_root
|
||||||
|
== Hash256::from_slice(&state.get_current_crosslink(data.shard)?.tree_hash_root()),
|
||||||
|
Invalid::BadPreviousCrosslink
|
||||||
|
);
|
||||||
|
} else if data.target_epoch == state.previous_epoch() {
|
||||||
|
verify!(
|
||||||
|
data.source_epoch == state.previous_justified_epoch,
|
||||||
Invalid::WrongJustifiedEpoch {
|
Invalid::WrongJustifiedEpoch {
|
||||||
state: state.previous_justified_epoch,
|
state: state.previous_justified_epoch,
|
||||||
attestation: attestation.data.source_epoch,
|
attestation: data.source_epoch,
|
||||||
is_current: false,
|
is_current: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
verify!(
|
verify!(
|
||||||
attestation.data.source_root == state.previous_justified_root,
|
data.source_root == state.previous_justified_root,
|
||||||
Invalid::WrongJustifiedRoot {
|
Invalid::WrongJustifiedRoot {
|
||||||
state: state.previous_justified_root,
|
state: state.previous_justified_root,
|
||||||
attestation: attestation.data.source_root,
|
attestation: data.source_root,
|
||||||
is_current: true,
|
is_current: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Verifies an aggregate signature for some given `AttestationData`, returning `true` if the
|
|
||||||
/// `aggregate_signature` is valid.
|
|
||||||
///
|
|
||||||
/// Returns `false` if:
|
|
||||||
/// - `aggregate_signature` was not signed correctly.
|
|
||||||
/// - `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.5.1
|
|
||||||
fn verify_attestation_signature<T: EthSpec>(
|
|
||||||
state: &BeaconState<T>,
|
|
||||||
committee: &[usize],
|
|
||||||
a: &Attestation,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2];
|
|
||||||
let mut message_exists = vec![false; 2];
|
|
||||||
let attestation_epoch = a.data.slot.epoch(spec.slots_per_epoch);
|
|
||||||
|
|
||||||
for (i, v) in committee.iter().enumerate() {
|
|
||||||
let validator_signed = a.aggregation_bitfield.get(i).map_err(|_| {
|
|
||||||
Error::Invalid(Invalid::BadAggregationBitfieldLength {
|
|
||||||
committee_len: committee.len(),
|
|
||||||
bitfield_len: a.aggregation_bitfield.len(),
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if validator_signed {
|
|
||||||
let custody_bit: bool = match a.custody_bitfield.get(i) {
|
|
||||||
Ok(bit) => bit,
|
|
||||||
// Invalidate signature if custody_bitfield.len() < committee
|
|
||||||
Err(_) => {
|
|
||||||
return Err(Error::Invalid(Invalid::BadCustodyBitfieldLength {
|
|
||||||
committee_len: committee.len(),
|
|
||||||
bitfield_len: a.aggregation_bitfield.len(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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`
|
|
||||||
let message_0 = AttestationDataAndCustodyBit {
|
|
||||||
data: a.data.clone(),
|
|
||||||
custody_bit: false,
|
|
||||||
}
|
|
||||||
.tree_hash_root();
|
|
||||||
|
|
||||||
// Message when custody bitfield is `true`
|
|
||||||
let message_1 = AttestationDataAndCustodyBit {
|
|
||||||
data: a.data.clone(),
|
|
||||||
custody_bit: true,
|
|
||||||
}
|
|
||||||
.tree_hash_root();
|
|
||||||
|
|
||||||
let mut messages = vec![];
|
|
||||||
let mut keys = vec![];
|
|
||||||
|
|
||||||
// If any validator signed a message with a `false` custody bit.
|
|
||||||
if message_exists[0] {
|
|
||||||
messages.push(&message_0[..]);
|
|
||||||
keys.push(&aggregate_pubs[0]);
|
|
||||||
}
|
|
||||||
// If any validator signed a message with a `true` custody bit.
|
|
||||||
if message_exists[1] {
|
|
||||||
messages.push(&message_1[..]);
|
|
||||||
keys.push(&aggregate_pubs[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let domain = spec.get_domain(attestation_epoch, Domain::Attestation, &state.fork);
|
|
||||||
|
|
||||||
verify!(
|
verify!(
|
||||||
a.aggregate_signature
|
data.previous_crosslink_root
|
||||||
.verify_multiple(&messages[..], domain, &keys[..]),
|
== Hash256::from_slice(&state.get_previous_crosslink(data.shard)?.tree_hash_root()),
|
||||||
Invalid::BadSignature
|
Invalid::BadPreviousCrosslink
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
invalid!(Invalid::BadTargetEpoch)
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use super::errors::{AttesterSlashingInvalid as Invalid, AttesterSlashingValidationError as Error};
|
use super::errors::{AttesterSlashingInvalid as Invalid, AttesterSlashingValidationError as Error};
|
||||||
use super::verify_slashable_attestation::verify_slashable_attestation;
|
use super::verify_indexed_attestation::verify_indexed_attestation;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
use types::*;
|
use types::*;
|
||||||
|
|
||||||
/// Indicates if an `AttesterSlashing` is valid to be included in a block in the current epoch of the given
|
/// Indicates if an `AttesterSlashing` is valid to be included in a block in the current epoch of the given
|
||||||
@ -7,90 +8,87 @@ use types::*;
|
|||||||
///
|
///
|
||||||
/// Returns `Ok(())` if the `AttesterSlashing` is valid, otherwise indicates the reason for invalidity.
|
/// Returns `Ok(())` if the `AttesterSlashing` is valid, otherwise indicates the reason for invalidity.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn verify_attester_slashing<T: EthSpec>(
|
pub fn verify_attester_slashing<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
attester_slashing: &AttesterSlashing,
|
attester_slashing: &AttesterSlashing,
|
||||||
should_verify_slashable_attestations: bool,
|
should_verify_indexed_attestations: bool,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let slashable_attestation_1 = &attester_slashing.slashable_attestation_1;
|
let attestation_1 = &attester_slashing.attestation_1;
|
||||||
let slashable_attestation_2 = &attester_slashing.slashable_attestation_2;
|
let attestation_2 = &attester_slashing.attestation_2;
|
||||||
|
|
||||||
|
// Spec: is_slashable_attestation_data
|
||||||
verify!(
|
verify!(
|
||||||
slashable_attestation_1.data != slashable_attestation_2.data,
|
attestation_1.is_double_vote(attestation_2)
|
||||||
Invalid::AttestationDataIdentical
|
|| attestation_1.is_surround_vote(attestation_2),
|
||||||
);
|
|
||||||
verify!(
|
|
||||||
slashable_attestation_1.is_double_vote(slashable_attestation_2, spec)
|
|
||||||
| slashable_attestation_1.is_surround_vote(slashable_attestation_2, spec),
|
|
||||||
Invalid::NotSlashable
|
Invalid::NotSlashable
|
||||||
);
|
);
|
||||||
|
|
||||||
if should_verify_slashable_attestations {
|
if should_verify_indexed_attestations {
|
||||||
verify_slashable_attestation(state, &slashable_attestation_1, spec)
|
verify_indexed_attestation(state, &attestation_1, spec)
|
||||||
.map_err(|e| Error::Invalid(Invalid::SlashableAttestation1Invalid(e.into())))?;
|
.map_err(|e| Error::Invalid(Invalid::IndexedAttestation1Invalid(e.into())))?;
|
||||||
verify_slashable_attestation(state, &slashable_attestation_2, spec)
|
verify_indexed_attestation(state, &attestation_2, spec)
|
||||||
.map_err(|e| Error::Invalid(Invalid::SlashableAttestation2Invalid(e.into())))?;
|
.map_err(|e| Error::Invalid(Invalid::IndexedAttestation2Invalid(e.into())))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// For a given attester slashing, return the indices able to be slashed.
|
/// For a given attester slashing, return the indices able to be slashed in ascending order.
|
||||||
///
|
///
|
||||||
/// Returns Ok(indices) if `indices.len() > 0`.
|
/// Returns Ok(indices) if `indices.len() > 0`.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn gather_attester_slashing_indices<T: EthSpec>(
|
pub fn get_slashable_indices<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
attester_slashing: &AttesterSlashing,
|
attester_slashing: &AttesterSlashing,
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Result<Vec<u64>, Error> {
|
) -> Result<Vec<u64>, Error> {
|
||||||
gather_attester_slashing_indices_modular(
|
get_slashable_indices_modular(state, attester_slashing, |_, validator| {
|
||||||
state,
|
validator.is_slashable_at(state.current_epoch())
|
||||||
attester_slashing,
|
})
|
||||||
|_, validator| validator.slashed,
|
|
||||||
spec,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Same as `gather_attester_slashing_indices` but allows the caller to specify the criteria
|
/// Same as `gather_attester_slashing_indices` but allows the caller to specify the criteria
|
||||||
/// for determining whether a given validator should be considered slashed.
|
/// for determining whether a given validator should be considered slashable.
|
||||||
pub fn gather_attester_slashing_indices_modular<F, T: EthSpec>(
|
pub fn get_slashable_indices_modular<F, T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
attester_slashing: &AttesterSlashing,
|
attester_slashing: &AttesterSlashing,
|
||||||
is_slashed: F,
|
is_slashable: F,
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Result<Vec<u64>, Error>
|
) -> Result<Vec<u64>, Error>
|
||||||
where
|
where
|
||||||
F: Fn(u64, &Validator) -> bool,
|
F: Fn(u64, &Validator) -> bool,
|
||||||
{
|
{
|
||||||
let slashable_attestation_1 = &attester_slashing.slashable_attestation_1;
|
let attestation_1 = &attester_slashing.attestation_1;
|
||||||
let slashable_attestation_2 = &attester_slashing.slashable_attestation_2;
|
let attestation_2 = &attester_slashing.attestation_2;
|
||||||
|
|
||||||
let mut slashable_indices = Vec::with_capacity(spec.max_indices_per_slashable_vote);
|
let attesting_indices_1 = attestation_1
|
||||||
for i in &slashable_attestation_1.validator_indices {
|
.custody_bit_0_indices
|
||||||
|
.iter()
|
||||||
|
.chain(&attestation_1.custody_bit_1_indices)
|
||||||
|
.cloned()
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
let attesting_indices_2 = attestation_2
|
||||||
|
.custody_bit_0_indices
|
||||||
|
.iter()
|
||||||
|
.chain(&attestation_2.custody_bit_1_indices)
|
||||||
|
.cloned()
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
|
||||||
|
let mut slashable_indices = vec![];
|
||||||
|
|
||||||
|
for index in &attesting_indices_1 & &attesting_indices_2 {
|
||||||
let validator = state
|
let validator = state
|
||||||
.validator_registry
|
.validator_registry
|
||||||
.get(*i as usize)
|
.get(index as usize)
|
||||||
.ok_or_else(|| Error::Invalid(Invalid::UnknownValidator(*i)))?;
|
.ok_or_else(|| Error::Invalid(Invalid::UnknownValidator(index)))?;
|
||||||
|
|
||||||
if slashable_attestation_2.validator_indices.contains(&i) & !is_slashed(*i, validator) {
|
if is_slashable(index, validator) {
|
||||||
// TODO: verify that we should reject any slashable attestation which includes a
|
slashable_indices.push(index);
|
||||||
// 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);
|
verify!(!slashable_indices.is_empty(), Invalid::NoSlashableIndices);
|
||||||
|
|
||||||
slashable_indices.shrink_to_fit();
|
|
||||||
|
|
||||||
Ok(slashable_indices)
|
Ok(slashable_indices)
|
||||||
}
|
}
|
||||||
|
@ -1,52 +1,31 @@
|
|||||||
use super::errors::{DepositInvalid as Invalid, DepositValidationError as Error};
|
use super::errors::{DepositInvalid as Invalid, DepositValidationError as Error};
|
||||||
use hashing::hash;
|
|
||||||
use merkle_proof::verify_merkle_proof;
|
use merkle_proof::verify_merkle_proof;
|
||||||
use ssz::ssz_encode;
|
use tree_hash::{SignedRoot, TreeHash};
|
||||||
use ssz_derive::Encode;
|
|
||||||
use types::*;
|
use types::*;
|
||||||
|
|
||||||
/// Indicates if a `Deposit` is valid to be included in a block in the current epoch of the given
|
/// Verify `Deposit.pubkey` signed `Deposit.signature`.
|
||||||
/// state.
|
|
||||||
///
|
///
|
||||||
/// Returns `Ok(())` if the `Deposit` is valid, otherwise indicates the reason for invalidity.
|
/// Spec v0.6.3
|
||||||
///
|
pub fn verify_deposit_signature<T: EthSpec>(
|
||||||
/// 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.5.1
|
|
||||||
pub fn verify_deposit<T: EthSpec>(
|
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
deposit: &Deposit,
|
deposit: &Deposit,
|
||||||
verify_merkle_branch: bool,
|
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
verify!(
|
verify!(
|
||||||
deposit
|
deposit.data.signature.verify(
|
||||||
.deposit_data
|
&deposit.data.signed_root(),
|
||||||
.deposit_input
|
spec.get_domain(state.current_epoch(), Domain::Deposit, &state.fork),
|
||||||
.validate_proof_of_possession(
|
&deposit.data.pubkey,
|
||||||
state.slot.epoch(spec.slots_per_epoch),
|
|
||||||
&state.fork,
|
|
||||||
spec
|
|
||||||
),
|
),
|
||||||
Invalid::BadProofOfPossession
|
Invalid::BadSignature
|
||||||
);
|
);
|
||||||
|
|
||||||
if verify_merkle_branch {
|
|
||||||
verify!(
|
|
||||||
verify_deposit_merkle_proof(state, deposit, spec),
|
|
||||||
Invalid::BadMerkleProof
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify that the `Deposit` index is correct.
|
/// Verify that the `Deposit` index is correct.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn verify_deposit_index<T: EthSpec>(
|
pub fn verify_deposit_index<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
deposit: &Deposit,
|
deposit: &Deposit,
|
||||||
@ -72,61 +51,30 @@ pub fn get_existing_validator_index<T: EthSpec>(
|
|||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
deposit: &Deposit,
|
deposit: &Deposit,
|
||||||
) -> Result<Option<u64>, Error> {
|
) -> Result<Option<u64>, Error> {
|
||||||
let deposit_input = &deposit.deposit_data.deposit_input;
|
let validator_index = state.get_validator_index(&deposit.data.pubkey)?;
|
||||||
|
Ok(validator_index.map(|idx| idx as u64))
|
||||||
let validator_index = state.get_validator_index(&deposit_input.pubkey)?;
|
|
||||||
|
|
||||||
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 as u64))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify that a deposit is included in the state's eth1 deposit root.
|
/// Verify that a deposit is included in the state's eth1 deposit root.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
fn verify_deposit_merkle_proof<T: EthSpec>(
|
pub fn verify_deposit_merkle_proof<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
deposit: &Deposit,
|
deposit: &Deposit,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> bool {
|
) -> Result<(), Error> {
|
||||||
let leaf = hash(&get_serialized_deposit_data(deposit));
|
let leaf = deposit.data.tree_hash_root();
|
||||||
|
|
||||||
|
verify!(
|
||||||
verify_merkle_proof(
|
verify_merkle_proof(
|
||||||
Hash256::from_slice(&leaf),
|
Hash256::from_slice(&leaf),
|
||||||
&deposit.proof[..],
|
&deposit.proof[..],
|
||||||
spec.deposit_contract_tree_depth as usize,
|
spec.deposit_contract_tree_depth as usize,
|
||||||
deposit.index as usize,
|
deposit.index as usize,
|
||||||
state.latest_eth1_data.deposit_root,
|
state.latest_eth1_data.deposit_root,
|
||||||
)
|
),
|
||||||
}
|
Invalid::BadMerkleProof
|
||||||
|
);
|
||||||
|
|
||||||
/// Helper struct for easily getting the serialized data generated by the deposit contract.
|
Ok(())
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
#[derive(Encode)]
|
|
||||||
struct SerializedDepositData {
|
|
||||||
amount: u64,
|
|
||||||
timestamp: u64,
|
|
||||||
input: DepositInput,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the serialized data generated by the deposit contract that is used to generate the
|
|
||||||
/// merkle proof.
|
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
fn get_serialized_deposit_data(deposit: &Deposit) -> Vec<u8> {
|
|
||||||
let serialized_deposit_data = SerializedDepositData {
|
|
||||||
amount: deposit.deposit_data.amount,
|
|
||||||
timestamp: deposit.deposit_data.timestamp,
|
|
||||||
input: deposit.deposit_data.deposit_input.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
ssz_encode(&serialized_deposit_data)
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ use types::*;
|
|||||||
///
|
///
|
||||||
/// Returns `Ok(())` if the `Exit` is valid, otherwise indicates the reason for invalidity.
|
/// Returns `Ok(())` if the `Exit` is valid, otherwise indicates the reason for invalidity.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn verify_exit<T: EthSpec>(
|
pub fn verify_exit<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
exit: &VoluntaryExit,
|
exit: &VoluntaryExit,
|
||||||
@ -17,6 +17,8 @@ pub fn verify_exit<T: EthSpec>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Like `verify_exit` but doesn't run checks which may become true in future states.
|
/// Like `verify_exit` but doesn't run checks which may become true in future states.
|
||||||
|
///
|
||||||
|
/// Spec v0.6.3
|
||||||
pub fn verify_exit_time_independent_only<T: EthSpec>(
|
pub fn verify_exit_time_independent_only<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
exit: &VoluntaryExit,
|
exit: &VoluntaryExit,
|
||||||
@ -26,6 +28,8 @@ pub fn verify_exit_time_independent_only<T: EthSpec>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parametric version of `verify_exit` that skips some checks if `time_independent_only` is true.
|
/// Parametric version of `verify_exit` that skips some checks if `time_independent_only` is true.
|
||||||
|
///
|
||||||
|
/// Spec v0.6.3
|
||||||
fn verify_exit_parametric<T: EthSpec>(
|
fn verify_exit_parametric<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
exit: &VoluntaryExit,
|
exit: &VoluntaryExit,
|
||||||
@ -37,29 +41,29 @@ fn verify_exit_parametric<T: EthSpec>(
|
|||||||
.get(exit.validator_index as usize)
|
.get(exit.validator_index as usize)
|
||||||
.ok_or_else(|| Error::Invalid(Invalid::ValidatorUnknown(exit.validator_index)))?;
|
.ok_or_else(|| Error::Invalid(Invalid::ValidatorUnknown(exit.validator_index)))?;
|
||||||
|
|
||||||
|
// Verify the validator is active.
|
||||||
|
verify!(
|
||||||
|
validator.is_active_at(state.current_epoch()),
|
||||||
|
Invalid::NotActive(exit.validator_index)
|
||||||
|
);
|
||||||
|
|
||||||
// Verify that the validator has not yet exited.
|
// Verify that the validator has not yet exited.
|
||||||
verify!(
|
verify!(
|
||||||
validator.exit_epoch == spec.far_future_epoch,
|
validator.exit_epoch == spec.far_future_epoch,
|
||||||
Invalid::AlreadyExited(exit.validator_index)
|
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.
|
// Exits must specify an epoch when they become valid; they are not valid before then.
|
||||||
verify!(
|
verify!(
|
||||||
time_independent_only || state.current_epoch(spec) >= exit.epoch,
|
time_independent_only || state.current_epoch() >= exit.epoch,
|
||||||
Invalid::FutureEpoch {
|
Invalid::FutureEpoch {
|
||||||
state: state.current_epoch(spec),
|
state: state.current_epoch(),
|
||||||
exit: exit.epoch
|
exit: exit.epoch
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Must have been in the validator set long enough.
|
// Verify the validator has been active long enough.
|
||||||
let lifespan = state.slot.epoch(spec.slots_per_epoch) - validator.activation_epoch;
|
let lifespan = state.current_epoch() - validator.activation_epoch;
|
||||||
verify!(
|
verify!(
|
||||||
lifespan >= spec.persistent_committee_period,
|
lifespan >= spec.persistent_committee_period,
|
||||||
Invalid::TooYoungToLeave {
|
Invalid::TooYoungToLeave {
|
||||||
@ -68,9 +72,9 @@ fn verify_exit_parametric<T: EthSpec>(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Verify signature.
|
||||||
let message = exit.signed_root();
|
let message = exit.signed_root();
|
||||||
let domain = spec.get_domain(exit.epoch, Domain::Exit, &state.fork);
|
let domain = spec.get_domain(exit.epoch, Domain::VoluntaryExit, &state.fork);
|
||||||
|
|
||||||
verify!(
|
verify!(
|
||||||
exit.signature
|
exit.signature
|
||||||
.verify(&message[..], domain, &validator.pubkey),
|
.verify(&message[..], domain, &validator.pubkey),
|
||||||
|
@ -0,0 +1,156 @@
|
|||||||
|
use super::errors::{
|
||||||
|
IndexedAttestationInvalid as Invalid, IndexedAttestationValidationError as Error,
|
||||||
|
};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::iter::FromIterator;
|
||||||
|
use tree_hash::TreeHash;
|
||||||
|
use types::*;
|
||||||
|
|
||||||
|
/// Verify an `IndexedAttestation`.
|
||||||
|
///
|
||||||
|
/// Spec v0.6.3
|
||||||
|
pub fn verify_indexed_attestation<T: EthSpec>(
|
||||||
|
state: &BeaconState<T>,
|
||||||
|
indexed_attestation: &IndexedAttestation,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
verify_indexed_attestation_parametric(state, indexed_attestation, spec, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify but don't check the signature.
|
||||||
|
///
|
||||||
|
/// Spec v0.6.3
|
||||||
|
pub fn verify_indexed_attestation_without_signature<T: EthSpec>(
|
||||||
|
state: &BeaconState<T>,
|
||||||
|
indexed_attestation: &IndexedAttestation,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
verify_indexed_attestation_parametric(state, indexed_attestation, spec, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optionally check the signature.
|
||||||
|
///
|
||||||
|
/// Spec v0.6.3
|
||||||
|
fn verify_indexed_attestation_parametric<T: EthSpec>(
|
||||||
|
state: &BeaconState<T>,
|
||||||
|
indexed_attestation: &IndexedAttestation,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
verify_signature: bool,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let custody_bit_0_indices = &indexed_attestation.custody_bit_0_indices;
|
||||||
|
let custody_bit_1_indices = &indexed_attestation.custody_bit_1_indices;
|
||||||
|
|
||||||
|
// Ensure no duplicate indices across custody bits
|
||||||
|
let custody_bit_intersection: HashSet<&u64> =
|
||||||
|
&HashSet::from_iter(custody_bit_0_indices) & &HashSet::from_iter(custody_bit_1_indices);
|
||||||
|
verify!(
|
||||||
|
custody_bit_intersection.is_empty(),
|
||||||
|
Invalid::CustodyBitValidatorsIntersect
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that nobody signed with custody bit 1 (to be removed in phase 1)
|
||||||
|
if !custody_bit_1_indices.is_empty() {
|
||||||
|
invalid!(Invalid::CustodyBitfieldHasSetBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
let total_indices = custody_bit_0_indices.len() + custody_bit_1_indices.len();
|
||||||
|
verify!(1 <= total_indices, Invalid::NoValidatorIndices);
|
||||||
|
verify!(
|
||||||
|
total_indices as u64 <= spec.max_indices_per_attestation,
|
||||||
|
Invalid::MaxIndicesExceed(spec.max_indices_per_attestation, total_indices)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that both vectors of indices are sorted
|
||||||
|
let check_sorted = |list: &Vec<u64>| {
|
||||||
|
list.windows(2).enumerate().try_for_each(|(i, pair)| {
|
||||||
|
if pair[0] >= pair[1] {
|
||||||
|
invalid!(Invalid::BadValidatorIndicesOrdering(i));
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
check_sorted(custody_bit_0_indices)?;
|
||||||
|
check_sorted(custody_bit_1_indices)?;
|
||||||
|
|
||||||
|
if verify_signature {
|
||||||
|
verify_indexed_attestation_signature(state, indexed_attestation, spec)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an aggregate public key for a list of validators, failing if any key can't be found.
|
||||||
|
fn create_aggregate_pubkey<'a, T, I>(
|
||||||
|
state: &BeaconState<T>,
|
||||||
|
validator_indices: I,
|
||||||
|
) -> Result<AggregatePublicKey, Error>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = &'a u64>,
|
||||||
|
T: EthSpec,
|
||||||
|
{
|
||||||
|
validator_indices.into_iter().try_fold(
|
||||||
|
AggregatePublicKey::new(),
|
||||||
|
|mut aggregate_pubkey, &validator_idx| {
|
||||||
|
state
|
||||||
|
.validator_registry
|
||||||
|
.get(validator_idx as usize)
|
||||||
|
.ok_or_else(|| Error::Invalid(Invalid::UnknownValidator(validator_idx)))
|
||||||
|
.map(|validator| {
|
||||||
|
aggregate_pubkey.add(&validator.pubkey);
|
||||||
|
aggregate_pubkey
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify the signature of an IndexedAttestation.
|
||||||
|
///
|
||||||
|
/// Spec v0.6.3
|
||||||
|
fn verify_indexed_attestation_signature<T: EthSpec>(
|
||||||
|
state: &BeaconState<T>,
|
||||||
|
indexed_attestation: &IndexedAttestation,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let bit_0_pubkey = create_aggregate_pubkey(state, &indexed_attestation.custody_bit_0_indices)?;
|
||||||
|
let bit_1_pubkey = create_aggregate_pubkey(state, &indexed_attestation.custody_bit_1_indices)?;
|
||||||
|
|
||||||
|
let message_0 = AttestationDataAndCustodyBit {
|
||||||
|
data: indexed_attestation.data.clone(),
|
||||||
|
custody_bit: false,
|
||||||
|
}
|
||||||
|
.tree_hash_root();
|
||||||
|
let message_1 = AttestationDataAndCustodyBit {
|
||||||
|
data: indexed_attestation.data.clone(),
|
||||||
|
custody_bit: true,
|
||||||
|
}
|
||||||
|
.tree_hash_root();
|
||||||
|
|
||||||
|
let mut messages = vec![];
|
||||||
|
let mut keys = vec![];
|
||||||
|
|
||||||
|
if !indexed_attestation.custody_bit_0_indices.is_empty() {
|
||||||
|
messages.push(&message_0[..]);
|
||||||
|
keys.push(&bit_0_pubkey);
|
||||||
|
}
|
||||||
|
if !indexed_attestation.custody_bit_1_indices.is_empty() {
|
||||||
|
messages.push(&message_1[..]);
|
||||||
|
keys.push(&bit_1_pubkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
let domain = spec.get_domain(
|
||||||
|
indexed_attestation.data.target_epoch,
|
||||||
|
Domain::Attestation,
|
||||||
|
&state.fork,
|
||||||
|
);
|
||||||
|
|
||||||
|
verify!(
|
||||||
|
indexed_attestation
|
||||||
|
.signature
|
||||||
|
.verify_multiple(&messages[..], domain, &keys[..]),
|
||||||
|
Invalid::BadSignature
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -7,7 +7,7 @@ use types::*;
|
|||||||
///
|
///
|
||||||
/// Returns `Ok(())` if the `ProposerSlashing` is valid, otherwise indicates the reason for invalidity.
|
/// Returns `Ok(())` if the `ProposerSlashing` is valid, otherwise indicates the reason for invalidity.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn verify_proposer_slashing<T: EthSpec>(
|
pub fn verify_proposer_slashing<T: EthSpec>(
|
||||||
proposer_slashing: &ProposerSlashing,
|
proposer_slashing: &ProposerSlashing,
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
@ -21,8 +21,8 @@ pub fn verify_proposer_slashing<T: EthSpec>(
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
verify!(
|
verify!(
|
||||||
proposer_slashing.header_1.slot.epoch(spec.slots_per_epoch)
|
proposer_slashing.header_1.slot.epoch(T::slots_per_epoch())
|
||||||
== proposer_slashing.header_2.slot.epoch(spec.slots_per_epoch),
|
== proposer_slashing.header_2.slot.epoch(T::slots_per_epoch()),
|
||||||
Invalid::ProposalEpochMismatch(
|
Invalid::ProposalEpochMismatch(
|
||||||
proposer_slashing.header_1.slot,
|
proposer_slashing.header_1.slot,
|
||||||
proposer_slashing.header_2.slot
|
proposer_slashing.header_2.slot
|
||||||
@ -34,15 +34,13 @@ pub fn verify_proposer_slashing<T: EthSpec>(
|
|||||||
Invalid::ProposalsIdentical
|
Invalid::ProposalsIdentical
|
||||||
);
|
);
|
||||||
|
|
||||||
verify!(!proposer.slashed, Invalid::ProposerAlreadySlashed);
|
|
||||||
|
|
||||||
verify!(
|
verify!(
|
||||||
proposer.withdrawable_epoch > state.slot.epoch(spec.slots_per_epoch),
|
proposer.is_slashable_at(state.current_epoch()),
|
||||||
Invalid::ProposerAlreadyWithdrawn(proposer_slashing.proposer_index)
|
Invalid::ProposerNotSlashable(proposer_slashing.proposer_index)
|
||||||
);
|
);
|
||||||
|
|
||||||
verify!(
|
verify!(
|
||||||
verify_header_signature(
|
verify_header_signature::<T>(
|
||||||
&proposer_slashing.header_1,
|
&proposer_slashing.header_1,
|
||||||
&proposer.pubkey,
|
&proposer.pubkey,
|
||||||
&state.fork,
|
&state.fork,
|
||||||
@ -51,7 +49,7 @@ pub fn verify_proposer_slashing<T: EthSpec>(
|
|||||||
Invalid::BadProposal1Signature
|
Invalid::BadProposal1Signature
|
||||||
);
|
);
|
||||||
verify!(
|
verify!(
|
||||||
verify_header_signature(
|
verify_header_signature::<T>(
|
||||||
&proposer_slashing.header_2,
|
&proposer_slashing.header_2,
|
||||||
&proposer.pubkey,
|
&proposer.pubkey,
|
||||||
&state.fork,
|
&state.fork,
|
||||||
@ -67,8 +65,8 @@ pub fn verify_proposer_slashing<T: EthSpec>(
|
|||||||
///
|
///
|
||||||
/// Returns `true` if the signature is valid.
|
/// Returns `true` if the signature is valid.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
fn verify_header_signature(
|
fn verify_header_signature<T: EthSpec>(
|
||||||
header: &BeaconBlockHeader,
|
header: &BeaconBlockHeader,
|
||||||
pubkey: &PublicKey,
|
pubkey: &PublicKey,
|
||||||
fork: &Fork,
|
fork: &Fork,
|
||||||
@ -76,8 +74,8 @@ fn verify_header_signature(
|
|||||||
) -> bool {
|
) -> bool {
|
||||||
let message = header.signed_root();
|
let message = header.signed_root();
|
||||||
let domain = spec.get_domain(
|
let domain = spec.get_domain(
|
||||||
header.slot.epoch(spec.slots_per_epoch),
|
header.slot.epoch(T::slots_per_epoch()),
|
||||||
Domain::BeaconBlock,
|
Domain::BeaconProposer,
|
||||||
fork,
|
fork,
|
||||||
);
|
);
|
||||||
header.signature.verify(&message[..], domain, pubkey)
|
header.signature.verify(&message[..], domain, pubkey)
|
||||||
|
@ -1,112 +0,0 @@
|
|||||||
use super::errors::{
|
|
||||||
SlashableAttestationInvalid as Invalid, SlashableAttestationValidationError as Error,
|
|
||||||
};
|
|
||||||
use crate::common::verify_bitfield_length;
|
|
||||||
use tree_hash::TreeHash;
|
|
||||||
use types::*;
|
|
||||||
|
|
||||||
/// Indicates if a `SlashableAttestation` is valid to be included in a block in the current epoch of the given
|
|
||||||
/// state.
|
|
||||||
///
|
|
||||||
/// Returns `Ok(())` if the `SlashableAttestation` is valid, otherwise indicates the reason for invalidity.
|
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
pub fn verify_slashable_attestation<T: EthSpec>(
|
|
||||||
state: &BeaconState<T>,
|
|
||||||
slashable_attestation: &SlashableAttestation,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
if slashable_attestation.custody_bitfield.num_set_bits() > 0 {
|
|
||||||
invalid!(Invalid::CustodyBitfieldHasSetBits);
|
|
||||||
}
|
|
||||||
|
|
||||||
if slashable_attestation.validator_indices.is_empty() {
|
|
||||||
invalid!(Invalid::NoValidatorIndices);
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in 0..(slashable_attestation.validator_indices.len() - 1) {
|
|
||||||
if slashable_attestation.validator_indices[i]
|
|
||||||
>= slashable_attestation.validator_indices[i + 1]
|
|
||||||
{
|
|
||||||
invalid!(Invalid::BadValidatorIndicesOrdering(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !verify_bitfield_length(
|
|
||||||
&slashable_attestation.custody_bitfield,
|
|
||||||
slashable_attestation.validator_indices.len(),
|
|
||||||
) {
|
|
||||||
invalid!(Invalid::BadCustodyBitfieldLength(
|
|
||||||
slashable_attestation.validator_indices.len(),
|
|
||||||
slashable_attestation.custody_bitfield.len()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if slashable_attestation.validator_indices.len() > spec.max_indices_per_slashable_vote as usize
|
|
||||||
{
|
|
||||||
invalid!(Invalid::MaxIndicesExceed(
|
|
||||||
spec.max_indices_per_slashable_vote as usize,
|
|
||||||
slashable_attestation.validator_indices.len()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: this signature verification could likely be replaced with:
|
|
||||||
//
|
|
||||||
// super::validate_attestation::validate_attestation_signature(..)
|
|
||||||
|
|
||||||
let mut aggregate_pubs = vec![AggregatePublicKey::new(); 2];
|
|
||||||
let mut message_exists = vec![false; 2];
|
|
||||||
|
|
||||||
for (i, v) in slashable_attestation.validator_indices.iter().enumerate() {
|
|
||||||
let custody_bit = match slashable_attestation.custody_bitfield.get(i) {
|
|
||||||
Ok(bit) => bit,
|
|
||||||
Err(_) => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
None => invalid!(Invalid::UnknownValidator(*v)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let message_0 = AttestationDataAndCustodyBit {
|
|
||||||
data: slashable_attestation.data.clone(),
|
|
||||||
custody_bit: false,
|
|
||||||
}
|
|
||||||
.tree_hash_root();
|
|
||||||
let message_1 = AttestationDataAndCustodyBit {
|
|
||||||
data: slashable_attestation.data.clone(),
|
|
||||||
custody_bit: true,
|
|
||||||
}
|
|
||||||
.tree_hash_root();
|
|
||||||
|
|
||||||
let mut messages = vec![];
|
|
||||||
let mut keys = vec![];
|
|
||||||
|
|
||||||
if message_exists[0] {
|
|
||||||
messages.push(&message_0[..]);
|
|
||||||
keys.push(&aggregate_pubs[0]);
|
|
||||||
}
|
|
||||||
if message_exists[1] {
|
|
||||||
messages.push(&message_1[..]);
|
|
||||||
keys.push(&aggregate_pubs[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let domain = {
|
|
||||||
let epoch = slashable_attestation.data.slot.epoch(spec.slots_per_epoch);
|
|
||||||
spec.get_domain(epoch, Domain::Attestation, &state.fork)
|
|
||||||
};
|
|
||||||
|
|
||||||
verify!(
|
|
||||||
slashable_attestation
|
|
||||||
.aggregate_signature
|
|
||||||
.verify_multiple(&messages[..], domain, &keys[..]),
|
|
||||||
Invalid::BadSignature
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -8,9 +8,7 @@ use types::*;
|
|||||||
///
|
///
|
||||||
/// Returns `Ok(())` if the `Transfer` is valid, otherwise indicates the reason for invalidity.
|
/// Returns `Ok(())` if the `Transfer` is valid, otherwise indicates the reason for invalidity.
|
||||||
///
|
///
|
||||||
/// Note: this function is incomplete.
|
/// Spec v0.6.3
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
pub fn verify_transfer<T: EthSpec>(
|
pub fn verify_transfer<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
transfer: &Transfer,
|
transfer: &Transfer,
|
||||||
@ -20,6 +18,8 @@ pub fn verify_transfer<T: EthSpec>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Like `verify_transfer` but doesn't run checks which may become true in future states.
|
/// Like `verify_transfer` but doesn't run checks which may become true in future states.
|
||||||
|
///
|
||||||
|
/// Spec v0.6.3
|
||||||
pub fn verify_transfer_time_independent_only<T: EthSpec>(
|
pub fn verify_transfer_time_independent_only<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
transfer: &Transfer,
|
transfer: &Transfer,
|
||||||
@ -29,6 +29,15 @@ pub fn verify_transfer_time_independent_only<T: EthSpec>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parametric version of `verify_transfer` that allows some checks to be skipped.
|
/// Parametric version of `verify_transfer` that allows some checks to be skipped.
|
||||||
|
///
|
||||||
|
/// When `time_independent_only == true`, time-specific parameters are ignored, including:
|
||||||
|
///
|
||||||
|
/// - Balance considerations (e.g., adequate balance, not dust, etc).
|
||||||
|
/// - `transfer.slot` does not have to exactly match `state.slot`, it just needs to be in the
|
||||||
|
/// present or future.
|
||||||
|
/// - Validator transfer eligibility (e.g., is withdrawable)
|
||||||
|
///
|
||||||
|
/// Spec v0.6.3
|
||||||
fn verify_transfer_parametric<T: EthSpec>(
|
fn verify_transfer_parametric<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
transfer: &Transfer,
|
transfer: &Transfer,
|
||||||
@ -36,35 +45,44 @@ fn verify_transfer_parametric<T: EthSpec>(
|
|||||||
time_independent_only: bool,
|
time_independent_only: bool,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let sender_balance = *state
|
let sender_balance = *state
|
||||||
.validator_balances
|
.balances
|
||||||
.get(transfer.sender as usize)
|
.get(transfer.sender as usize)
|
||||||
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
|
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
|
||||||
|
|
||||||
|
let recipient_balance = *state
|
||||||
|
.balances
|
||||||
|
.get(transfer.recipient as usize)
|
||||||
|
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.recipient)))?;
|
||||||
|
|
||||||
|
// Safely determine `amount + fee`.
|
||||||
let total_amount = transfer
|
let total_amount = transfer
|
||||||
.amount
|
.amount
|
||||||
.checked_add(transfer.fee)
|
.checked_add(transfer.fee)
|
||||||
.ok_or_else(|| Error::Invalid(Invalid::FeeOverflow(transfer.amount, transfer.fee)))?;
|
.ok_or_else(|| Error::Invalid(Invalid::FeeOverflow(transfer.amount, transfer.fee)))?;
|
||||||
|
|
||||||
|
// Verify the sender has adequate balance.
|
||||||
verify!(
|
verify!(
|
||||||
time_independent_only || sender_balance >= transfer.amount,
|
time_independent_only || sender_balance >= transfer.amount,
|
||||||
Invalid::FromBalanceInsufficient(transfer.amount, sender_balance)
|
Invalid::FromBalanceInsufficient(transfer.amount, sender_balance)
|
||||||
);
|
);
|
||||||
|
|
||||||
verify!(
|
// Verify sender balance will not be "dust" (i.e., greater than zero but less than the minimum deposit
|
||||||
time_independent_only || sender_balance >= transfer.fee,
|
// amount).
|
||||||
Invalid::FromBalanceInsufficient(transfer.fee, sender_balance)
|
|
||||||
);
|
|
||||||
|
|
||||||
verify!(
|
verify!(
|
||||||
time_independent_only
|
time_independent_only
|
||||||
|| (sender_balance == total_amount)
|
|| (sender_balance == total_amount)
|
||||||
|| (sender_balance >= (total_amount + spec.min_deposit_amount)),
|
|| (sender_balance >= (total_amount + spec.min_deposit_amount)),
|
||||||
Invalid::InvalidResultingFromBalance(
|
Invalid::SenderDust(sender_balance - total_amount, spec.min_deposit_amount)
|
||||||
sender_balance - total_amount,
|
|
||||||
spec.min_deposit_amount
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Verify the recipient balance will not be dust.
|
||||||
|
verify!(
|
||||||
|
time_independent_only || ((recipient_balance + transfer.amount) >= spec.min_deposit_amount),
|
||||||
|
Invalid::RecipientDust(sender_balance - total_amount, spec.min_deposit_amount)
|
||||||
|
);
|
||||||
|
|
||||||
|
// If loosely enforcing `transfer.slot`, ensure the slot is not in the past. Otherwise, ensure
|
||||||
|
// the transfer slot equals the state slot.
|
||||||
if time_independent_only {
|
if time_independent_only {
|
||||||
verify!(
|
verify!(
|
||||||
state.slot <= transfer.slot,
|
state.slot <= transfer.slot,
|
||||||
@ -77,19 +95,33 @@ fn verify_transfer_parametric<T: EthSpec>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load the sender `Validator` record from the state.
|
||||||
let sender_validator = state
|
let sender_validator = state
|
||||||
.validator_registry
|
.validator_registry
|
||||||
.get(transfer.sender as usize)
|
.get(transfer.sender as usize)
|
||||||
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
|
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
|
||||||
let epoch = state.slot.epoch(spec.slots_per_epoch);
|
|
||||||
|
|
||||||
|
let epoch = state.slot.epoch(T::slots_per_epoch());
|
||||||
|
|
||||||
|
// Ensure one of the following is met:
|
||||||
|
//
|
||||||
|
// - Time dependent checks are being ignored.
|
||||||
|
// - The sender has not been activated.
|
||||||
|
// - The sender is withdrawable at the state's epoch.
|
||||||
|
// - The transfer will not reduce the sender below the max effective balance.
|
||||||
verify!(
|
verify!(
|
||||||
time_independent_only
|
time_independent_only
|
||||||
|
|| sender_validator.activation_eligibility_epoch == spec.far_future_epoch
|
||||||
|| sender_validator.is_withdrawable_at(epoch)
|
|| sender_validator.is_withdrawable_at(epoch)
|
||||||
|| sender_validator.activation_epoch == spec.far_future_epoch,
|
|| total_amount + spec.max_effective_balance <= sender_balance,
|
||||||
Invalid::FromValidatorIneligableForTransfer(transfer.sender)
|
Invalid::FromValidatorIneligableForTransfer(transfer.sender)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Ensure the withdrawal credentials generated from the sender's pubkey match those stored in
|
||||||
|
// the validator registry.
|
||||||
|
//
|
||||||
|
// This ensures the validator can only perform a transfer when they are in control of the
|
||||||
|
// withdrawal address.
|
||||||
let transfer_withdrawal_credentials = Hash256::from_slice(
|
let transfer_withdrawal_credentials = Hash256::from_slice(
|
||||||
&get_withdrawal_credentials(&transfer.pubkey, spec.bls_withdrawal_prefix_byte)[..],
|
&get_withdrawal_credentials(&transfer.pubkey, spec.bls_withdrawal_prefix_byte)[..],
|
||||||
);
|
);
|
||||||
@ -101,13 +133,13 @@ fn verify_transfer_parametric<T: EthSpec>(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Verify the transfer signature.
|
||||||
let message = transfer.signed_root();
|
let message = transfer.signed_root();
|
||||||
let domain = spec.get_domain(
|
let domain = spec.get_domain(
|
||||||
transfer.slot.epoch(spec.slots_per_epoch),
|
transfer.slot.epoch(T::slots_per_epoch()),
|
||||||
Domain::Transfer,
|
Domain::Transfer,
|
||||||
&state.fork,
|
&state.fork,
|
||||||
);
|
);
|
||||||
|
|
||||||
verify!(
|
verify!(
|
||||||
transfer
|
transfer
|
||||||
.signature
|
.signature
|
||||||
@ -122,31 +154,31 @@ fn verify_transfer_parametric<T: EthSpec>(
|
|||||||
///
|
///
|
||||||
/// Does not check that the transfer is valid, however checks for overflow in all actions.
|
/// Does not check that the transfer is valid, however checks for overflow in all actions.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn execute_transfer<T: EthSpec>(
|
pub fn execute_transfer<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
transfer: &Transfer,
|
transfer: &Transfer,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let sender_balance = *state
|
let sender_balance = *state
|
||||||
.validator_balances
|
.balances
|
||||||
.get(transfer.sender as usize)
|
.get(transfer.sender as usize)
|
||||||
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
|
.ok_or_else(|| Error::Invalid(Invalid::FromValidatorUnknown(transfer.sender)))?;
|
||||||
let recipient_balance = *state
|
let recipient_balance = *state
|
||||||
.validator_balances
|
.balances
|
||||||
.get(transfer.recipient as usize)
|
.get(transfer.recipient as usize)
|
||||||
.ok_or_else(|| Error::Invalid(Invalid::ToValidatorUnknown(transfer.recipient)))?;
|
.ok_or_else(|| Error::Invalid(Invalid::ToValidatorUnknown(transfer.recipient)))?;
|
||||||
|
|
||||||
let proposer_index =
|
let proposer_index =
|
||||||
state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)?;
|
state.get_beacon_proposer_index(state.slot, RelativeEpoch::Current, spec)?;
|
||||||
let proposer_balance = state.validator_balances[proposer_index];
|
let proposer_balance = state.balances[proposer_index];
|
||||||
|
|
||||||
let total_amount = transfer
|
let total_amount = transfer
|
||||||
.amount
|
.amount
|
||||||
.checked_add(transfer.fee)
|
.checked_add(transfer.fee)
|
||||||
.ok_or_else(|| Error::Invalid(Invalid::FeeOverflow(transfer.amount, transfer.fee)))?;
|
.ok_or_else(|| Error::Invalid(Invalid::FeeOverflow(transfer.amount, transfer.fee)))?;
|
||||||
|
|
||||||
state.validator_balances[transfer.sender as usize] =
|
state.balances[transfer.sender as usize] =
|
||||||
sender_balance.checked_sub(total_amount).ok_or_else(|| {
|
sender_balance.checked_sub(total_amount).ok_or_else(|| {
|
||||||
Error::Invalid(Invalid::FromBalanceInsufficient(
|
Error::Invalid(Invalid::FromBalanceInsufficient(
|
||||||
total_amount,
|
total_amount,
|
||||||
@ -154,7 +186,7 @@ pub fn execute_transfer<T: EthSpec>(
|
|||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
state.validator_balances[transfer.recipient as usize] = recipient_balance
|
state.balances[transfer.recipient as usize] = recipient_balance
|
||||||
.checked_add(transfer.amount)
|
.checked_add(transfer.amount)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
Error::Invalid(Invalid::ToBalanceOverflow(
|
Error::Invalid(Invalid::ToBalanceOverflow(
|
||||||
@ -163,7 +195,7 @@ pub fn execute_transfer<T: EthSpec>(
|
|||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
state.validator_balances[proposer_index] =
|
state.balances[proposer_index] =
|
||||||
proposer_balance.checked_add(transfer.fee).ok_or_else(|| {
|
proposer_balance.checked_add(transfer.fee).ok_or_else(|| {
|
||||||
Error::Invalid(Invalid::ProposerBalanceOverflow(
|
Error::Invalid(Invalid::ProposerBalanceOverflow(
|
||||||
proposer_balance,
|
proposer_balance,
|
||||||
|
@ -1,24 +1,18 @@
|
|||||||
use apply_rewards::apply_rewards;
|
use apply_rewards::process_rewards_and_penalties;
|
||||||
use errors::EpochProcessingError as Error;
|
use errors::EpochProcessingError as Error;
|
||||||
use process_ejections::process_ejections;
|
|
||||||
use process_exit_queue::process_exit_queue;
|
|
||||||
use process_slashings::process_slashings;
|
use process_slashings::process_slashings;
|
||||||
|
use registry_updates::process_registry_updates;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use tree_hash::TreeHash;
|
use tree_hash::TreeHash;
|
||||||
use types::*;
|
use types::*;
|
||||||
use update_registry_and_shuffling_data::update_registry_and_shuffling_data;
|
|
||||||
use validator_statuses::{TotalBalances, ValidatorStatuses};
|
use validator_statuses::{TotalBalances, ValidatorStatuses};
|
||||||
use winning_root::{winning_root, WinningRoot};
|
use winning_root::{winning_root, WinningRoot};
|
||||||
|
|
||||||
pub mod apply_rewards;
|
pub mod apply_rewards;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod get_attestation_participants;
|
|
||||||
pub mod inclusion_distance;
|
|
||||||
pub mod process_ejections;
|
|
||||||
pub mod process_exit_queue;
|
|
||||||
pub mod process_slashings;
|
pub mod process_slashings;
|
||||||
|
pub mod registry_updates;
|
||||||
pub mod tests;
|
pub mod tests;
|
||||||
pub mod update_registry_and_shuffling_data;
|
|
||||||
pub mod validator_statuses;
|
pub mod validator_statuses;
|
||||||
pub mod winning_root;
|
pub mod winning_root;
|
||||||
|
|
||||||
@ -32,14 +26,14 @@ pub type WinningRootHashSet = HashMap<u64, WinningRoot>;
|
|||||||
/// Mutates the given `BeaconState`, returning early if an error is encountered. If an error is
|
/// 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.
|
/// returned, a state might be "half-processed" and therefore in an invalid state.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn per_epoch_processing<T: EthSpec>(
|
pub fn per_epoch_processing<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Ensure the previous and next epoch caches are built.
|
// Ensure the previous and next epoch caches are built.
|
||||||
state.build_epoch_cache(RelativeEpoch::Previous, spec)?;
|
state.build_committee_cache(RelativeEpoch::Previous, spec)?;
|
||||||
state.build_epoch_cache(RelativeEpoch::Current, spec)?;
|
state.build_committee_cache(RelativeEpoch::Current, spec)?;
|
||||||
|
|
||||||
// Load the struct we use to assign validators into sets based on their participation.
|
// Load the struct we use to assign validators into sets based on their participation.
|
||||||
//
|
//
|
||||||
@ -47,39 +41,28 @@ pub fn per_epoch_processing<T: EthSpec>(
|
|||||||
let mut validator_statuses = ValidatorStatuses::new(state, spec)?;
|
let mut validator_statuses = ValidatorStatuses::new(state, spec)?;
|
||||||
validator_statuses.process_attestations(&state, spec)?;
|
validator_statuses.process_attestations(&state, spec)?;
|
||||||
|
|
||||||
// Justification.
|
// Justification and finalization.
|
||||||
update_justification_and_finalization(state, &validator_statuses.total_balances, spec)?;
|
process_justification_and_finalization(state, &validator_statuses.total_balances)?;
|
||||||
|
|
||||||
// Crosslinks.
|
// Crosslinks.
|
||||||
let winning_root_for_shards = process_crosslinks(state, spec)?;
|
let winning_root_for_shards = process_crosslinks(state, spec)?;
|
||||||
|
|
||||||
// Eth1 data.
|
|
||||||
maybe_reset_eth1_period(state, spec);
|
|
||||||
|
|
||||||
// Rewards and Penalities.
|
// Rewards and Penalities.
|
||||||
apply_rewards(
|
process_rewards_and_penalties(
|
||||||
state,
|
state,
|
||||||
&mut validator_statuses,
|
&mut validator_statuses,
|
||||||
&winning_root_for_shards,
|
&winning_root_for_shards,
|
||||||
spec,
|
spec,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Ejections.
|
// Registry Updates.
|
||||||
process_ejections(state, spec)?;
|
process_registry_updates(state, spec)?;
|
||||||
|
|
||||||
// Validator Registry.
|
// Slashings.
|
||||||
update_registry_and_shuffling_data(
|
|
||||||
state,
|
|
||||||
validator_statuses.total_balances.current_epoch,
|
|
||||||
spec,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Slashings and exit queue.
|
|
||||||
process_slashings(state, validator_statuses.total_balances.current_epoch, spec)?;
|
process_slashings(state, validator_statuses.total_balances.current_epoch, spec)?;
|
||||||
process_exit_queue(state, spec);
|
|
||||||
|
|
||||||
// Final updates.
|
// Final updates.
|
||||||
finish_epoch_update(state, spec)?;
|
process_final_updates(state, spec)?;
|
||||||
|
|
||||||
// Rotate the epoch caches to suit the epoch transition.
|
// Rotate the epoch caches to suit the epoch transition.
|
||||||
state.advance_caches();
|
state.advance_caches();
|
||||||
@ -87,89 +70,71 @@ pub fn per_epoch_processing<T: EthSpec>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maybe resets the eth1 period.
|
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
pub fn maybe_reset_eth1_period<T: EthSpec>(state: &mut BeaconState<T>, 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 * spec.slots_per_epoch {
|
|
||||||
state.latest_eth1_data = eth1_data_vote.eth1_data.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
state.eth1_data_votes = vec![];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update the following fields on the `BeaconState`:
|
/// Update the following fields on the `BeaconState`:
|
||||||
///
|
///
|
||||||
/// - `justification_bitfield`.
|
/// - `justification_bitfield`.
|
||||||
/// - `finalized_epoch`
|
|
||||||
/// - `justified_epoch`
|
|
||||||
/// - `previous_justified_epoch`
|
/// - `previous_justified_epoch`
|
||||||
|
/// - `previous_justified_root`
|
||||||
|
/// - `current_justified_epoch`
|
||||||
|
/// - `current_justified_root`
|
||||||
|
/// - `finalized_epoch`
|
||||||
|
/// - `finalized_root`
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn update_justification_and_finalization<T: EthSpec>(
|
pub fn process_justification_and_finalization<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
total_balances: &TotalBalances,
|
total_balances: &TotalBalances,
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let previous_epoch = state.previous_epoch(spec);
|
if state.current_epoch() == T::genesis_epoch() {
|
||||||
let current_epoch = state.current_epoch(spec);
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let mut new_justified_epoch = state.current_justified_epoch;
|
let previous_epoch = state.previous_epoch();
|
||||||
let mut new_finalized_epoch = state.finalized_epoch;
|
let current_epoch = state.current_epoch();
|
||||||
|
|
||||||
// Rotate the justification bitfield up one epoch to make room for the current epoch.
|
let old_previous_justified_epoch = state.previous_justified_epoch;
|
||||||
|
let old_current_justified_epoch = state.current_justified_epoch;
|
||||||
|
|
||||||
|
// Process justifications
|
||||||
|
state.previous_justified_epoch = state.current_justified_epoch;
|
||||||
|
state.previous_justified_root = state.current_justified_root;
|
||||||
state.justification_bitfield <<= 1;
|
state.justification_bitfield <<= 1;
|
||||||
|
|
||||||
// If the previous epoch gets justified, full the second last bit.
|
if total_balances.previous_epoch_target_attesters * 3 >= total_balances.previous_epoch * 2 {
|
||||||
if (total_balances.previous_epoch_boundary_attesters * 3) >= (total_balances.previous_epoch * 2)
|
state.current_justified_epoch = previous_epoch;
|
||||||
{
|
state.current_justified_root =
|
||||||
new_justified_epoch = previous_epoch;
|
*state.get_block_root_at_epoch(state.current_justified_epoch)?;
|
||||||
state.justification_bitfield |= 2;
|
state.justification_bitfield |= 2;
|
||||||
}
|
}
|
||||||
// If the current epoch gets justified, fill the last bit.
|
// If the current epoch gets justified, fill the last bit.
|
||||||
if (total_balances.current_epoch_boundary_attesters * 3) >= (total_balances.current_epoch * 2) {
|
if total_balances.current_epoch_target_attesters * 3 >= total_balances.current_epoch * 2 {
|
||||||
new_justified_epoch = current_epoch;
|
state.current_justified_epoch = current_epoch;
|
||||||
|
state.current_justified_root =
|
||||||
|
*state.get_block_root_at_epoch(state.current_justified_epoch)?;
|
||||||
state.justification_bitfield |= 1;
|
state.justification_bitfield |= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let bitfield = state.justification_bitfield;
|
let bitfield = state.justification_bitfield;
|
||||||
|
|
||||||
// The 2nd/3rd/4th most recent epochs are all justified, the 2nd using the 4th as source.
|
// The 2nd/3rd/4th most recent epochs are all justified, the 2nd using the 4th as source.
|
||||||
if ((bitfield >> 1) % 8 == 0b111) & (state.previous_justified_epoch == current_epoch - 3) {
|
if (bitfield >> 1) % 8 == 0b111 && old_previous_justified_epoch == current_epoch - 3 {
|
||||||
new_finalized_epoch = state.previous_justified_epoch;
|
state.finalized_epoch = old_previous_justified_epoch;
|
||||||
|
state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch)?;
|
||||||
}
|
}
|
||||||
// The 2nd/3rd most recent epochs are both justified, the 2nd using the 3rd as source.
|
// The 2nd/3rd most recent epochs are both justified, the 2nd using the 3rd as source.
|
||||||
if ((bitfield >> 1) % 4 == 0b11) & (state.previous_justified_epoch == current_epoch - 2) {
|
if (bitfield >> 1) % 4 == 0b11 && state.previous_justified_epoch == current_epoch - 2 {
|
||||||
new_finalized_epoch = state.previous_justified_epoch;
|
state.finalized_epoch = old_previous_justified_epoch;
|
||||||
|
state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch)?;
|
||||||
}
|
}
|
||||||
// The 1st/2nd/3rd most recent epochs are all justified, the 1st using the 2nd as source.
|
// The 1st/2nd/3rd most recent epochs are all justified, the 1st using the 2nd as source.
|
||||||
if (bitfield % 8 == 0b111) & (state.current_justified_epoch == current_epoch - 2) {
|
if bitfield % 8 == 0b111 && state.current_justified_epoch == current_epoch - 2 {
|
||||||
new_finalized_epoch = state.current_justified_epoch;
|
state.finalized_epoch = old_current_justified_epoch;
|
||||||
|
state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch)?;
|
||||||
}
|
}
|
||||||
// The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source.
|
// The 1st/2nd most recent epochs are both justified, the 1st using the 2nd as source.
|
||||||
if (bitfield % 4 == 0b11) & (state.current_justified_epoch == current_epoch - 1) {
|
if bitfield % 4 == 0b11 && state.current_justified_epoch == current_epoch - 1 {
|
||||||
new_finalized_epoch = state.current_justified_epoch;
|
state.finalized_epoch = old_current_justified_epoch;
|
||||||
}
|
state.finalized_root = *state.get_block_root_at_epoch(state.finalized_epoch)?;
|
||||||
|
|
||||||
state.previous_justified_epoch = state.current_justified_epoch;
|
|
||||||
state.previous_justified_root = state.current_justified_root;
|
|
||||||
|
|
||||||
if new_justified_epoch != state.current_justified_epoch {
|
|
||||||
state.current_justified_epoch = new_justified_epoch;
|
|
||||||
state.current_justified_root =
|
|
||||||
*state.get_block_root(new_justified_epoch.start_slot(spec.slots_per_epoch))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if new_finalized_epoch != state.finalized_epoch {
|
|
||||||
state.finalized_epoch = new_finalized_epoch;
|
|
||||||
state.finalized_root =
|
|
||||||
*state.get_block_root(new_finalized_epoch.start_slot(spec.slots_per_epoch))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -177,42 +142,36 @@ pub fn update_justification_and_finalization<T: EthSpec>(
|
|||||||
|
|
||||||
/// Updates the following fields on the `BeaconState`:
|
/// Updates the following fields on the `BeaconState`:
|
||||||
///
|
///
|
||||||
/// - `latest_crosslinks`
|
/// - `previous_crosslinks`
|
||||||
|
/// - `current_crosslinks`
|
||||||
///
|
///
|
||||||
/// Also returns a `WinningRootHashSet` for later use during epoch processing.
|
/// Also returns a `WinningRootHashSet` for later use during epoch processing.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn process_crosslinks<T: EthSpec>(
|
pub fn process_crosslinks<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<WinningRootHashSet, Error> {
|
) -> Result<WinningRootHashSet, Error> {
|
||||||
let mut winning_root_for_shards: WinningRootHashSet = HashMap::new();
|
let mut winning_root_for_shards: WinningRootHashSet = HashMap::new();
|
||||||
|
|
||||||
let previous_and_current_epoch_slots: Vec<Slot> = state
|
state.previous_crosslinks = state.current_crosslinks.clone();
|
||||||
.previous_epoch(spec)
|
|
||||||
.slot_iter(spec.slots_per_epoch)
|
|
||||||
.chain(state.current_epoch(spec).slot_iter(spec.slots_per_epoch))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for slot in previous_and_current_epoch_slots {
|
for &relative_epoch in &[RelativeEpoch::Previous, RelativeEpoch::Current] {
|
||||||
// Clone removes the borrow which becomes an issue when mutating `state.balances`.
|
let epoch = relative_epoch.into_epoch(state.current_epoch());
|
||||||
let crosslink_committees_at_slot =
|
for offset in 0..state.get_epoch_committee_count(relative_epoch)? {
|
||||||
state.get_crosslink_committees_at_slot(slot, spec)?.clone();
|
let shard =
|
||||||
|
(state.get_epoch_start_shard(relative_epoch)? + offset) % T::ShardCount::to_u64();
|
||||||
|
let crosslink_committee =
|
||||||
|
state.get_crosslink_committee_for_shard(shard, relative_epoch)?;
|
||||||
|
|
||||||
for c in crosslink_committees_at_slot {
|
let winning_root = winning_root(state, shard, epoch, spec)?;
|
||||||
let shard = c.shard as u64;
|
|
||||||
|
|
||||||
let winning_root = winning_root(state, shard, spec)?;
|
|
||||||
|
|
||||||
if let Some(winning_root) = winning_root {
|
if let Some(winning_root) = winning_root {
|
||||||
let total_committee_balance = state.get_total_balance(&c.committee, spec)?;
|
let total_committee_balance =
|
||||||
|
state.get_total_balance(&crosslink_committee.committee, spec)?;
|
||||||
|
|
||||||
// TODO: I think this has a bug.
|
if 3 * winning_root.total_attesting_balance >= 2 * total_committee_balance {
|
||||||
if (3 * winning_root.total_attesting_balance) >= (2 * total_committee_balance) {
|
state.current_crosslinks[shard as usize] = winning_root.crosslink.clone();
|
||||||
state.latest_crosslinks[shard as usize] = Crosslink {
|
|
||||||
epoch: slot.epoch(spec.slots_per_epoch),
|
|
||||||
crosslink_data_root: winning_root.crosslink_data_root,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
winning_root_for_shards.insert(shard, winning_root);
|
winning_root_for_shards.insert(shard, winning_root);
|
||||||
}
|
}
|
||||||
@ -224,13 +183,35 @@ pub fn process_crosslinks<T: EthSpec>(
|
|||||||
|
|
||||||
/// Finish up an epoch update.
|
/// Finish up an epoch update.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn finish_epoch_update<T: EthSpec>(
|
pub fn process_final_updates<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let current_epoch = state.current_epoch(spec);
|
let current_epoch = state.current_epoch();
|
||||||
let next_epoch = state.next_epoch(spec);
|
let next_epoch = state.next_epoch();
|
||||||
|
|
||||||
|
// Reset eth1 data votes.
|
||||||
|
if (state.slot + 1) % spec.slots_per_eth1_voting_period == 0 {
|
||||||
|
state.eth1_data_votes = vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update effective balances with hysteresis (lag).
|
||||||
|
for (index, validator) in state.validator_registry.iter_mut().enumerate() {
|
||||||
|
let balance = state.balances[index];
|
||||||
|
let half_increment = spec.effective_balance_increment / 2;
|
||||||
|
if balance < validator.effective_balance
|
||||||
|
|| validator.effective_balance + 3 * half_increment < balance
|
||||||
|
{
|
||||||
|
validator.effective_balance = std::cmp::min(
|
||||||
|
balance - balance % spec.effective_balance_increment,
|
||||||
|
spec.max_effective_balance,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update start shard.
|
||||||
|
state.latest_start_shard = state.next_epoch_start_shard(spec)?;
|
||||||
|
|
||||||
// This is a hack to allow us to update index roots and slashed balances for the next epoch.
|
// This is a hack to allow us to update index roots and slashed balances for the next epoch.
|
||||||
//
|
//
|
||||||
@ -244,30 +225,31 @@ pub fn finish_epoch_update<T: EthSpec>(
|
|||||||
.get_active_validator_indices(next_epoch + spec.activation_exit_delay)
|
.get_active_validator_indices(next_epoch + spec.activation_exit_delay)
|
||||||
.tree_hash_root()[..],
|
.tree_hash_root()[..],
|
||||||
);
|
);
|
||||||
state.set_active_index_root(next_epoch, active_index_root, spec)?;
|
state.set_active_index_root(
|
||||||
|
next_epoch + spec.activation_exit_delay,
|
||||||
|
active_index_root,
|
||||||
|
spec,
|
||||||
|
)?;
|
||||||
|
|
||||||
// Set total slashed balances
|
// Set total slashed balances
|
||||||
state.set_slashed_balance(next_epoch, state.get_slashed_balance(current_epoch)?)?;
|
state.set_slashed_balance(next_epoch, state.get_slashed_balance(current_epoch)?)?;
|
||||||
|
|
||||||
// Set randao mix
|
// Set randao mix
|
||||||
state.set_randao_mix(
|
state.set_randao_mix(next_epoch, *state.get_randao_mix(current_epoch)?)?;
|
||||||
next_epoch,
|
|
||||||
*state.get_randao_mix(current_epoch, spec)?,
|
|
||||||
spec,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
state.slot -= 1;
|
state.slot -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if next_epoch.as_u64() % (T::SlotsPerHistoricalRoot::to_u64() / spec.slots_per_epoch) == 0 {
|
if next_epoch.as_u64() % (T::SlotsPerHistoricalRoot::to_u64() / T::slots_per_epoch()) == 0 {
|
||||||
let historical_batch = state.historical_batch();
|
let historical_batch = state.historical_batch();
|
||||||
state
|
state
|
||||||
.historical_roots
|
.historical_roots
|
||||||
.push(Hash256::from_slice(&historical_batch.tree_hash_root()[..]));
|
.push(Hash256::from_slice(&historical_batch.tree_hash_root()[..]));
|
||||||
}
|
}
|
||||||
|
|
||||||
state.previous_epoch_attestations = state.current_epoch_attestations.clone();
|
// Rotate current/previous epoch attestations
|
||||||
state.current_epoch_attestations = vec![];
|
state.previous_epoch_attestations =
|
||||||
|
std::mem::replace(&mut state.current_epoch_attestations, vec![]);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -32,32 +32,29 @@ impl std::ops::AddAssign for Delta {
|
|||||||
|
|
||||||
/// Apply attester and proposer rewards.
|
/// Apply attester and proposer rewards.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn apply_rewards<T: EthSpec>(
|
pub fn process_rewards_and_penalties<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
validator_statuses: &mut ValidatorStatuses,
|
validator_statuses: &mut ValidatorStatuses,
|
||||||
winning_root_for_shards: &WinningRootHashSet,
|
winning_root_for_shards: &WinningRootHashSet,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
if state.current_epoch() == T::genesis_epoch() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
// Guard against an out-of-bounds during the validator balance update.
|
// Guard against an out-of-bounds during the validator balance update.
|
||||||
if validator_statuses.statuses.len() != state.validator_balances.len() {
|
if validator_statuses.statuses.len() != state.balances.len()
|
||||||
return Err(Error::ValidatorStatusesInconsistent);
|
|| validator_statuses.statuses.len() != state.validator_registry.len()
|
||||||
}
|
{
|
||||||
// Guard against an out-of-bounds during the attester inclusion balance update.
|
|
||||||
if validator_statuses.statuses.len() != state.validator_registry.len() {
|
|
||||||
return Err(Error::ValidatorStatusesInconsistent);
|
return Err(Error::ValidatorStatusesInconsistent);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut deltas = vec![Delta::default(); state.validator_balances.len()];
|
let mut deltas = vec![Delta::default(); state.balances.len()];
|
||||||
|
|
||||||
get_justification_and_finalization_deltas(&mut deltas, state, &validator_statuses, spec)?;
|
get_attestation_deltas(&mut deltas, state, &validator_statuses, spec)?;
|
||||||
get_crosslink_deltas(&mut deltas, state, &validator_statuses, spec)?;
|
get_crosslink_deltas(&mut deltas, state, &validator_statuses, spec)?;
|
||||||
|
|
||||||
// Apply the proposer deltas if we are finalizing normally.
|
|
||||||
//
|
|
||||||
// This is executed slightly differently to the spec because of the way our functions are
|
|
||||||
// structured. It should be functionally equivalent.
|
|
||||||
if epochs_since_finality(state, spec) <= 4 {
|
|
||||||
get_proposer_deltas(
|
get_proposer_deltas(
|
||||||
&mut deltas,
|
&mut deltas,
|
||||||
state,
|
state,
|
||||||
@ -65,24 +62,22 @@ pub fn apply_rewards<T: EthSpec>(
|
|||||||
winning_root_for_shards,
|
winning_root_for_shards,
|
||||||
spec,
|
spec,
|
||||||
)?;
|
)?;
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the deltas, over-flowing but not under-flowing (saturating at 0 instead).
|
// Apply the deltas, over-flowing but not under-flowing (saturating at 0 instead).
|
||||||
for (i, delta) in deltas.iter().enumerate() {
|
for (i, delta) in deltas.iter().enumerate() {
|
||||||
state.validator_balances[i] += delta.rewards;
|
state.balances[i] += delta.rewards;
|
||||||
state.validator_balances[i] = state.validator_balances[i].saturating_sub(delta.penalties);
|
state.balances[i] = state.balances[i].saturating_sub(delta.penalties);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies the attestation inclusion reward to each proposer for every validator who included an
|
/// For each attesting validator, reward the proposer who was first to include their attestation.
|
||||||
/// attestation in the previous epoch.
|
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
fn get_proposer_deltas<T: EthSpec>(
|
fn get_proposer_deltas<T: EthSpec>(
|
||||||
deltas: &mut Vec<Delta>,
|
deltas: &mut Vec<Delta>,
|
||||||
state: &mut BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
validator_statuses: &mut ValidatorStatuses,
|
validator_statuses: &mut ValidatorStatuses,
|
||||||
winning_root_for_shards: &WinningRootHashSet,
|
winning_root_for_shards: &WinningRootHashSet,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
@ -90,9 +85,7 @@ fn get_proposer_deltas<T: EthSpec>(
|
|||||||
// Update statuses with the information from winning roots.
|
// Update statuses with the information from winning roots.
|
||||||
validator_statuses.process_winning_roots(state, winning_root_for_shards, spec)?;
|
validator_statuses.process_winning_roots(state, winning_root_for_shards, spec)?;
|
||||||
|
|
||||||
for (index, validator) in validator_statuses.statuses.iter().enumerate() {
|
for validator in &validator_statuses.statuses {
|
||||||
let mut delta = Delta::default();
|
|
||||||
|
|
||||||
if validator.is_previous_epoch_attester {
|
if validator.is_previous_epoch_attester {
|
||||||
let inclusion = validator
|
let inclusion = validator
|
||||||
.inclusion_info
|
.inclusion_info
|
||||||
@ -101,7 +94,7 @@ fn get_proposer_deltas<T: EthSpec>(
|
|||||||
let base_reward = get_base_reward(
|
let base_reward = get_base_reward(
|
||||||
state,
|
state,
|
||||||
inclusion.proposer_index,
|
inclusion.proposer_index,
|
||||||
validator_statuses.total_balances.previous_epoch,
|
validator_statuses.total_balances.current_epoch,
|
||||||
spec,
|
spec,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@ -109,10 +102,8 @@ fn get_proposer_deltas<T: EthSpec>(
|
|||||||
return Err(Error::ValidatorStatusesInconsistent);
|
return Err(Error::ValidatorStatusesInconsistent);
|
||||||
}
|
}
|
||||||
|
|
||||||
delta.reward(base_reward / spec.attestation_inclusion_reward_quotient);
|
deltas[inclusion.proposer_index].reward(base_reward / spec.proposer_reward_quotient);
|
||||||
}
|
}
|
||||||
|
|
||||||
deltas[index] += delta;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -120,40 +111,30 @@ fn get_proposer_deltas<T: EthSpec>(
|
|||||||
|
|
||||||
/// Apply rewards for participation in attestations during the previous epoch.
|
/// Apply rewards for participation in attestations during the previous epoch.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
fn get_justification_and_finalization_deltas<T: EthSpec>(
|
fn get_attestation_deltas<T: EthSpec>(
|
||||||
deltas: &mut Vec<Delta>,
|
deltas: &mut Vec<Delta>,
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
validator_statuses: &ValidatorStatuses,
|
validator_statuses: &ValidatorStatuses,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let epochs_since_finality = epochs_since_finality(state, spec);
|
let finality_delay = (state.previous_epoch() - state.finalized_epoch).as_u64();
|
||||||
|
|
||||||
for (index, validator) in validator_statuses.statuses.iter().enumerate() {
|
for (index, validator) in validator_statuses.statuses.iter().enumerate() {
|
||||||
let base_reward = get_base_reward(
|
let base_reward = get_base_reward(
|
||||||
state,
|
state,
|
||||||
index,
|
index,
|
||||||
validator_statuses.total_balances.previous_epoch,
|
validator_statuses.total_balances.current_epoch,
|
||||||
spec,
|
|
||||||
)?;
|
|
||||||
let inactivity_penalty = get_inactivity_penalty(
|
|
||||||
state,
|
|
||||||
index,
|
|
||||||
epochs_since_finality.as_u64(),
|
|
||||||
validator_statuses.total_balances.previous_epoch,
|
|
||||||
spec,
|
spec,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let delta = if epochs_since_finality <= 4 {
|
let delta = get_attestation_delta(
|
||||||
compute_normal_justification_and_finalization_delta(
|
|
||||||
&validator,
|
&validator,
|
||||||
&validator_statuses.total_balances,
|
&validator_statuses.total_balances,
|
||||||
base_reward,
|
base_reward,
|
||||||
|
finality_delay,
|
||||||
spec,
|
spec,
|
||||||
)
|
);
|
||||||
} else {
|
|
||||||
compute_inactivity_leak_delta(&validator, base_reward, inactivity_penalty, spec)
|
|
||||||
};
|
|
||||||
|
|
||||||
deltas[index] += delta;
|
deltas[index] += delta;
|
||||||
}
|
}
|
||||||
@ -161,51 +142,79 @@ fn get_justification_and_finalization_deltas<T: EthSpec>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine the delta for a single validator, if the chain is finalizing normally.
|
/// Determine the delta for a single validator, sans proposer rewards.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
fn compute_normal_justification_and_finalization_delta(
|
fn get_attestation_delta(
|
||||||
validator: &ValidatorStatus,
|
validator: &ValidatorStatus,
|
||||||
total_balances: &TotalBalances,
|
total_balances: &TotalBalances,
|
||||||
base_reward: u64,
|
base_reward: u64,
|
||||||
|
finality_delay: u64,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Delta {
|
) -> Delta {
|
||||||
let mut delta = Delta::default();
|
let mut delta = Delta::default();
|
||||||
|
|
||||||
let boundary_attesting_balance = total_balances.previous_epoch_boundary_attesters;
|
// Is this validator eligible to be rewarded or penalized?
|
||||||
let total_balance = total_balances.previous_epoch;
|
// Spec: validator index in `eligible_validator_indices`
|
||||||
|
let is_eligible = validator.is_active_in_previous_epoch
|
||||||
|
|| (validator.is_slashed && !validator.is_withdrawable_in_current_epoch);
|
||||||
|
|
||||||
|
if !is_eligible {
|
||||||
|
return delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
let total_balance = total_balances.current_epoch;
|
||||||
let total_attesting_balance = total_balances.previous_epoch_attesters;
|
let total_attesting_balance = total_balances.previous_epoch_attesters;
|
||||||
let matching_head_balance = total_balances.previous_epoch_boundary_attesters;
|
let matching_target_balance = total_balances.previous_epoch_target_attesters;
|
||||||
|
let matching_head_balance = total_balances.previous_epoch_head_attesters;
|
||||||
|
|
||||||
// Expected FFG source.
|
// Expected FFG source.
|
||||||
if validator.is_previous_epoch_attester {
|
// Spec:
|
||||||
|
// - validator index in `get_unslashed_attesting_indices(state, matching_source_attestations)`
|
||||||
|
if validator.is_previous_epoch_attester && !validator.is_slashed {
|
||||||
delta.reward(base_reward * total_attesting_balance / total_balance);
|
delta.reward(base_reward * total_attesting_balance / total_balance);
|
||||||
// Inclusion speed bonus
|
// Inclusion speed bonus
|
||||||
let inclusion = validator
|
let inclusion = validator
|
||||||
.inclusion_info
|
.inclusion_info
|
||||||
.expect("It is a logic error for an attester not to have an inclusion distance.");
|
.expect("It is a logic error for an attester not to have an inclusion distance.");
|
||||||
delta.reward(
|
delta.reward(base_reward * spec.min_attestation_inclusion_delay / inclusion.distance);
|
||||||
base_reward * spec.min_attestation_inclusion_delay / inclusion.distance.as_u64(),
|
} else {
|
||||||
);
|
|
||||||
} else if validator.is_active_in_previous_epoch {
|
|
||||||
delta.penalize(base_reward);
|
delta.penalize(base_reward);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expected FFG target.
|
// Expected FFG target.
|
||||||
if validator.is_previous_epoch_boundary_attester {
|
// Spec:
|
||||||
delta.reward(base_reward / boundary_attesting_balance / total_balance);
|
// - validator index in `get_unslashed_attesting_indices(state, matching_target_attestations)`
|
||||||
} else if validator.is_active_in_previous_epoch {
|
if validator.is_previous_epoch_target_attester && !validator.is_slashed {
|
||||||
|
delta.reward(base_reward * matching_target_balance / total_balance);
|
||||||
|
} else {
|
||||||
delta.penalize(base_reward);
|
delta.penalize(base_reward);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expected head.
|
// Expected head.
|
||||||
if validator.is_previous_epoch_head_attester {
|
// Spec:
|
||||||
|
// - validator index in `get_unslashed_attesting_indices(state, matching_head_attestations)`
|
||||||
|
if validator.is_previous_epoch_head_attester && !validator.is_slashed {
|
||||||
delta.reward(base_reward * matching_head_balance / total_balance);
|
delta.reward(base_reward * matching_head_balance / total_balance);
|
||||||
} else if validator.is_active_in_previous_epoch {
|
} else {
|
||||||
delta.penalize(base_reward);
|
delta.penalize(base_reward);
|
||||||
};
|
}
|
||||||
|
|
||||||
// Proposer bonus is handled in `apply_proposer_deltas`.
|
// Inactivity penalty
|
||||||
|
if finality_delay > spec.min_epochs_to_inactivity_penalty {
|
||||||
|
// All eligible validators are penalized
|
||||||
|
delta.penalize(spec.base_rewards_per_epoch * base_reward);
|
||||||
|
|
||||||
|
// Additionally, all validators whose FFG target didn't match are penalized extra
|
||||||
|
if !validator.is_previous_epoch_target_attester {
|
||||||
|
delta.penalize(
|
||||||
|
validator.current_epoch_effective_balance * finality_delay
|
||||||
|
/ spec.inactivity_penalty_quotient,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proposer bonus is handled in `get_proposer_deltas`.
|
||||||
//
|
//
|
||||||
// This function only computes the delta for a single validator, so it cannot also return a
|
// This function only computes the delta for a single validator, so it cannot also return a
|
||||||
// delta for a validator.
|
// delta for a validator.
|
||||||
@ -213,55 +222,9 @@ fn compute_normal_justification_and_finalization_delta(
|
|||||||
delta
|
delta
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine the delta for a single delta, assuming the chain is _not_ finalizing normally.
|
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
fn compute_inactivity_leak_delta(
|
|
||||||
validator: &ValidatorStatus,
|
|
||||||
base_reward: u64,
|
|
||||||
inactivity_penalty: u64,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Delta {
|
|
||||||
let mut delta = Delta::default();
|
|
||||||
|
|
||||||
if validator.is_active_in_previous_epoch {
|
|
||||||
if !validator.is_previous_epoch_attester {
|
|
||||||
delta.penalize(inactivity_penalty);
|
|
||||||
} else {
|
|
||||||
// If a validator did attest, apply a small penalty for getting attestations included
|
|
||||||
// late.
|
|
||||||
let inclusion = validator
|
|
||||||
.inclusion_info
|
|
||||||
.expect("It is a logic error for an attester not to have an inclusion distance.");
|
|
||||||
delta.reward(
|
|
||||||
base_reward * spec.min_attestation_inclusion_delay / inclusion.distance.as_u64(),
|
|
||||||
);
|
|
||||||
delta.penalize(base_reward);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !validator.is_previous_epoch_boundary_attester {
|
|
||||||
delta.reward(inactivity_penalty);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !validator.is_previous_epoch_head_attester {
|
|
||||||
delta.penalize(inactivity_penalty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Penalize slashed-but-inactive validators as though they were active but offline.
|
|
||||||
if !validator.is_active_in_previous_epoch
|
|
||||||
& validator.is_slashed
|
|
||||||
& !validator.is_withdrawable_in_current_epoch
|
|
||||||
{
|
|
||||||
delta.penalize(2 * inactivity_penalty + base_reward);
|
|
||||||
}
|
|
||||||
|
|
||||||
delta
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate the deltas based upon the winning roots for attestations during the previous epoch.
|
/// Calculate the deltas based upon the winning roots for attestations during the previous epoch.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
fn get_crosslink_deltas<T: EthSpec>(
|
fn get_crosslink_deltas<T: EthSpec>(
|
||||||
deltas: &mut Vec<Delta>,
|
deltas: &mut Vec<Delta>,
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
@ -274,7 +237,7 @@ fn get_crosslink_deltas<T: EthSpec>(
|
|||||||
let base_reward = get_base_reward(
|
let base_reward = get_base_reward(
|
||||||
state,
|
state,
|
||||||
index,
|
index,
|
||||||
validator_statuses.total_balances.previous_epoch,
|
validator_statuses.total_balances.current_epoch,
|
||||||
spec,
|
spec,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@ -295,40 +258,20 @@ fn get_crosslink_deltas<T: EthSpec>(
|
|||||||
|
|
||||||
/// Returns the base reward for some validator.
|
/// Returns the base reward for some validator.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
fn get_base_reward<T: EthSpec>(
|
fn get_base_reward<T: EthSpec>(
|
||||||
state: &BeaconState<T>,
|
state: &BeaconState<T>,
|
||||||
index: usize,
|
index: usize,
|
||||||
previous_total_balance: u64,
|
// Should be == get_total_active_balance(state, spec)
|
||||||
|
total_active_balance: u64,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<u64, BeaconStateError> {
|
) -> Result<u64, BeaconStateError> {
|
||||||
if previous_total_balance == 0 {
|
if total_active_balance == 0 {
|
||||||
Ok(0)
|
Ok(0)
|
||||||
} else {
|
} else {
|
||||||
let adjusted_quotient = previous_total_balance.integer_sqrt() / spec.base_reward_quotient;
|
let adjusted_quotient = total_active_balance.integer_sqrt() / spec.base_reward_quotient;
|
||||||
Ok(state.get_effective_balance(index, spec)? / adjusted_quotient / 5)
|
Ok(state.get_effective_balance(index, spec)?
|
||||||
|
/ adjusted_quotient
|
||||||
|
/ spec.base_rewards_per_epoch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the inactivity penalty for some validator.
|
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
fn get_inactivity_penalty<T: EthSpec>(
|
|
||||||
state: &BeaconState<T>,
|
|
||||||
index: usize,
|
|
||||||
epochs_since_finality: u64,
|
|
||||||
previous_total_balance: u64,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Result<u64, BeaconStateError> {
|
|
||||||
Ok(get_base_reward(state, index, previous_total_balance, spec)?
|
|
||||||
+ state.get_effective_balance(index, spec)? * epochs_since_finality
|
|
||||||
/ spec.inactivity_penalty_quotient
|
|
||||||
/ 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the epochs since the last finalized epoch.
|
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
fn epochs_since_finality<T: EthSpec>(state: &BeaconState<T>, spec: &ChainSpec) -> Epoch {
|
|
||||||
state.current_epoch(spec) + 1 - state.finalized_epoch
|
|
||||||
}
|
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
use crate::common::verify_bitfield_length;
|
|
||||||
use types::*;
|
|
||||||
|
|
||||||
/// Returns validator indices which participated in the attestation.
|
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
pub fn get_attestation_participants<T: EthSpec>(
|
|
||||||
state: &BeaconState<T>,
|
|
||||||
attestation_data: &AttestationData,
|
|
||||||
bitfield: &Bitfield,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Result<Vec<usize>, 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 => participants.push(*validator_index),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
participants.shrink_to_fit();
|
|
||||||
|
|
||||||
Ok(participants)
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
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.
|
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
pub fn inclusion_distance<T: EthSpec>(
|
|
||||||
state: &BeaconState<T>,
|
|
||||||
attestations: &[&PendingAttestation],
|
|
||||||
validator_index: usize,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Result<u64, InclusionError> {
|
|
||||||
let attestation = earliest_included_attestation(state, attestations, validator_index, spec)?;
|
|
||||||
Ok((attestation.inclusion_slot - attestation.data.slot).as_u64())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the slot of the earliest included attestation for some validator.
|
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
pub fn inclusion_slot<T: EthSpec>(
|
|
||||||
state: &BeaconState<T>,
|
|
||||||
attestations: &[&PendingAttestation],
|
|
||||||
validator_index: usize,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Result<Slot, InclusionError> {
|
|
||||||
let attestation = earliest_included_attestation(state, attestations, validator_index, spec)?;
|
|
||||||
Ok(attestation.inclusion_slot)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finds the earliest included attestation for some validator.
|
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
fn earliest_included_attestation<T: EthSpec>(
|
|
||||||
state: &BeaconState<T>,
|
|
||||||
attestations: &[&PendingAttestation],
|
|
||||||
validator_index: usize,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) -> Result<PendingAttestation, InclusionError> {
|
|
||||||
let mut included_attestations = vec![];
|
|
||||||
|
|
||||||
for (i, a) in attestations.iter().enumerate() {
|
|
||||||
let participants =
|
|
||||||
get_attestation_participants(state, &a.data, &a.aggregation_bitfield, spec)?;
|
|
||||||
if participants.iter().any(|i| *i == validator_index) {
|
|
||||||
included_attestations.push(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let earliest_attestation_index = included_attestations
|
|
||||||
.iter()
|
|
||||||
.min_by_key(|i| attestations[**i].inclusion_slot)
|
|
||||||
.ok_or_else(|| InclusionError::NoAttestationsForValidator)?;
|
|
||||||
Ok(attestations[*earliest_attestation_index].clone())
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
use crate::common::exit_validator;
|
|
||||||
use types::{BeaconStateError as Error, *};
|
|
||||||
|
|
||||||
/// Iterate through the validator registry and eject active validators with balance below
|
|
||||||
/// ``EJECTION_BALANCE``.
|
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
pub fn process_ejections<T: EthSpec>(
|
|
||||||
state: &mut BeaconState<T>,
|
|
||||||
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<usize> = state
|
|
||||||
.get_cached_active_validator_indices(RelativeEpoch::Current, spec)?
|
|
||||||
.iter()
|
|
||||||
.filter_map(|&i| {
|
|
||||||
if state.validator_balances[i as usize] < spec.ejection_balance {
|
|
||||||
Some(i)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for validator_index in exitable {
|
|
||||||
exit_validator(state, validator_index, spec)?
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
use types::*;
|
|
||||||
|
|
||||||
/// Process the exit queue.
|
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
pub fn process_exit_queue<T: EthSpec>(state: &mut BeaconState<T>, spec: &ChainSpec) {
|
|
||||||
let current_epoch = state.current_epoch(spec);
|
|
||||||
|
|
||||||
let eligible = |index: usize| {
|
|
||||||
let validator = &state.validator_registry[index];
|
|
||||||
|
|
||||||
if validator.withdrawable_epoch != spec.far_future_epoch {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
current_epoch >= validator.exit_epoch + spec.min_validator_withdrawability_delay
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut eligable_indices: Vec<usize> = (0..state.validator_registry.len())
|
|
||||||
.filter(|i| eligible(*i))
|
|
||||||
.collect();
|
|
||||||
eligable_indices.sort_by_key(|i| state.validator_registry[*i].exit_epoch);
|
|
||||||
|
|
||||||
for (dequeues, index) in eligable_indices.iter().enumerate() {
|
|
||||||
if dequeues as u64 >= spec.max_exit_dequeues_per_epoch {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
prepare_validator_for_withdrawal(state, *index, spec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initiate an exit for the validator of the given `index`.
|
|
||||||
///
|
|
||||||
/// Spec v0.5.1
|
|
||||||
fn prepare_validator_for_withdrawal<T: EthSpec>(
|
|
||||||
state: &mut BeaconState<T>,
|
|
||||||
validator_index: usize,
|
|
||||||
spec: &ChainSpec,
|
|
||||||
) {
|
|
||||||
state.validator_registry[validator_index].withdrawable_epoch =
|
|
||||||
state.current_epoch(spec) + spec.min_validator_withdrawability_delay;
|
|
||||||
}
|
|
@ -2,21 +2,21 @@ use types::{BeaconStateError as Error, *};
|
|||||||
|
|
||||||
/// Process slashings.
|
/// Process slashings.
|
||||||
///
|
///
|
||||||
/// Spec v0.5.1
|
/// Spec v0.6.3
|
||||||
pub fn process_slashings<T: EthSpec>(
|
pub fn process_slashings<T: EthSpec>(
|
||||||
state: &mut BeaconState<T>,
|
state: &mut BeaconState<T>,
|
||||||
current_total_balance: u64,
|
current_total_balance: u64,
|
||||||
spec: &ChainSpec,
|
spec: &ChainSpec,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let current_epoch = state.current_epoch(spec);
|
let current_epoch = state.current_epoch();
|
||||||
|
|
||||||
let total_at_start = state.get_slashed_balance(current_epoch + 1)?;
|
let total_at_start = state.get_slashed_balance(current_epoch + 1)?;
|
||||||
let total_at_end = state.get_slashed_balance(current_epoch)?;
|
let total_at_end = state.get_slashed_balance(current_epoch)?;
|
||||||
let total_penalities = total_at_end - total_at_start;
|
let total_penalities = total_at_end - total_at_start;
|
||||||
|
|
||||||
for (index, validator) in state.validator_registry.iter().enumerate() {
|
for (index, validator) in state.validator_registry.iter().enumerate() {
|
||||||
let should_penalize = current_epoch.as_usize()
|
let should_penalize = current_epoch.as_usize() + T::LatestSlashedExitLength::to_usize() / 2
|
||||||
== validator.withdrawable_epoch.as_usize() - T::LatestSlashedExitLength::to_usize() / 2;
|
== validator.withdrawable_epoch.as_usize();
|
||||||
|
|
||||||
if validator.slashed && should_penalize {
|
if validator.slashed && should_penalize {
|
||||||
let effective_balance = state.get_effective_balance(index, spec)?;
|
let effective_balance = state.get_effective_balance(index, spec)?;
|
||||||
@ -24,10 +24,10 @@ pub fn process_slashings<T: EthSpec>(
|
|||||||
let penalty = std::cmp::max(
|
let penalty = std::cmp::max(
|
||||||
effective_balance * std::cmp::min(total_penalities * 3, current_total_balance)
|
effective_balance * std::cmp::min(total_penalities * 3, current_total_balance)
|
||||||
/ current_total_balance,
|
/ current_total_balance,
|
||||||
effective_balance / spec.min_penalty_quotient,
|
effective_balance / spec.min_slashing_penalty_quotient,
|
||||||
);
|
);
|
||||||
|
|
||||||
state.validator_balances[index] -= penalty;
|
safe_sub_assign!(state.balances[index], penalty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
use super::super::common::initiate_validator_exit;
|
||||||
|
use super::Error;
|
||||||
|
use itertools::{Either, Itertools};
|
||||||
|
use types::*;
|
||||||
|
|
||||||
|
/// Peforms a validator registry update, if required.
|
||||||
|
///
|
||||||
|
/// Spec v0.6.3
|
||||||
|
pub fn process_registry_updates<T: EthSpec>(
|
||||||
|
state: &mut BeaconState<T>,
|
||||||
|
spec: &ChainSpec,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Process activation eligibility and ejections.
|
||||||
|
// Collect eligible and exiting validators (we need to avoid mutating the state while iterating).
|
||||||
|
// We assume it's safe to re-order the change in eligibility and `initiate_validator_exit`.
|
||||||
|
// Rest assured exiting validators will still be exited in the same order as in the spec.
|
||||||
|
let current_epoch = state.current_epoch();
|
||||||
|
let is_eligible = |validator: &Validator| {
|
||||||
|
validator.activation_eligibility_epoch == spec.far_future_epoch
|
||||||
|
&& validator.effective_balance >= spec.max_effective_balance
|
||||||
|
};
|
||||||
|
let is_exiting_validator = |validator: &Validator| {
|
||||||
|
validator.is_active_at(current_epoch)
|
||||||
|
&& validator.effective_balance <= spec.ejection_balance
|
||||||
|
};
|
||||||
|
let (eligible_validators, exiting_validators): (Vec<_>, Vec<_>) = state
|
||||||
|
.validator_registry
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, validator)| is_eligible(validator) || is_exiting_validator(validator))
|
||||||
|
.partition_map(|(index, validator)| {
|
||||||
|
if is_eligible(validator) {
|
||||||
|
Either::Left(index)
|
||||||
|
} else {
|
||||||
|
Either::Right(index)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for index in eligible_validators {
|
||||||
|
state.validator_registry[index].activation_eligibility_epoch = current_epoch;
|
||||||
|
}
|
||||||
|
for index in exiting_validators {
|
||||||
|
initiate_validator_exit(state, index, spec)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queue validators eligible for activation and not dequeued for activation prior to finalized epoch
|
||||||
|
let activation_queue = state
|
||||||
|
.validator_registry
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, validator)| {
|
||||||
|
validator.activation_eligibility_epoch != spec.far_future_epoch
|
||||||
|
&& validator.activation_epoch
|
||||||
|
>= state.get_delayed_activation_exit_epoch(state.finalized_epoch, spec)
|
||||||
|
})
|
||||||
|
.sorted_by_key(|(_, validator)| validator.activation_eligibility_epoch)
|
||||||
|
.map(|(index, _)| index)
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
|
let churn_limit = state.get_churn_limit(spec)? as usize;
|
||||||
|
let delayed_activation_epoch = state.get_delayed_activation_exit_epoch(current_epoch, spec);
|
||||||
|
for index in activation_queue.into_iter().take(churn_limit) {
|
||||||
|
let validator = &mut state.validator_registry[index];
|
||||||
|
if validator.activation_epoch == spec.far_future_epoch {
|
||||||
|
validator.activation_epoch = delayed_activation_epoch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user