add password and AccountProvider

This commit is contained in:
keorn 2016-12-05 17:08:16 +00:00
parent 53b479fb7a
commit 529a7fc33c
13 changed files with 119 additions and 57 deletions

View File

@ -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<Option<IoChannel<ClientIoMessage>>>,
step: AtomicUsize,
proposed: AtomicBool,
account_provider: Mutex<Option<Arc<AccountProvider>>>,
}
fn header_step(header: &Header) -> Result<usize, ::rlp::DecoderError> {
@ -104,7 +105,8 @@ impl AuthorityRound {
transition_service: try!(IoService::<BlockArrived>::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<Vec<Bytes>> {
fn generate_seal(&self, block: &ExecutedBlock) -> Option<Vec<Bytes>> {
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<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 {
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<ClientIoMessage>) {
*self.message_channel.lock() = Some(message_channel);
}
fn register_account_provider(&self, account_provider: Arc<AccountProvider>) {
*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());
}
}

View File

@ -58,6 +58,7 @@ pub struct BasicAuthority {
params: CommonParams,
our_params: BasicAuthorityParams,
builtins: BTreeMap<Address, Builtin>,
account_provider: Mutex<Option<Arc<AccountProvider>>>,
}
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<Vec<Bytes>> {
if let Some(ap) = accounts {
fn generate_seal(&self, block: &ExecutedBlock) -> Option<Vec<Bytes>> {
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<AccountProvider>) {
*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());
}

View File

@ -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<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())
}
}
@ -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());
}

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
/// 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)
/// 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<ClientIoMessage>) {}
/// 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<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::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<SealedBlock, Option<ClosedBlock>> {
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<Bytes>) -> Result<(), Error> {
fn submit_seal(&self, chain: &MiningBlockChainClient, block_hash: H256, seal: Vec<Bytes>) -> 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(())
})
}

View File

@ -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;

View File

@ -70,7 +70,7 @@ pub struct RunCmd {
pub http_conf: HttpConfiguration,
pub ipc_conf: IpcConfiguration,
pub net_conf: NetworkConfiguration,
pub network_id: Option<usize>,
pub network_id: Option<u64>,
pub warp_sync: bool,
pub acc_conf: AccountsConfig,
pub gas_pricer: GasPricerConfig,
@ -208,6 +208,9 @@ pub fn execute(cmd: RunCmd, logger: Arc<RotatingLogger>) -> 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);

View File

@ -116,6 +116,12 @@ impl<C, M, F> ParitySet for ParitySetClient<C, M, F> where
Ok(true)
}
fn set_consensus_signer(&self, address: H160, password: String) -> Result<bool, Error> {
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<bool, Error> {
try!(self.active());

View File

@ -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();

View File

@ -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<BTreeMap<H256, Receipt>>,
/// Last nonces.
pub last_nonces: RwLock<HashMap<Address, U256>>,
/// Password held by Engine.
pub password: RwLock<String>,
min_gas_price: RwLock<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)),
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;
}

View File

@ -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,
}

View File

@ -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();

View File

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