diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index f28a06117..646c107e0 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -21,13 +21,15 @@ use std::sync::Weak; use std::time::{UNIX_EPOCH, Duration}; use util::*; use ethkey::{verify_address, Signature}; -use rlp::{Rlp, UntrustedRlp, View, encode}; +use rlp::{UntrustedRlp, View, encode}; use account_provider::AccountProvider; use block::*; use spec::CommonParams; use engines::Engine; use header::Header; use error::{Error, BlockError}; +use blockchain::extras::BlockDetails; +use views::HeaderView; use evm::Schedule; use ethjson; use io::{IoContext, IoHandler, TimerToken, IoService, IoChannel}; @@ -35,8 +37,6 @@ use service::ClientIoMessage; use transaction::SignedTransaction; use env_info::EnvInfo; use builtin::Builtin; -use blockchain::extras::BlockDetails; -use views::HeaderView; /// `AuthorityRound` params. #[derive(Debug, PartialEq)] @@ -72,6 +72,7 @@ pub struct AuthorityRound { message_channel: Mutex>>, step: AtomicUsize, proposed: AtomicBool, + account_provider: Mutex>>, } fn header_step(header: &Header) -> Result { @@ -104,7 +105,8 @@ impl AuthorityRound { transition_service: try!(IoService::::start()), message_channel: Mutex::new(None), step: AtomicUsize::new(initial_step), - proposed: AtomicBool::new(false) + proposed: AtomicBool::new(false), + account_provider: Mutex::new(None), }); let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; try!(engine.transition_service.register_handler(Arc::new(handler))); @@ -196,7 +198,6 @@ impl Engine for AuthorityRound { } fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256, _gas_ceil_target: U256) { - header.set_difficulty(parent.difficulty().clone()); header.set_gas_limit({ let gas_limit = parent.gas_limit().clone(); let bound_divisor = self.our_params.gas_limit_bound_divisor; @@ -221,12 +222,12 @@ impl Engine for AuthorityRound { /// /// This operation is synchronous and may (quite reasonably) not be available, in which `false` will /// be returned. - fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { + fn generate_seal(&self, block: &ExecutedBlock) -> Option> { if self.proposed.load(AtomicOrdering::SeqCst) { return None; } let header = block.header(); let step = self.step(); if self.is_step_proposer(step, header.author()) { - if let Some(ap) = accounts { + if let Some(ref ap) = *self.account_provider.lock() { // Account should be permanently unlocked, otherwise sealing will fail. if let Ok(signature) = ap.sign(*header.author(), None, header.bare_hash()) { trace!(target: "poa", "generate_seal: Issuing a block for step {}.", step); @@ -303,22 +304,16 @@ impl Engine for AuthorityRound { t.sender().map(|_|()) // Perform EC recovery and cache sender } - fn register_message_channel(&self, message_channel: IoChannel) { - let mut guard = self.message_channel.lock(); - *guard = Some(message_channel); + fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { + new_header.number() > best_header.number() } - fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool { - let new_number = new_header.number(); - let best_number = best_header.number(); - if new_number != best_number { - new_number > best_number - } else { - // Take the oldest step at given height. - let new_step: usize = Rlp::new(&new_header.seal()[0]).as_val(); - let best_step: usize = Rlp::new(&best_header.seal()[0]).as_val(); - new_step < best_step - } + fn register_message_channel(&self, message_channel: IoChannel) { + *self.message_channel.lock() = Some(message_channel); + } + + fn register_account_provider(&self, account_provider: Arc) { + *self.account_provider.lock() = Some(account_provider); } } @@ -393,6 +388,7 @@ mod tests { let spec = Spec::new_test_round(); let engine = &*spec.engine; + engine.register_account_provider(Arc::new(tap)); let genesis_header = spec.genesis_header(); let mut db1 = get_temp_state_db().take(); spec.ensure_db_good(&mut db1, &TrieFactory::new(TrieSpec::Secure)).unwrap(); @@ -404,16 +400,16 @@ mod tests { let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes, addr2, (3141562.into(), 31415620.into()), vec![]).unwrap(); let b2 = b2.close_and_lock(); - if let Some(seal) = engine.generate_seal(b1.block(), Some(&tap)) { + if let Some(seal) = engine.generate_seal(b1.block()) { assert!(b1.clone().try_seal(engine, seal).is_ok()); // Second proposal is forbidden. - assert!(engine.generate_seal(b1.block(), Some(&tap)).is_none()); + assert!(engine.generate_seal(b1.block()).is_none()); } - if let Some(seal) = engine.generate_seal(b2.block(), Some(&tap)) { + if let Some(seal) = engine.generate_seal(b2.block()) { assert!(b2.clone().try_seal(engine, seal).is_ok()); // Second proposal is forbidden. - assert!(engine.generate_seal(b2.block(), Some(&tap)).is_none()); + assert!(engine.generate_seal(b2.block()).is_none()); } } diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index fb2f9bde6..0b9040a11 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -58,6 +58,7 @@ pub struct BasicAuthority { params: CommonParams, our_params: BasicAuthorityParams, builtins: BTreeMap, + account_provider: Mutex>>, } impl BasicAuthority { @@ -67,6 +68,7 @@ impl BasicAuthority { params: params, our_params: our_params, builtins: builtins, + account_provider: Mutex::new(None) } } } @@ -113,8 +115,8 @@ impl Engine for BasicAuthority { /// /// This operation is synchronous and may (quite reasonably) not be available, in which `false` will /// be returned. - fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option> { - if let Some(ap) = accounts { + fn generate_seal(&self, block: &ExecutedBlock) -> Option> { + if let Some(ref ap) = *self.account_provider.lock() { let header = block.header(); let message = header.bare_hash(); // account should be pernamently unlocked, otherwise sealing will fail @@ -179,6 +181,10 @@ impl Engine for BasicAuthority { fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { t.sender().map(|_|()) // Perform EC recovery and cache sender } + + fn register_account_provider(&self, ap: Arc) { + *self.account_provider.lock() = Some(ap); + } } #[cfg(test)] @@ -254,6 +260,7 @@ mod tests { let spec = new_test_authority(); let engine = &*spec.engine; + engine.register_account_provider(Arc::new(tap)); let genesis_header = spec.genesis_header(); let mut db_result = get_temp_state_db(); let mut db = db_result.take(); @@ -261,7 +268,7 @@ mod tests { let last_hashes = Arc::new(vec![genesis_header.hash()]); let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); - let seal = engine.generate_seal(b.block(), Some(&tap)).unwrap(); + let seal = engine.generate_seal(b.block()).unwrap(); assert!(b.try_seal(engine, seal).is_ok()); } diff --git a/ethcore/src/engines/instant_seal.rs b/ethcore/src/engines/instant_seal.rs index f50f7344b..83335fb03 100644 --- a/ethcore/src/engines/instant_seal.rs +++ b/ethcore/src/engines/instant_seal.rs @@ -23,7 +23,6 @@ use spec::CommonParams; use evm::Schedule; use block::ExecutedBlock; use util::Bytes; -use account_provider::AccountProvider; /// An engine which does not provide any consensus mechanism, just seals blocks internally. pub struct InstantSeal { @@ -60,7 +59,7 @@ impl Engine for InstantSeal { fn is_sealer(&self, _author: &Address) -> Option { Some(true) } - fn generate_seal(&self, _block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option> { + fn generate_seal(&self, _block: &ExecutedBlock) -> Option> { Some(Vec::new()) } } @@ -70,16 +69,12 @@ mod tests { use util::*; use util::trie::TrieSpec; use tests::helpers::*; - use account_provider::AccountProvider; use spec::Spec; use header::Header; use block::*; #[test] fn instant_can_seal() { - let tap = AccountProvider::transient_provider(); - let addr = tap.insert_account("".sha3(), "").unwrap(); - let spec = Spec::new_instant(); let engine = &*spec.engine; let genesis_header = spec.genesis_header(); @@ -87,10 +82,9 @@ mod tests { let mut db = db_result.take(); spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); let last_hashes = Arc::new(vec![genesis_header.hash()]); - let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![]).unwrap(); + let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap(); let b = b.close_and_lock(); - // Seal with empty AccountProvider. - let seal = engine.generate_seal(b.block(), Some(&tap)).unwrap(); + let seal = engine.generate_seal(b.block()).unwrap(); assert!(b.try_seal(engine, seal).is_ok()); } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 163901edc..0401ba667 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -94,7 +94,7 @@ pub trait Engine : Sync + Send { /// /// This operation is synchronous and may (quite reasonably) not be available, in which None will /// be returned. - fn generate_seal(&self, _block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option> { None } + fn generate_seal(&self, _block: &ExecutedBlock) -> Option> { None } /// Phase 1 quick block verification. Only does checks that are cheap. `block` (the header's full block) /// may be provided for additional checks. Returns either a null `Ok` or a general error detailing the problem with import. @@ -147,11 +147,17 @@ pub trait Engine : Sync + Send { self.builtins().get(a).expect("attempted to execute nonexistent builtin").execute(input, output); } - /// Add a channel for communication with Client which can be used for sealing. - fn register_message_channel(&self, _message_channel: IoChannel) {} - /// Check if new block should be chosen as the one in chain. fn is_new_best_block(&self, best_total_difficulty: U256, _best_header: HeaderView, parent_details: &BlockDetails, new_header: &HeaderView) -> bool { ethash::is_new_best_block(best_total_difficulty, parent_details, new_header) } + + /// Register an account which signs consensus messages. + fn set_signer(&self, _address: Address, _password: String) {} + + /// Add a channel for communication with Client which can be used for sealing. + fn register_message_channel(&self, _message_channel: IoChannel) {} + + /// Add an account provider useful for Engines that sign stuff. + fn register_account_provider(&self, _account_provider: Arc) {} } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index a543e608d..4b38a7e07 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -19,7 +19,7 @@ use std::time::{Instant, Duration}; use util::*; use util::using_queue::{UsingQueue, GetAction}; -use account_provider::AccountProvider; +use account_provider::{AccountProvider, Error as AccountError}; use views::{BlockView, HeaderView}; use header::Header; use state::{State, CleanupMode}; @@ -464,15 +464,12 @@ impl Miner { /// Attempts to perform internal sealing (one that does not require work) to return Ok(sealed), /// Err(Some(block)) returns for unsuccesful sealing while Err(None) indicates misspecified engine. fn seal_block_internally(&self, block: ClosedBlock) -> Result> { - trace!(target: "miner", "seal_block_internally: block has transaction - attempting internal seal."); - let s = self.engine.generate_seal(block.block(), match self.accounts { - Some(ref x) => Some(&**x), - None => None, - }); + trace!(target: "miner", "seal_block_internally: attempting internal seal."); + let s = self.engine.generate_seal(block.block()); if let Some(seal) = s { trace!(target: "miner", "seal_block_internally: managed internal seal. importing..."); - block.lock().try_seal(&*self.engine, seal).or_else(|_| { - warn!("prepare_sealing: ERROR: try_seal failed when given internally generated seal. WTF?"); + block.lock().try_seal(&*self.engine, seal).or_else(|(e, _)| { + warn!("prepare_sealing: ERROR: try_seal failed when given internally generated seal: {}", e); Err(None) }) } else { @@ -485,7 +482,7 @@ impl Miner { fn seal_and_import_block_internally(&self, chain: &MiningBlockChainClient, block: ClosedBlock) -> bool { if !block.transactions().is_empty() || self.forced_sealing() { if let Ok(sealed) = self.seal_block_internally(block) { - if chain.import_block(sealed.rlp_bytes()).is_ok() { + if chain.import_sealed_block(sealed).is_ok() { trace!(target: "miner", "import_block_internally: imported internally sealed block"); return true } @@ -740,6 +737,19 @@ impl MinerService for Miner { *self.author.write() = author; } + fn set_consensus_signer(&self, address: Address, password: String) -> Result<(), AccountError> { + if self.seals_internally { + if let Some(ref ap) = self.accounts { + try!(ap.sign(address.clone(), Some(password.clone()), Default::default())); + } + let mut sealing_work = self.sealing_work.lock(); + sealing_work.enabled = self.engine.is_sealer(&address).unwrap_or(false); + *self.author.write() = address; + self.engine.set_signer(address, password); + } + Ok(()) + } + fn set_extra_data(&self, extra_data: Bytes) { *self.extra_data.write() = extra_data; } @@ -1020,6 +1030,11 @@ impl MinerService for Miner { let (block, original_work_hash) = self.prepare_block(chain); if self.seals_internally { trace!(target: "miner", "update_sealing: engine indicates internal sealing"); + { + let mut sealing_work = self.sealing_work.lock(); + sealing_work.queue.push(block.clone()); + sealing_work.queue.use_last_ref(); + } self.seal_and_import_block_internally(chain, block); } else { trace!(target: "miner", "update_sealing: engine does not seal internally, preparing work"); @@ -1042,7 +1057,7 @@ impl MinerService for Miner { ret.map(f) } - fn submit_seal(&self, chain: &MiningBlockChainClient, pow_hash: H256, seal: Vec) -> Result<(), Error> { + fn submit_seal(&self, chain: &MiningBlockChainClient, block_hash: H256, seal: Vec) -> Result<(), Error> { let result = if let Some(b) = self.sealing_work.lock().queue.get_used_if( if self.options.enable_resubmission { @@ -1050,22 +1065,22 @@ impl MinerService for Miner { } else { GetAction::Take }, - |b| &b.hash() == &pow_hash + |b| &b.hash() == &block_hash ) { - trace!(target: "miner", "Sealing block {}={}={} with seal {:?}", pow_hash, b.hash(), b.header().bare_hash(), seal); + trace!(target: "miner", "Submitted block {}={}={} with seal {:?}", block_hash, b.hash(), b.header().bare_hash(), seal); b.lock().try_seal(&*self.engine, seal).or_else(|(e, _)| { warn!(target: "miner", "Mined solution rejected: {}", e); Err(Error::PowInvalid) }) } else { - warn!(target: "miner", "Mined solution rejected: Block unknown or out of date."); + warn!(target: "miner", "Submitted solution rejected: Block unknown or out of date."); Err(Error::PowHashInvalid) }; result.and_then(|sealed| { let n = sealed.header().number(); let h = sealed.header().hash(); try!(chain.import_sealed_block(sealed)); - info!(target: "miner", "Mined block imported OK. #{}: {}", Colour::White.bold().paint(format!("{}", n)), Colour::White.bold().paint(h.hex())); + info!(target: "miner", "Submitted block imported OK. #{}: {}", Colour::White.bold().paint(format!("{}", n)), Colour::White.bold().paint(h.hex())); Ok(()) }) } diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 1fb2244fd..26cefb295 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -76,6 +76,9 @@ pub trait MinerService : Send + Sync { /// Set the author that we will seal blocks as. fn set_author(&self, author: Address); + /// Set info necessary to sign consensus messages. + fn set_consensus_signer(&self, address: Address, password: String) -> Result<(), ::account_provider::Error>; + /// Get the extra_data that we will seal blocks with. fn extra_data(&self) -> Bytes; diff --git a/parity/run.rs b/parity/run.rs index f977c450c..2509d1347 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -70,7 +70,7 @@ pub struct RunCmd { pub http_conf: HttpConfiguration, pub ipc_conf: IpcConfiguration, pub net_conf: NetworkConfiguration, - pub network_id: Option, + pub network_id: Option, pub warp_sync: bool, pub acc_conf: AccountsConfig, pub gas_pricer: GasPricerConfig, @@ -208,6 +208,9 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { // prepare account provider let account_provider = Arc::new(try!(prepare_account_provider(&cmd.dirs, cmd.acc_conf))); + // let the Engine access the accounts + spec.engine.register_account_provider(account_provider.clone()); + // create miner let miner = Miner::new(cmd.miner_options, cmd.gas_pricer.into(), &spec, Some(account_provider.clone())); miner.set_author(cmd.miner_extras.author); diff --git a/rpc/src/v1/impls/parity_set.rs b/rpc/src/v1/impls/parity_set.rs index 47634d518..e28f9a573 100644 --- a/rpc/src/v1/impls/parity_set.rs +++ b/rpc/src/v1/impls/parity_set.rs @@ -116,6 +116,12 @@ impl ParitySet for ParitySetClient where Ok(true) } + fn set_consensus_signer(&self, address: H160, password: String) -> Result { + try!(self.active()); + try!(take_weak!(self.miner).set_consensus_signer(address.into(), password).map_err(Into::into).map_err(errors::from_password_error)); + Ok(true) + } + fn set_transactions_limit(&self, limit: usize) -> Result { try!(self.active()); diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 7894fa111..3aa83fec9 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -116,6 +116,7 @@ impl EthTester { fn from_spec(spec: Spec) -> Self { let dir = RandomTempPath::new(); let account_provider = account_provider(); + spec.engine.register_account_provider(account_provider.clone()); let miner_service = miner_service(&spec, account_provider.clone()); let snapshot_service = snapshot_service(); diff --git a/rpc/src/v1/tests/helpers/miner_service.rs b/rpc/src/v1/tests/helpers/miner_service.rs index ad55faa7b..6037cdd4a 100644 --- a/rpc/src/v1/tests/helpers/miner_service.rs +++ b/rpc/src/v1/tests/helpers/miner_service.rs @@ -25,6 +25,7 @@ use ethcore::header::BlockNumber; use ethcore::transaction::SignedTransaction; use ethcore::receipt::{Receipt, RichReceipt}; use ethcore::miner::{MinerService, MinerStatus, TransactionImportResult, LocalTransactionStatus}; +use ethcore::account_provider::Error as AccountError; /// Test miner service. pub struct TestMinerService { @@ -40,6 +41,8 @@ pub struct TestMinerService { pub pending_receipts: Mutex>, /// Last nonces. pub last_nonces: RwLock>, + /// Password held by Engine. + pub password: RwLock, min_gas_price: RwLock, gas_range_target: RwLock<(U256, U256)>, @@ -61,6 +64,7 @@ impl Default for TestMinerService { min_gas_price: RwLock::new(U256::from(20_000_000)), gas_range_target: RwLock::new((U256::from(12345), U256::from(54321))), author: RwLock::new(Address::zero()), + password: RwLock::new(String::new()), extra_data: RwLock::new(vec![1, 2, 3, 4]), limit: RwLock::new(1024), tx_gas_limit: RwLock::new(!U256::zero()), @@ -83,6 +87,12 @@ impl MinerService for TestMinerService { *self.author.write() = author; } + fn set_consensus_signer(&self, address: Address, password: String) -> Result<(), AccountError> { + *self.author.write() = address; + *self.password.write() = password; + Ok(()) + } + fn set_extra_data(&self, extra_data: Bytes) { *self.extra_data.write() = extra_data; } diff --git a/rpc/src/v1/tests/helpers/sync_provider.rs b/rpc/src/v1/tests/helpers/sync_provider.rs index 24be33417..8800d926a 100644 --- a/rpc/src/v1/tests/helpers/sync_provider.rs +++ b/rpc/src/v1/tests/helpers/sync_provider.rs @@ -23,7 +23,7 @@ use ethsync::{SyncProvider, SyncStatus, SyncState, PeerInfo, TransactionStats}; /// TestSyncProvider config. pub struct Config { /// Protocol version. - pub network_id: usize, + pub network_id: u64, /// Number of peers. pub num_peers: usize, } diff --git a/rpc/src/v1/tests/mocked/parity_set.rs b/rpc/src/v1/tests/mocked/parity_set.rs index 01f33e251..fdf3f2d0f 100644 --- a/rpc/src/v1/tests/mocked/parity_set.rs +++ b/rpc/src/v1/tests/mocked/parity_set.rs @@ -106,6 +106,23 @@ fn rpc_parity_set_author() { assert_eq!(miner.author(), Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap()); } +#[test] +fn rpc_parity_set_consensus_signer() { + let miner = miner_service(); + let client = client_service(); + let network = network_service(); + let io = IoHandler::new(); + io.add_delegate(parity_set_client(&client, &miner, &network).to_delegate()); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_setConsensusSigner", "params":["0xcd1722f3947def4cf144679da39c4c32bdc35681", "password"], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; + + assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); + assert_eq!(miner.author(), Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap()); + assert_eq!(*miner.password.read(), "password".to_string()); +} + + #[test] fn rpc_parity_set_transactions_limit() { let miner = miner_service(); diff --git a/rpc/src/v1/traits/parity_set.rs b/rpc/src/v1/traits/parity_set.rs index c83eff022..e196a7d21 100644 --- a/rpc/src/v1/traits/parity_set.rs +++ b/rpc/src/v1/traits/parity_set.rs @@ -44,6 +44,10 @@ build_rpc_trait! { #[rpc(name = "parity_setAuthor")] fn set_author(&self, H160) -> Result; + /// Sets account for signing consensus messages. + #[rpc(name = "parity_setConsensusSigner")] + fn set_consensus_signer(&self, H160, String) -> Result; + /// Sets the limits for transaction queue. #[rpc(name = "parity_setTransactionsLimit")] fn set_transactions_limit(&self, usize) -> Result;