Merge pull request #193 from ethcore/rpc

jsonrpc http server
This commit is contained in:
Arkadiy Paronyan 2016-01-27 16:48:16 +01:00
commit 5e6774a5ec
21 changed files with 495 additions and 10 deletions

View File

@ -20,17 +20,10 @@ time = "0.1"
evmjit = { path = "rust-evmjit", optional = true }
ethash = { path = "ethash" }
num_cpus = "0.2"
docopt = "0.6"
docopt_macros = "0.6"
ctrlc = "1.0"
crossbeam = "0.1.5"
clippy = "0.0.37"
crossbeam = "0.1.5"
[features]
jit = ["evmjit"]
evm_debug = []
test-heavy = []
evm-debug = []
[[bin]]
name = "client"
path = "src/bin/client/main.rs"

20
bin/Cargo.toml Normal file
View File

@ -0,0 +1,20 @@
[package]
description = "Ethcore client."
name = "ethcore-client"
version = "0.1.0"
license = "GPL-3.0"
authors = ["Ethcore <admin@ethcore.io>"]
[dependencies]
log = "0.3"
env_logger = "0.3"
rustc-serialize = "0.3"
docopt = "0.6"
docopt_macros = "0.6"
ctrlc = "1.0"
ethcore-util = { path = "../util" }
ethcore-rpc = { path = "../rpc", optional = true }
ethcore = { path = ".." }
[features]
rpc = ["ethcore-rpc"]

View File

@ -1,5 +1,6 @@
#![feature(plugin)]
#![plugin(docopt_macros)]
// required for serde, move it to a separate library
extern crate docopt;
extern crate rustc_serialize;
extern crate ethcore_util as util;
@ -8,6 +9,9 @@ extern crate log;
extern crate env_logger;
extern crate ctrlc;
#[cfg(feature = "rpc")]
extern crate ethcore_rpc as rpc;
use std::env;
use log::{LogLevelFilter};
use env_logger::LogBuilder;
@ -44,6 +48,23 @@ fn setup_log(init: &String) {
builder.init().unwrap();
}
#[cfg(feature = "rpc")]
fn setup_rpc_server(client: Arc<Client>) {
use rpc::*;
let mut server = HttpServer::new(1);
server.add_delegate(Web3Client::new().to_delegate());
server.add_delegate(EthClient::new(client.clone()).to_delegate());
server.add_delegate(EthFilterClient::new(client).to_delegate());
server.add_delegate(NetClient::new().to_delegate());
server.start_async("127.0.0.1:3030");
}
#[cfg(not(feature = "rpc"))]
fn setup_rpc_server(_client: Arc<Client>) {
}
fn main() {
let args: Args = Args::docopt().decode().unwrap_or_else(|e| e.exit());
@ -57,6 +78,7 @@ fn main() {
let mut net_settings = NetworkConfiguration::new();
net_settings.boot_nodes = init_nodes;
let mut service = ClientService::start(spec, net_settings).unwrap();
setup_rpc_server(service.client());
let io_handler = Arc::new(ClientIoHandler { client: service.client(), info: Default::default(), sync: service.sync() });
service.io().register_handler(io_handler).expect("Error registering IO handler");

18
rpc/Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
description = "Ethcore jsonrpc"
name = "ethcore-rpc"
version = "0.1.0"
license = "GPL-3.0"
authors = ["Marek Kotewicz <marek.kotewicz@gmail.com"]
[lib]
[dependencies]
serde = "0.6.7"
serde_macros = "0.6.10"
serde_json = "0.6.0"
jsonrpc-core = "1.1"
jsonrpc-http-server = "1.1"
ethcore-util = { path = "../util" }
ethcore = { path = ".." }

127
rpc/src/impls/eth.rs Normal file
View File

@ -0,0 +1,127 @@
use std::sync::Arc;
use jsonrpc_core::*;
use util::hash::*;
use util::uint::*;
use util::sha3::*;
use ethcore::client::*;
use ethcore::views::*;
use traits::{Eth, EthFilter};
use types::Block;
pub struct EthClient {
client: Arc<Client>,
}
impl EthClient {
pub fn new(client: Arc<Client>) -> Self {
EthClient {
client: client
}
}
}
impl Eth for EthClient {
fn protocol_version(&self, params: Params) -> Result<Value, Error> {
match params {
Params::None => Ok(Value::U64(63)),
_ => Err(Error::invalid_params())
}
}
fn author(&self, params: Params) -> Result<Value, Error> {
match params {
Params::None => to_value(&Address::new()),
_ => Err(Error::invalid_params())
}
}
fn gas_price(&self, params: Params) -> Result<Value, Error> {
match params {
Params::None => Ok(Value::U64(0)),
_ => Err(Error::invalid_params())
}
}
fn block_number(&self, params: Params) -> Result<Value, Error> {
match params {
Params::None => Ok(Value::U64(self.client.chain_info().best_block_number)),
_ => Err(Error::invalid_params())
}
}
fn is_mining(&self, params: Params) -> Result<Value, Error> {
match params {
Params::None => Ok(Value::Bool(false)),
_ => Err(Error::invalid_params())
}
}
fn hashrate(&self, params: Params) -> Result<Value, Error> {
match params {
Params::None => Ok(Value::U64(0)),
_ => Err(Error::invalid_params())
}
}
fn block_transaction_count(&self, _: Params) -> Result<Value, Error> {
Ok(Value::U64(0))
}
fn block(&self, params: Params) -> Result<Value, Error> {
match from_params::<(H256, bool)>(params) {
Ok((hash, _include_txs)) => match (self.client.block_header(&hash), self.client.block_total_difficulty(&hash)) {
(Some(bytes), Some(total_difficulty)) => {
let view = HeaderView::new(&bytes);
let block = Block {
hash: view.sha3(),
parent_hash: view.parent_hash(),
uncles_hash: view.uncles_hash(),
author: view.author(),
miner: view.author(),
state_root: view.state_root(),
transactions_root: view.transactions_root(),
receipts_root: view.receipts_root(),
number: U256::from(view.number()),
gas_used: view.gas_used(),
gas_limit: view.gas_limit(),
logs_bloom: view.log_bloom(),
timestamp: U256::from(view.timestamp()),
difficulty: view.difficulty(),
total_difficulty: total_difficulty,
uncles: vec![],
transactions: vec![]
};
to_value(&block)
},
_ => Ok(Value::Null)
},
Err(err) => Err(err)
}
}
}
pub struct EthFilterClient {
client: Arc<Client>
}
impl EthFilterClient {
pub fn new(client: Arc<Client>) -> Self {
EthFilterClient {
client: client
}
}
}
impl EthFilter for EthFilterClient {
fn new_block_filter(&self, _params: Params) -> Result<Value, Error> {
Ok(Value::U64(0))
}
fn new_pending_transaction_filter(&self, _params: Params) -> Result<Value, Error> {
Ok(Value::U64(1))
}
fn filter_changes(&self, _: Params) -> Result<Value, Error> {
to_value(&self.client.chain_info().best_block_hash).map(|v| Value::Array(vec![v]))
}
}

8
rpc/src/impls/mod.rs Normal file
View File

@ -0,0 +1,8 @@
//! Ethereum rpc interface implementation.
mod web3;
mod eth;
mod net;
pub use self::web3::Web3Client;
pub use self::eth::{EthClient, EthFilterClient};
pub use self::net::NetClient;

19
rpc/src/impls/net.rs Normal file
View File

@ -0,0 +1,19 @@
//! Net rpc implementation.
use jsonrpc_core::*;
use traits::Net;
pub struct NetClient;
impl NetClient {
pub fn new() -> Self { NetClient }
}
impl Net for NetClient {
fn version(&self, _: Params) -> Result<Value, Error> {
Ok(Value::U64(63))
}
fn peer_count(&self, _params: Params) -> Result<Value, Error> {
Ok(Value::U64(0))
}
}

18
rpc/src/impls/web3.rs Normal file
View File

@ -0,0 +1,18 @@
use jsonrpc_core::*;
use traits::Web3;
pub struct Web3Client;
impl Web3Client {
pub fn new() -> Self { Web3Client }
}
impl Web3 for Web3Client {
fn client_version(&self, params: Params) -> Result<Value, Error> {
match params {
//Params::None => Ok(Value::String("parity/0.1.0/-/rust1.7-nightly".to_owned())),
Params::None => Ok(Value::String("surprise/0.1.0/surprise/surprise".to_owned())),
_ => Err(Error::invalid_params())
}
}
}

46
rpc/src/lib.rs Normal file
View File

@ -0,0 +1,46 @@
#![feature(custom_derive, custom_attribute, plugin)]
#![feature(slice_patterns)]
#![plugin(serde_macros)]
extern crate serde;
extern crate serde_json;
extern crate jsonrpc_core;
extern crate jsonrpc_http_server;
extern crate ethcore_util as util;
extern crate ethcore;
use self::jsonrpc_core::{IoHandler, IoDelegate};
macro_rules! rpcerr {
() => (Err(Error::internal_error()))
}
pub mod traits;
mod impls;
mod types;
pub use self::traits::{Web3, Eth, EthFilter, Net};
pub use self::impls::*;
pub struct HttpServer {
handler: IoHandler,
threads: usize
}
impl HttpServer {
pub fn new(threads: usize) -> HttpServer {
HttpServer {
handler: IoHandler::new(),
threads: threads
}
}
pub fn add_delegate<D>(&mut self, delegate: IoDelegate<D>) where D: Send + Sync + 'static {
self.handler.add_delegate(delegate);
}
pub fn start_async(self, addr: &str) {
let server = jsonrpc_http_server::Server::new(self.handler, self.threads);
server.start_async(addr)
}
}

66
rpc/src/traits/eth.rs Normal file
View File

@ -0,0 +1,66 @@
//! Eth rpc interface.
use std::sync::Arc;
use jsonrpc_core::*;
/// Eth rpc interface.
pub trait Eth: Sized + Send + Sync + 'static {
/// Returns protocol version.
fn protocol_version(&self, _: Params) -> Result<Value, Error> { rpcerr!() }
/// Returns block author.
fn author(&self, _: Params) -> Result<Value, Error> { rpcerr!() }
/// Returns current gas_price.
fn gas_price(&self, _: Params) -> Result<Value, Error> { rpcerr!() }
/// Returns highest block number.
fn block_number(&self, _: Params) -> Result<Value, Error> { rpcerr!() }
/// Returns block with given index / hash.
fn block(&self, _: Params) -> Result<Value, Error> { rpcerr!() }
/// Returns true if client is actively mining new blocks.
fn is_mining(&self, _: Params) -> Result<Value, Error> { rpcerr!() }
/// Returns the number of hashes per second that the node is mining with.
fn hashrate(&self, _: Params) -> Result<Value, Error> { rpcerr!() }
/// Returns the number of transactions in a block.
fn block_transaction_count(&self, _: Params) -> Result<Value, Error> { rpcerr!() }
/// Should be used to convert object to io delegate.
fn to_delegate(self) -> IoDelegate<Self> {
let mut delegate = IoDelegate::new(Arc::new(self));
delegate.add_method("eth_protocolVersion", Eth::protocol_version);
delegate.add_method("eth_coinbase", Eth::author);
delegate.add_method("eth_gasPrice", Eth::gas_price);
delegate.add_method("eth_blockNumber", Eth::block_number);
delegate.add_method("eth_getBlockByHash", Eth::block);
delegate.add_method("eth_getBlockByNumber", Eth::block);
delegate.add_method("eth_mining", Eth::is_mining);
delegate.add_method("eth_hashrate", Eth::hashrate);
delegate.add_method("eth_getBlockTransactionCountByNumber", Eth::block_transaction_count);
delegate
}
}
// TODO: do filters api properly
pub trait EthFilter: Sized + Send + Sync + 'static {
/// Returns id of new block filter
fn new_block_filter(&self, _: Params) -> Result<Value, Error> { rpcerr!() }
/// Returns id of new block filter
fn new_pending_transaction_filter(&self, _: Params) -> Result<Value, Error> { rpcerr!() }
/// Returns filter changes since last poll
fn filter_changes(&self, _: Params) -> Result<Value, Error> { rpcerr!() }
/// Should be used to convert object to io delegate.
fn to_delegate(self) -> IoDelegate<Self> {
let mut delegate = IoDelegate::new(Arc::new(self));
delegate.add_method("eth_newBlockFilter", EthFilter::new_block_filter);
delegate.add_method("eth_newPendingTransactionFilter", EthFilter::new_pending_transaction_filter);
delegate.add_method("eth_getFilterChanges", EthFilter::filter_changes);
delegate
}
}

8
rpc/src/traits/mod.rs Normal file
View File

@ -0,0 +1,8 @@
//! Ethereum rpc interfaces.
pub mod web3;
pub mod eth;
pub mod net;
pub use self::web3::Web3;
pub use self::eth::{Eth, EthFilter};
pub use self::net::Net;

20
rpc/src/traits/net.rs Normal file
View File

@ -0,0 +1,20 @@
//! Net rpc interface.
use std::sync::Arc;
use jsonrpc_core::*;
/// Net rpc interface.
pub trait Net: Sized + Send + Sync + 'static {
/// Returns protocol version.
fn version(&self, _: Params) -> Result<Value, Error> { rpcerr!() }
/// Returns number of peers connected to node.
fn peer_count(&self, _: Params) -> Result<Value, Error> { rpcerr!() }
/// Should be used to convert object to io delegate.
fn to_delegate(self) -> IoDelegate<Self> {
let mut delegate = IoDelegate::new(Arc::new(self));
delegate.add_method("net_version", Net::version);
delegate.add_method("net_peerCount", Net::peer_count);
delegate
}
}

16
rpc/src/traits/web3.rs Normal file
View File

@ -0,0 +1,16 @@
//! Web3 rpc interface.
use std::sync::Arc;
use jsonrpc_core::*;
/// Web3 rpc interface.
pub trait Web3: Sized + Send + Sync + 'static {
/// Returns current client version.
fn client_version(&self, _: Params) -> Result<Value, Error> { rpcerr!() }
/// Should be used to convert object to io delegate.
fn to_delegate(self) -> IoDelegate<Self> {
let mut delegate = IoDelegate::new(Arc::new(self));
delegate.add_method("web3_clientVersion", Web3::client_version);
delegate
}
}

36
rpc/src/types/block.rs Normal file
View File

@ -0,0 +1,36 @@
use util::hash::*;
use util::uint::*;
#[derive(Default, Debug, Serialize)]
pub struct Block {
pub hash: H256,
#[serde(rename="parentHash")]
pub parent_hash: H256,
#[serde(rename="sha3Uncles")]
pub uncles_hash: H256,
pub author: Address,
// TODO: get rid of this one
pub miner: Address,
#[serde(rename="stateRoot")]
pub state_root: H256,
#[serde(rename="transactionsRoot")]
pub transactions_root: H256,
#[serde(rename="receiptsRoot")]
pub receipts_root: H256,
pub number: U256,
#[serde(rename="gasUsed")]
pub gas_used: U256,
#[serde(rename="gasLimit")]
pub gas_limit: U256,
// TODO: figure out how to properly serialize bytes
//#[serde(rename="extraData")]
//extra_data: Vec<u8>,
#[serde(rename="logsBloom")]
pub logs_bloom: H2048,
pub timestamp: U256,
pub difficulty: U256,
#[serde(rename="totalDifficulty")]
pub total_difficulty: U256,
pub uncles: Vec<U256>,
pub transactions: Vec<U256>
}

3
rpc/src/types/mod.rs Normal file
View File

@ -0,0 +1,3 @@
mod block;
pub use self::block::Block;

View File

@ -66,6 +66,9 @@ pub trait BlockChainClient : Sync + Send {
/// Get block status by block header hash.
fn block_status(&self, hash: &H256) -> BlockStatus;
/// Get block total difficulty.
fn block_total_difficulty(&self, hash: &H256) -> Option<U256>;
/// Get raw block header data by block number.
fn block_header_at(&self, n: BlockNumber) -> Option<Bytes>;
@ -79,6 +82,9 @@ pub trait BlockChainClient : Sync + Send {
/// Get block status by block number.
fn block_status_at(&self, n: BlockNumber) -> BlockStatus;
/// Get block total difficulty.
fn block_total_difficulty_at(&self, n: BlockNumber) -> Option<U256>;
/// Get a tree route between `from` and `to`.
/// See `BlockChain::tree_route`.
fn tree_route(&self, from: &H256, to: &H256) -> Option<TreeRoute>;
@ -320,6 +326,10 @@ impl BlockChainClient for Client {
if self.chain.read().unwrap().is_known(&hash) { BlockStatus::InChain } else { BlockStatus::Unknown }
}
fn block_total_difficulty(&self, hash: &H256) -> Option<U256> {
self.chain.read().unwrap().block_details(hash).map(|d| d.total_difficulty)
}
fn block_header_at(&self, n: BlockNumber) -> Option<Bytes> {
self.chain.read().unwrap().block_hash(n).and_then(|h| self.block_header(&h))
}
@ -339,6 +349,10 @@ impl BlockChainClient for Client {
}
}
fn block_total_difficulty_at(&self, n: BlockNumber) -> Option<U256> {
self.chain.read().unwrap().block_hash(n).and_then(|h| self.block_total_difficulty(&h))
}
fn tree_route(&self, from: &H256, to: &H256) -> Option<TreeRoute> {
self.chain.read().unwrap().tree_route(from.clone(), to.clone())
}

View File

@ -215,7 +215,6 @@ impl<'a> Ext for Externalities<'a> {
fn suicide(&mut self, refund_address: &Address) {
let address = self.origin_info.address.clone();
let balance = self.balance(&address);
trace!("Suiciding {} -> {} (xfer: {})", address, refund_address, balance);
self.state.transfer_balance(&address, refund_address, &balance);
self.substate.suicides.insert(address);
}

View File

@ -25,6 +25,7 @@ itertools = "0.4"
crossbeam = "0.2"
slab = { git = "https://github.com/arkpar/slab.git" }
sha3 = { path = "sha3" }
serde = "0.6.7"
clippy = "*" # Always newest, since we use nightly
[dev-dependencies]

View File

@ -8,6 +8,8 @@ use rand::os::OsRng;
use bytes::{BytesConvertable,Populatable};
use from_json::*;
use uint::{Uint, U256};
use rustc_serialize::hex::ToHex;
use serde;
/// Trait for a fixed-size byte array to be used as the output of hash functions.
///
@ -215,6 +217,41 @@ macro_rules! impl_hash {
}
}
impl serde::Serialize for $from {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: serde::Serializer {
let mut hex = "0x".to_owned();
hex.push_str(self.to_hex().as_ref());
serializer.visit_str(hex.as_ref())
}
}
impl serde::Deserialize for $from {
fn deserialize<D>(deserializer: &mut D) -> Result<$from, D::Error>
where D: serde::Deserializer {
struct HashVisitor;
impl serde::de::Visitor for HashVisitor {
type Value = $from;
fn visit_str<E>(&mut self, value: &str) -> Result<Self::Value, E> where E: serde::Error {
// 0x + len
if value.len() != 2 + $size * 2 {
return Err(serde::Error::syntax("Invalid length."));
}
value[2..].from_hex().map(|ref v| $from::from_slice(v)).map_err(|_| serde::Error::syntax("Invalid valid hex."))
}
fn visit_string<E>(&mut self, value: String) -> Result<Self::Value, E> where E: serde::Error {
self.visit_str(value.as_ref())
}
}
deserializer.visit(HashVisitor)
}
}
impl FromJson for $from {
fn from_json(json: &Json) -> Self {
match *json {

View File

@ -55,6 +55,7 @@ extern crate secp256k1;
extern crate arrayvec;
extern crate elastic_array;
extern crate crossbeam;
extern crate serde;
/// TODO [Gav Wood] Please document me
pub mod standard;

View File

@ -23,6 +23,8 @@
use standard::*;
use from_json::*;
use rustc_serialize::hex::ToHex;
use serde;
macro_rules! impl_map_from {
($thing:ident, $from:ty, $to:ty) => {
@ -436,6 +438,17 @@ macro_rules! construct_uint {
}
}
impl serde::Serialize for $name {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: serde::Serializer {
let mut hex = "0x".to_owned();
let mut bytes = [0u8; 8 * $n_words];
self.to_bytes(&mut bytes);
hex.push_str(bytes.to_hex().as_ref());
serializer.visit_str(hex.as_ref())
}
}
impl From<u64> for $name {
fn from(value: u64) -> $name {
let mut ret = [0; $n_words];