Aura: Broadcast empty step messages instead of creating empty blocks (#7605)

* aura: broadcast empty step message instead of sealing empty block

* aura: add empty_step messages to seal

* aura: include parent_hash in empty step message

* aura: verify received empty step messages

* aura: verify empty step messages in block

* aura: fix dead lock on empty_steps

* aura: fix EmptyStep Encodable

* aura: take number of empty steps into account in chain score

* aura: use empty step signers for finality

* aura: add empty "empty step" messages to seal when reading from spec

* aura: fix EmptyStep rlp encoding

* aura: use Vec<u8> instead of Bytes

* aura: fix block empty step verification

* Update .gitlab-ci.yml

fix lint

* aura: fix accumulation of empty step signatures for finality

* aura: include empty steps in seal signature

* aura: configurable max number of empty steps

* engine: pass block header to seal_fields method

This is necessary to make the number of seal fields dynamic,
e.g. activating a transition on a certain block number that changes
the seal.

* aura: add transition to enable empty step messages

* aura: clear old empty step messages on verify_block_external

* aura: ignore empty step messages from the future

* aura: report skipped primaries when empty steps are not enabled

* aura: fix tests

* aura: report misbehavior

* aura: add tests for rolling finality with multiple signatures

* engine: fix validator set test

In this test the block validation wasn't failing because the block was in the
future (expected failure) but was instead failing because the author of the
block isn't the expected authority. Since we added reporting of blocks produced
by the wrong authority this test started failing.

* aura: reward all the authors of empty step messages

* aura: fix reward attribution for new blocks

* aura: add tests for empty steps broadcasting and inclusion in blocks

* aura: reduce size of empty step messages in seal

* aura: add test for empty step inclusion in blocks

* aura: add test for rewarding of empty steps

* aura: add test for empty steps validation

* aura: fix rlp encoding of sealed empty step

* aura: fix grumbles
This commit is contained in:
André Silva 2018-02-15 00:39:29 +00:00 committed by Robert Habermeier
parent 5d6e7e1439
commit aab63c339d
17 changed files with 953 additions and 131 deletions

View File

@ -630,4 +630,8 @@ impl<T: ChainDataFetcher> ::ethcore::client::EngineClient for Client<T> {
fn block_number(&self, id: BlockId) -> Option<BlockNumber> { fn block_number(&self, id: BlockId) -> Option<BlockNumber> {
self.block_header(id).map(|hdr| hdr.number()) self.block_header(id).map(|hdr| hdr.number())
} }
fn block_header(&self, id: BlockId) -> Option<encoded::Header> {
Client::block_header(self, id)
}
} }

View File

@ -0,0 +1,51 @@
{
"name": "TestAuthorityRoundEmptySteps",
"engine": {
"authorityRound": {
"params": {
"stepDuration": 1,
"startStep": 2,
"validators": {
"list": [
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e",
"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1"
]
},
"blockReward": "10",
"immediateTransitions": true,
"emptyStepsTransition": "1",
"maximumEmptySteps": "2"
}
}
},
"params": {
"gasLimitBoundDivisor": "0x0400",
"accountStartNonce": "0x0",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x69"
},
"genesis": {
"seal": {
"authorityRound": {
"step": "0x0",
"signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
},
"difficulty": "0x20000",
"author": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x",
"gasLimit": "0x222222"
},
"accounts": {
"0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
"0000000000000000000000000000000000000002": { "balance": "1", "nonce": "1048576", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
"0000000000000000000000000000000000000003": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
"0000000000000000000000000000000000000004": { "balance": "1", "nonce": "1048576", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } },
"9cce34f7ab185c7aba1b7c8140d620b4bda941d6": { "balance": "1606938044258990275541962092341162602522202993782792835301376", "nonce": "1048576" },
"7d577a597b2742b498cb5cf0c26cdcd726d39e6e": { "balance": "1000000000" },
"82a978b3f5962a5b0957d9ee9eef472ee55b42f1": { "balance": "1000000000" }
}
}

View File

@ -543,9 +543,11 @@ impl LockedBlock {
/// ///
/// NOTE: This does not check the validity of `seal` with the engine. /// NOTE: This does not check the validity of `seal` with the engine.
pub fn seal(self, engine: &EthEngine, seal: Vec<Bytes>) -> Result<SealedBlock, BlockError> { pub fn seal(self, engine: &EthEngine, seal: Vec<Bytes>) -> Result<SealedBlock, BlockError> {
let expected_seal_fields = engine.seal_fields(self.header());
let mut s = self; let mut s = self;
if seal.len() != engine.seal_fields() { if seal.len() != expected_seal_fields {
return Err(BlockError::InvalidSealArity(Mismatch{expected: engine.seal_fields(), found: seal.len()})); return Err(BlockError::InvalidSealArity(
Mismatch { expected: expected_seal_fields, found: seal.len() }));
} }
s.block.header.set_seal(seal); s.block.header.set_seal(seal);
Ok(SealedBlock { block: s.block, uncle_bytes: s.uncle_bytes }) Ok(SealedBlock { block: s.block, uncle_bytes: s.uncle_bytes })

View File

@ -2002,6 +2002,10 @@ impl super::traits::EngineClient for Client {
fn block_number(&self, id: BlockId) -> Option<BlockNumber> { fn block_number(&self, id: BlockId) -> Option<BlockNumber> {
BlockChainClient::block_number(self, id) BlockChainClient::block_number(self, id)
} }
fn block_header(&self, id: BlockId) -> Option<::encoded::Header> {
BlockChainClient::block_header(self, id)
}
} }
impl ProvingBlockChainClient for Client { impl ProvingBlockChainClient for Client {

View File

@ -834,4 +834,8 @@ impl super::traits::EngineClient for TestBlockChainClient {
fn block_number(&self, id: BlockId) -> Option<BlockNumber> { fn block_number(&self, id: BlockId) -> Option<BlockNumber> {
BlockChainClient::block_number(self, id) BlockChainClient::block_number(self, id)
} }
fn block_header(&self, id: BlockId) -> Option<::encoded::Header> {
BlockChainClient::block_header(self, id)
}
} }

View File

@ -339,6 +339,9 @@ pub trait EngineClient: Sync + Send {
/// Get a block number by ID. /// Get a block number by ID.
fn block_number(&self, id: BlockId) -> Option<BlockNumber>; fn block_number(&self, id: BlockId) -> Option<BlockNumber>;
/// Get raw block header data by block id.
fn block_header(&self, id: BlockId) -> Option<encoded::Header>;
} }
/// Extended client interface for providing proofs of the state. /// Extended client interface for providing proofs of the state.

View File

@ -30,7 +30,7 @@ pub struct UnknownValidator;
/// Rolling finality checker for authority round consensus. /// Rolling finality checker for authority round consensus.
/// Stores a chain of unfinalized hashes that can be pushed onto. /// Stores a chain of unfinalized hashes that can be pushed onto.
pub struct RollingFinality { pub struct RollingFinality {
headers: VecDeque<(H256, Address)>, headers: VecDeque<(H256, Vec<Address>)>,
signers: SimpleList, signers: SimpleList,
sign_count: HashMap<Address, usize>, sign_count: HashMap<Address, usize>,
last_pushed: Option<H256>, last_pushed: Option<H256>,
@ -52,28 +52,31 @@ impl RollingFinality {
/// ///
/// Fails if any provided signature isn't part of the signers set. /// Fails if any provided signature isn't part of the signers set.
pub fn build_ancestry_subchain<I>(&mut self, iterable: I) -> Result<(), UnknownValidator> pub fn build_ancestry_subchain<I>(&mut self, iterable: I) -> Result<(), UnknownValidator>
where I: IntoIterator<Item=(H256, Address)> where I: IntoIterator<Item=(H256, Vec<Address>)>
{ {
self.clear(); self.clear();
for (hash, signer) in iterable { for (hash, signers) in iterable {
if !self.signers.contains(&signer) { return Err(UnknownValidator) } if signers.iter().any(|s| !self.signers.contains(s)) { return Err(UnknownValidator) }
if self.last_pushed.is_none() { self.last_pushed = Some(hash) } if self.last_pushed.is_none() { self.last_pushed = Some(hash) }
// break when we've got our first finalized block. // break when we've got our first finalized block.
{ {
let current_signed = self.sign_count.len(); let current_signed = self.sign_count.len();
let would_be_finalized = (current_signed + 1) * 2 > self.signers.len();
let entry = self.sign_count.entry(signer); let new_signers = signers.iter().filter(|s| !self.sign_count.contains_key(s)).count();
if let (true, &Entry::Vacant(_)) = (would_be_finalized, &entry) { let would_be_finalized = (current_signed + new_signers) * 2 > self.signers.len();
if would_be_finalized {
trace!(target: "finality", "Encountered already finalized block {}", hash); trace!(target: "finality", "Encountered already finalized block {}", hash);
break break
} }
*entry.or_insert(0) += 1; for signer in signers.iter() {
*self.sign_count.entry(*signer).or_insert(0) += 1;
}
} }
self.headers.push_front((hash, signer)); self.headers.push_front((hash, signers));
} }
trace!(target: "finality", "Rolling finality state: {:?}", self.headers); trace!(target: "finality", "Rolling finality state: {:?}", self.headers);
@ -103,30 +106,35 @@ impl RollingFinality {
/// Fails if `signer` isn't a member of the active validator set. /// Fails if `signer` isn't a member of the active validator set.
/// Returns a list of all newly finalized headers. /// Returns a list of all newly finalized headers.
// TODO: optimize with smallvec. // TODO: optimize with smallvec.
pub fn push_hash(&mut self, head: H256, signer: Address) -> Result<Vec<H256>, UnknownValidator> { pub fn push_hash(&mut self, head: H256, signers: Vec<Address>) -> Result<Vec<H256>, UnknownValidator> {
if !self.signers.contains(&signer) { return Err(UnknownValidator) } if signers.iter().any(|s| !self.signers.contains(s)) { return Err(UnknownValidator) }
self.headers.push_back((head, signer)); for signer in signers.iter() {
*self.sign_count.entry(signer).or_insert(0) += 1; *self.sign_count.entry(*signer).or_insert(0) += 1;
}
self.headers.push_back((head, signers));
let mut newly_finalized = Vec::new(); let mut newly_finalized = Vec::new();
while self.sign_count.len() * 2 > self.signers.len() { while self.sign_count.len() * 2 > self.signers.len() {
let (hash, signer) = self.headers.pop_front() let (hash, signers) = self.headers.pop_front()
.expect("headers length always greater than sign count length; qed"); .expect("headers length always greater than sign count length; qed");
newly_finalized.push(hash); newly_finalized.push(hash);
match self.sign_count.entry(signer) { for signer in signers {
Entry::Occupied(mut entry) => { match self.sign_count.entry(signer) {
// decrement count for this signer and purge on zero. Entry::Occupied(mut entry) => {
*entry.get_mut() -= 1; // decrement count for this signer and purge on zero.
*entry.get_mut() -= 1;
if *entry.get() == 0 { if *entry.get() == 0 {
entry.remove(); entry.remove();
}
} }
Entry::Vacant(_) => panic!("all hashes in `header` should have entries in `sign_count` for their signers; qed"),
} }
Entry::Vacant(_) => panic!("all hashes in `header` should have an entry in `sign_count` for their signer; qed"),
} }
} }
@ -137,7 +145,7 @@ impl RollingFinality {
} }
} }
pub struct Iter<'a>(::std::collections::vec_deque::Iter<'a, (H256, Address)>); pub struct Iter<'a>(::std::collections::vec_deque::Iter<'a, (H256, Vec<Address>)>);
impl<'a> Iterator for Iter<'a> { impl<'a> Iterator for Iter<'a> {
type Item = H256; type Item = H256;
@ -153,10 +161,10 @@ mod tests {
use super::RollingFinality; use super::RollingFinality;
#[test] #[test]
fn rejects_unknown_signer() { fn rejects_unknown_signers() {
let signers = (0..3).map(|_| Address::random()).collect(); let signers = (0..3).map(|_| Address::random()).collect::<Vec<_>>();
let mut finality = RollingFinality::blank(signers); let mut finality = RollingFinality::blank(signers.clone());
assert!(finality.push_hash(H256::random(), Address::random()).is_err()); assert!(finality.push_hash(H256::random(), vec![signers[0], Address::random()]).is_err());
} }
#[test] #[test]
@ -169,19 +177,29 @@ mod tests {
// 3 / 6 signers is < 51% so no finality. // 3 / 6 signers is < 51% so no finality.
for (i, hash) in hashes.iter().take(6).cloned().enumerate() { for (i, hash) in hashes.iter().take(6).cloned().enumerate() {
let i = i % 3; let i = i % 3;
assert!(finality.push_hash(hash, signers[i]).unwrap().len() == 0); assert!(finality.push_hash(hash, vec![signers[i]]).unwrap().len() == 0);
} }
// after pushing a block signed by a fourth validator, the first four // after pushing a block signed by a fourth validator, the first four
// blocks of the unverified chain become verified. // blocks of the unverified chain become verified.
assert_eq!(finality.push_hash(hashes[6], signers[4]).unwrap(), assert_eq!(finality.push_hash(hashes[6], vec![signers[4]]).unwrap(),
vec![hashes[0], hashes[1], hashes[2], hashes[3]]); vec![hashes[0], hashes[1], hashes[2], hashes[3]]);
} }
#[test]
fn finalize_multiple_signers() {
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
let mut finality = RollingFinality::blank(signers.clone());
let hash = H256::random();
// after pushing a block signed by four validators, it becomes verified right away.
assert_eq!(finality.push_hash(hash, signers[0..4].to_vec()).unwrap(), vec![hash]);
}
#[test] #[test]
fn from_ancestry() { fn from_ancestry() {
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect(); let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
let hashes: Vec<_> = (0..12).map(|i| (H256::random(), signers[i % 6])).collect(); let hashes: Vec<_> = (0..12).map(|i| (H256::random(), vec![signers[i % 6]])).collect();
let mut finality = RollingFinality::blank(signers.clone()); let mut finality = RollingFinality::blank(signers.clone());
finality.build_ancestry_subchain(hashes.iter().rev().cloned()).unwrap(); finality.build_ancestry_subchain(hashes.iter().rev().cloned()).unwrap();
@ -189,4 +207,20 @@ mod tests {
assert_eq!(finality.unfinalized_hashes().count(), 3); assert_eq!(finality.unfinalized_hashes().count(), 3);
assert_eq!(finality.subchain_head(), Some(hashes[11].0)); assert_eq!(finality.subchain_head(), Some(hashes[11].0));
} }
#[test]
fn from_ancestry_multiple_signers() {
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
let hashes: Vec<_> = (0..12).map(|i| {
(H256::random(), vec![signers[i % 6], signers[(i + 1) % 6], signers[(i + 2) % 6]])
}).collect();
let mut finality = RollingFinality::blank(signers.clone());
finality.build_ancestry_subchain(hashes.iter().rev().cloned()).unwrap();
// only the last hash has < 51% of authorities' signatures
assert_eq!(finality.unfinalized_hashes().count(), 1);
assert_eq!(finality.unfinalized_hashes().next(), Some(hashes[11].0));
assert_eq!(finality.subchain_head(), Some(hashes[11].0));
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -97,7 +97,7 @@ impl Engine<EthereumMachine> for BasicAuthority {
fn machine(&self) -> &EthereumMachine { &self.machine } fn machine(&self) -> &EthereumMachine { &self.machine }
// One field - the signature // One field - the signature
fn seal_fields(&self) -> usize { 1 } fn seal_fields(&self, _header: &Header) -> usize { 1 }
fn seals_internally(&self) -> Option<bool> { fn seals_internally(&self) -> Option<bool> {
Some(self.signer.read().is_some()) Some(self.signer.read().is_some())

View File

@ -181,7 +181,7 @@ pub trait Engine<M: Machine>: Sync + Send {
fn machine(&self) -> &M; fn machine(&self) -> &M;
/// The number of additional header fields required for this engine. /// The number of additional header fields required for this engine.
fn seal_fields(&self) -> usize { 0 } fn seal_fields(&self, _header: &M::Header) -> usize { 0 }
/// Additional engine-specific information for the user/developer concerning `header`. /// Additional engine-specific information for the user/developer concerning `header`.
fn extra_info(&self, _header: &M::Header) -> BTreeMap<String, String> { BTreeMap::new() } fn extra_info(&self, _header: &M::Header) -> BTreeMap<String, String> { BTreeMap::new() }
@ -406,27 +406,27 @@ pub mod common {
use trace::{Tracer, ExecutiveTracer, RewardType}; use trace::{Tracer, ExecutiveTracer, RewardType};
use state::CleanupMode; use state::CleanupMode;
use ethereum_types::U256; use ethereum_types::{Address, U256};
/// Give reward and trace. /// Give reward and trace.
pub fn bestow_block_reward(block: &mut ExecutedBlock, reward: U256) -> Result<(), Error> { pub fn bestow_block_reward(block: &mut ExecutedBlock, reward: U256, receiver: Address) -> Result<(), Error> {
let fields = block.fields_mut(); let fields = block.fields_mut();
// Bestow block reward // Bestow block reward
let res = fields.state.add_balance(fields.header.author(), &reward, CleanupMode::NoEmpty) let res = fields.state.add_balance(&receiver, &reward, CleanupMode::NoEmpty)
.map_err(::error::Error::from) .map_err(::error::Error::from)
.and_then(|_| fields.state.commit()); .and_then(|_| fields.state.commit());
let block_author = fields.header.author().clone();
fields.traces.as_mut().map(move |traces| { fields.traces.as_mut().map(move |traces| {
let mut tracer = ExecutiveTracer::default(); let mut tracer = ExecutiveTracer::default();
tracer.trace_reward(block_author, reward, RewardType::Block); tracer.trace_reward(receiver, reward, RewardType::Block);
traces.push(tracer.drain()) traces.push(tracer.drain())
}); });
// Commit state so that we can actually figure out the state root.
if let Err(ref e) = res { if let Err(ref e) = res {
warn!("Encountered error on bestowing reward: {}", e); warn!("Encountered error on bestowing reward: {}", e);
} }
res res
} }
} }

View File

@ -441,7 +441,7 @@ impl Engine<EthereumMachine> for Tendermint {
fn name(&self) -> &str { "Tendermint" } fn name(&self) -> &str { "Tendermint" }
/// (consensus view, proposal signature, authority signatures) /// (consensus view, proposal signature, authority signatures)
fn seal_fields(&self) -> usize { 3 } fn seal_fields(&self, _header: &Header) -> usize { 3 }
fn machine(&self) -> &EthereumMachine { &self.machine } fn machine(&self) -> &EthereumMachine { &self.machine }
@ -561,7 +561,8 @@ impl Engine<EthereumMachine> for Tendermint {
/// Apply the block reward on finalisation of the block. /// Apply the block reward on finalisation of the block.
fn on_close_block(&self, block: &mut ExecutedBlock) -> Result<(), Error>{ fn on_close_block(&self, block: &mut ExecutedBlock) -> Result<(), Error>{
::engines::common::bestow_block_reward(block, self.block_reward) let author = *block.header().author();
::engines::common::bestow_block_reward(block, self.block_reward, author)
} }
fn verify_local_seal(&self, _header: &Header) -> Result<(), Error> { fn verify_local_seal(&self, _header: &Header) -> Result<(), Error> {
@ -570,7 +571,8 @@ impl Engine<EthereumMachine> for Tendermint {
fn verify_block_basic(&self, header: &Header) -> Result<(), Error> { fn verify_block_basic(&self, header: &Header) -> Result<(), Error> {
let seal_length = header.seal().len(); let seal_length = header.seal().len();
if seal_length == self.seal_fields() { let expected_seal_fields = self.seal_fields(header);
if seal_length == expected_seal_fields {
// Either proposal or commit. // Either proposal or commit.
if (header.seal()[1] == ::rlp::NULL_RLP) if (header.seal()[1] == ::rlp::NULL_RLP)
!= (header.seal()[2] == ::rlp::EMPTY_LIST_RLP) { != (header.seal()[2] == ::rlp::EMPTY_LIST_RLP) {
@ -581,7 +583,7 @@ impl Engine<EthereumMachine> for Tendermint {
} }
} else { } else {
Err(BlockError::InvalidSealArity( Err(BlockError::InvalidSealArity(
Mismatch { expected: self.seal_fields(), found: seal_length } Mismatch { expected: expected_seal_fields, found: seal_length }
).into()) ).into())
} }
} }
@ -777,7 +779,6 @@ mod tests {
use block::*; use block::*;
use error::{Error, BlockError}; use error::{Error, BlockError};
use header::Header; use header::Header;
use client::ChainNotify;
use miner::MinerService; use miner::MinerService;
use tests::helpers::*; use tests::helpers::*;
use account_provider::AccountProvider; use account_provider::AccountProvider;
@ -837,17 +838,6 @@ mod tests {
addr addr
} }
#[derive(Default)]
struct TestNotify {
messages: RwLock<Vec<Bytes>>,
}
impl ChainNotify for TestNotify {
fn broadcast(&self, data: Vec<u8>) {
self.messages.write().push(data);
}
}
#[test] #[test]
fn has_valid_metadata() { fn has_valid_metadata() {
let engine = Spec::new_test_tendermint().engine; let engine = Spec::new_test_tendermint().engine;

View File

@ -174,7 +174,7 @@ mod tests {
// Check a block that is a bit in future, reject it but don't report the validator. // Check a block that is a bit in future, reject it but don't report the validator.
let mut header = Header::default(); let mut header = Header::default();
let seal = vec![encode(&5u8).into_vec(), encode(&(&H520::default() as &[u8])).into_vec()]; let seal = vec![encode(&4u8).into_vec(), encode(&(&H520::default() as &[u8])).into_vec()];
header.set_seal(seal); header.set_seal(seal);
header.set_author(v1); header.set_author(v1);
header.set_number(2); header.set_number(2);

View File

@ -169,11 +169,11 @@ impl Engine<EthereumMachine> for Arc<Ethash> {
fn machine(&self) -> &EthereumMachine { &self.machine } fn machine(&self) -> &EthereumMachine { &self.machine }
// Two fields - nonce and mix. // Two fields - nonce and mix.
fn seal_fields(&self) -> usize { 2 } fn seal_fields(&self, _header: &Header) -> usize { 2 }
/// Additional engine-specific information for the user/developer concerning `header`. /// Additional engine-specific information for the user/developer concerning `header`.
fn extra_info(&self, header: &Header) -> BTreeMap<String, String> { fn extra_info(&self, header: &Header) -> BTreeMap<String, String> {
if header.seal().len() == self.seal_fields() { if header.seal().len() == self.seal_fields(header) {
map![ map![
"nonce".to_owned() => format!("0x{:x}", header.nonce()), "nonce".to_owned() => format!("0x{:x}", header.nonce()),
"mixHash".to_owned() => format!("0x{:x}", header.mix_hash()) "mixHash".to_owned() => format!("0x{:x}", header.mix_hash())
@ -265,9 +265,10 @@ impl Engine<EthereumMachine> for Arc<Ethash> {
fn verify_block_basic(&self, header: &Header) -> Result<(), Error> { fn verify_block_basic(&self, header: &Header) -> Result<(), Error> {
// check the seal fields. // check the seal fields.
if header.seal().len() != self.seal_fields() { let expected_seal_fields = self.seal_fields(header);
if header.seal().len() != expected_seal_fields {
return Err(From::from(BlockError::InvalidSealArity( return Err(From::from(BlockError::InvalidSealArity(
Mismatch { expected: self.seal_fields(), found: header.seal().len() } Mismatch { expected: expected_seal_fields, found: header.seal().len() }
))); )));
} }
UntrustedRlp::new(&header.seal()[0]).as_val::<H256>()?; UntrustedRlp::new(&header.seal()[0]).as_val::<H256>()?;
@ -296,9 +297,10 @@ impl Engine<EthereumMachine> for Arc<Ethash> {
} }
fn verify_block_unordered(&self, header: &Header) -> Result<(), Error> { fn verify_block_unordered(&self, header: &Header) -> Result<(), Error> {
if header.seal().len() != self.seal_fields() { let expected_seal_fields = self.seal_fields(header);
if header.seal().len() != expected_seal_fields {
return Err(From::from(BlockError::InvalidSealArity( return Err(From::from(BlockError::InvalidSealArity(
Mismatch { expected: self.seal_fields(), found: header.seal().len() } Mismatch { expected: expected_seal_fields, found: header.seal().len() }
))); )));
} }
let result = self.pow.compute_light(header.number() as u64, &header.bare_hash().0, header.nonce().low_u64()); let result = self.pow.compute_light(header.number() as u64, &header.bare_hash().0, header.nonce().low_u64());

View File

@ -762,6 +762,13 @@ impl Spec {
load_bundled!("authority_round") load_bundled!("authority_round")
} }
/// Create a new Spec with AuthorityRound consensus which does internal sealing (not
/// requiring work) with empty step messages enabled.
/// Accounts with secrets keccak("0") and keccak("1") are the validators.
pub fn new_test_round_empty_steps() -> Self {
load_bundled!("authority_round_empty_steps")
}
/// Create a new Spec with Tendermint consensus which does internal sealing (not requiring /// Create a new Spec with Tendermint consensus which does internal sealing (not requiring
/// work). /// work).
/// Account keccak("0") and keccak("1") are a authorities. /// Account keccak("0") and keccak("1") are a authorities.

View File

@ -19,7 +19,7 @@ use ethereum_types::{H256, U256};
use block::{OpenBlock, Drain}; use block::{OpenBlock, Drain};
use blockchain::{BlockChain, Config as BlockChainConfig}; use blockchain::{BlockChain, Config as BlockChainConfig};
use bytes::Bytes; use bytes::Bytes;
use client::{BlockChainClient, Client, ClientConfig}; use client::{BlockChainClient, ChainNotify, Client, ClientConfig};
use ethereum::ethash::EthashParams; use ethereum::ethash::EthashParams;
use ethkey::KeyPair; use ethkey::KeyPair;
use evm::Factory as EvmFactory; use evm::Factory as EvmFactory;
@ -29,6 +29,7 @@ use header::Header;
use io::*; use io::*;
use machine::EthashExtensions; use machine::EthashExtensions;
use miner::Miner; use miner::Miner;
use parking_lot::RwLock;
use rlp::{self, RlpStream}; use rlp::{self, RlpStream};
use spec::*; use spec::*;
use state_db::StateDB; use state_db::StateDB;
@ -388,3 +389,14 @@ pub fn get_default_ethash_params() -> EthashParams {
expip2_duration_limit: 30, expip2_duration_limit: 30,
} }
} }
#[derive(Default)]
pub struct TestNotify {
pub messages: RwLock<Vec<Bytes>>,
}
impl ChainNotify for TestNotify {
fn broadcast(&self, data: Vec<u8>) {
self.messages.write().push(data);
}
}

View File

@ -242,9 +242,10 @@ pub fn verify_block_final(expected: &Header, got: &Header) -> Result<(), Error>
/// Check basic header parameters. /// Check basic header parameters.
pub fn verify_header_params(header: &Header, engine: &EthEngine, is_full: bool) -> Result<(), Error> { pub fn verify_header_params(header: &Header, engine: &EthEngine, is_full: bool) -> Result<(), Error> {
if header.seal().len() != engine.seal_fields() { let expected_seal_fields = engine.seal_fields(header);
if header.seal().len() != expected_seal_fields {
return Err(From::from(BlockError::InvalidSealArity( return Err(From::from(BlockError::InvalidSealArity(
Mismatch { expected: engine.seal_fields(), found: header.seal().len() } Mismatch { expected: expected_seal_fields, found: header.seal().len() }
))); )));
} }

View File

@ -49,6 +49,12 @@ pub struct AuthorityRoundParams {
/// Maximum number of accepted uncles. /// Maximum number of accepted uncles.
#[serde(rename="maximumUncleCount")] #[serde(rename="maximumUncleCount")]
pub maximum_uncle_count: Option<Uint>, pub maximum_uncle_count: Option<Uint>,
/// Block at which empty step messages should start.
#[serde(rename="emptyStepsTransition")]
pub empty_steps_transition: Option<Uint>,
/// Maximum number of accepted empty steps.
#[serde(rename="maximumEmptySteps")]
pub maximum_empty_steps: Option<Uint>,
} }
/// Authority engine deserialization. /// Authority engine deserialization.