Merge pull request #587 from ethcore/rpc-signing-extend

Rpc transaction signing
This commit is contained in:
Marek Kotewicz 2016-03-11 11:14:17 +01:00
commit fd39975cf0
8 changed files with 158 additions and 32 deletions

View File

@ -37,10 +37,10 @@ use transaction::LocalizedTransaction;
use extras::TransactionAddress; use extras::TransactionAddress;
use filter::Filter; use filter::Filter;
use log_entry::LocalizedLogEntry; use log_entry::LocalizedLogEntry;
use util::keys::store::SecretStore;
pub use block_queue::{BlockQueueConfig, BlockQueueInfo}; pub use block_queue::{BlockQueueConfig, BlockQueueInfo};
pub use blockchain::{TreeRoute, BlockChainConfig, CacheSize as BlockChainCacheSize}; pub use blockchain::{TreeRoute, BlockChainConfig, CacheSize as BlockChainCacheSize};
/// Uniquely identifies block. /// Uniquely identifies block.
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum BlockId { pub enum BlockId {
@ -233,7 +233,6 @@ pub struct Client<V = CanonVerifier> where V: Verifier {
author: RwLock<Address>, author: RwLock<Address>,
extra_data: RwLock<Bytes>, extra_data: RwLock<Bytes>,
verifier: PhantomData<V>, verifier: PhantomData<V>,
secret_store: Arc<RwLock<SecretStore>>,
} }
const HISTORY: u64 = 1000; const HISTORY: u64 = 1000;
@ -269,9 +268,6 @@ impl<V> Client<V> where V: Verifier {
let panic_handler = PanicHandler::new_in_arc(); let panic_handler = PanicHandler::new_in_arc();
panic_handler.forward_from(&block_queue); panic_handler.forward_from(&block_queue);
let secret_store = Arc::new(RwLock::new(SecretStore::new()));
secret_store.write().unwrap().try_import_existing();
Ok(Arc::new(Client { Ok(Arc::new(Client {
chain: chain, chain: chain,
engine: engine, engine: engine,
@ -285,7 +281,6 @@ impl<V> Client<V> where V: Verifier {
author: RwLock::new(Address::new()), author: RwLock::new(Address::new()),
extra_data: RwLock::new(Vec::new()), extra_data: RwLock::new(Vec::new()),
verifier: PhantomData, verifier: PhantomData,
secret_store: secret_store,
})) }))
} }
@ -309,11 +304,6 @@ impl<V> Client<V> where V: Verifier {
last_hashes last_hashes
} }
/// Secret store (key manager)
pub fn secret_store(&self) -> &Arc<RwLock<SecretStore>> {
&self.secret_store
}
fn check_and_close_block(&self, block: &PreverifiedBlock) -> Result<ClosedBlock, ()> { fn check_and_close_block(&self, block: &PreverifiedBlock) -> Result<ClosedBlock, ()> {
let engine = self.engine.deref().deref(); let engine = self.engine.deref().deref();
let header = &block.header; let header = &block.header;

View File

@ -53,6 +53,7 @@ use ethsync::{EthSync, SyncConfig, SyncProvider};
use docopt::Docopt; use docopt::Docopt;
use daemonize::Daemonize; use daemonize::Daemonize;
use number_prefix::{binary_prefix, Standalone, Prefixed}; use number_prefix::{binary_prefix, Standalone, Prefixed};
use util::keys::store::*;
fn die_with_message(msg: &str) -> ! { fn die_with_message(msg: &str) -> ! {
println!("ERROR: {}", msg); println!("ERROR: {}", msg);
@ -195,7 +196,7 @@ fn setup_log(init: &Option<String>) {
} }
#[cfg(feature = "rpc")] #[cfg(feature = "rpc")]
fn setup_rpc_server(client: Arc<Client>, sync: Arc<EthSync>, url: &str, cors_domain: &str, apis: Vec<&str>) -> Option<Arc<PanicHandler>> { fn setup_rpc_server(client: Arc<Client>, sync: Arc<EthSync>, secret_store: Arc<AccountService>, url: &str, cors_domain: &str, apis: Vec<&str>) -> Option<Arc<PanicHandler>> {
use rpc::v1::*; use rpc::v1::*;
let server = rpc::RpcServer::new(); let server = rpc::RpcServer::new();
@ -204,7 +205,7 @@ fn setup_rpc_server(client: Arc<Client>, sync: Arc<EthSync>, url: &str, cors_dom
"web3" => server.add_delegate(Web3Client::new().to_delegate()), "web3" => server.add_delegate(Web3Client::new().to_delegate()),
"net" => server.add_delegate(NetClient::new(&sync).to_delegate()), "net" => server.add_delegate(NetClient::new(&sync).to_delegate()),
"eth" => { "eth" => {
server.add_delegate(EthClient::new(&client, &sync).to_delegate()); server.add_delegate(EthClient::new(&client, &sync, &secret_store).to_delegate());
server.add_delegate(EthFilterClient::new(&client).to_delegate()); server.add_delegate(EthFilterClient::new(&client).to_delegate());
} }
_ => { _ => {
@ -414,6 +415,9 @@ impl Configuration {
// Sync // Sync
let sync = EthSync::register(service.network(), sync_config, client); let sync = EthSync::register(service.network(), sync_config, client);
// Secret Store
let account_service = Arc::new(AccountService::new());
// Setup rpc // Setup rpc
if self.args.flag_jsonrpc || self.args.flag_rpc { if self.args.flag_jsonrpc || self.args.flag_rpc {
let url = format!("{}:{}", let url = format!("{}:{}",
@ -424,7 +428,7 @@ impl Configuration {
let cors = self.args.flag_rpccorsdomain.as_ref().unwrap_or(&self.args.flag_jsonrpc_cors); let cors = self.args.flag_rpccorsdomain.as_ref().unwrap_or(&self.args.flag_jsonrpc_cors);
// TODO: use this as the API list. // TODO: use this as the API list.
let apis = self.args.flag_rpcapi.as_ref().unwrap_or(&self.args.flag_jsonrpc_apis); let apis = self.args.flag_rpcapi.as_ref().unwrap_or(&self.args.flag_jsonrpc_apis);
let server_handler = setup_rpc_server(service.client(), sync.clone(), &url, cors, apis.split(",").collect()); let server_handler = setup_rpc_server(service.client(), sync.clone(), account_service.clone(), &url, cors, apis.split(",").collect());
if let Some(handler) = server_handler { if let Some(handler) = server_handler {
panic_handler.forward_from(handler.deref()); panic_handler.forward_from(handler.deref());
} }

View File

@ -28,22 +28,25 @@ use ethcore::views::*;
use ethcore::ethereum::Ethash; use ethcore::ethereum::Ethash;
use ethcore::ethereum::denominations::shannon; use ethcore::ethereum::denominations::shannon;
use v1::traits::{Eth, EthFilter}; use v1::traits::{Eth, EthFilter};
use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, OptionalValue, Index, Filter, Log}; use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, TransactionRequest, OptionalValue, Index, Filter, Log};
use v1::helpers::{PollFilter, PollManager}; use v1::helpers::{PollFilter, PollManager};
use util::keys::store::AccountProvider;
/// Eth rpc implementation. /// Eth rpc implementation.
pub struct EthClient<C, S> where C: BlockChainClient, S: SyncProvider { pub struct EthClient<C, S, A> where C: BlockChainClient, S: SyncProvider, A: AccountProvider {
client: Weak<C>, client: Weak<C>,
sync: Weak<S>, sync: Weak<S>,
accounts: Weak<A>,
hashrates: RwLock<HashMap<H256, u64>>, hashrates: RwLock<HashMap<H256, u64>>,
} }
impl<C, S> EthClient<C, S> where C: BlockChainClient, S: SyncProvider { impl<C, S, A> EthClient<C, S, A> where C: BlockChainClient, S: SyncProvider, A: AccountProvider {
/// Creates new EthClient. /// Creates new EthClient.
pub fn new(client: &Arc<C>, sync: &Arc<S>) -> Self { pub fn new(client: &Arc<C>, sync: &Arc<S>, accounts: &Arc<A>) -> Self {
EthClient { EthClient {
client: Arc::downgrade(client), client: Arc::downgrade(client),
sync: Arc::downgrade(sync), sync: Arc::downgrade(sync),
accounts: Arc::downgrade(accounts),
hashrates: RwLock::new(HashMap::new()), hashrates: RwLock::new(HashMap::new()),
} }
} }
@ -94,7 +97,7 @@ impl<C, S> EthClient<C, S> where C: BlockChainClient, S: SyncProvider {
} }
} }
impl<C, S> Eth for EthClient<C, S> where C: BlockChainClient + 'static, S: SyncProvider + 'static { impl<C, S, A> Eth for EthClient<C, S, A> where C: BlockChainClient + 'static, S: SyncProvider + 'static, A: AccountProvider + 'static {
fn protocol_version(&self, params: Params) -> Result<Value, Error> { fn protocol_version(&self, params: Params) -> Result<Value, Error> {
match params { match params {
Params::None => to_value(&U256::from(take_weak!(self.sync).status().protocol_version)), Params::None => to_value(&U256::from(take_weak!(self.sync).status().protocol_version)),
@ -263,6 +266,24 @@ impl<C, S> Eth for EthClient<C, S> where C: BlockChainClient + 'static, S: SyncP
to_value(&true) to_value(&true)
}) })
} }
fn send_transaction(&self, params: Params) -> Result<Value, Error> {
from_params::<(TransactionRequest, )>(params)
.and_then(|(transaction_request, )| {
let accounts = take_weak!(self.accounts);
match accounts.account_secret(&transaction_request.from) {
Ok(secret) => {
let sync = take_weak!(self.sync);
let (transaction, _) = transaction_request.to_eth();
let signed_transaction = transaction.sign(&secret);
let hash = signed_transaction.hash();
sync.insert_transaction(signed_transaction);
to_value(&hash)
},
Err(_) => { to_value(&U256::zero()) }
}
})
}
} }
/// Eth filter rpc implementation. /// Eth filter rpc implementation.

View File

@ -20,30 +20,28 @@ use jsonrpc_core::*;
use v1::traits::Personal; use v1::traits::Personal;
use util::keys::store::*; use util::keys::store::*;
use util::Address; use util::Address;
use std::sync::RwLock;
/// Account management (personal) rpc implementation. /// Account management (personal) rpc implementation.
pub struct PersonalClient { pub struct PersonalClient {
secret_store: Weak<RwLock<SecretStore>>, accounts: Weak<AccountProvider>,
} }
impl PersonalClient { impl PersonalClient {
/// Creates new PersonalClient /// Creates new PersonalClient
pub fn new(store: &Arc<RwLock<SecretStore>>) -> Self { pub fn new(store: &Arc<AccountProvider>) -> Self {
PersonalClient { PersonalClient {
secret_store: Arc::downgrade(store), accounts: Arc::downgrade(store),
} }
} }
} }
impl Personal for PersonalClient { impl Personal for PersonalClient {
fn accounts(&self, _: Params) -> Result<Value, Error> { fn accounts(&self, _: Params) -> Result<Value, Error> {
let store_wk = take_weak!(self.secret_store); let store = take_weak!(self.accounts);
let store = store_wk.read().unwrap();
match store.accounts() { match store.accounts() {
Ok(account_list) => { Ok(account_list) => {
Ok(Value::Array(account_list.iter() Ok(Value::Array(account_list.iter()
.map(|&(account, _)| Value::String(format!("{:?}", account))) .map(|&account| Value::String(format!("{:?}", account)))
.collect::<Vec<Value>>()) .collect::<Vec<Value>>())
) )
} }
@ -54,8 +52,7 @@ impl Personal for PersonalClient {
fn new_account(&self, params: Params) -> Result<Value, Error> { fn new_account(&self, params: Params) -> Result<Value, Error> {
from_params::<(String, )>(params).and_then( from_params::<(String, )>(params).and_then(
|(pass, )| { |(pass, )| {
let store_wk = take_weak!(self.secret_store); let store = take_weak!(self.accounts);
let mut store = store_wk.write().unwrap();
match store.new_account(&pass) { match store.new_account(&pass) {
Ok(address) => Ok(Value::String(format!("{:?}", address))), Ok(address) => Ok(Value::String(format!("{:?}", address))),
Err(_) => Err(Error::internal_error()) Err(_) => Err(Error::internal_error())
@ -67,8 +64,7 @@ impl Personal for PersonalClient {
fn unlock_account(&self, params: Params) -> Result<Value, Error> { fn unlock_account(&self, params: Params) -> Result<Value, Error> {
from_params::<(Address, String, u64)>(params).and_then( from_params::<(Address, String, u64)>(params).and_then(
|(account, account_pass, _)|{ |(account, account_pass, _)|{
let store_wk = take_weak!(self.secret_store); let store = take_weak!(self.accounts);
let store = store_wk.read().unwrap();
match store.unlock_account(&account, &account_pass) { match store.unlock_account(&account, &account_pass) {
Ok(_) => Ok(Value::Bool(true)), Ok(_) => Ok(Value::Bool(true)),
Err(_) => Ok(Value::Bool(false)), Err(_) => Ok(Value::Bool(false)),

View File

@ -15,7 +15,9 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use rustc_serialize::hex::ToHex; use rustc_serialize::hex::ToHex;
use serde::{Serialize, Serializer}; use serde::{Serialize, Serializer, Deserialize, Deserializer, Error};
use serde::de::Visitor;
use util::common::FromHex;
/// Wrapper structure around vector of bytes. /// Wrapper structure around vector of bytes.
#[derive(Debug)] #[derive(Debug)]
@ -26,6 +28,7 @@ impl Bytes {
pub fn new(bytes: Vec<u8>) -> Bytes { pub fn new(bytes: Vec<u8>) -> Bytes {
Bytes(bytes) Bytes(bytes)
} }
pub fn to_vec(self) -> Vec<u8> { let Bytes(x) = self; x }
} }
impl Default for Bytes { impl Default for Bytes {
@ -44,6 +47,32 @@ impl Serialize for Bytes {
} }
} }
impl Deserialize for Bytes {
fn deserialize<D>(deserializer: &mut D) -> Result<Bytes, D::Error>
where D: Deserializer {
deserializer.deserialize(BytesVisitor)
}
}
struct BytesVisitor;
impl Visitor for BytesVisitor {
type Value = Bytes;
fn visit_str<E>(&mut self, value: &str) -> Result<Self::Value, E> where E: Error {
if value.len() >= 2 && &value[0..2] == "0x" {
Ok(Bytes::new(FromHex::from_hex(&value[2..]).unwrap_or_else(|_| vec![])))
} else {
Err(Error::custom("invalid hex"))
}
}
fn visit_string<E>(&mut self, value: String) -> Result<Self::Value, E> where E: Error {
self.visit_str(value.as_ref())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -33,3 +33,5 @@ pub use self::log::Log;
pub use self::optionals::OptionalValue; pub use self::optionals::OptionalValue;
pub use self::sync::{SyncStatus, SyncInfo}; pub use self::sync::{SyncStatus, SyncInfo};
pub use self::transaction::Transaction; pub use self::transaction::Transaction;
pub use self::transaction::TransactionRequest;

View File

@ -17,6 +17,8 @@
use util::numbers::*; use util::numbers::*;
use ethcore::transaction::{LocalizedTransaction, Action}; use ethcore::transaction::{LocalizedTransaction, Action};
use v1::types::{Bytes, OptionalValue}; use v1::types::{Bytes, OptionalValue};
use serde::{Deserializer, Error};
use ethcore;
#[derive(Debug, Default, Serialize)] #[derive(Debug, Default, Serialize)]
pub struct Transaction { pub struct Transaction {
@ -37,6 +39,35 @@ pub struct Transaction {
pub input: Bytes pub input: Bytes
} }
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct TransactionRequest {
pub from: Address,
pub to: Option<Address>,
#[serde(rename="gasPrice")]
pub gas_price: Option<U256>,
pub gas: Option<U256>,
pub value: Option<U256>,
pub data: Bytes,
pub nonce: Option<U256>,
}
impl TransactionRequest {
/// maps transaction request to the transaction that can be signed and inserted
pub fn to_eth(self) -> (ethcore::transaction::Transaction, Address) {
(ethcore::transaction::Transaction {
nonce: self.nonce.unwrap_or(U256::zero()),
action: match self.to {
None => ethcore::transaction::Action::Create,
Some(addr) => ethcore::transaction::Action::Call(addr)
},
gas: self.gas.unwrap_or(U256::zero()),
gas_price: self.gas_price.unwrap_or(U256::zero()),
value: self.value.unwrap_or(U256::zero()),
data: self.data.to_vec()
}, self.from)
}
}
impl From<LocalizedTransaction> for Transaction { impl From<LocalizedTransaction> for Transaction {
fn from(t: LocalizedTransaction) -> Transaction { fn from(t: LocalizedTransaction) -> Transaction {
Transaction { Transaction {

View File

@ -78,6 +78,59 @@ struct AccountUnlock {
expires: DateTime<UTC>, expires: DateTime<UTC>,
} }
/// Basic account management trait
pub trait AccountProvider : Send + Sync {
/// Lists all accounts
fn accounts(&self) -> Result<Vec<Address>, ::std::io::Error>;
/// Unlocks account with the password provided
fn unlock_account(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError>;
/// Creates account
fn new_account(&self, pass: &str) -> Result<Address, ::std::io::Error>;
/// Returns secret for unlocked account
fn account_secret(&self, account: &Address) -> Result<crypto::Secret, SigningError>;
/// Returns secret for unlocked account
fn sign(&self, account: &Address, message: &H256) -> Result<crypto::Signature, SigningError>;
}
/// Thread-safe accounts management
pub struct AccountService {
secret_store: RwLock<SecretStore>,
}
impl AccountProvider for AccountService {
/// Lists all accounts
fn accounts(&self) -> Result<Vec<Address>, ::std::io::Error> {
Ok(try!(self.secret_store.read().unwrap().accounts()).iter().map(|&(addr, _)| addr).collect::<Vec<Address>>())
}
/// Unlocks account with the password provided
fn unlock_account(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> {
self.secret_store.read().unwrap().unlock_account(account, pass)
}
/// Creates account
fn new_account(&self, pass: &str) -> Result<Address, ::std::io::Error> {
self.secret_store.write().unwrap().new_account(pass)
}
/// Returns secret for unlocked account
fn account_secret(&self, account: &Address) -> Result<crypto::Secret, SigningError> {
self.secret_store.read().unwrap().account_secret(account)
}
/// Returns secret for unlocked account
fn sign(&self, account: &Address, message: &H256) -> Result<crypto::Signature, SigningError> {
self.secret_store.read().unwrap().sign(account, message)
}
}
impl AccountService {
/// New account service with the default location
pub fn new() -> AccountService {
let secret_store = RwLock::new(SecretStore::new());
secret_store.write().unwrap().try_import_existing();
AccountService {
secret_store: secret_store
}
}
}
impl SecretStore { impl SecretStore {
/// new instance of Secret Store in default home directory /// new instance of Secret Store in default home directory
pub fn new() -> SecretStore { pub fn new() -> SecretStore {