diff --git a/casper/src/casper.rs b/casper/src/casper.rs index 038c32461a3f382fd7ebccc6c62a2537b1e14c42..7c4fd5262e568d0d7e251fb6b74d4515bc367c26 100644 --- a/casper/src/casper.rs +++ b/casper/src/casper.rs @@ -135,7 +135,6 @@ impl CasperContext where Epoch=PendingAttestationsStoreEpoch >, { - assert!(self.epoch() == store.epoch(), "Store block epoch must equal to casper context."); debug_assert!({ store.attestations().into_iter().all(|attestation| { self.validate_attestation(&attestation) @@ -173,6 +172,8 @@ impl CasperContext where self.previous_justified_epoch = self.justified_epoch; self.justified_epoch = new_justified_epoch; self.epoch += One::one(); + + assert!(self.epoch() == store.epoch(), "Store block epoch must equal to casper context."); } } @@ -284,8 +285,8 @@ mod tests { let mut casper = CasperContext::::default(); // Attesting on the zero round doesn't do anything, because it's already justified and finalized. - casper.advance_epoch(&mut store); store.epoch += 1; + casper.advance_epoch(&mut store); // First round, four validators attest. store.pending_attestations.append(&mut vec![ @@ -310,8 +311,8 @@ mod tests { target_epoch: 1, }, ]); - casper.advance_epoch(&mut store); store.epoch += 1; + casper.advance_epoch(&mut store); assert_eq!(casper.epoch, 2); assert_eq!(casper.justified_epoch, 1); assert_eq!(casper.finalized_epoch, 0); @@ -334,8 +335,8 @@ mod tests { target_epoch: 2, }, ]); - casper.advance_epoch(&mut store); store.epoch += 1; + casper.advance_epoch(&mut store); assert_eq!(casper.epoch, 3); assert_eq!(casper.justified_epoch, 2); assert_eq!(casper.finalized_epoch, 1); @@ -358,8 +359,8 @@ mod tests { target_epoch: 3, }, ]); - casper.advance_epoch(&mut store); store.epoch += 1; + casper.advance_epoch(&mut store); assert_eq!(casper.epoch, 4); assert_eq!(casper.justified_epoch, 3); assert_eq!(casper.finalized_epoch, 2); @@ -377,8 +378,8 @@ mod tests { target_epoch: 4, }, ]); - casper.advance_epoch(&mut store); store.epoch += 1; + casper.advance_epoch(&mut store); assert_eq!(casper.epoch, 5); assert_eq!(casper.justified_epoch, 3); assert_eq!(casper.finalized_epoch, 2); diff --git a/consensus/primitives/src/lib.rs b/consensus/primitives/src/lib.rs index fb34189a4a83fd41261442a61f19da4d8e4a7ded..5b458b43349bdb7f274e08160c264de90f80cb15 100644 --- a/consensus/primitives/src/lib.rs +++ b/consensus/primitives/src/lib.rs @@ -132,7 +132,7 @@ pub mod id { /// Runtime-APIs pub mod api { - use primitives::{Epoch, UncheckedAttestation, CheckedAttestation, Slot}; + use primitives::{Epoch, UncheckedAttestation, CheckedAttestation, Slot, ValidatorId, ValidatorIndex}; use client::decl_runtime_apis; decl_runtime_apis! { @@ -155,6 +155,9 @@ pub mod api { /// Check an attestation. fn check_attestation(unchecked: UncheckedAttestation) -> Option; + + /// Given an attestation, return the validator index. + fn validator_index(validator_id: ValidatorId) -> Option; } } } diff --git a/consensus/src/lib.rs b/consensus/src/lib.rs index 015a5d830567e6670442592c3176ffc8ac025384..075ea35fc03fe3aa8cda014bea27e9e0116773b4 100644 --- a/consensus/src/lib.rs +++ b/consensus/src/lib.rs @@ -39,16 +39,19 @@ use consensus_common::import_queue::{Verifier, BasicQueue}; use client::{blockchain::HeaderBackend, ChainHead}; use client::backend::AuxStore; use client::block_builder::api::BlockBuilder as BlockBuilderApi; +use runtime::UncheckedExtrinsic; use runtime::utils::epoch_to_slot; use runtime_primitives::{generic::BlockId, Justification, RuntimeString}; -use runtime_primitives::traits::{Block, Header, Digest, DigestItemFor, DigestItem, ProvideRuntimeApi}; -use primitives::{ValidatorId, H256, Slot}; +use runtime_primitives::traits::{Block, Header, Digest, DigestItemFor, DigestItem, ProvideRuntimeApi, NumberFor}; +use primitives::{ValidatorId, H256, Slot, Epoch, BlockNumber, UnsignedAttestation}; use aura_slots::{SlotCompatible, CheckedHeader, SlotWorker, SlotInfo}; use inherents::InherentDataProviders; use casper::Attestation; +use transaction_pool::txpool::{ChainApi as PoolChainApi, Pool}; use futures::{Future, IntoFuture, future}; use tokio::timer::Timeout; +use parking_lot::Mutex; use api::ShasperApi; use crypto::bls; @@ -114,6 +117,10 @@ fn inherent_to_common_error(err: RuntimeString) -> consensus_common::Error { consensus_common::ErrorKind::InherentData(err.into()).into() } +fn client_to_common_error(err: client::error::Error) -> consensus_common::Error { + consensus_common::ErrorKind::Other(Box::new(err)).into() +} + /// Register the shasper inherent data provider. pub fn register_shasper_inherent_data_provider( inherent_data_providers: &InherentDataProviders, @@ -161,17 +168,19 @@ fn replace_inherent_data_slot( } /// Start the shasper worker. The returned future should be run in a tokio runtime. -pub fn start_shasper( +pub fn start_shasper( slot_duration: SlotDuration, local_key: Arc, client: Arc, block_import: Arc, env: Arc, sync_oracle: SO, + pool: Arc>, on_exit: OnExit, inherent_data_providers: InherentDataProviders, ) -> Result, consensus_common::Error> where - B: Block, + B: Block, + NumberFor: From, C: Authorities + ChainHead + HeaderBackend + AuxStore + ProvideRuntimeApi, C::Api: ShasperApi, B::Extrinsic: CompatibleExtrinsic, @@ -181,12 +190,19 @@ pub fn start_shasper( I: BlockImport + Send + Sync + 'static, Error: From + From, SO: SyncOracle + Send + Clone, + P: PoolChainApi, OnExit: Future + Send + 'static, DigestItemFor: CompatibleDigestItem + DigestItem, Error: ::std::error::Error + Send + 'static + From<::consensus_common::Error>, { let worker = ShasperWorker { - client: client.clone(), block_import, env, local_key, inherent_data_providers: inherent_data_providers.clone() + client: client.clone(), + block_import, + env, + local_key, + last_proposed_epoch: Default::default(), + pool, + inherent_data_providers: inherent_data_providers.clone(), }; aura_slots::start_slot_worker::<_, _, _, _, ShasperSlotCompatible, _>( @@ -199,20 +215,25 @@ pub fn start_shasper( ) } -struct ShasperWorker { +struct ShasperWorker { client: Arc, block_import: Arc, env: Arc, local_key: Arc, + last_proposed_epoch: Mutex, inherent_data_providers: InherentDataProviders, + pool: Arc>, } -impl SlotWorker for ShasperWorker where - C: Authorities, +impl, C, E, I, P, Error> SlotWorker for ShasperWorker where + C: Authorities + ChainHead + HeaderBackend + ProvideRuntimeApi, + C::Api: ShasperApi, + NumberFor: From, E: Environment, E::Proposer: Proposer, <>::Create as IntoFuture>::Future: Send + 'static, I: BlockImport + Send + Sync + 'static, + P: PoolChainApi, Error: From + From, DigestItemFor: CompatibleDigestItem + DigestItem, Error: ::std::error::Error + Send + 'static + From<::consensus_common::Error>, @@ -223,7 +244,15 @@ impl SlotWorker for ShasperWorker where &self, slot_duration: u64 ) -> Result<(), consensus_common::Error> { - register_shasper_inherent_data_provider(&self.inherent_data_providers, slot_duration) + register_shasper_inherent_data_provider(&self.inherent_data_providers, slot_duration)?; + + let chain_head_hash = self.client.best_block_header().map_err(client_to_common_error)?.hash(); + let current_epoch = runtime::utils::slot_to_epoch( + self.client.runtime_api().slot(&BlockId::Hash(chain_head_hash)).map_err(client_to_common_error)? - 1 + ); + *self.last_proposed_epoch.lock() = current_epoch; + + Ok(()) } fn on_slot( @@ -244,6 +273,81 @@ impl SlotWorker for ShasperWorker where } }; + let chain_head = match self.client.best_block_header() { + Ok(header) => header, + Err(_) => { + warn!("Unable to fetch chain head"); + return Box::new(future::ok(())); + } + }; + let current_slot = match self.client.runtime_api().slot(&BlockId::Hash(chain_head.hash())) { + Ok(slot) => slot - 1, + Err(_) => { + warn!("Unable to get current slot"); + return Box::new(future::ok(())); + }, + }; + let current_epoch = runtime::utils::slot_to_epoch(current_slot); + + if *self.last_proposed_epoch.lock() < current_epoch { + debug!(target: "shasper", "Last proposed epoch {} is less than current epoch {}, submitting a new attestation", *self.last_proposed_epoch.lock(), current_epoch); + let validator_id = ValidatorId::from_public(public_key.clone()); + let validator_index = match self.client.runtime_api().validator_index(&BlockId::Hash(chain_head.hash()), validator_id) { + Ok(validator_index) => validator_index, + Err(_) => { + warn!("Fetching validator index failed"); + return Box::new(future::ok(())); + }, + }; + + if let Some(validator_index) = validator_index { + let justified_epoch = match self.client.runtime_api().justified_epoch(&BlockId::Hash(chain_head.hash())) { + Ok(v) => v, + Err(_) => { + warn!("Fetching justified epoch failed"); + return Box::new(future::ok(())); + }, + }; + let justified_header = match self.client.header(BlockId::Number(runtime::utils::epoch_to_slot(justified_epoch).into())) { + Ok(Some(v)) => v, + Err(_) | Ok(None) => { + warn!("Fetching justified header failed"); + return Box::new(future::ok(())); + }, + }; + let target_header = match self.client.header(BlockId::Number(runtime::utils::epoch_to_slot(current_epoch).into())) { + Ok(Some(v)) => v, + Err(_) | Ok(None) => { + warn!("Fetching current header failed"); + return Box::new(future::ok(())); + }, + }; + + let unsigned = UnsignedAttestation { + slot: current_slot, + slot_block_hash: chain_head.hash(), + source_epoch: justified_epoch, + source_epoch_block_hash: justified_header.hash(), + target_epoch: current_epoch, + target_epoch_block_hash: target_header.hash(), + validator_index, + }; + let signed = unsigned.sign_with(&self.local_key.secret); + + debug!(target: "shasper", "Signed attestation: {:?}", signed); + if self.pool.submit_one(&BlockId::Hash(chain_head.hash()), UncheckedExtrinsic::Attestation(signed)).is_err() { + warn!("Submitting attestation failed"); + return Box::new(future::ok(())); + } + debug!(target: "shasper", "Submitted the attestation to transaction pool"); + + *self.last_proposed_epoch.lock() = current_epoch; + debug!(target: "shasper", "Successfully submitted attestation for current epoch"); + } else { + debug!(target: "shasper", "Given public key {} is not in the validator set", validator_id); + } + } + let proposal_work = match utils::slot_author(slot_num, &authorities) { None => return Box::new(future::ok(())), Some(author) => if author == ValidatorId::from_public(public_key.clone()) { diff --git a/primitives/src/attestation.rs b/primitives/src/attestation.rs index 738ac31878b3feb4f1223f88c224880bbc23951d..9e97c9cc9e3e2c12cdd6b77dc28c48ab8041936f 100644 --- a/primitives/src/attestation.rs +++ b/primitives/src/attestation.rs @@ -1,4 +1,6 @@ use rstd::prelude::*; +use crypto::bls; +use codec::Encode; use codec_derive::{Encode, Decode}; #[cfg(feature = "std")] use serde_derive::{Serialize, Deserialize}; @@ -17,6 +19,18 @@ pub struct UnsignedAttestation { pub validator_index: u32, } +impl UnsignedAttestation { + pub fn sign_with(self, secret: &bls::Secret) -> UncheckedAttestation { + let to_sign = self.encode(); + let signature = secret.sign(&to_sign[..]); + + UncheckedAttestation { + signature: signature.into(), + data: self, + } + } +} + #[derive(Eq, PartialEq, Clone, Encode, Decode)] #[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] pub struct UncheckedAttestation { diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 0f4f3cce4968b6178d9e1b2a1c4df861b17957c3..3efdf27de91feed29a0869343ed4d357f401b569 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -56,3 +56,6 @@ pub type Epoch = u64; /// Balance value in Shasper. pub type Balance = u128; + +/// Validator index in Shasper. +pub type ValidatorIndex = u32; diff --git a/runtime/src/consts.rs b/runtime/src/consts.rs index 0e9fdd635c59e6f57f1431809f3641a50963cf28..3136c5f1959b2a6d5f3769f6b234080e75ec8328 100644 --- a/runtime/src/consts.rs +++ b/runtime/src/consts.rs @@ -16,9 +16,9 @@ use primitives::{Slot, Balance}; -pub const CYCLE_LENGTH: Slot = 64; +pub const CYCLE_LENGTH: Slot = 4; pub const BASE_REWARD_QUOTIENT: Balance = 32; pub const INACTIVITY_PENALTY_QUOTIENT: Balance = 16777216; pub const INCLUDER_REWARD_QUOTIENT: Balance = 8; -pub const MIN_ATTESTATION_INCLUSION_DELAY: Slot = 4; +pub const MIN_ATTESTATION_INCLUSION_DELAY: Slot = 0; pub const WHISTLEBLOWER_REWARD_QUOTIENT: Balance = 512; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 729dc536ff4a0e73a408d349a8056ed07aa49ef6..4fbbc6f24664b5a56037fb1ce542ecbe5cd54c05 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -35,7 +35,7 @@ mod state; pub mod utils; use rstd::prelude::*; -use primitives::{BlockNumber, ValidatorId, OpaqueMetadata, Hash, UncheckedAttestation, CheckedAttestation}; +use primitives::{BlockNumber, ValidatorId, OpaqueMetadata, UncheckedAttestation, CheckedAttestation}; use client::block_builder::api::runtime_decl_for_BlockBuilder::BlockBuilder; use runtime_primitives::{ ApplyResult, transaction_validity::TransactionValidity, generic, @@ -129,11 +129,11 @@ impl_runtime_apis! { } fn initialise_block(header: &::Header) { - storage::Number::put(header.number); - storage::ParentHash::put(header.parent_hash); - storage::ExtrinsicsRoot::put(header.extrinsics_root); + use runtime_primitives::traits::Header; + + storage::Number::put(header.number()); + storage::ParentHash::put(header.parent_hash()); storage::Digest::put(header.digest.clone()); - storage::ExtrinsicsRoot::put(Hash::from(BlakeTwo256::enumerated_trie_root(&[]))); storage::note_parent_hash(); } @@ -152,10 +152,6 @@ impl_runtime_apis! { let mut extrinsics = ::items(); extrinsics.push(Some(extrinsic.clone())); - let extrinsics_data: Vec> = extrinsics.iter().map(Encode::encode).collect(); - let extrinsics_root = BlakeTwo256::enumerated_trie_root(&extrinsics_data.iter().map(Vec::as_slice).collect::>()); - ::put(Hash::from(extrinsics_root)); - ::set_items(extrinsics); match extrinsic { @@ -208,13 +204,20 @@ impl_runtime_apis! { } casper.advance_epoch(&mut store); + storage::CasperContext::put(casper); } - ::set_count(0); - - ::take(); - let parent_hash = ::take(); - let extrinsics_root = ::take(); + let extrinsics = storage::UncheckedExtrinsics::items() + .into_iter() + .filter(|e| e.is_some()) + .map(|e| e.expect("Checked is_some in filter; qed")) + .collect::>(); + let extrinsic_data = extrinsics.iter().map(Encode::encode).collect::>(); + storage::UncheckedExtrinsics::set_count(0); + + storage::Number::take(); + let parent_hash = storage::ParentHash::take(); + let extrinsics_root = BlakeTwo256::enumerated_trie_root(&extrinsic_data.iter().map(Vec::as_slice).collect::>()); let digest = ::take(); let state_root = BlakeTwo256::storage_root(); @@ -259,7 +262,7 @@ impl_runtime_apis! { impl aura_primitives::AuraApi for Runtime { fn slot_duration() -> u64 { - 10 + 2 } } @@ -291,5 +294,16 @@ impl_runtime_apis! { fn check_attestation(unchecked: UncheckedAttestation) -> Option { state::check_attestation(unchecked) } + + fn validator_index(validator_id: ValidatorId) -> Option { + for (i, record) in storage::Validators::items().into_iter().enumerate() { + if let Some(record) = record { + if record.validator_id == validator_id { + return Some(i as u32); + } + } + } + None + } } } diff --git a/runtime/src/state.rs b/runtime/src/state.rs index 46f83e09e8eb77f04237fa632ae96c2fb10aaec4..9322b366fa77db8f0e1727d02ddb3060aa7eea4a 100644 --- a/runtime/src/state.rs +++ b/runtime/src/state.rs @@ -93,7 +93,7 @@ pub fn check_attestation(unchecked: UncheckedAttestation) -> Option= unchecked.data.slot { + if unchecked.data.slot >= current_slot { return None; } diff --git a/runtime/src/storage.rs b/runtime/src/storage.rs index 87128e60576b3aa803a1ee1c34652a9b11827b54..8dfcef96fd724713c0044099f8b93e37df2a852d 100644 --- a/runtime/src/storage.rs +++ b/runtime/src/storage.rs @@ -24,7 +24,6 @@ use crate::{UncheckedExtrinsic, Digest as DigestT, utils}; storage_items! { pub Number: b"sys:num" => default BlockNumber; pub ParentHash: b"sys:parenthash" => default Hash; - pub ExtrinsicsRoot: b"sys:extrinsicsroot" => default Hash; pub Digest: b"sys:digest" => default DigestT; pub CasperContext: b"sys:caspercontext" => default casper::CasperContext; } @@ -50,8 +49,10 @@ impl unhashed::StorageVec for PendingAttestations { pub fn note_parent_hash() { let slot = Number::get() - 1; let hash = ParentHash::get(); - assert!(LatestBlockHashes::count() <= slot as u32); - for i in LatestBlockHashes::count()..(slot as u32) { + let current_count = LatestBlockHashes::count(); + assert!(current_count <= slot as u32 + 1); + LatestBlockHashes::set_count(slot as u32 + 1); + for i in current_count..(slot as u32) { LatestBlockHashes::set_item(i, &None); } LatestBlockHashes::set_item(slot as u32, &Some(hash)); diff --git a/src/service.rs b/src/service.rs index d22bffe0813850f4e80f71c2d2c8e3cd912f0785..583fcf705d8211b7b1ea2bcdbe9c80244c017a93 100644 --- a/src/service.rs +++ b/src/service.rs @@ -98,6 +98,7 @@ construct_service_factory! { Arc::new(ShasperBlockImport::new(client)?), proposer, service.network(), + service.transaction_pool(), service.on_exit(), service.config.custom.inherent_data_providers.clone(), )?);