Merge pull request #3725 from ethcore/engine-password

Make engine hold AccountProvider
This commit is contained in:
Gav Wood 2016-12-08 20:52:12 +01:00 committed by GitHub
commit 2d6656fc43
19 changed files with 169 additions and 78 deletions

View File

@ -21,13 +21,15 @@ use std::sync::Weak;
use std::time::{UNIX_EPOCH, Duration}; use std::time::{UNIX_EPOCH, Duration};
use util::*; use util::*;
use ethkey::{verify_address, Signature}; use ethkey::{verify_address, Signature};
use rlp::{Rlp, UntrustedRlp, View, encode}; use rlp::{UntrustedRlp, Rlp, View, encode};
use account_provider::AccountProvider; use account_provider::AccountProvider;
use block::*; use block::*;
use spec::CommonParams; use spec::CommonParams;
use engines::Engine; use engines::Engine;
use header::Header; use header::Header;
use error::{Error, BlockError}; use error::{Error, BlockError};
use blockchain::extras::BlockDetails;
use views::HeaderView;
use evm::Schedule; use evm::Schedule;
use ethjson; use ethjson;
use io::{IoContext, IoHandler, TimerToken, IoService, IoChannel}; use io::{IoContext, IoHandler, TimerToken, IoService, IoChannel};
@ -35,8 +37,6 @@ use service::ClientIoMessage;
use transaction::SignedTransaction; use transaction::SignedTransaction;
use env_info::EnvInfo; use env_info::EnvInfo;
use builtin::Builtin; use builtin::Builtin;
use blockchain::extras::BlockDetails;
use views::HeaderView;
/// `AuthorityRound` params. /// `AuthorityRound` params.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -68,18 +68,20 @@ pub struct AuthorityRound {
params: CommonParams, params: CommonParams,
our_params: AuthorityRoundParams, our_params: AuthorityRoundParams,
builtins: BTreeMap<Address, Builtin>, builtins: BTreeMap<Address, Builtin>,
transition_service: IoService<BlockArrived>, transition_service: IoService<()>,
message_channel: Mutex<Option<IoChannel<ClientIoMessage>>>, message_channel: Mutex<Option<IoChannel<ClientIoMessage>>>,
step: AtomicUsize, step: AtomicUsize,
proposed: AtomicBool, proposed: AtomicBool,
account_provider: Mutex<Option<Arc<AccountProvider>>>,
password: RwLock<Option<String>>,
} }
fn header_step(header: &Header) -> Result<usize, ::rlp::DecoderError> { fn header_step(header: &Header) -> Result<usize, ::rlp::DecoderError> {
UntrustedRlp::new(&header.seal()[0]).as_val() UntrustedRlp::new(&header.seal().get(0).expect("was either checked with verify_block_basic or is genesis; has 2 fields; qed (Make sure the spec file has a correct genesis seal)")).as_val()
} }
fn header_signature(header: &Header) -> Result<Signature, ::rlp::DecoderError> { fn header_signature(header: &Header) -> Result<Signature, ::rlp::DecoderError> {
UntrustedRlp::new(&header.seal()[1]).as_val::<H520>().map(Into::into) UntrustedRlp::new(&header.seal().get(1).expect("was checked with verify_block_basic; has 2 fields; qed")).as_val::<H520>().map(Into::into)
} }
trait AsMillis { trait AsMillis {
@ -101,10 +103,12 @@ impl AuthorityRound {
params: params, params: params,
our_params: our_params, our_params: our_params,
builtins: builtins, builtins: builtins,
transition_service: try!(IoService::<BlockArrived>::start()), transition_service: try!(IoService::<()>::start()),
message_channel: Mutex::new(None), message_channel: Mutex::new(None),
step: AtomicUsize::new(initial_step), step: AtomicUsize::new(initial_step),
proposed: AtomicBool::new(false) proposed: AtomicBool::new(false),
account_provider: Mutex::new(None),
password: RwLock::new(None),
}); });
let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; let handler = TransitionHandler { engine: Arc::downgrade(&engine) };
try!(engine.transition_service.register_handler(Arc::new(handler))); try!(engine.transition_service.register_handler(Arc::new(handler)));
@ -143,20 +147,17 @@ struct TransitionHandler {
engine: Weak<AuthorityRound>, engine: Weak<AuthorityRound>,
} }
#[derive(Clone)]
struct BlockArrived;
const ENGINE_TIMEOUT_TOKEN: TimerToken = 23; const ENGINE_TIMEOUT_TOKEN: TimerToken = 23;
impl IoHandler<BlockArrived> for TransitionHandler { impl IoHandler<()> for TransitionHandler {
fn initialize(&self, io: &IoContext<BlockArrived>) { fn initialize(&self, io: &IoContext<()>) {
if let Some(engine) = self.engine.upgrade() { if let Some(engine) = self.engine.upgrade() {
io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_duration().as_millis()) io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.remaining_step_duration().as_millis())
.unwrap_or_else(|e| warn!(target: "poa", "Failed to start consensus step timer: {}.", e)) .unwrap_or_else(|e| warn!(target: "poa", "Failed to start consensus step timer: {}.", e))
} }
} }
fn timeout(&self, io: &IoContext<BlockArrived>, timer: TimerToken) { fn timeout(&self, io: &IoContext<()>, timer: TimerToken) {
if timer == ENGINE_TIMEOUT_TOKEN { if timer == ENGINE_TIMEOUT_TOKEN {
if let Some(engine) = self.engine.upgrade() { if let Some(engine) = self.engine.upgrade() {
engine.step.fetch_add(1, AtomicOrdering::SeqCst); engine.step.fetch_add(1, AtomicOrdering::SeqCst);
@ -208,10 +209,6 @@ impl Engine for AuthorityRound {
}); });
} }
/// Apply the block reward on finalisation of the block.
/// This assumes that all uncles are valid uncles (i.e. of at least one generation before the current).
fn on_close_block(&self, _block: &mut ExecutedBlock) {}
fn is_sealer(&self, author: &Address) -> Option<bool> { fn is_sealer(&self, author: &Address) -> Option<bool> {
let p = &self.our_params; let p = &self.our_params;
Some(p.authorities.contains(author)) Some(p.authorities.contains(author))
@ -221,14 +218,14 @@ impl Engine for AuthorityRound {
/// ///
/// This operation is synchronous and may (quite reasonably) not be available, in which `false` will /// This operation is synchronous and may (quite reasonably) not be available, in which `false` will
/// be returned. /// be returned.
fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option<Vec<Bytes>> { fn generate_seal(&self, block: &ExecutedBlock) -> Option<Vec<Bytes>> {
if self.proposed.load(AtomicOrdering::SeqCst) { return None; } if self.proposed.load(AtomicOrdering::SeqCst) { return None; }
let header = block.header(); let header = block.header();
let step = self.step(); let step = self.step();
if self.is_step_proposer(step, header.author()) { 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. // Account should be permanently unlocked, otherwise sealing will fail.
if let Ok(signature) = ap.sign(*header.author(), None, header.bare_hash()) { if let Ok(signature) = ap.sign(*header.author(), self.password.read().clone(), header.bare_hash()) {
trace!(target: "poa", "generate_seal: Issuing a block for step {}.", step); trace!(target: "poa", "generate_seal: Issuing a block for step {}.", step);
self.proposed.store(true, AtomicOrdering::SeqCst); self.proposed.store(true, AtomicOrdering::SeqCst);
return Some(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]); return Some(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
@ -303,23 +300,30 @@ impl Engine for AuthorityRound {
t.sender().map(|_|()) // Perform EC recovery and cache sender t.sender().map(|_|()) // Perform EC recovery and cache sender
} }
fn register_message_channel(&self, message_channel: IoChannel<ClientIoMessage>) {
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 { 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 new_number = new_header.number();
let best_number = best_header.number(); let best_number = best_header.number();
if new_number != best_number { if new_number != best_number {
new_number > best_number new_number > best_number
} else { } else {
// Take the oldest step at given height. // Take the oldest step at given height.
let new_step: usize = Rlp::new(&new_header.seal()[0]).as_val(); let new_step: usize = Rlp::new(&new_header.seal()[0]).as_val();
let best_step: usize = Rlp::new(&best_header.seal()[0]).as_val(); let best_step: usize = Rlp::new(&best_header.seal()[0]).as_val();
new_step < best_step new_step < best_step
} }
} }
fn register_message_channel(&self, message_channel: IoChannel<ClientIoMessage>) {
*self.message_channel.lock() = Some(message_channel);
}
fn set_signer(&self, _address: Address, password: String) {
*self.password.write() = Some(password);
}
fn register_account_provider(&self, account_provider: Arc<AccountProvider>) {
*self.account_provider.lock() = Some(account_provider);
}
} }
#[cfg(test)] #[cfg(test)]
@ -387,12 +391,11 @@ mod tests {
fn generates_seal_and_does_not_double_propose() { fn generates_seal_and_does_not_double_propose() {
let tap = AccountProvider::transient_provider(); let tap = AccountProvider::transient_provider();
let addr1 = tap.insert_account("1".sha3(), "1").unwrap(); let addr1 = tap.insert_account("1".sha3(), "1").unwrap();
tap.unlock_account_permanently(addr1, "1".into()).unwrap();
let addr2 = tap.insert_account("2".sha3(), "2").unwrap(); let addr2 = tap.insert_account("2".sha3(), "2").unwrap();
tap.unlock_account_permanently(addr2, "2".into()).unwrap();
let spec = Spec::new_test_round(); let spec = Spec::new_test_round();
let engine = &*spec.engine; let engine = &*spec.engine;
engine.register_account_provider(Arc::new(tap));
let genesis_header = spec.genesis_header(); let genesis_header = spec.genesis_header();
let mut db1 = get_temp_state_db().take(); let mut db1 = get_temp_state_db().take();
spec.ensure_db_good(&mut db1, &TrieFactory::new(TrieSpec::Secure)).unwrap(); spec.ensure_db_good(&mut db1, &TrieFactory::new(TrieSpec::Secure)).unwrap();
@ -404,16 +407,18 @@ mod tests {
let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes, addr2, (3141562.into(), 31415620.into()), vec![]).unwrap(); 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(); let b2 = b2.close_and_lock();
if let Some(seal) = engine.generate_seal(b1.block(), Some(&tap)) { engine.set_signer(addr1, "1".into());
if let Some(seal) = engine.generate_seal(b1.block()) {
assert!(b1.clone().try_seal(engine, seal).is_ok()); assert!(b1.clone().try_seal(engine, seal).is_ok());
// Second proposal is forbidden. // 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)) { engine.set_signer(addr2, "2".into());
if let Some(seal) = engine.generate_seal(b2.block()) {
assert!(b2.clone().try_seal(engine, seal).is_ok()); assert!(b2.clone().try_seal(engine, seal).is_ok());
// Second proposal is forbidden. // Second proposal is forbidden.
assert!(engine.generate_seal(b2.block(), Some(&tap)).is_none()); assert!(engine.generate_seal(b2.block()).is_none());
} }
} }

View File

@ -58,6 +58,8 @@ pub struct BasicAuthority {
params: CommonParams, params: CommonParams,
our_params: BasicAuthorityParams, our_params: BasicAuthorityParams,
builtins: BTreeMap<Address, Builtin>, builtins: BTreeMap<Address, Builtin>,
account_provider: Mutex<Option<Arc<AccountProvider>>>,
password: RwLock<Option<String>>,
} }
impl BasicAuthority { impl BasicAuthority {
@ -67,6 +69,8 @@ impl BasicAuthority {
params: params, params: params,
our_params: our_params, our_params: our_params,
builtins: builtins, builtins: builtins,
account_provider: Mutex::new(None),
password: RwLock::new(None),
} }
} }
} }
@ -98,13 +102,8 @@ impl Engine for BasicAuthority {
max(gas_floor_target, gas_limit - gas_limit / bound_divisor + 1.into()) max(gas_floor_target, gas_limit - gas_limit / bound_divisor + 1.into())
} }
}); });
// info!("ethash: populate_from_parent #{}: difficulty={} and gas_limit={}", header.number, header.difficulty, header.gas_limit);
} }
/// Apply the block reward on finalisation of the block.
/// This assumes that all uncles are valid uncles (i.e. of at least one generation before the current).
fn on_close_block(&self, _block: &mut ExecutedBlock) {}
fn is_sealer(&self, author: &Address) -> Option<bool> { fn is_sealer(&self, author: &Address) -> Option<bool> {
Some(self.our_params.authorities.contains(author)) Some(self.our_params.authorities.contains(author))
} }
@ -113,12 +112,12 @@ impl Engine for BasicAuthority {
/// ///
/// This operation is synchronous and may (quite reasonably) not be available, in which `false` will /// This operation is synchronous and may (quite reasonably) not be available, in which `false` will
/// be returned. /// be returned.
fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option<Vec<Bytes>> { fn generate_seal(&self, block: &ExecutedBlock) -> Option<Vec<Bytes>> {
if let Some(ap) = accounts { if let Some(ref ap) = *self.account_provider.lock() {
let header = block.header(); let header = block.header();
let message = header.bare_hash(); let message = header.bare_hash();
// account should be pernamently unlocked, otherwise sealing will fail // account should be pernamently unlocked, otherwise sealing will fail
if let Ok(signature) = ap.sign(*block.header().author(), None, message) { if let Ok(signature) = ap.sign(*block.header().author(), self.password.read().clone(), message) {
return Some(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]); return Some(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]);
} else { } else {
trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable"); trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable");
@ -179,6 +178,14 @@ impl Engine for BasicAuthority {
fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> {
t.sender().map(|_|()) // Perform EC recovery and cache sender t.sender().map(|_|()) // Perform EC recovery and cache sender
} }
fn set_signer(&self, _address: Address, password: String) {
*self.password.write() = Some(password);
}
fn register_account_provider(&self, ap: Arc<AccountProvider>) {
*self.account_provider.lock() = Some(ap);
}
} }
#[cfg(test)] #[cfg(test)]
@ -250,10 +257,11 @@ mod tests {
fn can_generate_seal() { fn can_generate_seal() {
let tap = AccountProvider::transient_provider(); let tap = AccountProvider::transient_provider();
let addr = tap.insert_account("".sha3(), "").unwrap(); let addr = tap.insert_account("".sha3(), "").unwrap();
tap.unlock_account_permanently(addr, "".into()).unwrap();
let spec = new_test_authority(); let spec = new_test_authority();
let engine = &*spec.engine; let engine = &*spec.engine;
engine.set_signer(addr, "".into());
engine.register_account_provider(Arc::new(tap));
let genesis_header = spec.genesis_header(); let genesis_header = spec.genesis_header();
let mut db_result = get_temp_state_db(); let mut db_result = get_temp_state_db();
let mut db = db_result.take(); let mut db = db_result.take();
@ -261,7 +269,7 @@ mod tests {
let last_hashes = Arc::new(vec![genesis_header.hash()]); 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, addr, (3141562.into(), 31415620.into()), vec![]).unwrap();
let b = b.close_and_lock(); 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()); assert!(b.try_seal(engine, seal).is_ok());
} }

View File

@ -23,7 +23,6 @@ use spec::CommonParams;
use evm::Schedule; use evm::Schedule;
use block::ExecutedBlock; use block::ExecutedBlock;
use util::Bytes; use util::Bytes;
use account_provider::AccountProvider;
/// An engine which does not provide any consensus mechanism, just seals blocks internally. /// An engine which does not provide any consensus mechanism, just seals blocks internally.
pub struct InstantSeal { pub struct InstantSeal {
@ -60,7 +59,7 @@ impl Engine for InstantSeal {
fn is_sealer(&self, _author: &Address) -> Option<bool> { Some(true) } fn is_sealer(&self, _author: &Address) -> Option<bool> { Some(true) }
fn generate_seal(&self, _block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option<Vec<Bytes>> { fn generate_seal(&self, _block: &ExecutedBlock) -> Option<Vec<Bytes>> {
Some(Vec::new()) Some(Vec::new())
} }
} }
@ -70,16 +69,12 @@ mod tests {
use util::*; use util::*;
use util::trie::TrieSpec; use util::trie::TrieSpec;
use tests::helpers::*; use tests::helpers::*;
use account_provider::AccountProvider;
use spec::Spec; use spec::Spec;
use header::Header; use header::Header;
use block::*; use block::*;
#[test] #[test]
fn instant_can_seal() { fn instant_can_seal() {
let tap = AccountProvider::transient_provider();
let addr = tap.insert_account("".sha3(), "").unwrap();
let spec = Spec::new_instant(); let spec = Spec::new_instant();
let engine = &*spec.engine; let engine = &*spec.engine;
let genesis_header = spec.genesis_header(); let genesis_header = spec.genesis_header();
@ -87,10 +82,9 @@ mod tests {
let mut db = db_result.take(); let mut db = db_result.take();
spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap(); spec.ensure_db_good(&mut db, &TrieFactory::new(TrieSpec::Secure)).unwrap();
let last_hashes = Arc::new(vec![genesis_header.hash()]); 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(); let b = b.close_and_lock();
// Seal with empty AccountProvider. let seal = engine.generate_seal(b.block()).unwrap();
let seal = engine.generate_seal(b.block(), Some(&tap)).unwrap();
assert!(b.try_seal(engine, seal).is_ok()); assert!(b.try_seal(engine, seal).is_ok());
} }

View File

@ -94,7 +94,7 @@ pub trait Engine : Sync + Send {
/// ///
/// This operation is synchronous and may (quite reasonably) not be available, in which None will /// This operation is synchronous and may (quite reasonably) not be available, in which None will
/// be returned. /// be returned.
fn generate_seal(&self, _block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option<Vec<Bytes>> { None } fn generate_seal(&self, _block: &ExecutedBlock) -> Option<Vec<Bytes>> { None }
/// Phase 1 quick block verification. Only does checks that are cheap. `block` (the header's full block) /// 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. /// 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); 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<ClientIoMessage>) {}
/// Check if new block should be chosen as the one in chain. /// 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 { 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) 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<ClientIoMessage>) {}
/// Add an account provider useful for Engines that sign stuff.
fn register_account_provider(&self, _account_provider: Arc<AccountProvider>) {}
} }

View File

@ -19,7 +19,7 @@ use std::time::{Instant, Duration};
use util::*; use util::*;
use util::using_queue::{UsingQueue, GetAction}; use util::using_queue::{UsingQueue, GetAction};
use account_provider::AccountProvider; use account_provider::{AccountProvider, Error as AccountError};
use views::{BlockView, HeaderView}; use views::{BlockView, HeaderView};
use header::Header; use header::Header;
use state::{State, CleanupMode}; 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), /// 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. /// Err(Some(block)) returns for unsuccesful sealing while Err(None) indicates misspecified engine.
fn seal_block_internally(&self, block: ClosedBlock) -> Result<SealedBlock, Option<ClosedBlock>> { fn seal_block_internally(&self, block: ClosedBlock) -> Result<SealedBlock, Option<ClosedBlock>> {
trace!(target: "miner", "seal_block_internally: block has transaction - attempting internal seal."); trace!(target: "miner", "seal_block_internally: attempting internal seal.");
let s = self.engine.generate_seal(block.block(), match self.accounts { let s = self.engine.generate_seal(block.block());
Some(ref x) => Some(&**x),
None => None,
});
if let Some(seal) = s { if let Some(seal) = s {
trace!(target: "miner", "seal_block_internally: managed internal seal. importing..."); trace!(target: "miner", "seal_block_internally: managed internal seal. importing...");
block.lock().try_seal(&*self.engine, seal).or_else(|_| { block.lock().try_seal(&*self.engine, seal).or_else(|(e, _)| {
warn!("prepare_sealing: ERROR: try_seal failed when given internally generated seal. WTF?"); warn!("prepare_sealing: ERROR: try_seal failed when given internally generated seal: {}", e);
Err(None) Err(None)
}) })
} else { } else {
@ -485,7 +482,7 @@ impl Miner {
fn seal_and_import_block_internally(&self, chain: &MiningBlockChainClient, block: ClosedBlock) -> bool { fn seal_and_import_block_internally(&self, chain: &MiningBlockChainClient, block: ClosedBlock) -> bool {
if !block.transactions().is_empty() || self.forced_sealing() { if !block.transactions().is_empty() || self.forced_sealing() {
if let Ok(sealed) = self.seal_block_internally(block) { 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"); trace!(target: "miner", "import_block_internally: imported internally sealed block");
return true return true
} }
@ -740,6 +737,19 @@ impl MinerService for Miner {
*self.author.write() = author; *self.author.write() = author;
} }
fn set_engine_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) { fn set_extra_data(&self, extra_data: Bytes) {
*self.extra_data.write() = extra_data; *self.extra_data.write() = extra_data;
} }
@ -1042,7 +1052,7 @@ impl MinerService for Miner {
ret.map(f) ret.map(f)
} }
fn submit_seal(&self, chain: &MiningBlockChainClient, pow_hash: H256, seal: Vec<Bytes>) -> Result<(), Error> { fn submit_seal(&self, chain: &MiningBlockChainClient, block_hash: H256, seal: Vec<Bytes>) -> Result<(), Error> {
let result = let result =
if let Some(b) = self.sealing_work.lock().queue.get_used_if( if let Some(b) = self.sealing_work.lock().queue.get_used_if(
if self.options.enable_resubmission { if self.options.enable_resubmission {
@ -1050,22 +1060,22 @@ impl MinerService for Miner {
} else { } else {
GetAction::Take 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, _)| { b.lock().try_seal(&*self.engine, seal).or_else(|(e, _)| {
warn!(target: "miner", "Mined solution rejected: {}", e); warn!(target: "miner", "Mined solution rejected: {}", e);
Err(Error::PowInvalid) Err(Error::PowInvalid)
}) })
} else { } 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) Err(Error::PowHashInvalid)
}; };
result.and_then(|sealed| { result.and_then(|sealed| {
let n = sealed.header().number(); let n = sealed.header().number();
let h = sealed.header().hash(); let h = sealed.header().hash();
try!(chain.import_sealed_block(sealed)); 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(()) Ok(())
}) })
} }

View File

@ -76,6 +76,9 @@ pub trait MinerService : Send + Sync {
/// Set the author that we will seal blocks as. /// Set the author that we will seal blocks as.
fn set_author(&self, author: Address); fn set_author(&self, author: Address);
/// Set info necessary to sign consensus messages.
fn set_engine_signer(&self, address: Address, password: String) -> Result<(), ::account_provider::Error>;
/// Get the extra_data that we will seal blocks with. /// Get the extra_data that we will seal blocks with.
fn extra_data(&self) -> Bytes; fn extra_data(&self) -> Bytes;

View File

@ -61,6 +61,7 @@ pass = "test_pass"
[mining] [mining]
author = "0xdeadbeefcafe0000000000000000000000000001" author = "0xdeadbeefcafe0000000000000000000000000001"
engine_signer = "0xdeadbeefcafe0000000000000000000000000001"
force_sealing = true force_sealing = true
reseal_on_txs = "all" reseal_on_txs = "all"
reseal_min_period = 4000 reseal_min_period = 4000

View File

@ -40,6 +40,7 @@ pass = "password"
[mining] [mining]
author = "0xdeadbeefcafe0000000000000000000000000001" author = "0xdeadbeefcafe0000000000000000000000000001"
engine_signer = "0xdeadbeefcafe0000000000000000000000000001"
force_sealing = true force_sealing = true
reseal_on_txs = "all" reseal_on_txs = "all"
reseal_min_period = 4000 reseal_min_period = 4000

View File

@ -178,6 +178,8 @@ usage! {
// -- Sealing/Mining Options // -- Sealing/Mining Options
flag_author: Option<String> = None, flag_author: Option<String> = None,
or |c: &Config| otry!(c.mining).author.clone().map(Some), or |c: &Config| otry!(c.mining).author.clone().map(Some),
flag_engine_signer: Option<String> = None,
or |c: &Config| otry!(c.mining).engine_signer.clone().map(Some),
flag_force_sealing: bool = false, flag_force_sealing: bool = false,
or |c: &Config| otry!(c.mining).force_sealing.clone(), or |c: &Config| otry!(c.mining).force_sealing.clone(),
flag_reseal_on_txs: String = "own", flag_reseal_on_txs: String = "own",
@ -371,6 +373,7 @@ struct Dapps {
#[derive(Default, Debug, PartialEq, RustcDecodable)] #[derive(Default, Debug, PartialEq, RustcDecodable)]
struct Mining { struct Mining {
author: Option<String>, author: Option<String>,
engine_signer: Option<String>,
force_sealing: Option<bool>, force_sealing: Option<bool>,
reseal_on_txs: Option<String>, reseal_on_txs: Option<String>,
reseal_min_period: Option<u64>, reseal_min_period: Option<u64>,
@ -575,6 +578,7 @@ mod tests {
// -- Sealing/Mining Options // -- Sealing/Mining Options
flag_author: Some("0xdeadbeefcafe0000000000000000000000000001".into()), flag_author: Some("0xdeadbeefcafe0000000000000000000000000001".into()),
flag_engine_signer: Some("0xdeadbeefcafe0000000000000000000000000001".into()),
flag_force_sealing: true, flag_force_sealing: true,
flag_reseal_on_txs: "all".into(), flag_reseal_on_txs: "all".into(),
flag_reseal_min_period: 4000u64, flag_reseal_min_period: 4000u64,
@ -746,6 +750,7 @@ mod tests {
}), }),
mining: Some(Mining { mining: Some(Mining {
author: Some("0xdeadbeefcafe0000000000000000000000000001".into()), author: Some("0xdeadbeefcafe0000000000000000000000000001".into()),
engine_signer: Some("0xdeadbeefcafe0000000000000000000000000001".into()),
force_sealing: Some(true), force_sealing: Some(true),
reseal_on_txs: Some("all".into()), reseal_on_txs: Some("all".into()),
reseal_min_period: Some(4000), reseal_min_period: Some(4000),

View File

@ -149,6 +149,10 @@ Sealing/Mining Options:
for sending block rewards from sealed blocks. for sending block rewards from sealed blocks.
NOTE: MINING WILL NOT WORK WITHOUT THIS OPTION. NOTE: MINING WILL NOT WORK WITHOUT THIS OPTION.
(default: {flag_author:?}) (default: {flag_author:?})
--engine-signer ADDRESS Specify the address which should be used to
sign consensus messages and issue blocks.
Relevant only to non-PoW chains.
(default: {flag_engine_signer:?})
--force-sealing Force the node to author new blocks as if it were --force-sealing Force the node to author new blocks as if it were
always sealing/mining. always sealing/mining.
(default: {flag_force_sealing}) (default: {flag_force_sealing})

View File

@ -305,6 +305,7 @@ impl Configuration {
gas_floor_target: try!(to_u256(&self.args.flag_gas_floor_target)), gas_floor_target: try!(to_u256(&self.args.flag_gas_floor_target)),
gas_ceil_target: try!(to_u256(&self.args.flag_gas_cap)), gas_ceil_target: try!(to_u256(&self.args.flag_gas_cap)),
transactions_limit: self.args.flag_tx_queue_size, transactions_limit: self.args.flag_tx_queue_size,
engine_signer: try!(self.engine_signer()),
}; };
Ok(extras) Ok(extras)
@ -314,6 +315,10 @@ impl Configuration {
to_address(self.args.flag_etherbase.clone().or(self.args.flag_author.clone())) to_address(self.args.flag_etherbase.clone().or(self.args.flag_author.clone()))
} }
fn engine_signer(&self) -> Result<Address, String> {
to_address(self.args.flag_engine_signer.clone())
}
fn format(&self) -> Result<Option<DataFormat>, String> { fn format(&self) -> Result<Option<DataFormat>, String> {
match self.args.flag_format { match self.args.flag_format {
Some(ref f) => Ok(Some(try!(f.parse()))), Some(ref f) => Ok(Some(try!(f.parse()))),

View File

@ -300,14 +300,14 @@ pub fn password_prompt() -> Result<String, String> {
/// Read a password from password file. /// Read a password from password file.
pub fn password_from_file(path: String) -> Result<String, String> { pub fn password_from_file(path: String) -> Result<String, String> {
let passwords = try!(passwords_from_files(vec![path])); let passwords = try!(passwords_from_files(&[path]));
// use only first password from the file // use only first password from the file
passwords.get(0).map(String::to_owned) passwords.get(0).map(String::to_owned)
.ok_or_else(|| "Password file seems to be empty.".to_owned()) .ok_or_else(|| "Password file seems to be empty.".to_owned())
} }
/// Reads passwords from files. Treats each line as a separate password. /// Reads passwords from files. Treats each line as a separate password.
pub fn passwords_from_files(files: Vec<String>) -> Result<Vec<String>, String> { pub fn passwords_from_files(files: &[String]) -> Result<Vec<String>, String> {
let passwords = files.iter().map(|filename| { let passwords = files.iter().map(|filename| {
let file = try!(File::open(filename).map_err(|_| format!("{} Unable to read password file. Ensure it exists and permissions are correct.", filename))); let file = try!(File::open(filename).map_err(|_| format!("{} Unable to read password file. Ensure it exists and permissions are correct.", filename)));
let reader = BufReader::new(&file); let reader = BufReader::new(&file);

View File

@ -204,6 +204,7 @@ pub struct MinerExtras {
pub gas_floor_target: U256, pub gas_floor_target: U256,
pub gas_ceil_target: U256, pub gas_ceil_target: U256,
pub transactions_limit: usize, pub transactions_limit: usize,
pub engine_signer: Address,
} }
impl Default for MinerExtras { impl Default for MinerExtras {
@ -214,6 +215,7 @@ impl Default for MinerExtras {
gas_floor_target: U256::from(4_700_000), gas_floor_target: U256::from(4_700_000),
gas_ceil_target: U256::from(6_283_184), gas_ceil_target: U256::from(6_283_184),
transactions_limit: 1024, transactions_limit: 1024,
engine_signer: Default::default(),
} }
} }
} }

View File

@ -207,8 +207,13 @@ pub fn execute(cmd: RunCmd, logger: Arc<RotatingLogger>) -> Result<(), String> {
sync_config.warp_sync = cmd.warp_sync; sync_config.warp_sync = cmd.warp_sync;
sync_config.download_old_blocks = cmd.download_old_blocks; sync_config.download_old_blocks = cmd.download_old_blocks;
let passwords = try!(passwords_from_files(&cmd.acc_conf.password_files));
// prepare account provider // prepare account provider
let account_provider = Arc::new(try!(prepare_account_provider(&cmd.dirs, cmd.acc_conf))); let account_provider = Arc::new(try!(prepare_account_provider(&cmd.dirs, cmd.acc_conf, &passwords)));
// let the Engine access the accounts
spec.engine.register_account_provider(account_provider.clone());
// create miner // create miner
let miner = Miner::new(cmd.miner_options, cmd.gas_pricer.into(), &spec, Some(account_provider.clone())); let miner = Miner::new(cmd.miner_options, cmd.gas_pricer.into(), &spec, Some(account_provider.clone()));
@ -217,6 +222,12 @@ pub fn execute(cmd: RunCmd, logger: Arc<RotatingLogger>) -> Result<(), String> {
miner.set_gas_ceil_target(cmd.miner_extras.gas_ceil_target); miner.set_gas_ceil_target(cmd.miner_extras.gas_ceil_target);
miner.set_extra_data(cmd.miner_extras.extra_data); miner.set_extra_data(cmd.miner_extras.extra_data);
miner.set_transactions_limit(cmd.miner_extras.transactions_limit); miner.set_transactions_limit(cmd.miner_extras.transactions_limit);
let engine_signer = cmd.miner_extras.engine_signer;
if engine_signer != Default::default() {
if !passwords.into_iter().any(|p| miner.set_engine_signer(engine_signer, p).is_ok()) {
return Err(format!("No password found for the consensus signer {}. Make sure valid password is present in files passed using `--password`.", engine_signer));
}
}
// create client config // create client config
let mut client_config = to_client_config( let mut client_config = to_client_config(
@ -425,19 +436,17 @@ fn daemonize(_pid_file: String) -> Result<(), String> {
Err("daemon is no supported on windows".into()) Err("daemon is no supported on windows".into())
} }
fn prepare_account_provider(dirs: &Directories, cfg: AccountsConfig) -> Result<AccountProvider, String> { fn prepare_account_provider(dirs: &Directories, cfg: AccountsConfig, passwords: &[String]) -> Result<AccountProvider, String> {
use ethcore::ethstore::EthStore; use ethcore::ethstore::EthStore;
use ethcore::ethstore::dir::DiskDirectory; use ethcore::ethstore::dir::DiskDirectory;
let passwords = try!(passwords_from_files(cfg.password_files));
let dir = Box::new(try!(DiskDirectory::create(dirs.keys.clone()).map_err(|e| format!("Could not open keys directory: {}", e)))); let dir = Box::new(try!(DiskDirectory::create(dirs.keys.clone()).map_err(|e| format!("Could not open keys directory: {}", e))));
let account_service = AccountProvider::new(Box::new( let account_service = AccountProvider::new(Box::new(
try!(EthStore::open_with_iterations(dir, cfg.iterations).map_err(|e| format!("Could not open keys directory: {}", e))) try!(EthStore::open_with_iterations(dir, cfg.iterations).map_err(|e| format!("Could not open keys directory: {}", e)))
)); ));
for a in cfg.unlocked_accounts { for a in cfg.unlocked_accounts {
if passwords.iter().find(|p| account_service.unlock_account_permanently(a, (*p).clone()).is_ok()).is_none() { if !passwords.iter().any(|p| account_service.unlock_account_permanently(a, (*p).clone()).is_ok()) {
return Err(format!("No password found to unlock account {}. Make sure valid password is present in files passed using `--password`.", a)); return Err(format!("No password found to unlock account {}. Make sure valid password is present in files passed using `--password`.", a));
} }
} }

View File

@ -116,6 +116,12 @@ impl<C, M, F> ParitySet for ParitySetClient<C, M, F> where
Ok(true) Ok(true)
} }
fn set_engine_signer(&self, address: H160, password: String) -> Result<bool, Error> {
try!(self.active());
try!(take_weak!(self.miner).set_engine_signer(address.into(), password).map_err(Into::into).map_err(errors::from_password_error));
Ok(true)
}
fn set_transactions_limit(&self, limit: usize) -> Result<bool, Error> { fn set_transactions_limit(&self, limit: usize) -> Result<bool, Error> {
try!(self.active()); try!(self.active());

View File

@ -116,6 +116,7 @@ impl EthTester {
fn from_spec(spec: Spec) -> Self { fn from_spec(spec: Spec) -> Self {
let dir = RandomTempPath::new(); let dir = RandomTempPath::new();
let account_provider = account_provider(); let account_provider = account_provider();
spec.engine.register_account_provider(account_provider.clone());
let miner_service = miner_service(&spec, account_provider.clone()); let miner_service = miner_service(&spec, account_provider.clone());
let snapshot_service = snapshot_service(); let snapshot_service = snapshot_service();

View File

@ -25,6 +25,7 @@ use ethcore::header::BlockNumber;
use ethcore::transaction::SignedTransaction; use ethcore::transaction::SignedTransaction;
use ethcore::receipt::{Receipt, RichReceipt}; use ethcore::receipt::{Receipt, RichReceipt};
use ethcore::miner::{MinerService, MinerStatus, TransactionImportResult, LocalTransactionStatus}; use ethcore::miner::{MinerService, MinerStatus, TransactionImportResult, LocalTransactionStatus};
use ethcore::account_provider::Error as AccountError;
/// Test miner service. /// Test miner service.
pub struct TestMinerService { pub struct TestMinerService {
@ -40,6 +41,8 @@ pub struct TestMinerService {
pub pending_receipts: Mutex<BTreeMap<H256, Receipt>>, pub pending_receipts: Mutex<BTreeMap<H256, Receipt>>,
/// Last nonces. /// Last nonces.
pub last_nonces: RwLock<HashMap<Address, U256>>, pub last_nonces: RwLock<HashMap<Address, U256>>,
/// Password held by Engine.
pub password: RwLock<String>,
min_gas_price: RwLock<U256>, min_gas_price: RwLock<U256>,
gas_range_target: RwLock<(U256, U256)>, gas_range_target: RwLock<(U256, U256)>,
@ -61,6 +64,7 @@ impl Default for TestMinerService {
min_gas_price: RwLock::new(U256::from(20_000_000)), min_gas_price: RwLock::new(U256::from(20_000_000)),
gas_range_target: RwLock::new((U256::from(12345), U256::from(54321))), gas_range_target: RwLock::new((U256::from(12345), U256::from(54321))),
author: RwLock::new(Address::zero()), author: RwLock::new(Address::zero()),
password: RwLock::new(String::new()),
extra_data: RwLock::new(vec![1, 2, 3, 4]), extra_data: RwLock::new(vec![1, 2, 3, 4]),
limit: RwLock::new(1024), limit: RwLock::new(1024),
tx_gas_limit: RwLock::new(!U256::zero()), tx_gas_limit: RwLock::new(!U256::zero()),
@ -83,6 +87,12 @@ impl MinerService for TestMinerService {
*self.author.write() = author; *self.author.write() = author;
} }
fn set_engine_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) { fn set_extra_data(&self, extra_data: Bytes) {
*self.extra_data.write() = extra_data; *self.extra_data.write() = extra_data;
} }

View File

@ -106,6 +106,23 @@ fn rpc_parity_set_author() {
assert_eq!(miner.author(), Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap()); assert_eq!(miner.author(), Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap());
} }
#[test]
fn rpc_parity_set_engine_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_setEngineSigner", "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] #[test]
fn rpc_parity_set_transactions_limit() { fn rpc_parity_set_transactions_limit() {
let miner = miner_service(); let miner = miner_service();

View File

@ -44,6 +44,10 @@ build_rpc_trait! {
#[rpc(name = "parity_setAuthor")] #[rpc(name = "parity_setAuthor")]
fn set_author(&self, H160) -> Result<bool, Error>; fn set_author(&self, H160) -> Result<bool, Error>;
/// Sets account for signing consensus messages.
#[rpc(name = "parity_setEngineSigner")]
fn set_engine_signer(&self, H160, String) -> Result<bool, Error>;
/// Sets the limits for transaction queue. /// Sets the limits for transaction queue.
#[rpc(name = "parity_setTransactionsLimit")] #[rpc(name = "parity_setTransactionsLimit")]
fn set_transactions_limit(&self, usize) -> Result<bool, Error>; fn set_transactions_limit(&self, usize) -> Result<bool, Error>;