Deprecate account management (#10213)
* Extract accounts from ethcore. * Fix ethcore. * Get rid of AccountProvider in test_helpers * Fix rest of the code. * Re-use EngineSigner, fix tests. * Simplify EngineSigner to always have an Address. * Fix RPC tests. * Add deprecation notice to RPCs. * Feature to disable accounts. * extract accounts in RPC * Run with accounts in tests. * Fix RPC compilation and tests. * Fix compilation of the binary. * Fix compilation of the binary. * Fix compilation with accounts enabled. * Fix tests. * Update submodule. * Remove android. * Use derive for Default * Don't build secretstore by default. * Add link to issue. * Refresh Cargo.lock. * Fix miner tests. * Update rpc/Cargo.toml Co-Authored-By: tomusdrw <tomusdrw@users.noreply.github.com> * Fix private tests.
This commit is contained in:
committed by
Afri Schoedon
parent
8fa56add47
commit
d5c19f8719
@@ -1,809 +0,0 @@
|
||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Ethereum.
|
||||
|
||||
// Parity Ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Account management.
|
||||
|
||||
mod stores;
|
||||
|
||||
use self::stores::AddressBook;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::time::{Instant, Duration};
|
||||
|
||||
use ethstore::accounts_dir::MemoryDirectory;
|
||||
use ethstore::ethkey::{Address, Message, Public, Secret, Password, Random, Generator};
|
||||
use ethjson::misc::AccountMeta;
|
||||
use ethstore::{
|
||||
SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore,
|
||||
random_string, SecretVaultRef, StoreAccountRef, OpaqueSecret,
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
use types::transaction::{Action, Transaction};
|
||||
|
||||
pub use ethstore::ethkey::Signature;
|
||||
pub use ethstore::{Derivation, IndexDerivation, KeyFile};
|
||||
pub use hardware_wallet::{Error as HardwareError, HardwareWalletManager, KeyPath, TransactionInfo};
|
||||
|
||||
/// Type of unlock.
|
||||
#[derive(Clone, PartialEq)]
|
||||
enum Unlock {
|
||||
/// If account is unlocked temporarily, it should be locked after first usage.
|
||||
OneTime,
|
||||
/// Account unlocked permanently can always sign message.
|
||||
/// Use with caution.
|
||||
Perm,
|
||||
/// Account unlocked with a timeout
|
||||
Timed(Instant),
|
||||
}
|
||||
|
||||
/// Data associated with account.
|
||||
#[derive(Clone)]
|
||||
struct AccountData {
|
||||
unlock: Unlock,
|
||||
password: Password,
|
||||
}
|
||||
|
||||
/// Signing error
|
||||
#[derive(Debug)]
|
||||
pub enum SignError {
|
||||
/// Account is not unlocked
|
||||
NotUnlocked,
|
||||
/// Account does not exist.
|
||||
NotFound,
|
||||
/// Low-level hardware device error.
|
||||
Hardware(HardwareError),
|
||||
/// Low-level error from store
|
||||
SStore(SSError),
|
||||
}
|
||||
|
||||
impl fmt::Display for SignError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
match *self {
|
||||
SignError::NotUnlocked => write!(f, "Account is locked"),
|
||||
SignError::NotFound => write!(f, "Account does not exist"),
|
||||
SignError::Hardware(ref e) => write!(f, "{}", e),
|
||||
SignError::SStore(ref e) => write!(f, "{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HardwareError> for SignError {
|
||||
fn from(e: HardwareError) -> Self {
|
||||
SignError::Hardware(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SSError> for SignError {
|
||||
fn from(e: SSError) -> Self {
|
||||
SignError::SStore(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// `AccountProvider` errors.
|
||||
pub type Error = SSError;
|
||||
|
||||
fn transient_sstore() -> EthMultiStore {
|
||||
EthMultiStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed")
|
||||
}
|
||||
|
||||
type AccountToken = Password;
|
||||
|
||||
/// Account management.
|
||||
/// Responsible for unlocking accounts.
|
||||
pub struct AccountProvider {
|
||||
/// For performance reasons some methods can re-use unlocked secrets.
|
||||
unlocked_secrets: RwLock<HashMap<StoreAccountRef, OpaqueSecret>>,
|
||||
/// Unlocked account data.
|
||||
unlocked: RwLock<HashMap<StoreAccountRef, AccountData>>,
|
||||
/// Address book.
|
||||
address_book: RwLock<AddressBook>,
|
||||
/// Accounts on disk
|
||||
sstore: Box<SecretStore>,
|
||||
/// Accounts unlocked with rolling tokens
|
||||
transient_sstore: EthMultiStore,
|
||||
/// Accounts in hardware wallets.
|
||||
hardware_store: Option<HardwareWalletManager>,
|
||||
/// When unlocking account permanently we additionally keep a raw secret in memory
|
||||
/// to increase the performance of transaction signing.
|
||||
unlock_keep_secret: bool,
|
||||
/// Disallowed accounts.
|
||||
blacklisted_accounts: Vec<Address>,
|
||||
}
|
||||
|
||||
/// Account management settings.
|
||||
pub struct AccountProviderSettings {
|
||||
/// Enable hardware wallet support.
|
||||
pub enable_hardware_wallets: bool,
|
||||
/// Use the classic chain key on the hardware wallet.
|
||||
pub hardware_wallet_classic_key: bool,
|
||||
/// Store raw account secret when unlocking the account permanently.
|
||||
pub unlock_keep_secret: bool,
|
||||
/// Disallowed accounts.
|
||||
pub blacklisted_accounts: Vec<Address>,
|
||||
}
|
||||
|
||||
impl Default for AccountProviderSettings {
|
||||
fn default() -> Self {
|
||||
AccountProviderSettings {
|
||||
enable_hardware_wallets: false,
|
||||
hardware_wallet_classic_key: false,
|
||||
unlock_keep_secret: false,
|
||||
blacklisted_accounts: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AccountProvider {
|
||||
/// Creates new account provider.
|
||||
pub fn new(sstore: Box<SecretStore>, settings: AccountProviderSettings) -> Self {
|
||||
let mut hardware_store = None;
|
||||
|
||||
if settings.enable_hardware_wallets {
|
||||
match HardwareWalletManager::new() {
|
||||
Ok(manager) => {
|
||||
manager.set_key_path(if settings.hardware_wallet_classic_key { KeyPath::EthereumClassic } else { KeyPath::Ethereum });
|
||||
hardware_store = Some(manager)
|
||||
},
|
||||
Err(e) => debug!("Error initializing hardware wallets: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(accounts) = sstore.accounts() {
|
||||
for account in accounts.into_iter().filter(|a| settings.blacklisted_accounts.contains(&a.address)) {
|
||||
warn!("Local Account {} has a blacklisted (known to be weak) address and will be ignored",
|
||||
account.address);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove blacklisted accounts from address book.
|
||||
let mut address_book = AddressBook::new(&sstore.local_path());
|
||||
for addr in &settings.blacklisted_accounts {
|
||||
address_book.remove(*addr);
|
||||
}
|
||||
|
||||
AccountProvider {
|
||||
unlocked_secrets: RwLock::new(HashMap::new()),
|
||||
unlocked: RwLock::new(HashMap::new()),
|
||||
address_book: RwLock::new(address_book),
|
||||
sstore: sstore,
|
||||
transient_sstore: transient_sstore(),
|
||||
hardware_store: hardware_store,
|
||||
unlock_keep_secret: settings.unlock_keep_secret,
|
||||
blacklisted_accounts: settings.blacklisted_accounts,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates not disk backed provider.
|
||||
pub fn transient_provider() -> Self {
|
||||
AccountProvider {
|
||||
unlocked_secrets: RwLock::new(HashMap::new()),
|
||||
unlocked: RwLock::new(HashMap::new()),
|
||||
address_book: RwLock::new(AddressBook::transient()),
|
||||
sstore: Box::new(EthStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed")),
|
||||
transient_sstore: transient_sstore(),
|
||||
hardware_store: None,
|
||||
unlock_keep_secret: false,
|
||||
blacklisted_accounts: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates new random account.
|
||||
pub fn new_account(&self, password: &Password) -> Result<Address, Error> {
|
||||
self.new_account_and_public(password).map(|d| d.0)
|
||||
}
|
||||
|
||||
/// Creates new random account and returns address and public key
|
||||
pub fn new_account_and_public(&self, password: &Password) -> Result<(Address, Public), Error> {
|
||||
let acc = Random.generate().expect("secp context has generation capabilities; qed");
|
||||
let public = acc.public().clone();
|
||||
let secret = acc.secret().clone();
|
||||
let account = self.sstore.insert_account(SecretVaultRef::Root, secret, password)?;
|
||||
Ok((account.address, public))
|
||||
}
|
||||
|
||||
/// Inserts new account into underlying store.
|
||||
/// Does not unlock account!
|
||||
pub fn insert_account(&self, secret: Secret, password: &Password) -> Result<Address, Error> {
|
||||
let account = self.sstore.insert_account(SecretVaultRef::Root, secret, password)?;
|
||||
if self.blacklisted_accounts.contains(&account.address) {
|
||||
self.sstore.remove_account(&account, password)?;
|
||||
return Err(SSError::InvalidAccount.into());
|
||||
}
|
||||
Ok(account.address)
|
||||
}
|
||||
|
||||
/// Generates new derived account based on the existing one
|
||||
/// If password is not provided, account must be unlocked
|
||||
/// New account will be created with the same password (if save: true)
|
||||
pub fn derive_account(&self, address: &Address, password: Option<Password>, derivation: Derivation, save: bool)
|
||||
-> Result<Address, SignError>
|
||||
{
|
||||
let account = self.sstore.account_ref(&address)?;
|
||||
let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?;
|
||||
Ok(
|
||||
if save { self.sstore.insert_derived(SecretVaultRef::Root, &account, &password, derivation)?.address }
|
||||
else { self.sstore.generate_derived(&account, &password, derivation)? }
|
||||
)
|
||||
}
|
||||
|
||||
/// Import a new presale wallet.
|
||||
pub fn import_presale(&self, presale_json: &[u8], password: &Password) -> Result<Address, Error> {
|
||||
let account = self.sstore.import_presale(SecretVaultRef::Root, presale_json, password)?;
|
||||
Ok(Address::from(account.address).into())
|
||||
}
|
||||
|
||||
/// Import a new wallet.
|
||||
pub fn import_wallet(&self, json: &[u8], password: &Password, gen_id: bool) -> Result<Address, Error> {
|
||||
let account = self.sstore.import_wallet(SecretVaultRef::Root, json, password, gen_id)?;
|
||||
if self.blacklisted_accounts.contains(&account.address) {
|
||||
self.sstore.remove_account(&account, password)?;
|
||||
return Err(SSError::InvalidAccount.into());
|
||||
}
|
||||
Ok(Address::from(account.address).into())
|
||||
}
|
||||
|
||||
/// Checks whether an account with a given address is present.
|
||||
pub fn has_account(&self, address: Address) -> bool {
|
||||
self.sstore.account_ref(&address).is_ok() && !self.blacklisted_accounts.contains(&address)
|
||||
}
|
||||
|
||||
/// Returns addresses of all accounts.
|
||||
pub fn accounts(&self) -> Result<Vec<Address>, Error> {
|
||||
let accounts = self.sstore.accounts()?;
|
||||
Ok(accounts
|
||||
.into_iter()
|
||||
.map(|a| a.address)
|
||||
.filter(|address| !self.blacklisted_accounts.contains(address))
|
||||
.collect()
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the address of default account.
|
||||
pub fn default_account(&self) -> Result<Address, Error> {
|
||||
Ok(self.accounts()?.first().cloned().unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Returns addresses of hardware accounts.
|
||||
pub fn hardware_accounts(&self) -> Result<Vec<Address>, Error> {
|
||||
if let Some(accounts) = self.hardware_store.as_ref().map(|h| h.list_wallets()) {
|
||||
if !accounts.is_empty() {
|
||||
return Ok(accounts.into_iter().map(|a| a.address).collect());
|
||||
}
|
||||
}
|
||||
Err(SSError::Custom("No hardware wallet accounts were found".into()))
|
||||
}
|
||||
|
||||
/// Get a list of paths to locked hardware wallets
|
||||
pub fn locked_hardware_accounts(&self) -> Result<Vec<String>, SignError> {
|
||||
match self.hardware_store.as_ref().map(|h| h.list_locked_wallets()) {
|
||||
None => Err(SignError::NotFound),
|
||||
Some(Err(e)) => Err(SignError::Hardware(e)),
|
||||
Some(Ok(s)) => Ok(s),
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide a pin to a locked hardware wallet on USB path to unlock it
|
||||
pub fn hardware_pin_matrix_ack(&self, path: &str, pin: &str) -> Result<bool, SignError> {
|
||||
match self.hardware_store.as_ref().map(|h| h.pin_matrix_ack(path, pin)) {
|
||||
None => Err(SignError::NotFound),
|
||||
Some(Err(e)) => Err(SignError::Hardware(e)),
|
||||
Some(Ok(s)) => Ok(s),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns each address along with metadata.
|
||||
pub fn addresses_info(&self) -> HashMap<Address, AccountMeta> {
|
||||
self.address_book.read().get()
|
||||
}
|
||||
|
||||
/// Returns each address along with metadata.
|
||||
pub fn set_address_name(&self, account: Address, name: String) {
|
||||
self.address_book.write().set_name(account, name)
|
||||
}
|
||||
|
||||
/// Returns each address along with metadata.
|
||||
pub fn set_address_meta(&self, account: Address, meta: String) {
|
||||
self.address_book.write().set_meta(account, meta)
|
||||
}
|
||||
|
||||
/// Removes and address from the address book
|
||||
pub fn remove_address(&self, addr: Address) {
|
||||
self.address_book.write().remove(addr)
|
||||
}
|
||||
|
||||
/// Returns each account along with name and meta.
|
||||
pub fn accounts_info(&self) -> Result<HashMap<Address, AccountMeta>, Error> {
|
||||
let r = self.sstore.accounts()?
|
||||
.into_iter()
|
||||
.filter(|a| !self.blacklisted_accounts.contains(&a.address))
|
||||
.map(|a| (a.address.clone(), self.account_meta(a.address).ok().unwrap_or_default()))
|
||||
.collect();
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
/// Returns each hardware account along with name and meta.
|
||||
pub fn hardware_accounts_info(&self) -> Result<HashMap<Address, AccountMeta>, Error> {
|
||||
let r = self.hardware_accounts()?
|
||||
.into_iter()
|
||||
.map(|address| (address.clone(), self.account_meta(address).ok().unwrap_or_default()))
|
||||
.collect();
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
/// Returns each hardware account along with name and meta.
|
||||
pub fn is_hardware_address(&self, address: &Address) -> bool {
|
||||
self.hardware_store.as_ref().and_then(|s| s.wallet_info(address)).is_some()
|
||||
}
|
||||
|
||||
/// Returns each account along with name and meta.
|
||||
pub fn account_meta(&self, address: Address) -> Result<AccountMeta, Error> {
|
||||
if let Some(info) = self.hardware_store.as_ref().and_then(|s| s.wallet_info(&address)) {
|
||||
Ok(AccountMeta {
|
||||
name: info.name,
|
||||
meta: info.manufacturer,
|
||||
uuid: None,
|
||||
})
|
||||
} else {
|
||||
let account = self.sstore.account_ref(&address)?;
|
||||
Ok(AccountMeta {
|
||||
name: self.sstore.name(&account)?,
|
||||
meta: self.sstore.meta(&account)?,
|
||||
uuid: self.sstore.uuid(&account).ok().map(Into::into), // allowed to not have a Uuid
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns account public key.
|
||||
pub fn account_public(&self, address: Address, password: &Password) -> Result<Public, Error> {
|
||||
self.sstore.public(&self.sstore.account_ref(&address)?, password)
|
||||
}
|
||||
|
||||
/// Returns each account along with name and meta.
|
||||
pub fn set_account_name(&self, address: Address, name: String) -> Result<(), Error> {
|
||||
self.sstore.set_name(&self.sstore.account_ref(&address)?, name)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns each account along with name and meta.
|
||||
pub fn set_account_meta(&self, address: Address, meta: String) -> Result<(), Error> {
|
||||
self.sstore.set_meta(&self.sstore.account_ref(&address)?, meta)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns `true` if the password for `account` is `password`. `false` if not.
|
||||
pub fn test_password(&self, address: &Address, password: &Password) -> Result<bool, Error> {
|
||||
self.sstore.test_password(&self.sstore.account_ref(&address)?, password)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Permanently removes an account.
|
||||
pub fn kill_account(&self, address: &Address, password: &Password) -> Result<(), Error> {
|
||||
self.sstore.remove_account(&self.sstore.account_ref(&address)?, &password)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Changes the password of `account` from `password` to `new_password`. Fails if incorrect `password` given.
|
||||
pub fn change_password(&self, address: &Address, password: Password, new_password: Password) -> Result<(), Error> {
|
||||
self.sstore.change_password(&self.sstore.account_ref(address)?, &password, &new_password)
|
||||
}
|
||||
|
||||
/// Exports an account for given address.
|
||||
pub fn export_account(&self, address: &Address, password: Password) -> Result<KeyFile, Error> {
|
||||
self.sstore.export_account(&self.sstore.account_ref(address)?, &password)
|
||||
}
|
||||
|
||||
/// Helper method used for unlocking accounts.
|
||||
fn unlock_account(&self, address: Address, password: Password, unlock: Unlock) -> Result<(), Error> {
|
||||
let account = self.sstore.account_ref(&address)?;
|
||||
|
||||
// check if account is already unlocked permanently, if it is, do nothing
|
||||
let mut unlocked = self.unlocked.write();
|
||||
if let Some(data) = unlocked.get(&account) {
|
||||
if let Unlock::Perm = data.unlock {
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
if self.unlock_keep_secret && unlock == Unlock::Perm {
|
||||
// verify password and get the secret
|
||||
let secret = self.sstore.raw_secret(&account, &password)?;
|
||||
self.unlocked_secrets.write().insert(account.clone(), secret);
|
||||
} else {
|
||||
// verify password by signing dump message
|
||||
// result may be discarded
|
||||
let _ = self.sstore.sign(&account, &password, &Default::default())?;
|
||||
}
|
||||
|
||||
let data = AccountData {
|
||||
unlock: unlock,
|
||||
password: password,
|
||||
};
|
||||
|
||||
unlocked.insert(account, data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn password(&self, account: &StoreAccountRef) -> Result<Password, SignError> {
|
||||
let mut unlocked = self.unlocked.write();
|
||||
let data = unlocked.get(account).ok_or(SignError::NotUnlocked)?.clone();
|
||||
if let Unlock::OneTime = data.unlock {
|
||||
unlocked.remove(account).expect("data exists: so key must exist: qed");
|
||||
}
|
||||
if let Unlock::Timed(ref end) = data.unlock {
|
||||
if Instant::now() > *end {
|
||||
unlocked.remove(account).expect("data exists: so key must exist: qed");
|
||||
return Err(SignError::NotUnlocked);
|
||||
}
|
||||
}
|
||||
Ok(data.password)
|
||||
}
|
||||
|
||||
/// Unlocks account permanently.
|
||||
pub fn unlock_account_permanently(&self, account: Address, password: Password) -> Result<(), Error> {
|
||||
self.unlock_account(account, password, Unlock::Perm)
|
||||
}
|
||||
|
||||
/// Unlocks account temporarily (for one signing).
|
||||
pub fn unlock_account_temporarily(&self, account: Address, password: Password) -> Result<(), Error> {
|
||||
self.unlock_account(account, password, Unlock::OneTime)
|
||||
}
|
||||
|
||||
/// Unlocks account temporarily with a timeout.
|
||||
pub fn unlock_account_timed(&self, account: Address, password: Password, duration: Duration) -> Result<(), Error> {
|
||||
self.unlock_account(account, password, Unlock::Timed(Instant::now() + duration))
|
||||
}
|
||||
|
||||
/// Checks if given account is unlocked
|
||||
pub fn is_unlocked(&self, address: &Address) -> bool {
|
||||
let unlocked = self.unlocked.read();
|
||||
let unlocked_secrets = self.unlocked_secrets.read();
|
||||
self.sstore.account_ref(address)
|
||||
.map(|r| unlocked.get(&r).is_some() || unlocked_secrets.get(&r).is_some())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Checks if given account is unlocked permanently
|
||||
pub fn is_unlocked_permanently(&self, address: &Address) -> bool {
|
||||
let unlocked = self.unlocked.read();
|
||||
self.sstore.account_ref(address)
|
||||
.map(|r| unlocked.get(&r).map_or(false, |account| account.unlock == Unlock::Perm))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Signs the message. If password is not provided the account must be unlocked.
|
||||
pub fn sign(&self, address: Address, password: Option<Password>, message: Message) -> Result<Signature, SignError> {
|
||||
let account = self.sstore.account_ref(&address)?;
|
||||
match self.unlocked_secrets.read().get(&account) {
|
||||
Some(secret) => {
|
||||
Ok(self.sstore.sign_with_secret(&secret, &message)?)
|
||||
},
|
||||
None => {
|
||||
let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?;
|
||||
Ok(self.sstore.sign(&account, &password, &message)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Signs message using the derived secret. If password is not provided the account must be unlocked.
|
||||
pub fn sign_derived(&self, address: &Address, password: Option<Password>, derivation: Derivation, message: Message)
|
||||
-> Result<Signature, SignError>
|
||||
{
|
||||
let account = self.sstore.account_ref(address)?;
|
||||
let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?;
|
||||
Ok(self.sstore.sign_derived(&account, &password, derivation, &message)?)
|
||||
}
|
||||
|
||||
/// Signs given message with supplied token. Returns a token to use in next signing within this session.
|
||||
pub fn sign_with_token(&self, address: Address, token: AccountToken, message: Message) -> Result<(Signature, AccountToken), SignError> {
|
||||
let account = self.sstore.account_ref(&address)?;
|
||||
let is_std_password = self.sstore.test_password(&account, &token)?;
|
||||
|
||||
let new_token = Password::from(random_string(16));
|
||||
let signature = if is_std_password {
|
||||
// Insert to transient store
|
||||
self.sstore.copy_account(&self.transient_sstore, SecretVaultRef::Root, &account, &token, &new_token)?;
|
||||
// sign
|
||||
self.sstore.sign(&account, &token, &message)?
|
||||
} else {
|
||||
// check transient store
|
||||
self.transient_sstore.change_password(&account, &token, &new_token)?;
|
||||
// and sign
|
||||
self.transient_sstore.sign(&account, &new_token, &message)?
|
||||
};
|
||||
|
||||
Ok((signature, new_token))
|
||||
}
|
||||
|
||||
/// Decrypts a message with given token. Returns a token to use in next operation for this account.
|
||||
pub fn decrypt_with_token(&self, address: Address, token: AccountToken, shared_mac: &[u8], message: &[u8])
|
||||
-> Result<(Vec<u8>, AccountToken), SignError>
|
||||
{
|
||||
let account = self.sstore.account_ref(&address)?;
|
||||
let is_std_password = self.sstore.test_password(&account, &token)?;
|
||||
|
||||
let new_token = Password::from(random_string(16));
|
||||
let message = if is_std_password {
|
||||
// Insert to transient store
|
||||
self.sstore.copy_account(&self.transient_sstore, SecretVaultRef::Root, &account, &token, &new_token)?;
|
||||
// decrypt
|
||||
self.sstore.decrypt(&account, &token, shared_mac, message)?
|
||||
} else {
|
||||
// check transient store
|
||||
self.transient_sstore.change_password(&account, &token, &new_token)?;
|
||||
// and decrypt
|
||||
self.transient_sstore.decrypt(&account, &token, shared_mac, message)?
|
||||
};
|
||||
|
||||
Ok((message, new_token))
|
||||
}
|
||||
|
||||
/// Decrypts a message. If password is not provided the account must be unlocked.
|
||||
pub fn decrypt(&self, address: Address, password: Option<Password>, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, SignError> {
|
||||
let account = self.sstore.account_ref(&address)?;
|
||||
let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?;
|
||||
Ok(self.sstore.decrypt(&account, &password, shared_mac, message)?)
|
||||
}
|
||||
|
||||
/// Agree on shared key.
|
||||
pub fn agree(&self, address: Address, password: Option<Password>, other_public: &Public) -> Result<Secret, SignError> {
|
||||
let account = self.sstore.account_ref(&address)?;
|
||||
let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?;
|
||||
Ok(self.sstore.agree(&account, &password, other_public)?)
|
||||
}
|
||||
|
||||
/// Returns the underlying `SecretStore` reference if one exists.
|
||||
pub fn list_geth_accounts(&self, testnet: bool) -> Vec<Address> {
|
||||
self.sstore.list_geth_accounts(testnet).into_iter().map(|a| Address::from(a).into()).collect()
|
||||
}
|
||||
|
||||
/// Returns the underlying `SecretStore` reference if one exists.
|
||||
pub fn import_geth_accounts(&self, desired: Vec<Address>, testnet: bool) -> Result<Vec<Address>, Error> {
|
||||
self.sstore.import_geth_accounts(SecretVaultRef::Root, desired, testnet)
|
||||
.map(|a| a.into_iter().map(|a| a.address).collect())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Create new vault.
|
||||
pub fn create_vault(&self, name: &str, password: &Password) -> Result<(), Error> {
|
||||
self.sstore.create_vault(name, password)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Open existing vault.
|
||||
pub fn open_vault(&self, name: &str, password: &Password) -> Result<(), Error> {
|
||||
self.sstore.open_vault(name, password)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Close previously opened vault.
|
||||
pub fn close_vault(&self, name: &str) -> Result<(), Error> {
|
||||
self.sstore.close_vault(name)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// List all vaults
|
||||
pub fn list_vaults(&self) -> Result<Vec<String>, Error> {
|
||||
self.sstore.list_vaults()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// List all currently opened vaults
|
||||
pub fn list_opened_vaults(&self) -> Result<Vec<String>, Error> {
|
||||
self.sstore.list_opened_vaults()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Change vault password.
|
||||
pub fn change_vault_password(&self, name: &str, new_password: &Password) -> Result<(), Error> {
|
||||
self.sstore.change_vault_password(name, new_password)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Change vault of the given address.
|
||||
pub fn change_vault(&self, address: Address, new_vault: &str) -> Result<(), Error> {
|
||||
let new_vault_ref = if new_vault.is_empty() { SecretVaultRef::Root } else { SecretVaultRef::Vault(new_vault.to_owned()) };
|
||||
let old_account_ref = self.sstore.account_ref(&address)?;
|
||||
self.sstore.change_account_vault(new_vault_ref, old_account_ref)
|
||||
.map_err(Into::into)
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
/// Get vault metadata string.
|
||||
pub fn get_vault_meta(&self, name: &str) -> Result<String, Error> {
|
||||
self.sstore.get_vault_meta(name)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Set vault metadata string.
|
||||
pub fn set_vault_meta(&self, name: &str, meta: &str) -> Result<(), Error> {
|
||||
self.sstore.set_vault_meta(name, meta)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Sign message with hardware wallet.
|
||||
pub fn sign_message_with_hardware(&self, address: &Address, message: &[u8]) -> Result<Signature, SignError> {
|
||||
match self.hardware_store.as_ref().map(|s| s.sign_message(address, message)) {
|
||||
None | Some(Err(HardwareError::KeyNotFound)) => Err(SignError::NotFound),
|
||||
Some(Err(e)) => Err(From::from(e)),
|
||||
Some(Ok(s)) => Ok(s),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sign transaction with hardware wallet.
|
||||
pub fn sign_transaction_with_hardware(&self, address: &Address, transaction: &Transaction, chain_id: Option<u64>, rlp_encoded_transaction: &[u8]) -> Result<Signature, SignError> {
|
||||
let t_info = TransactionInfo {
|
||||
nonce: transaction.nonce,
|
||||
gas_price: transaction.gas_price,
|
||||
gas_limit: transaction.gas,
|
||||
to: match transaction.action {
|
||||
Action::Create => None,
|
||||
Action::Call(ref to) => Some(to.clone()),
|
||||
},
|
||||
value: transaction.value,
|
||||
data: transaction.data.to_vec(),
|
||||
chain_id: chain_id,
|
||||
};
|
||||
match self.hardware_store.as_ref().map(|s| s.sign_transaction(&address, &t_info, rlp_encoded_transaction)) {
|
||||
None | Some(Err(HardwareError::KeyNotFound)) => Err(SignError::NotFound),
|
||||
Some(Err(e)) => Err(From::from(e)),
|
||||
Some(Ok(s)) => Ok(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{AccountProvider, Unlock};
|
||||
use std::time::{Duration, Instant};
|
||||
use ethstore::ethkey::{Generator, Random, Address};
|
||||
use ethstore::{StoreAccountRef, Derivation};
|
||||
use ethereum_types::H256;
|
||||
|
||||
#[test]
|
||||
fn unlock_account_temp() {
|
||||
let kp = Random.generate().unwrap();
|
||||
let ap = AccountProvider::transient_provider();
|
||||
assert!(ap.insert_account(kp.secret().clone(), &"test".into()).is_ok());
|
||||
assert!(ap.unlock_account_temporarily(kp.address(), "test1".into()).is_err());
|
||||
assert!(ap.unlock_account_temporarily(kp.address(), "test".into()).is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derived_account_nosave() {
|
||||
let kp = Random.generate().unwrap();
|
||||
let ap = AccountProvider::transient_provider();
|
||||
assert!(ap.insert_account(kp.secret().clone(), &"base".into()).is_ok());
|
||||
assert!(ap.unlock_account_permanently(kp.address(), "base".into()).is_ok());
|
||||
|
||||
let derived_addr = ap.derive_account(
|
||||
&kp.address(),
|
||||
None,
|
||||
Derivation::SoftHash(H256::from(999)),
|
||||
false,
|
||||
).expect("Derivation should not fail");
|
||||
|
||||
assert!(ap.unlock_account_permanently(derived_addr, "base".into()).is_err(),
|
||||
"There should be an error because account is not supposed to be saved");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derived_account_save() {
|
||||
let kp = Random.generate().unwrap();
|
||||
let ap = AccountProvider::transient_provider();
|
||||
assert!(ap.insert_account(kp.secret().clone(), &"base".into()).is_ok());
|
||||
assert!(ap.unlock_account_permanently(kp.address(), "base".into()).is_ok());
|
||||
|
||||
let derived_addr = ap.derive_account(
|
||||
&kp.address(),
|
||||
None,
|
||||
Derivation::SoftHash(H256::from(999)),
|
||||
true,
|
||||
).expect("Derivation should not fail");
|
||||
|
||||
assert!(ap.unlock_account_permanently(derived_addr, "base_wrong".into()).is_err(),
|
||||
"There should be an error because password is invalid");
|
||||
|
||||
assert!(ap.unlock_account_permanently(derived_addr, "base".into()).is_ok(),
|
||||
"Should be ok because account is saved and password is valid");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derived_account_sign() {
|
||||
let kp = Random.generate().unwrap();
|
||||
let ap = AccountProvider::transient_provider();
|
||||
assert!(ap.insert_account(kp.secret().clone(), &"base".into()).is_ok());
|
||||
assert!(ap.unlock_account_permanently(kp.address(), "base".into()).is_ok());
|
||||
|
||||
let derived_addr = ap.derive_account(
|
||||
&kp.address(),
|
||||
None,
|
||||
Derivation::SoftHash(H256::from(1999)),
|
||||
true,
|
||||
).expect("Derivation should not fail");
|
||||
ap.unlock_account_permanently(derived_addr, "base".into())
|
||||
.expect("Should be ok because account is saved and password is valid");
|
||||
|
||||
let msg = Default::default();
|
||||
let signed_msg1 = ap.sign(derived_addr, None, msg)
|
||||
.expect("Signing with existing unlocked account should not fail");
|
||||
let signed_msg2 = ap.sign_derived(
|
||||
&kp.address(),
|
||||
None,
|
||||
Derivation::SoftHash(H256::from(1999)),
|
||||
msg,
|
||||
).expect("Derived signing with existing unlocked account should not fail");
|
||||
|
||||
assert_eq!(signed_msg1, signed_msg2,
|
||||
"Signed messages should match");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unlock_account_perm() {
|
||||
let kp = Random.generate().unwrap();
|
||||
let ap = AccountProvider::transient_provider();
|
||||
assert!(ap.insert_account(kp.secret().clone(), &"test".into()).is_ok());
|
||||
assert!(ap.unlock_account_permanently(kp.address(), "test1".into()).is_err());
|
||||
assert!(ap.unlock_account_permanently(kp.address(), "test".into()).is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||
assert!(ap.unlock_account_temporarily(kp.address(), "test".into()).is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unlock_account_timer() {
|
||||
let kp = Random.generate().unwrap();
|
||||
let ap = AccountProvider::transient_provider();
|
||||
assert!(ap.insert_account(kp.secret().clone(), &"test".into()).is_ok());
|
||||
assert!(ap.unlock_account_timed(kp.address(), "test1".into(), Duration::from_secs(60)).is_err());
|
||||
assert!(ap.unlock_account_timed(kp.address(), "test".into(), Duration::from_secs(60)).is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||
ap.unlocked.write().get_mut(&StoreAccountRef::root(kp.address())).unwrap().unlock = Unlock::Timed(Instant::now());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_sign_and_return_token() {
|
||||
// given
|
||||
let kp = Random.generate().unwrap();
|
||||
let ap = AccountProvider::transient_provider();
|
||||
assert!(ap.insert_account(kp.secret().clone(), &"test".into()).is_ok());
|
||||
|
||||
// when
|
||||
let (_signature, token) = ap.sign_with_token(kp.address(), "test".into(), Default::default()).unwrap();
|
||||
|
||||
// then
|
||||
ap.sign_with_token(kp.address(), token.clone(), Default::default())
|
||||
.expect("First usage of token should be correct.");
|
||||
assert!(ap.sign_with_token(kp.address(), token, Default::default()).is_err(), "Second usage of the same token should fail.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_return_blacklisted_account() {
|
||||
// given
|
||||
let mut ap = AccountProvider::transient_provider();
|
||||
let acc = ap.new_account(&"test".into()).unwrap();
|
||||
ap.blacklisted_accounts = vec![acc];
|
||||
|
||||
// then
|
||||
assert_eq!(ap.accounts_info().unwrap().keys().cloned().collect::<Vec<Address>>(), vec![]);
|
||||
assert_eq!(ap.accounts().unwrap(), vec![]);
|
||||
}
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity Ethereum.
|
||||
|
||||
// Parity Ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity Ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Address Book Store
|
||||
|
||||
use std::{fs, fmt, hash, ops};
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use ethstore::ethkey::Address;
|
||||
use ethjson::misc::AccountMeta;
|
||||
|
||||
/// Disk-backed map from Address to String. Uses JSON.
|
||||
pub struct AddressBook {
|
||||
cache: DiskMap<Address, AccountMeta>,
|
||||
}
|
||||
|
||||
impl AddressBook {
|
||||
/// Creates new address book at given directory.
|
||||
pub fn new(path: &Path) -> Self {
|
||||
let mut r = AddressBook {
|
||||
cache: DiskMap::new(path, "address_book.json")
|
||||
};
|
||||
r.cache.revert(AccountMeta::read);
|
||||
r
|
||||
}
|
||||
|
||||
/// Creates transient address book (no changes are saved to disk).
|
||||
pub fn transient() -> Self {
|
||||
AddressBook {
|
||||
cache: DiskMap::transient()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the address book.
|
||||
pub fn get(&self) -> HashMap<Address, AccountMeta> {
|
||||
self.cache.clone()
|
||||
}
|
||||
|
||||
fn save(&self) {
|
||||
self.cache.save(AccountMeta::write)
|
||||
}
|
||||
|
||||
/// Sets new name for given address.
|
||||
pub fn set_name(&mut self, a: Address, name: String) {
|
||||
{
|
||||
let x = self.cache.entry(a)
|
||||
.or_insert_with(|| AccountMeta {name: Default::default(), meta: "{}".to_owned(), uuid: None});
|
||||
x.name = name;
|
||||
}
|
||||
self.save();
|
||||
}
|
||||
|
||||
/// Sets new meta for given address.
|
||||
pub fn set_meta(&mut self, a: Address, meta: String) {
|
||||
{
|
||||
let x = self.cache.entry(a)
|
||||
.or_insert_with(|| AccountMeta {name: "Anonymous".to_owned(), meta: Default::default(), uuid: None});
|
||||
x.meta = meta;
|
||||
}
|
||||
self.save();
|
||||
}
|
||||
|
||||
/// Removes an entry
|
||||
pub fn remove(&mut self, a: Address) {
|
||||
self.cache.remove(&a);
|
||||
self.save();
|
||||
}
|
||||
}
|
||||
|
||||
/// Disk-serializable HashMap
|
||||
#[derive(Debug)]
|
||||
struct DiskMap<K: hash::Hash + Eq, V> {
|
||||
path: PathBuf,
|
||||
cache: HashMap<K, V>,
|
||||
transient: bool,
|
||||
}
|
||||
|
||||
impl<K: hash::Hash + Eq, V> ops::Deref for DiskMap<K, V> {
|
||||
type Target = HashMap<K, V>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.cache
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: hash::Hash + Eq, V> ops::DerefMut for DiskMap<K, V> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.cache
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: hash::Hash + Eq, V> DiskMap<K, V> {
|
||||
pub fn new(path: &Path, file_name: &str) -> Self {
|
||||
let mut path = path.to_owned();
|
||||
path.push(file_name);
|
||||
trace!(target: "diskmap", "path={:?}", path);
|
||||
DiskMap {
|
||||
path: path,
|
||||
cache: HashMap::new(),
|
||||
transient: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transient() -> Self {
|
||||
let mut map = DiskMap::new(&PathBuf::new(), "diskmap.json".into());
|
||||
map.transient = true;
|
||||
map
|
||||
}
|
||||
|
||||
fn revert<F, E>(&mut self, read: F) where
|
||||
F: Fn(fs::File) -> Result<HashMap<K, V>, E>,
|
||||
E: fmt::Display,
|
||||
{
|
||||
if self.transient { return; }
|
||||
trace!(target: "diskmap", "revert {:?}", self.path);
|
||||
let _ = fs::File::open(self.path.clone())
|
||||
.map_err(|e| trace!(target: "diskmap", "Couldn't open disk map: {}", e))
|
||||
.and_then(|f| read(f).map_err(|e| warn!(target: "diskmap", "Couldn't read disk map: {}", e)))
|
||||
.and_then(|m| {
|
||||
self.cache = m;
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
fn save<F, E>(&self, write: F) where
|
||||
F: Fn(&HashMap<K, V>, &mut fs::File) -> Result<(), E>,
|
||||
E: fmt::Display,
|
||||
{
|
||||
if self.transient { return; }
|
||||
trace!(target: "diskmap", "save {:?}", self.path);
|
||||
let _ = fs::File::create(self.path.clone())
|
||||
.map_err(|e| warn!(target: "diskmap", "Couldn't open disk map for writing: {}", e))
|
||||
.and_then(|mut f| {
|
||||
write(&self.cache, &mut f).map_err(|e| warn!(target: "diskmap", "Couldn't write to disk map: {}", e))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::AddressBook;
|
||||
use std::collections::HashMap;
|
||||
use ethjson::misc::AccountMeta;
|
||||
use tempdir::TempDir;
|
||||
|
||||
#[test]
|
||||
fn should_save_and_reload_address_book() {
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let mut b = AddressBook::new(tempdir.path());
|
||||
b.set_name(1.into(), "One".to_owned());
|
||||
b.set_meta(1.into(), "{1:1}".to_owned());
|
||||
let b = AddressBook::new(tempdir.path());
|
||||
assert_eq!(b.get(), hash_map![1.into() => AccountMeta{name: "One".to_owned(), meta: "{1:1}".to_owned(), uuid: None}]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_remove_address() {
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let mut b = AddressBook::new(tempdir.path());
|
||||
|
||||
b.set_name(1.into(), "One".to_owned());
|
||||
b.set_name(2.into(), "Two".to_owned());
|
||||
b.set_name(3.into(), "Three".to_owned());
|
||||
b.remove(2.into());
|
||||
|
||||
let b = AddressBook::new(tempdir.path());
|
||||
assert_eq!(b.get(), hash_map![
|
||||
1.into() => AccountMeta{name: "One".to_owned(), meta: "{}".to_owned(), uuid: None},
|
||||
3.into() => AccountMeta{name: "Three".to_owned(), meta: "{}".to_owned(), uuid: None}
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering as AtomicOrdering};
|
||||
use std::sync::{Weak, Arc};
|
||||
use std::time::{UNIX_EPOCH, SystemTime, Duration};
|
||||
|
||||
use account_provider::AccountProvider;
|
||||
use block::*;
|
||||
use client::EngineClient;
|
||||
use engines::{Engine, Seal, EngineError, ConstructedVerifier};
|
||||
@@ -37,7 +36,7 @@ use hash::keccak;
|
||||
use super::signer::EngineSigner;
|
||||
use super::validator_set::{ValidatorSet, SimpleList, new_validator_set};
|
||||
use self::finality::RollingFinality;
|
||||
use ethkey::{self, Password, Signature};
|
||||
use ethkey::{self, Signature};
|
||||
use io::{IoContext, IoHandler, TimerToken, IoService};
|
||||
use itertools::{self, Itertools};
|
||||
use rlp::{encode, Decodable, DecoderError, Encodable, RlpStream, Rlp};
|
||||
@@ -412,7 +411,7 @@ pub struct AuthorityRound {
|
||||
transition_service: IoService<()>,
|
||||
step: Arc<PermissionedStep>,
|
||||
client: Arc<RwLock<Option<Weak<EngineClient>>>>,
|
||||
signer: RwLock<EngineSigner>,
|
||||
signer: RwLock<Option<Box<EngineSigner>>>,
|
||||
validators: Box<ValidatorSet>,
|
||||
validate_score_transition: u64,
|
||||
validate_step_transition: u64,
|
||||
@@ -665,7 +664,7 @@ impl AuthorityRound {
|
||||
can_propose: AtomicBool::new(true),
|
||||
}),
|
||||
client: Arc::new(RwLock::new(None)),
|
||||
signer: Default::default(),
|
||||
signer: RwLock::new(None),
|
||||
validators: our_params.validators,
|
||||
validate_score_transition: our_params.validate_score_transition,
|
||||
validate_step_transition: our_params.validate_step_transition,
|
||||
@@ -788,7 +787,7 @@ impl AuthorityRound {
|
||||
return;
|
||||
}
|
||||
|
||||
if let (true, Some(me)) = (current_step > parent_step + 1, self.signer.read().address()) {
|
||||
if let (true, Some(me)) = (current_step > parent_step + 1, self.signer.read().as_ref().map(|s| s.address())) {
|
||||
debug!(target: "engine", "Author {} built block with step gap. current step: {}, parent step: {}",
|
||||
header.author(), current_step, parent_step);
|
||||
let mut reported = HashSet::new();
|
||||
@@ -1492,12 +1491,16 @@ impl Engine<EthereumMachine> for AuthorityRound {
|
||||
self.validators.register_client(client);
|
||||
}
|
||||
|
||||
fn set_signer(&self, ap: Arc<AccountProvider>, address: Address, password: Password) {
|
||||
self.signer.write().set(ap, address, password);
|
||||
fn set_signer(&self, signer: Box<EngineSigner>) {
|
||||
*self.signer.write() = Some(signer);
|
||||
}
|
||||
|
||||
fn sign(&self, hash: H256) -> Result<Signature, Error> {
|
||||
Ok(self.signer.read().sign(hash)?)
|
||||
Ok(self.signer.read()
|
||||
.as_ref()
|
||||
.ok_or(ethkey::Error::InvalidAddress)?
|
||||
.sign(hash)?
|
||||
)
|
||||
}
|
||||
|
||||
fn snapshot_components(&self) -> Option<Box<::snapshot::SnapshotComponents>> {
|
||||
@@ -1532,16 +1535,16 @@ mod tests {
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
|
||||
use hash::keccak;
|
||||
use accounts::AccountProvider;
|
||||
use ethereum_types::{Address, H520, H256, U256};
|
||||
use ethkey::Signature;
|
||||
use types::header::Header;
|
||||
use rlp::encode;
|
||||
use block::*;
|
||||
use test_helpers::{
|
||||
generate_dummy_client_with_spec_and_accounts, get_temp_state_db,
|
||||
generate_dummy_client_with_spec, get_temp_state_db,
|
||||
TestNotify
|
||||
};
|
||||
use account_provider::AccountProvider;
|
||||
use spec::Spec;
|
||||
use types::transaction::{Action, Transaction};
|
||||
use engines::{Seal, Engine, EngineError, EthEngine};
|
||||
@@ -1620,14 +1623,14 @@ mod tests {
|
||||
let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes, addr2, (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
|
||||
let b2 = b2.close_and_lock().unwrap();
|
||||
|
||||
engine.set_signer(tap.clone(), addr1, "1".into());
|
||||
engine.set_signer(Box::new((tap.clone(), addr1, "1".into())));
|
||||
if let Seal::Regular(seal) = engine.generate_seal(b1.block(), &genesis_header) {
|
||||
assert!(b1.clone().try_seal(engine, seal).is_ok());
|
||||
// Second proposal is forbidden.
|
||||
assert!(engine.generate_seal(b1.block(), &genesis_header) == Seal::None);
|
||||
}
|
||||
|
||||
engine.set_signer(tap, addr2, "2".into());
|
||||
engine.set_signer(Box::new((tap, addr2, "2".into())));
|
||||
if let Seal::Regular(seal) = engine.generate_seal(b2.block(), &genesis_header) {
|
||||
assert!(b2.clone().try_seal(engine, seal).is_ok());
|
||||
// Second proposal is forbidden.
|
||||
@@ -1654,13 +1657,13 @@ mod tests {
|
||||
let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes, addr2, (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
|
||||
let b2 = b2.close_and_lock().unwrap();
|
||||
|
||||
engine.set_signer(tap.clone(), addr1, "1".into());
|
||||
engine.set_signer(Box::new((tap.clone(), addr1, "1".into())));
|
||||
match engine.generate_seal(b1.block(), &genesis_header) {
|
||||
Seal::None | Seal::Proposal(_) => panic!("wrong seal"),
|
||||
Seal::Regular(_) => {
|
||||
engine.step();
|
||||
|
||||
engine.set_signer(tap.clone(), addr2, "0".into());
|
||||
engine.set_signer(Box::new((tap.clone(), addr2, "0".into())));
|
||||
match engine.generate_seal(b2.block(), &genesis_header) {
|
||||
Seal::Regular(_) | Seal::Proposal(_) => panic!("sealed despite wrong difficulty"),
|
||||
Seal::None => {}
|
||||
@@ -1768,7 +1771,7 @@ mod tests {
|
||||
assert!(aura.verify_block_family(&header, &parent_header).is_ok());
|
||||
assert_eq!(last_benign.load(AtomicOrdering::SeqCst), 0);
|
||||
|
||||
aura.set_signer(Arc::new(AccountProvider::transient_provider()), Default::default(), "".into());
|
||||
aura.set_signer(Box::new((Arc::new(AccountProvider::transient_provider()), Default::default(), "".into())));
|
||||
|
||||
// Do not report on steps skipped between genesis and first block.
|
||||
header.set_number(1);
|
||||
@@ -1878,12 +1881,12 @@ mod tests {
|
||||
|
||||
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
||||
|
||||
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_test_round_empty_steps, None);
|
||||
let client = generate_dummy_client_with_spec(Spec::new_test_round_empty_steps);
|
||||
let notify = Arc::new(TestNotify::default());
|
||||
client.add_notify(notify.clone());
|
||||
engine.register_client(Arc::downgrade(&client) as _);
|
||||
|
||||
engine.set_signer(tap.clone(), addr1, "1".into());
|
||||
engine.set_signer(Box::new((tap.clone(), addr1, "1".into())));
|
||||
|
||||
let b1 = OpenBlock::new(engine, Default::default(), false, db1, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
|
||||
let b1 = b1.close_and_lock().unwrap();
|
||||
@@ -1917,7 +1920,7 @@ mod tests {
|
||||
|
||||
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
||||
|
||||
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_test_round_empty_steps, None);
|
||||
let client = generate_dummy_client_with_spec(Spec::new_test_round_empty_steps);
|
||||
let notify = Arc::new(TestNotify::default());
|
||||
client.add_notify(notify.clone());
|
||||
engine.register_client(Arc::downgrade(&client) as _);
|
||||
@@ -1927,7 +1930,7 @@ mod tests {
|
||||
let b1 = b1.close_and_lock().unwrap();
|
||||
|
||||
// since the block is empty it isn't sealed and we generate empty steps
|
||||
engine.set_signer(tap.clone(), addr1, "1".into());
|
||||
engine.set_signer(Box::new((tap.clone(), addr1, "1".into())));
|
||||
assert_eq!(engine.generate_seal(b1.block(), &genesis_header), Seal::None);
|
||||
engine.step();
|
||||
|
||||
@@ -1944,9 +1947,9 @@ mod tests {
|
||||
let b2 = b2.close_and_lock().unwrap();
|
||||
|
||||
// we will now seal a block with 1tx and include the accumulated empty step message
|
||||
engine.set_signer(tap.clone(), addr2, "0".into());
|
||||
engine.set_signer(Box::new((tap.clone(), addr2, "0".into())));
|
||||
if let Seal::Regular(seal) = engine.generate_seal(b2.block(), &genesis_header) {
|
||||
engine.set_signer(tap.clone(), addr1, "1".into());
|
||||
engine.set_signer(Box::new((tap.clone(), addr1, "1".into())));
|
||||
let empty_step2 = sealed_empty_step(engine, 2, &genesis_header.hash());
|
||||
let empty_steps = ::rlp::encode_list(&vec![empty_step2]);
|
||||
|
||||
@@ -1970,7 +1973,7 @@ mod tests {
|
||||
|
||||
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
||||
|
||||
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_test_round_empty_steps, None);
|
||||
let client = generate_dummy_client_with_spec(Spec::new_test_round_empty_steps);
|
||||
let notify = Arc::new(TestNotify::default());
|
||||
client.add_notify(notify.clone());
|
||||
engine.register_client(Arc::downgrade(&client) as _);
|
||||
@@ -1980,14 +1983,14 @@ mod tests {
|
||||
let b1 = b1.close_and_lock().unwrap();
|
||||
|
||||
// since the block is empty it isn't sealed and we generate empty steps
|
||||
engine.set_signer(tap.clone(), addr1, "1".into());
|
||||
engine.set_signer(Box::new((tap.clone(), addr1, "1".into())));
|
||||
assert_eq!(engine.generate_seal(b1.block(), &genesis_header), Seal::None);
|
||||
engine.step();
|
||||
|
||||
// step 3
|
||||
let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes.clone(), addr2, (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
|
||||
let b2 = b2.close_and_lock().unwrap();
|
||||
engine.set_signer(tap.clone(), addr2, "0".into());
|
||||
engine.set_signer(Box::new((tap.clone(), addr2, "0".into())));
|
||||
assert_eq!(engine.generate_seal(b2.block(), &genesis_header), Seal::None);
|
||||
engine.step();
|
||||
|
||||
@@ -1996,10 +1999,10 @@ mod tests {
|
||||
let b3 = OpenBlock::new(engine, Default::default(), false, db3, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
|
||||
let b3 = b3.close_and_lock().unwrap();
|
||||
|
||||
engine.set_signer(tap.clone(), addr1, "1".into());
|
||||
engine.set_signer(Box::new((tap.clone(), addr1, "1".into())));
|
||||
if let Seal::Regular(seal) = engine.generate_seal(b3.block(), &genesis_header) {
|
||||
let empty_step2 = sealed_empty_step(engine, 2, &genesis_header.hash());
|
||||
engine.set_signer(tap.clone(), addr2, "0".into());
|
||||
engine.set_signer(Box::new((tap.clone(), addr2, "0".into())));
|
||||
let empty_step3 = sealed_empty_step(engine, 3, &genesis_header.hash());
|
||||
|
||||
let empty_steps = ::rlp::encode_list(&vec![empty_step2, empty_step3]);
|
||||
@@ -2022,7 +2025,7 @@ mod tests {
|
||||
|
||||
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
||||
|
||||
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_test_round_empty_steps, None);
|
||||
let client = generate_dummy_client_with_spec(Spec::new_test_round_empty_steps);
|
||||
engine.register_client(Arc::downgrade(&client) as _);
|
||||
|
||||
// step 2
|
||||
@@ -2030,7 +2033,7 @@ mod tests {
|
||||
let b1 = b1.close_and_lock().unwrap();
|
||||
|
||||
// since the block is empty it isn't sealed and we generate empty steps
|
||||
engine.set_signer(tap.clone(), addr1, "1".into());
|
||||
engine.set_signer(Box::new((tap.clone(), addr1, "1".into())));
|
||||
assert_eq!(engine.generate_seal(b1.block(), &genesis_header), Seal::None);
|
||||
engine.step();
|
||||
|
||||
@@ -2084,7 +2087,7 @@ mod tests {
|
||||
);
|
||||
|
||||
// empty step with valid signature from incorrect proposer for step
|
||||
engine.set_signer(tap.clone(), addr1, "1".into());
|
||||
engine.set_signer(Box::new((tap.clone(), addr1, "1".into())));
|
||||
let empty_steps = vec![sealed_empty_step(engine, 1, &parent_header.hash())];
|
||||
set_empty_steps_seal(&mut header, 2, &signature, &empty_steps);
|
||||
|
||||
@@ -2094,9 +2097,9 @@ mod tests {
|
||||
);
|
||||
|
||||
// valid empty steps
|
||||
engine.set_signer(tap.clone(), addr1, "1".into());
|
||||
engine.set_signer(Box::new((tap.clone(), addr1, "1".into())));
|
||||
let empty_step2 = sealed_empty_step(engine, 2, &parent_header.hash());
|
||||
engine.set_signer(tap.clone(), addr2, "0".into());
|
||||
engine.set_signer(Box::new((tap.clone(), addr2, "0".into())));
|
||||
let empty_step3 = sealed_empty_step(engine, 3, &parent_header.hash());
|
||||
|
||||
let empty_steps = vec![empty_step2, empty_step3];
|
||||
@@ -2121,10 +2124,7 @@ mod tests {
|
||||
|
||||
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
||||
|
||||
let client = generate_dummy_client_with_spec_and_accounts(
|
||||
Spec::new_test_round_block_reward_contract,
|
||||
None,
|
||||
);
|
||||
let client = generate_dummy_client_with_spec(Spec::new_test_round_block_reward_contract);
|
||||
engine.register_client(Arc::downgrade(&client) as _);
|
||||
|
||||
// step 2
|
||||
@@ -2144,7 +2144,7 @@ mod tests {
|
||||
let b1 = b1.close_and_lock().unwrap();
|
||||
|
||||
// since the block is empty it isn't sealed and we generate empty steps
|
||||
engine.set_signer(tap.clone(), addr1, "1".into());
|
||||
engine.set_signer(Box::new((tap.clone(), addr1, "1".into())));
|
||||
assert_eq!(engine.generate_seal(b1.block(), &genesis_header), Seal::None);
|
||||
engine.step();
|
||||
|
||||
@@ -2182,7 +2182,7 @@ mod tests {
|
||||
let engine = &*spec.engine;
|
||||
|
||||
let addr1 = accounts[0];
|
||||
engine.set_signer(tap.clone(), addr1, "1".into());
|
||||
engine.set_signer(Box::new((tap.clone(), addr1, "1".into())));
|
||||
|
||||
let mut header: Header = Header::default();
|
||||
let empty_step = empty_step(engine, 1, &header.parent_hash());
|
||||
@@ -2263,7 +2263,7 @@ mod tests {
|
||||
header.set_author(accounts[0]);
|
||||
|
||||
// when
|
||||
engine.set_signer(tap.clone(), accounts[1], "0".into());
|
||||
engine.set_signer(Box::new((tap.clone(), accounts[1], "0".into())));
|
||||
let empty_steps = vec![
|
||||
sealed_empty_step(&*engine, 1, &parent.hash()),
|
||||
sealed_empty_step(&*engine, 1, &parent.hash()),
|
||||
@@ -2300,9 +2300,9 @@ mod tests {
|
||||
header.set_author(accounts[0]);
|
||||
|
||||
// when
|
||||
engine.set_signer(tap.clone(), accounts[1], "0".into());
|
||||
engine.set_signer(Box::new((tap.clone(), accounts[1], "0".into())));
|
||||
let es1 = sealed_empty_step(&*engine, 1, &parent.hash());
|
||||
engine.set_signer(tap.clone(), accounts[0], "1".into());
|
||||
engine.set_signer(Box::new((tap.clone(), accounts[0], "1".into())));
|
||||
let es2 = sealed_empty_step(&*engine, 2, &parent.hash());
|
||||
|
||||
let mut empty_steps = vec![es2, es1];
|
||||
|
||||
@@ -16,19 +16,18 @@
|
||||
|
||||
//! A blockchain engine that supports a basic, non-BFT proof-of-authority.
|
||||
|
||||
use std::sync::{Weak, Arc};
|
||||
use ethereum_types::{H256, H520, Address};
|
||||
use std::sync::Weak;
|
||||
use ethereum_types::{H256, H520};
|
||||
use parking_lot::RwLock;
|
||||
use ethkey::{self, Password, Signature};
|
||||
use account_provider::AccountProvider;
|
||||
use ethkey::{self, Signature};
|
||||
use block::*;
|
||||
use engines::{Engine, Seal, ConstructedVerifier, EngineError};
|
||||
use engines::signer::EngineSigner;
|
||||
use error::{BlockError, Error};
|
||||
use ethjson;
|
||||
use client::EngineClient;
|
||||
use machine::{AuxiliaryData, Call, EthereumMachine};
|
||||
use types::header::{Header, ExtendedHeader};
|
||||
use super::signer::EngineSigner;
|
||||
use super::validator_set::{ValidatorSet, SimpleList, new_validator_set};
|
||||
|
||||
/// `BasicAuthority` params.
|
||||
@@ -76,7 +75,7 @@ fn verify_external(header: &Header, validators: &ValidatorSet) -> Result<(), Err
|
||||
/// Engine using `BasicAuthority`, trivial proof-of-authority consensus.
|
||||
pub struct BasicAuthority {
|
||||
machine: EthereumMachine,
|
||||
signer: RwLock<EngineSigner>,
|
||||
signer: RwLock<Option<Box<EngineSigner>>>,
|
||||
validators: Box<ValidatorSet>,
|
||||
}
|
||||
|
||||
@@ -85,7 +84,7 @@ impl BasicAuthority {
|
||||
pub fn new(our_params: BasicAuthorityParams, machine: EthereumMachine) -> Self {
|
||||
BasicAuthority {
|
||||
machine: machine,
|
||||
signer: Default::default(),
|
||||
signer: RwLock::new(None),
|
||||
validators: new_validator_set(our_params.validators),
|
||||
}
|
||||
}
|
||||
@@ -190,12 +189,16 @@ impl Engine<EthereumMachine> for BasicAuthority {
|
||||
self.validators.register_client(client);
|
||||
}
|
||||
|
||||
fn set_signer(&self, ap: Arc<AccountProvider>, address: Address, password: Password) {
|
||||
self.signer.write().set(ap, address, password);
|
||||
fn set_signer(&self, signer: Box<EngineSigner>) {
|
||||
*self.signer.write() = Some(signer);
|
||||
}
|
||||
|
||||
fn sign(&self, hash: H256) -> Result<Signature, Error> {
|
||||
Ok(self.signer.read().sign(hash)?)
|
||||
Ok(self.signer.read()
|
||||
.as_ref()
|
||||
.ok_or_else(|| ethkey::Error::InvalidAddress)?
|
||||
.sign(hash)?
|
||||
)
|
||||
}
|
||||
|
||||
fn snapshot_components(&self) -> Option<Box<::snapshot::SnapshotComponents>> {
|
||||
@@ -214,7 +217,7 @@ mod tests {
|
||||
use ethereum_types::H520;
|
||||
use block::*;
|
||||
use test_helpers::get_temp_state_db;
|
||||
use account_provider::AccountProvider;
|
||||
use accounts::AccountProvider;
|
||||
use types::header::Header;
|
||||
use spec::Spec;
|
||||
use engines::Seal;
|
||||
@@ -257,7 +260,7 @@ mod tests {
|
||||
|
||||
let spec = new_test_authority();
|
||||
let engine = &*spec.engine;
|
||||
engine.set_signer(Arc::new(tap), addr, "".into());
|
||||
engine.set_signer(Box::new((Arc::new(tap), addr, "".into())));
|
||||
let genesis_header = spec.genesis_header();
|
||||
let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
|
||||
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
||||
@@ -275,7 +278,7 @@ mod tests {
|
||||
|
||||
let engine = new_test_authority().engine;
|
||||
assert!(!engine.seals_internally().unwrap());
|
||||
engine.set_signer(Arc::new(tap), authority, "".into());
|
||||
engine.set_signer(Box::new((Arc::new(tap), authority, "".into())));
|
||||
assert!(engine.seals_internally().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,17 +170,14 @@ mod test {
|
||||
use client::PrepareOpenBlock;
|
||||
use ethereum_types::U256;
|
||||
use spec::Spec;
|
||||
use test_helpers::generate_dummy_client_with_spec_and_accounts;
|
||||
use test_helpers::generate_dummy_client_with_spec;
|
||||
|
||||
use engines::SystemOrCodeCallKind;
|
||||
use super::{BlockRewardContract, RewardKind};
|
||||
|
||||
#[test]
|
||||
fn block_reward_contract() {
|
||||
let client = generate_dummy_client_with_spec_and_accounts(
|
||||
Spec::new_test_round_block_reward_contract,
|
||||
None,
|
||||
);
|
||||
let client = generate_dummy_client_with_spec(Spec::new_test_round_block_reward_contract);
|
||||
|
||||
let machine = Spec::new_test_machine();
|
||||
|
||||
|
||||
@@ -20,16 +20,17 @@ mod authority_round;
|
||||
mod basic_authority;
|
||||
mod instant_seal;
|
||||
mod null_engine;
|
||||
mod signer;
|
||||
mod validator_set;
|
||||
|
||||
pub mod block_reward;
|
||||
pub mod signer;
|
||||
|
||||
pub use self::authority_round::AuthorityRound;
|
||||
pub use self::basic_authority::BasicAuthority;
|
||||
pub use self::epoch::{EpochVerifier, Transition as EpochTransition};
|
||||
pub use self::instant_seal::{InstantSeal, InstantSealParams};
|
||||
pub use self::null_engine::NullEngine;
|
||||
pub use self::signer::EngineSigner;
|
||||
|
||||
// TODO [ToDr] Remove re-export (#10130)
|
||||
pub use types::engines::ForkChoice;
|
||||
@@ -39,7 +40,6 @@ use std::sync::{Weak, Arc};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::{fmt, error};
|
||||
|
||||
use account_provider::AccountProvider;
|
||||
use builtin::Builtin;
|
||||
use vm::{EnvInfo, Schedule, CreateContractAddress, CallType, ActionValue};
|
||||
use error::Error;
|
||||
@@ -49,7 +49,7 @@ use snapshot::SnapshotComponents;
|
||||
use spec::CommonParams;
|
||||
use types::transaction::{self, UnverifiedTransaction, SignedTransaction};
|
||||
|
||||
use ethkey::{Password, Signature};
|
||||
use ethkey::{Signature};
|
||||
use parity_machine::{Machine, LocalizedMachine as Localized, TotalScoredHeader};
|
||||
use ethereum_types::{H256, U256, Address};
|
||||
use unexpected::{Mismatch, OutOfBounds};
|
||||
@@ -380,8 +380,8 @@ pub trait Engine<M: Machine>: Sync + Send {
|
||||
/// Takes a header of a fully verified block.
|
||||
fn is_proposal(&self, _verified_header: &M::Header) -> bool { false }
|
||||
|
||||
/// Register an account which signs consensus messages.
|
||||
fn set_signer(&self, _account_provider: Arc<AccountProvider>, _address: Address, _password: Password) {}
|
||||
/// Register a component which signs consensus messages.
|
||||
fn set_signer(&self, _signer: Box<EngineSigner>) {}
|
||||
|
||||
/// Sign using the EngineSigner, to be used for consensus tx signing.
|
||||
fn sign(&self, _hash: H256) -> Result<Signature, M::Error> { unimplemented!() }
|
||||
|
||||
@@ -16,49 +16,68 @@
|
||||
|
||||
//! A signer used by Engines which need to sign messages.
|
||||
|
||||
use std::sync::Arc;
|
||||
use ethereum_types::{H256, Address};
|
||||
use ethkey::{Password, Signature};
|
||||
use account_provider::{self, AccountProvider};
|
||||
use ethkey::{self, Signature};
|
||||
|
||||
/// Everything that an Engine needs to sign messages.
|
||||
pub struct EngineSigner {
|
||||
account_provider: Arc<AccountProvider>,
|
||||
address: Option<Address>,
|
||||
password: Option<Password>,
|
||||
pub trait EngineSigner: Send + Sync {
|
||||
/// Sign a consensus message hash.
|
||||
fn sign(&self, hash: H256) -> Result<Signature, ethkey::Error>;
|
||||
|
||||
/// Signing address
|
||||
fn address(&self) -> Address;
|
||||
}
|
||||
|
||||
impl Default for EngineSigner {
|
||||
fn default() -> Self {
|
||||
EngineSigner {
|
||||
account_provider: Arc::new(AccountProvider::transient_provider()),
|
||||
address: Default::default(),
|
||||
password: Default::default(),
|
||||
/// Creates a new `EngineSigner` from given key pair.
|
||||
pub fn from_keypair(keypair: ethkey::KeyPair) -> Box<EngineSigner> {
|
||||
Box::new(Signer(keypair))
|
||||
}
|
||||
|
||||
struct Signer(ethkey::KeyPair);
|
||||
|
||||
impl EngineSigner for Signer {
|
||||
fn sign(&self, hash: H256) -> Result<Signature, ethkey::Error> {
|
||||
ethkey::sign(self.0.secret(), &hash)
|
||||
}
|
||||
|
||||
fn address(&self) -> Address {
|
||||
self.0.address()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_signer {
|
||||
use std::sync::Arc;
|
||||
|
||||
use ethkey::Password;
|
||||
use accounts::{self, AccountProvider, SignError};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl EngineSigner for (Arc<AccountProvider>, Address, Password) {
|
||||
fn sign(&self, hash: H256) -> Result<Signature, ethkey::Error> {
|
||||
match self.0.sign(self.1, Some(self.2.clone()), hash) {
|
||||
Err(SignError::NotUnlocked) => unreachable!(),
|
||||
Err(SignError::NotFound) => Err(ethkey::Error::InvalidAddress),
|
||||
Err(SignError::Hardware(err)) => {
|
||||
warn!("Error using hardware wallet for engine: {:?}", err);
|
||||
Err(ethkey::Error::InvalidSecret)
|
||||
},
|
||||
Err(SignError::SStore(accounts::Error::EthKey(err))) => Err(err),
|
||||
Err(SignError::SStore(accounts::Error::EthKeyCrypto(err))) => {
|
||||
warn!("Low level crypto error: {:?}", err);
|
||||
Err(ethkey::Error::InvalidSecret)
|
||||
},
|
||||
Err(SignError::SStore(err)) => {
|
||||
warn!("Error signing for engine: {:?}", err);
|
||||
Err(ethkey::Error::InvalidSignature)
|
||||
},
|
||||
Ok(ok) => Ok(ok),
|
||||
}
|
||||
}
|
||||
|
||||
fn address(&self) -> Address {
|
||||
self.1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EngineSigner {
|
||||
/// Set up the signer to sign with given address and password.
|
||||
pub fn set(&mut self, ap: Arc<AccountProvider>, address: Address, password: Password) {
|
||||
self.account_provider = ap;
|
||||
self.address = Some(address);
|
||||
self.password = Some(password);
|
||||
debug!(target: "poa", "Setting Engine signer to {}", address);
|
||||
}
|
||||
|
||||
/// Sign a consensus message hash.
|
||||
pub fn sign(&self, hash: H256) -> Result<Signature, account_provider::SignError> {
|
||||
self.account_provider.sign(self.address.unwrap_or_else(Default::default), self.password.clone(), hash)
|
||||
}
|
||||
|
||||
/// Signing address.
|
||||
pub fn address(&self) -> Option<Address> {
|
||||
self.address.clone()
|
||||
}
|
||||
|
||||
/// Check if the signing address was set.
|
||||
pub fn is_some(&self) -> bool {
|
||||
self.address.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,10 +141,10 @@ mod tests {
|
||||
use rlp::encode;
|
||||
use spec::Spec;
|
||||
use types::header::Header;
|
||||
use account_provider::AccountProvider;
|
||||
use miner::MinerService;
|
||||
use accounts::AccountProvider;
|
||||
use miner::{self, MinerService};
|
||||
use types::ids::BlockId;
|
||||
use test_helpers::generate_dummy_client_with_spec_and_accounts;
|
||||
use test_helpers::generate_dummy_client_with_spec;
|
||||
use call_contract::CallContract;
|
||||
use client::{BlockChainClient, ChainInfo, BlockInfo};
|
||||
use super::super::ValidatorSet;
|
||||
@@ -152,7 +152,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn fetches_validators() {
|
||||
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_contract, None);
|
||||
let client = generate_dummy_client_with_spec(Spec::new_validator_contract);
|
||||
let vc = Arc::new(ValidatorContract::new("0000000000000000000000000000000000000005".parse::<Address>().unwrap()));
|
||||
vc.register_client(Arc::downgrade(&client) as _);
|
||||
let last_hash = client.best_block_header().hash();
|
||||
@@ -164,13 +164,14 @@ mod tests {
|
||||
fn reports_validators() {
|
||||
let tap = Arc::new(AccountProvider::transient_provider());
|
||||
let v1 = tap.insert_account(keccak("1").into(), &"".into()).unwrap();
|
||||
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_contract, Some(tap.clone()));
|
||||
let client = generate_dummy_client_with_spec(Spec::new_validator_contract);
|
||||
client.engine().register_client(Arc::downgrade(&client) as _);
|
||||
let validator_contract = "0000000000000000000000000000000000000005".parse::<Address>().unwrap();
|
||||
|
||||
// Make sure reporting can be done.
|
||||
client.miner().set_gas_range_target((1_000_000.into(), 1_000_000.into()));
|
||||
client.miner().set_author(v1, Some("".into())).unwrap();
|
||||
let signer = Box::new((tap.clone(), v1, "".into()));
|
||||
client.miner().set_author(miner::Author::Sealer(signer));
|
||||
|
||||
// Check a block that is a bit in future, reject it but don't report the validator.
|
||||
let mut header = Header::default();
|
||||
|
||||
@@ -150,15 +150,15 @@ mod tests {
|
||||
use std::sync::Arc;
|
||||
use std::collections::BTreeMap;
|
||||
use hash::keccak;
|
||||
use account_provider::AccountProvider;
|
||||
use accounts::AccountProvider;
|
||||
use client::{BlockChainClient, ChainInfo, BlockInfo, ImportBlock};
|
||||
use engines::EpochChange;
|
||||
use engines::validator_set::ValidatorSet;
|
||||
use ethkey::Secret;
|
||||
use types::header::Header;
|
||||
use miner::MinerService;
|
||||
use miner::{self, MinerService};
|
||||
use spec::Spec;
|
||||
use test_helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data};
|
||||
use test_helpers::{generate_dummy_client_with_spec, generate_dummy_client_with_spec_and_data};
|
||||
use types::ids::BlockId;
|
||||
use ethereum_types::Address;
|
||||
use verification::queue::kind::blocks::Unverified;
|
||||
@@ -171,26 +171,29 @@ mod tests {
|
||||
let s0: Secret = keccak("0").into();
|
||||
let v0 = tap.insert_account(s0.clone(), &"".into()).unwrap();
|
||||
let v1 = tap.insert_account(keccak("1").into(), &"".into()).unwrap();
|
||||
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_multi, Some(tap));
|
||||
let client = generate_dummy_client_with_spec(Spec::new_validator_multi);
|
||||
client.engine().register_client(Arc::downgrade(&client) as _);
|
||||
|
||||
// Make sure txs go through.
|
||||
client.miner().set_gas_range_target((1_000_000.into(), 1_000_000.into()));
|
||||
|
||||
// Wrong signer for the first block.
|
||||
client.miner().set_author(v1, Some("".into())).unwrap();
|
||||
let signer = Box::new((tap.clone(), v1, "".into()));
|
||||
client.miner().set_author(miner::Author::Sealer(signer));
|
||||
client.transact_contract(Default::default(), Default::default()).unwrap();
|
||||
::client::EngineClient::update_sealing(&*client);
|
||||
assert_eq!(client.chain_info().best_block_number, 0);
|
||||
// Right signer for the first block.
|
||||
client.miner().set_author(v0, Some("".into())).unwrap();
|
||||
let signer = Box::new((tap.clone(), v0, "".into()));
|
||||
client.miner().set_author(miner::Author::Sealer(signer));
|
||||
::client::EngineClient::update_sealing(&*client);
|
||||
assert_eq!(client.chain_info().best_block_number, 1);
|
||||
// This time v0 is wrong.
|
||||
client.transact_contract(Default::default(), Default::default()).unwrap();
|
||||
::client::EngineClient::update_sealing(&*client);
|
||||
assert_eq!(client.chain_info().best_block_number, 1);
|
||||
client.miner().set_author(v1, Some("".into())).unwrap();
|
||||
let signer = Box::new((tap.clone(), v1, "".into()));
|
||||
client.miner().set_author(miner::Author::Sealer(signer));
|
||||
::client::EngineClient::update_sealing(&*client);
|
||||
assert_eq!(client.chain_info().best_block_number, 2);
|
||||
// v1 is still good.
|
||||
|
||||
@@ -445,19 +445,19 @@ mod tests {
|
||||
use ethereum_types::Address;
|
||||
use types::ids::BlockId;
|
||||
use spec::Spec;
|
||||
use account_provider::AccountProvider;
|
||||
use accounts::AccountProvider;
|
||||
use types::transaction::{Transaction, Action};
|
||||
use client::{ChainInfo, BlockInfo, ImportBlock};
|
||||
use ethkey::Secret;
|
||||
use miner::MinerService;
|
||||
use test_helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data};
|
||||
use miner::{self, MinerService};
|
||||
use test_helpers::{generate_dummy_client_with_spec, generate_dummy_client_with_spec_and_data};
|
||||
use super::super::ValidatorSet;
|
||||
use super::{ValidatorSafeContract, EVENT_NAME_HASH};
|
||||
use verification::queue::kind::blocks::Unverified;
|
||||
|
||||
#[test]
|
||||
fn fetches_validators() {
|
||||
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, None);
|
||||
let client = generate_dummy_client_with_spec(Spec::new_validator_safe_contract);
|
||||
let vc = Arc::new(ValidatorSafeContract::new("0000000000000000000000000000000000000005".parse::<Address>().unwrap()));
|
||||
vc.register_client(Arc::downgrade(&client) as _);
|
||||
let last_hash = client.best_block_header().hash();
|
||||
@@ -472,11 +472,12 @@ mod tests {
|
||||
let v0 = tap.insert_account(s0.clone(), &"".into()).unwrap();
|
||||
let v1 = tap.insert_account(keccak("0").into(), &"".into()).unwrap();
|
||||
let chain_id = Spec::new_validator_safe_contract().chain_id();
|
||||
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, Some(tap));
|
||||
let client = generate_dummy_client_with_spec(Spec::new_validator_safe_contract);
|
||||
client.engine().register_client(Arc::downgrade(&client) as _);
|
||||
let validator_contract = "0000000000000000000000000000000000000005".parse::<Address>().unwrap();
|
||||
let signer = Box::new((tap.clone(), v1, "".into()));
|
||||
|
||||
client.miner().set_author(v1, Some("".into())).unwrap();
|
||||
client.miner().set_author(miner::Author::Sealer(signer));
|
||||
// Remove "1" validator.
|
||||
let tx = Transaction {
|
||||
nonce: 0.into(),
|
||||
@@ -504,11 +505,13 @@ mod tests {
|
||||
assert_eq!(client.chain_info().best_block_number, 1);
|
||||
|
||||
// Switch to the validator that is still there.
|
||||
client.miner().set_author(v0, Some("".into())).unwrap();
|
||||
let signer = Box::new((tap.clone(), v0, "".into()));
|
||||
client.miner().set_author(miner::Author::Sealer(signer));
|
||||
::client::EngineClient::update_sealing(&*client);
|
||||
assert_eq!(client.chain_info().best_block_number, 2);
|
||||
// Switch back to the added validator, since the state is updated.
|
||||
client.miner().set_author(v1, Some("".into())).unwrap();
|
||||
let signer = Box::new((tap.clone(), v1, "".into()));
|
||||
client.miner().set_author(miner::Author::Sealer(signer));
|
||||
let tx = Transaction {
|
||||
nonce: 2.into(),
|
||||
gas_price: 0.into(),
|
||||
@@ -539,7 +542,7 @@ mod tests {
|
||||
use types::header::Header;
|
||||
use types::log_entry::LogEntry;
|
||||
|
||||
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, None);
|
||||
let client = generate_dummy_client_with_spec(Spec::new_validator_safe_contract);
|
||||
let engine = client.engine().clone();
|
||||
let validator_contract = "0000000000000000000000000000000000000005".parse::<Address>().unwrap();
|
||||
|
||||
@@ -576,7 +579,7 @@ mod tests {
|
||||
use types::header::Header;
|
||||
use engines::{EpochChange, Proof};
|
||||
|
||||
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, None);
|
||||
let client = generate_dummy_client_with_spec(Spec::new_validator_safe_contract);
|
||||
let engine = client.engine().clone();
|
||||
|
||||
let mut new_header = Header::default();
|
||||
|
||||
@@ -25,11 +25,10 @@ use ethtrie::TrieError;
|
||||
use rlp;
|
||||
use snappy::InvalidInput;
|
||||
use snapshot::Error as SnapshotError;
|
||||
use types::transaction::Error as TransactionError;
|
||||
use types::BlockNumber;
|
||||
use types::transaction::Error as TransactionError;
|
||||
use unexpected::{Mismatch, OutOfBounds};
|
||||
|
||||
use account_provider::SignError as AccountsError;
|
||||
use engines::EngineError;
|
||||
|
||||
pub use executed::{ExecutionError, CallError};
|
||||
@@ -244,12 +243,6 @@ error_chain! {
|
||||
display("Snapshot error {}", err)
|
||||
}
|
||||
|
||||
#[doc = "Account Provider error"]
|
||||
AccountProvider(err: AccountsError) {
|
||||
description("Accounts Provider error")
|
||||
display("Accounts Provider error {}", err)
|
||||
}
|
||||
|
||||
#[doc = "PoW hash is invalid or out of date."]
|
||||
PowHashInvalid {
|
||||
description("PoW hash is invalid or out of date.")
|
||||
@@ -270,12 +263,6 @@ error_chain! {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AccountsError> for Error {
|
||||
fn from(err: AccountsError) -> Error {
|
||||
ErrorKind::AccountProvider(err).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SnapshotError> for Error {
|
||||
fn from(err: SnapshotError) -> Error {
|
||||
match err {
|
||||
|
||||
@@ -73,7 +73,6 @@ extern crate ethcore_miner;
|
||||
extern crate ethereum_types;
|
||||
extern crate ethjson;
|
||||
extern crate ethkey;
|
||||
extern crate ethstore;
|
||||
extern crate hashdb;
|
||||
extern crate heapsize;
|
||||
extern crate itertools;
|
||||
@@ -107,6 +106,8 @@ extern crate using_queue;
|
||||
extern crate vm;
|
||||
extern crate wasm;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate ethcore_accounts as accounts;
|
||||
#[cfg(feature = "stratum")]
|
||||
extern crate ethcore_stratum;
|
||||
#[cfg(any(test, feature = "tempdir"))]
|
||||
@@ -115,12 +116,10 @@ extern crate tempdir;
|
||||
extern crate kvdb_rocksdb;
|
||||
#[cfg(any(test, feature = "blooms-db"))]
|
||||
extern crate blooms_db;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
|
||||
extern crate hardware_wallet;
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
|
||||
extern crate fake_hardware_wallet as hardware_wallet;
|
||||
#[cfg(any(test, feature = "env_logger"))]
|
||||
extern crate env_logger;
|
||||
#[cfg(test)]
|
||||
extern crate rlp_compress;
|
||||
|
||||
#[macro_use]
|
||||
extern crate ethabi_derive;
|
||||
@@ -144,12 +143,6 @@ extern crate serde_derive;
|
||||
#[cfg_attr(test, macro_use)]
|
||||
extern crate evm;
|
||||
|
||||
#[cfg(any(test, feature = "env_logger"))]
|
||||
extern crate env_logger;
|
||||
#[cfg(test)]
|
||||
extern crate rlp_compress;
|
||||
|
||||
pub mod account_provider;
|
||||
pub mod block;
|
||||
pub mod builtin;
|
||||
pub mod client;
|
||||
|
||||
@@ -23,11 +23,11 @@ use ansi_term::Colour;
|
||||
use bytes::Bytes;
|
||||
use call_contract::CallContract;
|
||||
use ethcore_miner::gas_pricer::GasPricer;
|
||||
use ethcore_miner::local_accounts::LocalAccounts;
|
||||
use ethcore_miner::pool::{self, TransactionQueue, VerifiedTransaction, QueueStatus, PrioritizationStrategy};
|
||||
#[cfg(feature = "work-notify")]
|
||||
use ethcore_miner::work_notify::NotifyWork;
|
||||
use ethereum_types::{H256, U256, Address};
|
||||
use ethkey::Password;
|
||||
use io::IoChannel;
|
||||
use miner::pool_client::{PoolClient, CachedNonceClient, NonceCache};
|
||||
use miner;
|
||||
@@ -46,13 +46,12 @@ use types::header::Header;
|
||||
use types::receipt::RichReceipt;
|
||||
use using_queue::{UsingQueue, GetAction};
|
||||
|
||||
use account_provider::{AccountProvider, SignError as AccountError};
|
||||
use block::{ClosedBlock, IsBlock, SealedBlock};
|
||||
use client::{
|
||||
BlockChain, ChainInfo, BlockProducer, SealedBlockImporter, Nonce, TransactionInfo, TransactionId
|
||||
};
|
||||
use client::{BlockId, ClientIoMessage};
|
||||
use engines::{EthEngine, Seal};
|
||||
use engines::{EthEngine, Seal, EngineSigner};
|
||||
use error::{Error, ErrorKind};
|
||||
use executed::ExecutionError;
|
||||
use executive::contract_address;
|
||||
@@ -140,8 +139,6 @@ pub struct MinerOptions {
|
||||
/// will be invalid if mined.
|
||||
pub infinite_pending_block: bool,
|
||||
|
||||
/// Prioritized Local Addresses
|
||||
pub tx_queue_locals: HashSet<Address>,
|
||||
/// Strategy to use for prioritizing transactions in the queue.
|
||||
pub tx_queue_strategy: PrioritizationStrategy,
|
||||
/// Simple senders penalization.
|
||||
@@ -169,7 +166,6 @@ impl Default for MinerOptions {
|
||||
work_queue_size: 20,
|
||||
enable_resubmission: true,
|
||||
infinite_pending_block: false,
|
||||
tx_queue_locals: HashSet::new(),
|
||||
tx_queue_strategy: PrioritizationStrategy::GasPriceOnly,
|
||||
tx_queue_penalization: Penalization::Disabled,
|
||||
tx_queue_no_unfamiliar_locals: false,
|
||||
@@ -200,6 +196,25 @@ pub struct AuthoringParams {
|
||||
pub extra_data: Bytes,
|
||||
}
|
||||
|
||||
/// Block sealing mechanism
|
||||
pub enum Author {
|
||||
/// Sealing block is external and we only need a reward beneficiary (i.e. PoW)
|
||||
External(Address),
|
||||
/// Sealing is done internally, we need a way to create signatures to seal block (i.e. PoA)
|
||||
Sealer(Box<EngineSigner>),
|
||||
}
|
||||
|
||||
impl Author {
|
||||
/// Get author's address.
|
||||
pub fn address(&self) -> Address {
|
||||
match *self {
|
||||
Author::External(address) => address,
|
||||
Author::Sealer(ref sealer) => sealer.address(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct SealingWork {
|
||||
queue: UsingQueue<ClosedBlock>,
|
||||
enabled: bool,
|
||||
@@ -230,7 +245,7 @@ pub struct Miner {
|
||||
// TODO [ToDr] Arc is only required because of price updater
|
||||
transaction_queue: Arc<TransactionQueue>,
|
||||
engine: Arc<EthEngine>,
|
||||
accounts: Option<Arc<AccountProvider>>,
|
||||
accounts: Arc<LocalAccounts>,
|
||||
io_channel: RwLock<Option<IoChannel<ClientIoMessage>>>,
|
||||
}
|
||||
|
||||
@@ -248,11 +263,11 @@ impl Miner {
|
||||
}
|
||||
|
||||
/// Creates new instance of miner Arc.
|
||||
pub fn new(
|
||||
pub fn new<A: LocalAccounts + 'static>(
|
||||
options: MinerOptions,
|
||||
gas_pricer: GasPricer,
|
||||
spec: &Spec,
|
||||
accounts: Option<Arc<AccountProvider>>,
|
||||
accounts: A,
|
||||
) -> Self {
|
||||
let limits = options.pool_limits.clone();
|
||||
let verifier_options = options.pool_verification_options.clone();
|
||||
@@ -275,7 +290,7 @@ impl Miner {
|
||||
nonce_cache: NonceCache::new(nonce_cache_size),
|
||||
options,
|
||||
transaction_queue: Arc::new(TransactionQueue::new(limits, verifier_options, tx_queue_strategy)),
|
||||
accounts,
|
||||
accounts: Arc::new(accounts),
|
||||
engine: spec.engine.clone(),
|
||||
io_channel: RwLock::new(None),
|
||||
}
|
||||
@@ -284,7 +299,7 @@ impl Miner {
|
||||
/// Creates new instance of miner with given spec and accounts.
|
||||
///
|
||||
/// NOTE This should be only used for tests.
|
||||
pub fn new_for_tests(spec: &Spec, accounts: Option<Arc<AccountProvider>>) -> Miner {
|
||||
pub fn new_for_tests(spec: &Spec, accounts: Option<HashSet<Address>>) -> Miner {
|
||||
let minimal_gas_price = 0.into();
|
||||
Miner::new(MinerOptions {
|
||||
pool_verification_options: pool::verifier::Options {
|
||||
@@ -295,7 +310,7 @@ impl Miner {
|
||||
},
|
||||
reseal_min_period: Duration::from_secs(0),
|
||||
..Default::default()
|
||||
}, GasPricer::new_fixed(minimal_gas_price), spec, accounts)
|
||||
}, GasPricer::new_fixed(minimal_gas_price), spec, accounts.unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Sets `IoChannel`
|
||||
@@ -362,7 +377,7 @@ impl Miner {
|
||||
chain,
|
||||
&self.nonce_cache,
|
||||
&*self.engine,
|
||||
self.accounts.as_ref().map(|x| &**x),
|
||||
&*self.accounts,
|
||||
self.options.refuse_service_transactions,
|
||||
)
|
||||
}
|
||||
@@ -830,14 +845,11 @@ impl miner::MinerService for Miner {
|
||||
self.params.write().extra_data = extra_data;
|
||||
}
|
||||
|
||||
fn set_author(&self, address: Address, password: Option<Password>) -> Result<(), AccountError> {
|
||||
self.params.write().author = address;
|
||||
fn set_author(&self, author: Author) {
|
||||
self.params.write().author = author.address();
|
||||
|
||||
if self.engine.seals_internally().is_some() && password.is_some() {
|
||||
if let Some(ref ap) = self.accounts {
|
||||
let password = password.unwrap_or_else(|| Password::from(String::new()));
|
||||
// Sign test message
|
||||
ap.sign(address.clone(), Some(password.clone()), Default::default())?;
|
||||
if let Author::Sealer(signer) = author {
|
||||
if self.engine.seals_internally().is_some() {
|
||||
// Enable sealing
|
||||
self.sealing.lock().enabled = true;
|
||||
// --------------------------------------------------------------------------
|
||||
@@ -845,14 +857,10 @@ impl miner::MinerService for Miner {
|
||||
// | (some `Engine`s call `EngineClient.update_sealing()`) |
|
||||
// | Make sure to release the locks before calling that method. |
|
||||
// --------------------------------------------------------------------------
|
||||
self.engine.set_signer(ap.clone(), address, password);
|
||||
Ok(())
|
||||
self.engine.set_signer(signer);
|
||||
} else {
|
||||
warn!(target: "miner", "No account provider");
|
||||
Err(AccountError::NotFound)
|
||||
warn!("Setting an EngineSigner while Engine does not require one.");
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -925,8 +933,7 @@ impl miner::MinerService for Miner {
|
||||
let sender = pending.sender();
|
||||
let treat_as_local = trusted
|
||||
|| !self.options.tx_queue_no_unfamiliar_locals
|
||||
|| self.accounts.as_ref().map(|accts| accts.has_account(sender)).unwrap_or(false)
|
||||
|| self.options.tx_queue_locals.contains(&sender);
|
||||
|| self.accounts.is_local(&sender);
|
||||
|
||||
if treat_as_local {
|
||||
self.import_own_transaction(chain, pending)
|
||||
@@ -1255,7 +1262,7 @@ impl miner::MinerService for Miner {
|
||||
chain,
|
||||
&nonce_cache,
|
||||
&*engine,
|
||||
accounts.as_ref().map(|x| &**x),
|
||||
&*accounts,
|
||||
refuse_service_transactions,
|
||||
);
|
||||
queue.cull(client);
|
||||
@@ -1292,6 +1299,7 @@ mod tests {
|
||||
use std::iter::FromIterator;
|
||||
|
||||
use super::*;
|
||||
use accounts::AccountProvider;
|
||||
use ethkey::{Generator, Random};
|
||||
use hash::keccak;
|
||||
use rustc_hex::FromHex;
|
||||
@@ -1299,7 +1307,7 @@ mod tests {
|
||||
|
||||
use client::{TestBlockChainClient, EachBlockWith, ChainInfo, ImportSealedBlock};
|
||||
use miner::{MinerService, PendingOrdering};
|
||||
use test_helpers::{generate_dummy_client, generate_dummy_client_with_spec_and_accounts};
|
||||
use test_helpers::{generate_dummy_client, generate_dummy_client_with_spec};
|
||||
use types::transaction::{Transaction};
|
||||
|
||||
#[test]
|
||||
@@ -1349,7 +1357,6 @@ mod tests {
|
||||
enable_resubmission: true,
|
||||
infinite_pending_block: false,
|
||||
tx_queue_penalization: Penalization::Disabled,
|
||||
tx_queue_locals: HashSet::new(),
|
||||
tx_queue_strategy: PrioritizationStrategy::GasPriceOnly,
|
||||
tx_queue_no_unfamiliar_locals: false,
|
||||
refuse_service_transactions: false,
|
||||
@@ -1363,7 +1370,7 @@ mod tests {
|
||||
},
|
||||
GasPricer::new_fixed(0u64.into()),
|
||||
&Spec::new_test(),
|
||||
None, // accounts provider
|
||||
::std::collections::HashSet::new(), // local accounts
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1476,8 +1483,8 @@ mod tests {
|
||||
// given
|
||||
let keypair = Random.generate().unwrap();
|
||||
let client = TestBlockChainClient::default();
|
||||
let account_provider = AccountProvider::transient_provider();
|
||||
account_provider.insert_account(keypair.secret().clone(), &"".into()).expect("can add accounts to the provider we just created");
|
||||
let mut local_accounts = ::std::collections::HashSet::new();
|
||||
local_accounts.insert(keypair.address());
|
||||
|
||||
let miner = Miner::new(
|
||||
MinerOptions {
|
||||
@@ -1486,7 +1493,7 @@ mod tests {
|
||||
},
|
||||
GasPricer::new_fixed(0u64.into()),
|
||||
&Spec::new_test(),
|
||||
Some(Arc::new(account_provider)),
|
||||
local_accounts,
|
||||
);
|
||||
let transaction = transaction();
|
||||
let best_block = 0;
|
||||
@@ -1520,22 +1527,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn should_prioritize_locals() {
|
||||
let keypair = Random.generate().unwrap();
|
||||
let client = TestBlockChainClient::default();
|
||||
let account_provider = AccountProvider::transient_provider();
|
||||
account_provider.insert_account(keypair.secret().clone(), &"".into())
|
||||
.expect("can add accounts to the provider we just created");
|
||||
|
||||
let transaction = transaction();
|
||||
let miner = Miner::new(
|
||||
MinerOptions {
|
||||
tx_queue_no_unfamiliar_locals: true, // should work even with this enabled
|
||||
tx_queue_locals: HashSet::from_iter(vec![transaction.sender()].into_iter()),
|
||||
..miner().options
|
||||
},
|
||||
GasPricer::new_fixed(0u64.into()),
|
||||
&Spec::new_test(),
|
||||
Some(Arc::new(account_provider)),
|
||||
HashSet::from_iter(vec![transaction.sender()].into_iter()),
|
||||
);
|
||||
let best_block = 0;
|
||||
|
||||
@@ -1587,12 +1588,19 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fail_setting_engine_signer_without_account_provider() {
|
||||
let spec = Spec::new_instant;
|
||||
fn should_not_fail_setting_engine_signer_without_account_provider() {
|
||||
let spec = Spec::new_test_round;
|
||||
let tap = Arc::new(AccountProvider::transient_provider());
|
||||
let addr = tap.insert_account(keccak("1").into(), &"".into()).unwrap();
|
||||
let client = generate_dummy_client_with_spec_and_accounts(spec, None);
|
||||
assert!(match client.miner().set_author(addr, Some("".into())) { Err(AccountError::NotFound) => true, _ => false });
|
||||
let client = generate_dummy_client_with_spec(spec);
|
||||
let engine_signer = Box::new((tap.clone(), addr, "".into()));
|
||||
let msg = Default::default();
|
||||
assert!(client.engine().sign(msg).is_err());
|
||||
|
||||
// should set engine signer and miner author
|
||||
client.miner().set_author(Author::Sealer(engine_signer));
|
||||
assert_eq!(client.miner().authoring_params().author, addr);
|
||||
assert!(client.engine().sign(msg).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -25,7 +25,8 @@ pub mod pool_client;
|
||||
#[cfg(feature = "stratum")]
|
||||
pub mod stratum;
|
||||
|
||||
pub use self::miner::{Miner, MinerOptions, Penalization, PendingSet, AuthoringParams};
|
||||
pub use self::miner::{Miner, MinerOptions, Penalization, PendingSet, AuthoringParams, Author};
|
||||
pub use ethcore_miner::local_accounts::LocalAccounts;
|
||||
pub use ethcore_miner::pool::PendingOrdering;
|
||||
|
||||
use std::sync::Arc;
|
||||
@@ -34,7 +35,6 @@ use std::collections::{BTreeSet, BTreeMap};
|
||||
use bytes::Bytes;
|
||||
use ethcore_miner::pool::{VerifiedTransaction, QueueStatus, local_transactions};
|
||||
use ethereum_types::{H256, U256, Address};
|
||||
use ethkey::Password;
|
||||
use types::transaction::{self, UnverifiedTransaction, SignedTransaction, PendingTransaction};
|
||||
use types::BlockNumber;
|
||||
use types::block::Block;
|
||||
@@ -130,8 +130,8 @@ pub trait MinerService : Send + Sync {
|
||||
|
||||
/// Set info necessary to sign consensus messages and block authoring.
|
||||
///
|
||||
/// On PoW password is optional.
|
||||
fn set_author(&self, address: Address, password: Option<Password>) -> Result<(), ::account_provider::SignError>;
|
||||
/// On chains where sealing is done externally (e.g. PoW) we provide only reward beneficiary.
|
||||
fn set_author(&self, author: Author);
|
||||
|
||||
// Transaction Pool
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ use std::{
|
||||
};
|
||||
|
||||
use ethereum_types::{H256, U256, Address};
|
||||
use ethcore_miner::local_accounts::LocalAccounts;
|
||||
use ethcore_miner::pool;
|
||||
use ethcore_miner::pool::client::NonceClient;
|
||||
use ethcore_miner::service_transaction_checker::ServiceTransactionChecker;
|
||||
@@ -34,7 +35,6 @@ use types::transaction::{
|
||||
use types::header::Header;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use account_provider::AccountProvider;
|
||||
use call_contract::CallContract;
|
||||
use client::{TransactionId, BlockInfo, Nonce};
|
||||
use engines::EthEngine;
|
||||
@@ -73,7 +73,7 @@ pub struct PoolClient<'a, C: 'a> {
|
||||
chain: &'a C,
|
||||
cached_nonces: CachedNonceClient<'a, C>,
|
||||
engine: &'a EthEngine,
|
||||
accounts: Option<&'a AccountProvider>,
|
||||
accounts: &'a LocalAccounts,
|
||||
best_block_header: Header,
|
||||
service_transaction_checker: Option<ServiceTransactionChecker>,
|
||||
}
|
||||
@@ -92,14 +92,14 @@ impl<'a, C: 'a> Clone for PoolClient<'a, C> {
|
||||
}
|
||||
|
||||
impl<'a, C: 'a> PoolClient<'a, C> where
|
||||
C: BlockInfo + CallContract,
|
||||
C: BlockInfo + CallContract,
|
||||
{
|
||||
/// Creates new client given chain, nonce cache, accounts and service transaction verifier.
|
||||
pub fn new(
|
||||
chain: &'a C,
|
||||
cache: &'a NonceCache,
|
||||
engine: &'a EthEngine,
|
||||
accounts: Option<&'a AccountProvider>,
|
||||
accounts: &'a LocalAccounts,
|
||||
refuse_service_transactions: bool,
|
||||
) -> Self {
|
||||
let best_block_header = chain.best_block_header();
|
||||
@@ -151,7 +151,7 @@ impl<'a, C: 'a> pool::client::Client for PoolClient<'a, C> where
|
||||
pool::client::AccountDetails {
|
||||
nonce: self.cached_nonces.account_nonce(address),
|
||||
balance: self.chain.latest_balance(address),
|
||||
is_local: self.accounts.map_or(false, |accounts| accounts.has_account(*address)),
|
||||
is_local: self.accounts.is_local(address),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -340,7 +340,7 @@ impl Service {
|
||||
// replace one the client's database with our own.
|
||||
fn replace_client_db(&self) -> Result<(), Error> {
|
||||
let migrated_blocks = self.migrate_blocks()?;
|
||||
trace!(target: "snapshot", "Migrated {} ancient blocks", migrated_blocks);
|
||||
info!(target: "snapshot", "Migrated {} ancient blocks", migrated_blocks);
|
||||
|
||||
let rest_db = self.restoration_db();
|
||||
self.client.restore_db(&*rest_db.to_string_lossy())?;
|
||||
@@ -424,7 +424,7 @@ impl Service {
|
||||
}
|
||||
|
||||
if block_number % 10_000 == 0 {
|
||||
trace!(target: "snapshot", "Block restoration at #{}", block_number);
|
||||
info!(target: "snapshot", "Block restoration at #{}", block_number);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,12 +20,12 @@ use std::cell::RefCell;
|
||||
use std::sync::Arc;
|
||||
use std::str::FromStr;
|
||||
|
||||
use account_provider::AccountProvider;
|
||||
use accounts::AccountProvider;
|
||||
use client::{Client, BlockChainClient, ChainInfo};
|
||||
use ethkey::Secret;
|
||||
use snapshot::tests::helpers as snapshot_helpers;
|
||||
use spec::Spec;
|
||||
use test_helpers::generate_dummy_client_with_spec_and_accounts;
|
||||
use test_helpers::generate_dummy_client_with_spec;
|
||||
use types::transaction::{Transaction, Action, SignedTransaction};
|
||||
use tempdir::TempDir;
|
||||
|
||||
@@ -88,8 +88,7 @@ enum Transition {
|
||||
|
||||
// create a chain with the given transitions and some blocks beyond that transition.
|
||||
fn make_chain(accounts: Arc<AccountProvider>, blocks_beyond: usize, transitions: Vec<Transition>) -> Arc<Client> {
|
||||
let client = generate_dummy_client_with_spec_and_accounts(
|
||||
spec_fixed_to_contract, Some(accounts.clone()));
|
||||
let client = generate_dummy_client_with_spec(spec_fixed_to_contract);
|
||||
|
||||
let mut cur_signers = vec![*RICH_ADDR];
|
||||
{
|
||||
@@ -100,13 +99,14 @@ fn make_chain(accounts: Arc<AccountProvider>, blocks_beyond: usize, transitions:
|
||||
{
|
||||
// push a block with given number, signed by one of the signers, with given transactions.
|
||||
let push_block = |signers: &[Address], n, txs: Vec<SignedTransaction>| {
|
||||
use miner::MinerService;
|
||||
use miner::{self, MinerService};
|
||||
|
||||
let idx = n as usize % signers.len();
|
||||
trace!(target: "snapshot", "Pushing block #{}, {} txs, author={}",
|
||||
n, txs.len(), signers[idx]);
|
||||
|
||||
client.miner().set_author(signers[idx], Some(PASS.into())).unwrap();
|
||||
let signer = Box::new((accounts.clone(), signers[idx], PASS.into()));
|
||||
client.miner().set_author(miner::Author::Sealer(signer));
|
||||
client.miner().import_external_transactions(&*client,
|
||||
txs.into_iter().map(Into::into).collect());
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ use types::header::Header;
|
||||
use types::view;
|
||||
use types::views::BlockView;
|
||||
|
||||
use account_provider::AccountProvider;
|
||||
use block::{OpenBlock, Drain};
|
||||
use client::{Client, ClientConfig, ChainInfo, ImportBlock, ChainNotify, ChainMessageType, PrepareOpenBlock};
|
||||
use factory::Factories;
|
||||
@@ -109,18 +108,15 @@ pub fn generate_dummy_client_with_data(block_number: u32, txs_per_block: usize,
|
||||
generate_dummy_client_with_spec_and_data(Spec::new_null, block_number, txs_per_block, tx_gas_prices)
|
||||
}
|
||||
|
||||
/// Generates dummy client (not test client) with corresponding amount of blocks, txs per block and spec
|
||||
pub fn generate_dummy_client_with_spec_and_data<F>(test_spec: F, block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> Arc<Client> where F: Fn()->Spec {
|
||||
generate_dummy_client_with_spec_accounts_and_data(test_spec, None, block_number, txs_per_block, tx_gas_prices)
|
||||
}
|
||||
|
||||
/// Generates dummy client (not test client) with corresponding spec and accounts
|
||||
pub fn generate_dummy_client_with_spec_and_accounts<F>(test_spec: F, accounts: Option<Arc<AccountProvider>>) -> Arc<Client> where F: Fn()->Spec {
|
||||
generate_dummy_client_with_spec_accounts_and_data(test_spec, accounts, 0, 0, &[])
|
||||
pub fn generate_dummy_client_with_spec<F>(test_spec: F) -> Arc<Client> where F: Fn()->Spec {
|
||||
generate_dummy_client_with_spec_and_data(test_spec, 0, 0, &[])
|
||||
}
|
||||
|
||||
/// Generates dummy client (not test client) with corresponding blocks, accounts and spec
|
||||
pub fn generate_dummy_client_with_spec_accounts_and_data<F>(test_spec: F, accounts: Option<Arc<AccountProvider>>, block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> Arc<Client> where F: Fn()->Spec {
|
||||
/// Generates dummy client (not test client) with corresponding amount of blocks, txs per block and spec
|
||||
pub fn generate_dummy_client_with_spec_and_data<F>(test_spec: F, block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> Arc<Client> where
|
||||
F: Fn() -> Spec
|
||||
{
|
||||
let test_spec = test_spec();
|
||||
let client_db = new_db();
|
||||
|
||||
@@ -128,7 +124,7 @@ pub fn generate_dummy_client_with_spec_accounts_and_data<F>(test_spec: F, accoun
|
||||
ClientConfig::default(),
|
||||
&test_spec,
|
||||
client_db,
|
||||
Arc::new(Miner::new_for_tests(&test_spec, accounts)),
|
||||
Arc::new(Miner::new_for_tests(&test_spec, None)),
|
||||
IoChannel::disconnected(),
|
||||
).unwrap();
|
||||
let test_engine = &*test_spec.engine;
|
||||
|
||||
Reference in New Issue
Block a user