diff --git a/ethcore/res/authority_round.json b/ethcore/res/authority_round.json index ac7eb5041..dba7e28a5 100644 --- a/ethcore/res/authority_round.json +++ b/ethcore/res/authority_round.json @@ -33,7 +33,7 @@ "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", - "gasLimit": "0x2fefd8" + "gasLimit": "0x222222" }, "accounts": { "0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, diff --git a/ethcore/res/tendermint.json b/ethcore/res/tendermint.json index 83372fea5..642f5b385 100644 --- a/ethcore/res/tendermint.json +++ b/ethcore/res/tendermint.json @@ -38,7 +38,7 @@ "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", - "gasLimit": "0x2fefd8" + "gasLimit": "0x222222" }, "accounts": { "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, diff --git a/ethcore/res/validator_contract.json b/ethcore/res/validator_contract.json index 33fdf4c4f..6c2f87758 100644 --- a/ethcore/res/validator_contract.json +++ b/ethcore/res/validator_contract.json @@ -27,7 +27,7 @@ "timestamp": "0x00", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", - "gasLimit": "0x2fefd8" + "gasLimit": "0x222222" }, "accounts": { "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index ffca97f59..ba36e176f 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -253,7 +253,7 @@ impl Client { if let Some(reg_addr) = client.additional_params().get("registrar").and_then(|s| Address::from_str(s).ok()) { trace!(target: "client", "Found registrar at {}", reg_addr); let weak = Arc::downgrade(&client); - let registrar = Registry::new(reg_addr, move |a, d| weak.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d))); + let registrar = Registry::new(reg_addr, move |a, d| weak.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(BlockId::Latest, a, d))); *client.registrar.lock() = Some(registrar); } Ok(client) @@ -1445,7 +1445,7 @@ impl BlockChainClient for Client { } } - fn call_contract(&self, address: Address, data: Bytes) -> Result { + fn call_contract(&self, block_id: BlockId, address: Address, data: Bytes) -> Result { let from = Address::default(); let transaction = Transaction { nonce: self.latest_nonce(&from), @@ -1456,7 +1456,7 @@ impl BlockChainClient for Client { data: data, }.fake_sign(from); - self.call(&transaction, BlockId::Latest, Default::default()) + self.call(&transaction, block_id, Default::default()) .map_err(|e| format!("{:?}", e)) .map(|executed| { executed.output diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index dc9cb5944..3898f73b7 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -731,7 +731,7 @@ impl BlockChainClient for TestBlockChainClient { } } - fn call_contract(&self, _address: Address, _data: Bytes) -> Result { Ok(vec![]) } + fn call_contract(&self, _id: BlockId, _address: Address, _data: Bytes) -> Result { Ok(vec![]) } fn transact_contract(&self, address: Address, data: Bytes) -> Result { let transaction = Transaction { diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 6e1ea9d31..6d5e87b4b 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -254,7 +254,7 @@ pub trait BlockChainClient : Sync + Send { fn pruning_info(&self) -> PruningInfo; /// Like `call`, but with various defaults. Designed to be used for calling contracts. - fn call_contract(&self, address: Address, data: Bytes) -> Result; + fn call_contract(&self, id: BlockId, address: Address, data: Bytes) -> Result; /// Import a transaction: used for misbehaviour reporting. fn transact_contract(&self, address: Address, data: Bytes) -> Result; diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index ff984257d..ef1bced4d 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -150,12 +150,12 @@ impl AuthorityRound { } } - fn step_proposer(&self, step: usize) -> Address { - self.validators.get(step) + fn step_proposer(&self, bh: &H256, step: usize) -> Address { + self.validators.get(bh, step) } - fn is_step_proposer(&self, step: usize, address: &Address) -> bool { - self.step_proposer(step) == *address + fn is_step_proposer(&self, bh: &H256, step: usize, address: &Address) -> bool { + self.step_proposer(bh, step) == *address } fn is_future_step(&self, step: usize) -> bool { @@ -249,7 +249,7 @@ impl Engine for AuthorityRound { } fn seals_internally(&self) -> Option { - Some(self.validators.contains(&self.signer.address())) + Some(self.signer.address() != Address::default()) } /// Attempt to seal the block internally. @@ -260,7 +260,7 @@ impl Engine for AuthorityRound { if self.proposed.load(AtomicOrdering::SeqCst) { return Seal::None; } let header = block.header(); let step = self.step.load(AtomicOrdering::SeqCst); - if self.is_step_proposer(step, header.author()) { + if self.is_step_proposer(header.parent_hash(), step, header.author()) { if let Ok(signature) = self.signer.sign(header.bare_hash()) { trace!(target: "engine", "generate_seal: Issuing a block for step {}.", step); self.proposed.store(true, AtomicOrdering::SeqCst); @@ -299,32 +299,33 @@ impl Engine for AuthorityRound { } } - /// Check if the signature belongs to the correct proposer. - fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { - let header_step = header_step(header)?; + fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + Ok(()) + } + + /// Do the validator and gas limit validation. + fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + let step = header_step(header)?; // Give one step slack if step is lagging, double vote is still not possible. - if self.is_future_step(header_step) { + if self.is_future_step(step) { trace!(target: "engine", "verify_block_unordered: block from the future"); self.validators.report_benign(header.author()); Err(BlockError::InvalidSeal)? } else { + // Check if the signature belongs to a validator, can depend on parent state. let proposer_signature = header_signature(header)?; - let correct_proposer = self.step_proposer(header_step); - if verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? { - Ok(()) - } else { - trace!(target: "engine", "verify_block_unordered: bad proposer for step: {}", header_step); + let correct_proposer = self.step_proposer(header.parent_hash(), step); + if !verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? { + trace!(target: "engine", "verify_block_unordered: bad proposer for step: {}", step); Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))? } } - } - fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + // Do not calculate difficulty for genesis blocks. if header.number() == 0 { return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); } - let step = header_step(header)?; // Check if parent is from a previous step. if step == header_step(parent)? { trace!(target: "engine", "Multiple blocks proposed for step {}.", step); @@ -412,7 +413,7 @@ mod tests { let mut header: Header = Header::default(); header.set_seal(vec![encode(&H520::default()).to_vec()]); - let verify_result = engine.verify_block_unordered(&header, None); + let verify_result = engine.verify_block_family(&header, &Default::default(), None); assert!(verify_result.is_err()); } @@ -450,10 +451,14 @@ mod tests { #[test] fn proposer_switching() { - let mut header: Header = Header::default(); let tap = AccountProvider::transient_provider(); let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap(); - + let mut parent_header: Header = Header::default(); + parent_header.set_seal(vec![encode(&0usize).to_vec()]); + parent_header.set_gas_limit(U256::from_str("222222").unwrap()); + let mut header: Header = Header::default(); + header.set_number(1); + header.set_gas_limit(U256::from_str("222222").unwrap()); header.set_author(addr); let engine = Spec::new_test_round().engine; @@ -462,17 +467,22 @@ mod tests { // Two validators. // Spec starts with step 2. header.set_seal(vec![encode(&2usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); - assert!(engine.verify_block_seal(&header).is_err()); + assert!(engine.verify_block_family(&header, &parent_header, None).is_err()); header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); - assert!(engine.verify_block_seal(&header).is_ok()); + assert!(engine.verify_block_family(&header, &parent_header, None).is_ok()); } #[test] fn rejects_future_block() { - let mut header: Header = Header::default(); let tap = AccountProvider::transient_provider(); let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap(); + let mut parent_header: Header = Header::default(); + parent_header.set_seal(vec![encode(&0usize).to_vec()]); + parent_header.set_gas_limit(U256::from_str("222222").unwrap()); + let mut header: Header = Header::default(); + header.set_number(1); + header.set_gas_limit(U256::from_str("222222").unwrap()); header.set_author(addr); let engine = Spec::new_test_round().engine; @@ -481,8 +491,8 @@ mod tests { // Two validators. // Spec starts with step 2. header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); - assert!(engine.verify_block_seal(&header).is_ok()); + assert!(engine.verify_block_family(&header, &parent_header, None).is_ok()); header.set_seal(vec![encode(&5usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); - assert!(engine.verify_block_seal(&header).is_err()); + assert!(engine.verify_block_family(&header, &parent_header, None).is_err()); } } diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 50051bf7e..34b89b2d6 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -104,14 +104,14 @@ impl Engine for BasicAuthority { } fn seals_internally(&self) -> Option { - Some(self.validators.contains(&self.signer.address())) + Some(self.signer.address() != Address::default()) } /// Attempt to seal the block internally. fn generate_seal(&self, block: &ExecutedBlock) -> Seal { let header = block.header(); let author = header.author(); - if self.validators.contains(author) { + if self.validators.contains(header.parent_hash(), author) { // account should be pernamently unlocked, otherwise sealing will fail if let Ok(signature) = self.signer.sign(header.bare_hash()) { return Seal::Regular(vec![::rlp::encode(&(&H520::from(signature) as &[u8])).to_vec()]); @@ -133,20 +133,20 @@ impl Engine for BasicAuthority { Ok(()) } - fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { - use rlp::{UntrustedRlp, View}; - - // check the signature is legit. - let sig = UntrustedRlp::new(&header.seal()[0]).as_val::()?; - let signer = public_to_address(&recover(&sig.into(), &header.bare_hash())?); - if !self.validators.contains(&signer) { - return Err(BlockError::InvalidSeal)?; - } + fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { Ok(()) } fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> { - // we should not calculate difficulty for genesis blocks + use rlp::{UntrustedRlp, View}; + // Check if the signature belongs to a validator, can depend on parent state. + let sig = UntrustedRlp::new(&header.seal()[0]).as_val::()?; + let signer = public_to_address(&recover(&sig.into(), &header.bare_hash())?); + if !self.validators.contains(header.parent_hash(), &signer) { + return Err(BlockError::InvalidSeal)?; + } + + // Do not calculate difficulty for genesis blocks. if header.number() == 0 { return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))); } @@ -239,7 +239,7 @@ mod tests { let mut header: Header = Header::default(); header.set_seal(vec![::rlp::encode(&H520::default()).to_vec()]); - let verify_result = engine.verify_block_unordered(&header, None); + let verify_result = engine.verify_block_family(&header, &Default::default(), None); assert!(verify_result.is_err()); } diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 4c825cf20..aac101447 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -95,6 +95,8 @@ pub struct Tendermint { last_lock: AtomicUsize, /// Bare hash of the proposed block, used for seal submission. proposal: RwLock>, + /// Hash of the proposal parent block. + proposal_parent: RwLock, /// Set used to determine the current validators. validators: Box, } @@ -114,11 +116,12 @@ impl Tendermint { height: AtomicUsize::new(1), view: AtomicUsize::new(0), step: RwLock::new(Step::Propose), - votes: VoteCollector::default(), + votes: Default::default(), signer: Default::default(), lock_change: RwLock::new(None), last_lock: AtomicUsize::new(0), proposal: RwLock::new(None), + proposal_parent: Default::default(), validators: new_validator_set(our_params.validators), }); let handler = TransitionHandler::new(Arc::downgrade(&engine) as Weak, Box::new(our_params.timeouts)); @@ -232,7 +235,7 @@ impl Tendermint { let height = self.height.load(AtomicOrdering::SeqCst); if let Some(block_hash) = *self.proposal.read() { // Generate seal and remove old votes. - if self.is_signer_proposer() { + if self.is_signer_proposer(&*self.proposal_parent.read()) { let proposal_step = VoteStep::new(height, view, Step::Propose); let precommit_step = VoteStep::new(proposal_step.height, proposal_step.view, Step::Precommit); if let Some(seal) = self.votes.seal_signatures(proposal_step, precommit_step, &block_hash) { @@ -254,23 +257,23 @@ impl Tendermint { } fn is_authority(&self, address: &Address) -> bool { - self.validators.contains(address) + self.validators.contains(&*self.proposal_parent.read(), address) } fn is_above_threshold(&self, n: usize) -> bool { - n > self.validators.count() * 2/3 + n > self.validators.count(&*self.proposal_parent.read()) * 2/3 } /// Find the designated for the given view. - fn view_proposer(&self, height: Height, view: View) -> Address { + fn view_proposer(&self, bh: &H256, height: Height, view: View) -> Address { let proposer_nonce = height + view; trace!(target: "engine", "Proposer nonce: {}", proposer_nonce); - self.validators.get(proposer_nonce) + self.validators.get(bh, proposer_nonce) } /// Check if address is a proposer for given view. - fn is_view_proposer(&self, height: Height, view: View, address: &Address) -> Result<(), EngineError> { - let proposer = self.view_proposer(height, view); + fn is_view_proposer(&self, bh: &H256, height: Height, view: View, address: &Address) -> Result<(), EngineError> { + let proposer = self.view_proposer(bh, height, view); if proposer == *address { Ok(()) } else { @@ -279,8 +282,8 @@ impl Tendermint { } /// Check if current signer is the current proposer. - fn is_signer_proposer(&self) -> bool { - let proposer = self.view_proposer(self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst)); + fn is_signer_proposer(&self, bh: &H256) -> bool { + let proposer = self.view_proposer(bh, self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst)); self.signer.is_address(&proposer) } @@ -419,7 +422,7 @@ impl Engine for Tendermint { /// Should this node participate. fn seals_internally(&self) -> Option { - Some(self.is_authority(&self.signer.address())) + Some(self.signer.address() != Address::default()) } /// Attempt to seal generate a proposal seal. @@ -427,7 +430,7 @@ impl Engine for Tendermint { let header = block.header(); let author = header.author(); // Only proposer can generate seal if None was generated. - if !self.is_signer_proposer() || self.proposal.read().is_some() { + if !self.is_signer_proposer(header.parent_hash()) || self.proposal.read().is_some() { return Seal::None; } @@ -441,6 +444,7 @@ impl Engine for Tendermint { self.votes.vote(ConsensusMessage::new(signature, height, view, Step::Propose, bh), author); // Remember proposal for later seal submission. *self.proposal.write() = bh; + *self.proposal_parent.write() = header.parent_hash().clone(); Seal::Proposal(vec![ ::rlp::encode(&view).to_vec(), ::rlp::encode(&signature).to_vec(), @@ -505,7 +509,12 @@ impl Engine for Tendermint { } - fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> { + Ok(()) + } + + /// Verify validators and gas limit. + fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { let proposal = ConsensusMessage::new_proposal(header)?; let proposer = proposal.verify()?; if !self.is_authority(&proposer) { @@ -522,7 +531,7 @@ impl Engine for Tendermint { Some(a) => a, None => public_to_address(&recover(&precommit.signature.into(), &precommit_hash)?), }; - if !self.validators.contains(&address) { + if !self.validators.contains(header.parent_hash(), &address) { Err(EngineError::NotAuthorized(address.to_owned()))? } @@ -545,12 +554,9 @@ impl Engine for Tendermint { found: signatures_len }))?; } - self.is_view_proposer(proposal.vote_step.height, proposal.vote_step.view, &proposer)?; + self.is_view_proposer(header.parent_hash(), proposal.vote_step.height, proposal.vote_step.view, &proposer)?; } - Ok(()) - } - fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { if header.number() == 0 { Err(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))?; } @@ -595,6 +601,7 @@ impl Engine for Tendermint { debug!(target: "engine", "Received a new proposal {:?} from {}.", proposal.vote_step, proposer); if self.is_view(&proposal) { *self.proposal.write() = proposal.block_hash.clone(); + *self.proposal_parent.write() = header.parent_hash().clone(); } self.votes.vote(proposal, &proposer); true @@ -607,7 +614,7 @@ impl Engine for Tendermint { trace!(target: "engine", "Propose timeout."); if self.proposal.read().is_none() { // Report the proposer if no proposal was received. - let current_proposer = self.view_proposer(self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst)); + let current_proposer = self.view_proposer(&*self.proposal_parent.read(), self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst)); self.validators.report_benign(¤t_proposer); } Step::Prevote @@ -765,20 +772,25 @@ mod tests { let (spec, tap) = setup(); let engine = spec.engine; - let mut header = Header::default(); - let validator = insert_and_unlock(&tap, "0"); - header.set_author(validator); - let seal = proposal_seal(&tap, &header, 0); - header.set_seal(seal); - // Good proposer. - assert!(engine.verify_block_unordered(&header.clone(), None).is_ok()); + let mut parent_header: Header = Header::default(); + parent_header.set_gas_limit(U256::from_str("222222").unwrap()); + let mut header = Header::default(); + header.set_number(1); + header.set_gas_limit(U256::from_str("222222").unwrap()); let validator = insert_and_unlock(&tap, "1"); header.set_author(validator); let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); + // Good proposer. + assert!(engine.verify_block_family(&header, &parent_header, None).is_ok()); + + let validator = insert_and_unlock(&tap, "0"); + header.set_author(validator); + let seal = proposal_seal(&tap, &header, 0); + header.set_seal(seal); // Bad proposer. - match engine.verify_block_unordered(&header, None) { + match engine.verify_block_family(&header, &parent_header, None) { Err(Error::Engine(EngineError::NotProposer(_))) => {}, _ => panic!(), } @@ -788,7 +800,7 @@ mod tests { let seal = proposal_seal(&tap, &header, 0); header.set_seal(seal); // Not authority. - match engine.verify_block_unordered(&header, None) { + match engine.verify_block_family(&header, &parent_header, None) { Err(Error::Engine(EngineError::NotAuthorized(_))) => {}, _ => panic!(), }; @@ -800,19 +812,24 @@ mod tests { let (spec, tap) = setup(); let engine = spec.engine; + let mut parent_header: Header = Header::default(); + parent_header.set_gas_limit(U256::from_str("222222").unwrap()); + let mut header = Header::default(); + header.set_number(2); + header.set_gas_limit(U256::from_str("222222").unwrap()); let proposer = insert_and_unlock(&tap, "1"); header.set_author(proposer); let mut seal = proposal_seal(&tap, &header, 0); - let vote_info = message_info_rlp(&VoteStep::new(0, 0, Step::Precommit), Some(header.bare_hash())); + let vote_info = message_info_rlp(&VoteStep::new(2, 0, Step::Precommit), Some(header.bare_hash())); let signature1 = tap.sign(proposer, None, vote_info.sha3()).unwrap(); seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone())]).to_vec(); header.set_seal(seal.clone()); // One good signature is not enough. - match engine.verify_block_unordered(&header, None) { + match engine.verify_block_family(&header, &parent_header, None) { Err(Error::Engine(EngineError::BadSealFieldSize(_))) => {}, _ => panic!(), } @@ -823,7 +840,7 @@ mod tests { seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone()), H520::from(signature0.clone())]).to_vec(); header.set_seal(seal.clone()); - assert!(engine.verify_block_unordered(&header, None).is_ok()); + assert!(engine.verify_block_family(&header, &parent_header, None).is_ok()); let bad_voter = insert_and_unlock(&tap, "101"); let bad_signature = tap.sign(bad_voter, None, vote_info.sha3()).unwrap(); @@ -832,7 +849,7 @@ mod tests { header.set_seal(seal); // One good and one bad signature. - match engine.verify_block_unordered(&header, None) { + match engine.verify_block_family(&header, &parent_header, None) { Err(Error::Engine(EngineError::NotAuthorized(_))) => {}, _ => panic!(), }; diff --git a/ethcore/src/engines/validator_set/contract.rs b/ethcore/src/engines/validator_set/contract.rs index 81bd6b089..91fbf5fab 100644 --- a/ethcore/src/engines/validator_set/contract.rs +++ b/ethcore/src/engines/validator_set/contract.rs @@ -26,30 +26,30 @@ use super::safe_contract::ValidatorSafeContract; /// The validator contract should have the following interface: /// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}] pub struct ValidatorContract { - validators: Arc, + validators: ValidatorSafeContract, provider: RwLock>, } impl ValidatorContract { pub fn new(contract_address: Address) -> Self { ValidatorContract { - validators: Arc::new(ValidatorSafeContract::new(contract_address)), + validators: ValidatorSafeContract::new(contract_address), provider: RwLock::new(None), } } } -impl ValidatorSet for Arc { - fn contains(&self, address: &Address) -> bool { - self.validators.contains(address) +impl ValidatorSet for ValidatorContract { + fn contains(&self, bh: &H256, address: &Address) -> bool { + self.validators.contains(bh, address) } - fn get(&self, nonce: usize) -> Address { - self.validators.get(nonce) + fn get(&self, bh: &H256, nonce: usize) -> Address { + self.validators.get(bh, nonce) } - fn count(&self) -> usize { - self.validators.count() + fn count(&self, bh: &H256) -> usize { + self.validators.count(bh) } fn report_malicious(&self, address: &Address) { @@ -144,6 +144,7 @@ mod tests { use header::Header; use account_provider::AccountProvider; use miner::MinerService; + use types::ids::BlockId; use client::BlockChainClient; use tests::helpers::generate_dummy_client_with_spec_and_accounts; use super::super::ValidatorSet; @@ -154,8 +155,9 @@ mod tests { let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_contract, None); let vc = Arc::new(ValidatorContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap())); vc.register_contract(Arc::downgrade(&client)); - assert!(vc.contains(&Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap())); - assert!(vc.contains(&Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap())); + let last_hash = client.best_block_header().hash(); + assert!(vc.contains(&last_hash, &Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap())); + assert!(vc.contains(&last_hash, &Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap())); } #[test] @@ -171,18 +173,21 @@ mod tests { client.miner().set_engine_signer(v1, "".into()).unwrap(); let mut header = Header::default(); - let seal = encode(&vec!(5u8)).to_vec(); - header.set_seal(vec!(seal)); + let seal = vec![encode(&5u8).to_vec(), encode(&(&H520::default() as &[u8])).to_vec()]; + header.set_seal(seal); header.set_author(v1); - header.set_number(1); + header.set_number(2); + header.set_parent_hash(client.chain_info().best_block_hash); + // `reportBenign` when the designated proposer releases block from the future (bad clock). - assert!(client.engine().verify_block_unordered(&header, None).is_err()); + assert!(client.engine().verify_block_family(&header, &header, None).is_err()); // Seal a block. client.engine().step(); assert_eq!(client.chain_info().best_block_number, 1); // Check if the unresponsive validator is `disliked`. - assert_eq!(client.call_contract(validator_contract, "d8f2e0bf".from_hex().unwrap()).unwrap().to_hex(), "0000000000000000000000007d577a597b2742b498cb5cf0c26cdcd726d39e6e"); + assert_eq!(client.call_contract(BlockId::Latest, validator_contract, "d8f2e0bf".from_hex().unwrap()).unwrap().to_hex(), "0000000000000000000000007d577a597b2742b498cb5cf0c26cdcd726d39e6e"); // Simulate a misbehaving validator by handling a double proposal. + let header = client.best_block_header().decode(); assert!(client.engine().verify_block_family(&header, &header, None).is_err()); // Seal a block. client.engine().step(); diff --git a/ethcore/src/engines/validator_set/mod.rs b/ethcore/src/engines/validator_set/mod.rs index 43a6f71d1..3e86c357f 100644 --- a/ethcore/src/engines/validator_set/mod.rs +++ b/ethcore/src/engines/validator_set/mod.rs @@ -21,7 +21,7 @@ mod safe_contract; mod contract; use std::sync::Weak; -use util::{Address, Arc}; +use util::{Address, H256}; use ethjson::spec::ValidatorSet as ValidatorSpec; use client::Client; use self::simple_list::SimpleList; @@ -32,18 +32,18 @@ use self::safe_contract::ValidatorSafeContract; pub fn new_validator_set(spec: ValidatorSpec) -> Box { match spec { ValidatorSpec::List(list) => Box::new(SimpleList::new(list.into_iter().map(Into::into).collect())), - ValidatorSpec::SafeContract(address) => Box::new(Arc::new(ValidatorSafeContract::new(address.into()))), - ValidatorSpec::Contract(address) => Box::new(Arc::new(ValidatorContract::new(address.into()))), + ValidatorSpec::SafeContract(address) => Box::new(ValidatorSafeContract::new(address.into())), + ValidatorSpec::Contract(address) => Box::new(ValidatorContract::new(address.into())), } } pub trait ValidatorSet { /// Checks if a given address is a validator. - fn contains(&self, address: &Address) -> bool; + fn contains(&self, bh: &H256, address: &Address) -> bool; /// Draws an validator nonce modulo number of validators. - fn get(&self, nonce: usize) -> Address; + fn get(&self, bh: &H256, nonce: usize) -> Address; /// Returns the current number of validators. - fn count(&self) -> usize; + fn count(&self, bh: &H256) -> usize; /// Notifies about malicious behaviour. fn report_malicious(&self, _validator: &Address) {} /// Notifies about benign misbehaviour. diff --git a/ethcore/src/engines/validator_set/safe_contract.rs b/ethcore/src/engines/validator_set/safe_contract.rs index 1a068d858..0a0eaecfd 100644 --- a/ethcore/src/engines/validator_set/safe_contract.rs +++ b/ethcore/src/engines/validator_set/safe_contract.rs @@ -17,17 +17,23 @@ /// Validator set maintained in a contract, updated using `getValidators` method. use std::sync::Weak; +use ethabi; use util::*; +use util::cache::MemoryLruCache; +use types::ids::BlockId; use client::{Client, BlockChainClient}; -use client::chain_notify::ChainNotify; use super::ValidatorSet; use super::simple_list::SimpleList; +const MEMOIZE_CAPACITY: usize = 500; +const CONTRACT_INTERFACE: &'static [u8] = b"[{\"constant\":true,\"inputs\":[],\"name\":\"getValidators\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"type\":\"function\"}]"; +const GET_VALIDATORS: &'static str = "getValidators"; + /// The validator contract should have the following interface: /// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}] pub struct ValidatorSafeContract { pub address: Address, - validators: RwLock, + validators: RwLock>, provider: RwLock>, } @@ -35,102 +41,127 @@ impl ValidatorSafeContract { pub fn new(contract_address: Address) -> Self { ValidatorSafeContract { address: contract_address, - validators: Default::default(), + validators: RwLock::new(MemoryLruCache::new(MEMOIZE_CAPACITY)), provider: RwLock::new(None), } } - /// Queries the state and updates the set of validators. - pub fn update(&self) { + /// Queries the state and gets the set of validators. + fn get_list(&self, block_hash: H256) -> Option { if let Some(ref provider) = *self.provider.read() { - match provider.get_validators() { + match provider.get_validators(BlockId::Hash(block_hash)) { Ok(new) => { debug!(target: "engine", "Set of validators obtained: {:?}", new); - *self.validators.write() = SimpleList::new(new); + Some(SimpleList::new(new)) + }, + Err(s) => { + debug!(target: "engine", "Set of validators could not be updated: {}", s); + None }, - Err(s) => warn!(target: "engine", "Set of validators could not be updated: {}", s), } } else { - warn!(target: "engine", "Set of validators could not be updated: no provider contract.") + warn!(target: "engine", "Set of validators could not be updated: no provider contract."); + None } } } -/// Checks validators on every block. -impl ChainNotify for ValidatorSafeContract { - fn new_blocks( - &self, - _: Vec, - _: Vec, - enacted: Vec, - _: Vec, - _: Vec, - _: Vec, - _duration: u64) { - if !enacted.is_empty() { - self.update(); - } - } -} - -impl ValidatorSet for Arc { - fn contains(&self, address: &Address) -> bool { - self.validators.read().contains(address) +impl ValidatorSet for ValidatorSafeContract { + fn contains(&self, block_hash: &H256, address: &Address) -> bool { + let mut guard = self.validators.write(); + let maybe_existing = guard + .get_mut(block_hash) + .map(|list| list.contains(block_hash, address)); + maybe_existing + .unwrap_or_else(|| self + .get_list(block_hash.clone()) + .map_or(false, |list| { + let contains = list.contains(block_hash, address); + guard.insert(block_hash.clone(), list); + contains + })) } - fn get(&self, nonce: usize) -> Address { - self.validators.read().get(nonce) + fn get(&self, block_hash: &H256, nonce: usize) -> Address { + let mut guard = self.validators.write(); + let maybe_existing = guard + .get_mut(block_hash) + .map(|list| list.get(block_hash, nonce)); + maybe_existing + .unwrap_or_else(|| self + .get_list(block_hash.clone()) + .map_or_else(Default::default, |list| { + let address = list.get(block_hash, nonce); + guard.insert(block_hash.clone(), list); + address + })) } - fn count(&self) -> usize { - self.validators.read().count() + fn count(&self, block_hash: &H256) -> usize { + let mut guard = self.validators.write(); + let maybe_existing = guard + .get_mut(block_hash) + .map(|list| list.count(block_hash)); + maybe_existing + .unwrap_or_else(|| self + .get_list(block_hash.clone()) + .map_or_else(usize::max_value, |list| { + let address = list.count(block_hash); + guard.insert(block_hash.clone(), list); + address + })) } fn register_contract(&self, client: Weak) { - if let Some(c) = client.upgrade() { - c.add_notify(self.clone()); - } - { - *self.provider.write() = Some(provider::Contract::new(self.address, move |a, d| client.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d)))); - } - self.update(); + trace!(target: "engine", "Setting up contract caller."); + let contract = ethabi::Contract::new(ethabi::Interface::load(CONTRACT_INTERFACE).expect("JSON interface is valid; qed")); + let call = contract.function(GET_VALIDATORS.into()).expect("Method name is valid; qed"); + let data = call.encode_call(vec![]).expect("get_validators does not take any arguments; qed"); + let contract_address = self.address.clone(); + let do_call = move |id| client + .upgrade() + .ok_or("No client!".into()) + .and_then(|c| c.call_contract(id, contract_address.clone(), data.clone())) + .map(|raw_output| call.decode_output(raw_output).expect("ethabi is correct; qed")); + *self.provider.write() = Some(provider::Contract::new(do_call)); } } mod provider { - // Autogenerated from JSON contract definition using Rust contract convertor. - #![allow(unused_imports)] use std::string::String; use std::result::Result; - use std::fmt; use {util, ethabi}; - use util::{FixedHash, Uint}; + use types::ids::BlockId; pub struct Contract { - contract: ethabi::Contract, - address: util::Address, - do_call: Box) -> Result, String> + Send + Sync + 'static>, + do_call: Box Result, String> + Send + Sync + 'static>, } + impl Contract { - pub fn new(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec) -> Result, String> + Send + Sync + 'static { + pub fn new(do_call: F) -> Self where F: Fn(BlockId) -> Result, String> + Send + Sync + 'static { Contract { - contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":true,\"inputs\":[],\"name\":\"getValidators\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"type\":\"function\"}]").expect("JSON is autogenerated; qed")), - address: address, do_call: Box::new(do_call), } } - fn as_string(e: T) -> String { format!("{:?}", e) } - - /// Auto-generated from: `{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}` - #[allow(dead_code)] - pub fn get_validators(&self) -> Result, String> { - let call = self.contract.function("getValidators".into()).map_err(Self::as_string)?; - let data = call.encode_call( - vec![] - ).map_err(Self::as_string)?; - let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?; - let mut result = output.into_iter().rev().collect::>(); - Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_array().and_then(|v| v.into_iter().map(|a| a.to_address()).collect::>>()).ok_or("Invalid type returned")?; r.into_iter().map(|a| util::Address::from(a)).collect::>() })) + + /// Gets validators from contract with interface: `{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}` + pub fn get_validators(&self, id: BlockId) -> Result, String> { + Ok((self.do_call)(id)? + .into_iter() + .rev() + .collect::>() + .pop() + .expect("get_validators returns one argument; qed") + .to_array() + .and_then(|v| v + .into_iter() + .map(|a| a.to_address()) + .collect::>>()) + .expect("get_validators returns a list of addresses; qed") + .into_iter() + .map(util::Address::from) + .collect::>() + ) } } } @@ -138,13 +169,14 @@ mod provider { #[cfg(test)] mod tests { use util::*; + use types::ids::BlockId; use spec::Spec; use account_provider::AccountProvider; use transaction::{Transaction, Action}; use client::{BlockChainClient, EngineClient}; use ethkey::Secret; use miner::MinerService; - use tests::helpers::generate_dummy_client_with_spec_and_accounts; + use tests::helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data}; use super::super::ValidatorSet; use super::ValidatorSafeContract; @@ -153,12 +185,13 @@ mod tests { let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, None); let vc = Arc::new(ValidatorSafeContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap())); vc.register_contract(Arc::downgrade(&client)); - assert!(vc.contains(&Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap())); - assert!(vc.contains(&Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap())); + let last_hash = client.best_block_header().hash(); + assert!(vc.contains(&last_hash, &Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap())); + assert!(vc.contains(&last_hash, &Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap())); } #[test] - fn updates_validators() { + fn knows_validators() { let tap = Arc::new(AccountProvider::transient_provider()); let s0 = Secret::from_slice(&"1".sha3()).unwrap(); let v0 = tap.insert_account(s0.clone(), "").unwrap(); @@ -212,5 +245,14 @@ mod tests { client.update_sealing(); // Able to seal again. assert_eq!(client.chain_info().best_block_number, 3); + + // Check syncing. + let sync_client = generate_dummy_client_with_spec_and_data(Spec::new_validator_safe_contract, 0, 0, &[]); + sync_client.engine().register_client(Arc::downgrade(&sync_client)); + for i in 1..4 { + sync_client.import_block(client.block(BlockId::Number(i)).unwrap().into_inner()).unwrap(); + } + sync_client.flush_queue(); + assert_eq!(sync_client.chain_info().best_block_number, 3); } } diff --git a/ethcore/src/engines/validator_set/simple_list.rs b/ethcore/src/engines/validator_set/simple_list.rs index 3ba574b5a..2d7687979 100644 --- a/ethcore/src/engines/validator_set/simple_list.rs +++ b/ethcore/src/engines/validator_set/simple_list.rs @@ -16,7 +16,7 @@ /// Preconfigured validator list. -use util::Address; +use util::{H256, Address, HeapSizeOf}; use super::ValidatorSet; #[derive(Debug, PartialEq, Eq, Default)] @@ -34,16 +34,22 @@ impl SimpleList { } } +impl HeapSizeOf for SimpleList { + fn heap_size_of_children(&self) -> usize { + self.validators.heap_size_of_children() + self.validator_n.heap_size_of_children() + } +} + impl ValidatorSet for SimpleList { - fn contains(&self, address: &Address) -> bool { + fn contains(&self, _bh: &H256, address: &Address) -> bool { self.validators.contains(address) } - fn get(&self, nonce: usize) -> Address { + fn get(&self, _bh: &H256, nonce: usize) -> Address { self.validators.get(nonce % self.validator_n).expect("There are validator_n authorities; taking number modulo validator_n gives number in validator_n range; qed").clone() } - fn count(&self) -> usize { + fn count(&self, _bh: &H256) -> usize { self.validator_n } } @@ -60,9 +66,9 @@ mod tests { let a1 = Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap(); let a2 = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap(); let list = SimpleList::new(vec![a1.clone(), a2.clone()]); - assert!(list.contains(&a1)); - assert_eq!(list.get(0), a1); - assert_eq!(list.get(1), a2); - assert_eq!(list.get(2), a1); + assert!(list.contains(&Default::default(), &a1)); + assert_eq!(list.get(&Default::default(), 0), a1); + assert_eq!(list.get(&Default::default(), 1), a2); + assert_eq!(list.get(&Default::default(), 2), a1); } } diff --git a/ethcore/src/miner/service_transaction_checker.rs b/ethcore/src/miner/service_transaction_checker.rs index f3281dade..5a7ab04e9 100644 --- a/ethcore/src/miner/service_transaction_checker.rs +++ b/ethcore/src/miner/service_transaction_checker.rs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use types::ids::BlockId; use client::MiningBlockChainClient; use transaction::SignedTransaction; use util::{U256, Uint, Mutex}; @@ -45,7 +46,7 @@ impl ServiceTransactionChecker { debug_assert_eq!(tx.gas_price, U256::zero()); if let Some(ref contract) = *self.contract.lock() { - let do_call = |a, d| client.call_contract(a, d); + let do_call = |a, d| client.call_contract(BlockId::Latest, a, d); contract.certified(&do_call, &tx.sender()) } else { Err("contract is not configured".to_owned()) diff --git a/updater/src/updater.rs b/updater/src/updater.rs index 1266258fe..a6c374e10 100644 --- a/updater/src/updater.rs +++ b/updater/src/updater.rs @@ -247,7 +247,7 @@ impl Updater { if let Some(ops_addr) = self.client.upgrade().and_then(|c| c.registry_address("operations".into())) { trace!(target: "updater", "Found operations at {}", ops_addr); let client = self.client.clone(); - *self.operations.lock() = Some(Operations::new(ops_addr, move |a, d| client.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d)))); + *self.operations.lock() = Some(Operations::new(ops_addr, move |a, d| client.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(BlockId::Latest, a, d)))); } else { // No Operations contract - bail. return; @@ -340,7 +340,7 @@ impl fetch::urlhint::ContractClient for Updater { fn call(&self, address: Address, data: Bytes) -> Result { self.client.upgrade().ok_or_else(|| "Client not available".to_owned())? - .call_contract(address, data) + .call_contract(BlockId::Latest, address, data) } }