From f31d42d0c58dfc50673e1bd2244fd155a769841b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 4 Nov 2016 17:35:02 +0100 Subject: [PATCH] Exposing engine extra info in block RPC (#3169) * Exposing extra info in RPC * Proper serialization and client trait API --- ethcore/src/client/client.rs | 14 +++- ethcore/src/client/test_client.rs | 12 +++ ethcore/src/client/traits.rs | 12 ++- ethcore/src/engines/basic_authority.rs | 2 +- ethcore/src/engines/mod.rs | 2 +- ethcore/src/ethereum/ethash.rs | 4 +- ethcore/src/types/ids.rs | 2 +- rpc/src/v1/impls/eth.rs | 110 +++++++++++++------------ rpc/src/v1/traits/eth.rs | 10 +-- rpc/src/v1/types/block.rs | 53 +++++++++++- rpc/src/v1/types/mod.rs.in | 2 +- 11 files changed, 153 insertions(+), 70 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index ef36d356c..777d561e3 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -65,7 +65,7 @@ use evm::{Factory as EvmFactory, Schedule}; use miner::{Miner, MinerService}; use snapshot::{self, io as snapshot_io}; use factory::Factories; -use rlp::{View, UntrustedRlp}; +use rlp::{decode, View, UntrustedRlp}; use state_db::StateDB; use rand::OsRng; @@ -1189,6 +1189,18 @@ impl BlockChainClient for Client { fn signing_network_id(&self) -> Option { self.engine.signing_network_id(&self.latest_env_info()) } + + fn block_extra_info(&self, id: BlockID) -> Option> { + self.block_header(id) + .map(|block| decode(&block)) + .map(|header| self.engine.extra_info(&header)) + } + + fn uncle_extra_info(&self, id: UncleID) -> Option> { + self.uncle(id) + .map(|block| BlockView::new(&block).header()) + .map(|header| self.engine.extra_info(&header)) + } } impl MiningBlockChainClient for Client { diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index e7731b73d..434edd3e8 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -38,6 +38,7 @@ use evm::{Factory as EvmFactory, VMType, Schedule}; use miner::{Miner, MinerService, TransactionImportResult}; use spec::Spec; use types::mode::Mode; +use views::BlockView; use verification::queue::QueueInfo; use block::{OpenBlock, SealedBlock}; @@ -417,6 +418,10 @@ impl BlockChainClient for TestBlockChainClient { None // Simple default. } + fn uncle_extra_info(&self, _id: UncleID) -> Option> { + None + } + fn transaction_receipt(&self, id: TransactionID) -> Option { self.receipts.read().get(&id).cloned() } @@ -459,6 +464,13 @@ impl BlockChainClient for TestBlockChainClient { self.block_hash(id).and_then(|hash| self.blocks.read().get(&hash).cloned()) } + fn block_extra_info(&self, id: BlockID) -> Option> { + self.block(id) + .map(|block| BlockView::new(&block).header()) + .map(|header| self.spec.engine.extra_info(&header)) + } + + fn block_status(&self, id: BlockID) -> BlockStatus { match id { BlockID::Number(number) if (number as usize) < self.blocks.read().len() => BlockStatus::InChain, diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 60be4ba1b..67092e986 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -29,13 +29,13 @@ use error::{ImportResult, CallError}; use receipt::LocalizedReceipt; use trace::LocalizedTrace; use evm::{Factory as EvmFactory, Schedule}; -use types::ids::*; -use types::trace_filter::Filter as TraceFilter; use executive::Executed; use env_info::LastHashes; -use types::call_analytics::CallAnalytics; use block_import_error::BlockImportError; use ipc::IpcConfig; +use types::ids::*; +use types::trace_filter::Filter as TraceFilter; +use types::call_analytics::CallAnalytics; use types::blockchain_info::BlockChainInfo; use types::block_status::BlockStatus; use types::mode::Mode; @@ -235,6 +235,12 @@ pub trait BlockChainClient : Sync + Send { /// Set the mode. fn set_mode(&self, mode: Mode); + + /// Returns engine-related extra info for `BlockID`. + fn block_extra_info(&self, id: BlockID) -> Option>; + + /// Returns engine-related extra info for `UncleID`. + fn uncle_extra_info(&self, id: UncleID) -> Option>; } /// Extended client interface used for mining diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 815d2b43a..5a55c6210 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -81,7 +81,7 @@ impl Engine for BasicAuthority { fn builtins(&self) -> &BTreeMap { &self.builtins } /// Additional engine-specific information for the user/developer concerning `header`. - fn extra_info(&self, _header: &Header) -> HashMap { hash_map!["signature".to_owned() => "TODO".to_owned()] } + fn extra_info(&self, _header: &Header) -> BTreeMap { map!["signature".to_owned() => "TODO".to_owned()] } fn schedule(&self, _env_info: &EnvInfo) -> Schedule { Schedule::new_homestead() diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 7af88790c..52812f45e 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -47,7 +47,7 @@ pub trait Engine : Sync + Send { fn seal_fields(&self) -> usize { 0 } /// Additional engine-specific information for the user/developer concerning `header`. - fn extra_info(&self, _header: &Header) -> HashMap { HashMap::new() } + fn extra_info(&self, _header: &Header) -> BTreeMap { BTreeMap::new() } /// Additional information. fn additional_params(&self) -> HashMap { HashMap::new() } diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index 7fbb408cc..f2468dc95 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -133,8 +133,8 @@ impl Engine for Ethash { } /// Additional engine-specific information for the user/developer concerning `header`. - fn extra_info(&self, header: &Header) -> HashMap { - hash_map!["nonce".to_owned() => format!("0x{}", header.nonce().hex()), "mixHash".to_owned() => format!("0x{}", header.mix_hash().hex())] + fn extra_info(&self, header: &Header) -> BTreeMap { + map!["nonce".to_owned() => format!("0x{}", header.nonce().hex()), "mixHash".to_owned() => format!("0x{}", header.mix_hash().hex())] } fn schedule(&self, env_info: &EnvInfo) -> Schedule { diff --git a/ethcore/src/types/ids.rs b/ethcore/src/types/ids.rs index d248a45bc..1fe81f392 100644 --- a/ethcore/src/types/ids.rs +++ b/ethcore/src/types/ids.rs @@ -55,7 +55,7 @@ pub struct TraceId { } /// Uniquely identifies Uncle. -#[derive(Debug, Binary)] +#[derive(Debug, PartialEq, Eq, Copy, Clone, Binary)] pub struct UncleID { /// Block id. pub block: BlockID, diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 18f2cc37c..a6dc86bc2 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -44,7 +44,7 @@ use ethcore::snapshot::SnapshotService; use self::ethash::SeedHashCompute; use v1::traits::Eth; use v1::types::{ - Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, + RichBlock, Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, CallRequest, Index, Filter, Log, Receipt, Work, H64 as RpcH64, H256 as RpcH256, H160 as RpcH160, U256 as RpcU256, }; @@ -53,6 +53,8 @@ use v1::helpers::dispatch::{default_gas_price, dispatch_transaction}; use v1::helpers::block_import::is_major_importing; use v1::helpers::auto_args::Trailing; +const EXTRA_INFO_PROOF: &'static str = "Object exists in in blockchain (fetched earlier), extra_info is always available if object exists; qed"; + /// Eth RPC options pub struct EthClientOptions { /// Returns receipt from pending blocks @@ -117,36 +119,39 @@ impl EthClient where } } - fn block(&self, id: BlockID, include_txs: bool) -> Result, Error> { + fn block(&self, id: BlockID, include_txs: bool) -> Result, Error> { let client = take_weak!(self.client); match (client.block(id.clone()), client.block_total_difficulty(id)) { (Some(bytes), Some(total_difficulty)) => { let block_view = BlockView::new(&bytes); let view = block_view.header_view(); - let block = Block { - hash: Some(view.sha3().into()), - size: Some(bytes.len().into()), - parent_hash: view.parent_hash().into(), - uncles_hash: view.uncles_hash().into(), - author: view.author().into(), - miner: view.author().into(), - state_root: view.state_root().into(), - transactions_root: view.transactions_root().into(), - receipts_root: view.receipts_root().into(), - number: Some(view.number().into()), - gas_used: view.gas_used().into(), - gas_limit: view.gas_limit().into(), - logs_bloom: view.log_bloom().into(), - timestamp: view.timestamp().into(), - difficulty: view.difficulty().into(), - total_difficulty: total_difficulty.into(), - seal_fields: view.seal().into_iter().map(|f| rlp::decode(&f)).map(Bytes::new).collect(), - uncles: block_view.uncle_hashes().into_iter().map(Into::into).collect(), - transactions: match include_txs { - true => BlockTransactions::Full(block_view.localized_transactions().into_iter().map(Into::into).collect()), - false => BlockTransactions::Hashes(block_view.transaction_hashes().into_iter().map(Into::into).collect()), + let block = RichBlock { + block: Block { + hash: Some(view.sha3().into()), + size: Some(bytes.len().into()), + parent_hash: view.parent_hash().into(), + uncles_hash: view.uncles_hash().into(), + author: view.author().into(), + miner: view.author().into(), + state_root: view.state_root().into(), + transactions_root: view.transactions_root().into(), + receipts_root: view.receipts_root().into(), + number: Some(view.number().into()), + gas_used: view.gas_used().into(), + gas_limit: view.gas_limit().into(), + logs_bloom: view.log_bloom().into(), + timestamp: view.timestamp().into(), + difficulty: view.difficulty().into(), + total_difficulty: total_difficulty.into(), + seal_fields: view.seal().into_iter().map(|f| rlp::decode(&f)).map(Bytes::new).collect(), + uncles: block_view.uncle_hashes().into_iter().map(Into::into).collect(), + transactions: match include_txs { + true => BlockTransactions::Full(block_view.localized_transactions().into_iter().map(Into::into).collect()), + false => BlockTransactions::Hashes(block_view.transaction_hashes().into_iter().map(Into::into).collect()), + }, + extra_data: Bytes::new(view.extra_data()), }, - extra_data: Bytes::new(view.extra_data()) + extra_info: client.block_extra_info(id.clone()).expect(EXTRA_INFO_PROOF), }; Ok(Some(block)) }, @@ -161,7 +166,7 @@ impl EthClient where } } - fn uncle(&self, id: UncleID) -> Result, Error> { + fn uncle(&self, id: UncleID) -> Result, Error> { let client = take_weak!(self.client); let uncle: BlockHeader = match client.uncle(id) { Some(rlp) => rlp::decode(&rlp), @@ -172,27 +177,30 @@ impl EthClient where None => { return Ok(None); } }; - let block = Block { - hash: Some(uncle.hash().into()), - size: None, - parent_hash: uncle.parent_hash().clone().into(), - uncles_hash: uncle.uncles_hash().clone().into(), - author: uncle.author().clone().into(), - miner: uncle.author().clone().into(), - state_root: uncle.state_root().clone().into(), - transactions_root: uncle.transactions_root().clone().into(), - number: Some(uncle.number().into()), - gas_used: uncle.gas_used().clone().into(), - gas_limit: uncle.gas_limit().clone().into(), - logs_bloom: uncle.log_bloom().clone().into(), - timestamp: uncle.timestamp().into(), - difficulty: uncle.difficulty().clone().into(), - total_difficulty: (uncle.difficulty().clone() + parent_difficulty).into(), - receipts_root: uncle.receipts_root().clone().into(), - extra_data: uncle.extra_data().clone().into(), - seal_fields: uncle.seal().clone().into_iter().map(|f| rlp::decode(&f)).map(Bytes::new).collect(), - uncles: vec![], - transactions: BlockTransactions::Hashes(vec![]), + let block = RichBlock { + block: Block { + hash: Some(uncle.hash().into()), + size: None, + parent_hash: uncle.parent_hash().clone().into(), + uncles_hash: uncle.uncles_hash().clone().into(), + author: uncle.author().clone().into(), + miner: uncle.author().clone().into(), + state_root: uncle.state_root().clone().into(), + transactions_root: uncle.transactions_root().clone().into(), + number: Some(uncle.number().into()), + gas_used: uncle.gas_used().clone().into(), + gas_limit: uncle.gas_limit().clone().into(), + logs_bloom: uncle.log_bloom().clone().into(), + timestamp: uncle.timestamp().into(), + difficulty: uncle.difficulty().clone().into(), + total_difficulty: (uncle.difficulty().clone() + parent_difficulty).into(), + receipts_root: uncle.receipts_root().clone().into(), + extra_data: uncle.extra_data().clone().into(), + seal_fields: uncle.seal().clone().into_iter().map(|f| rlp::decode(&f)).map(Bytes::new).collect(), + uncles: vec![], + transactions: BlockTransactions::Hashes(vec![]), + }, + extra_info: client.uncle_extra_info(id).expect(EXTRA_INFO_PROOF), }; Ok(Some(block)) } @@ -435,13 +443,13 @@ impl Eth for EthClient where } } - fn block_by_hash(&self, hash: RpcH256, include_txs: bool) -> Result, Error> { + fn block_by_hash(&self, hash: RpcH256, include_txs: bool) -> Result, Error> { try!(self.active()); self.block(BlockID::Hash(hash.into()), include_txs) } - fn block_by_number(&self, num: BlockNumber, include_txs: bool) -> Result, Error> { + fn block_by_number(&self, num: BlockNumber, include_txs: bool) -> Result, Error> { try!(self.active()); self.block(num.into(), include_txs) @@ -483,13 +491,13 @@ impl Eth for EthClient where } } - fn uncle_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> Result, Error> { + fn uncle_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> Result, Error> { try!(self.active()); self.uncle(UncleID { block: BlockID::Hash(hash.into()), position: index.value() }) } - fn uncle_by_block_number_and_index(&self, num: BlockNumber, index: Index) -> Result, Error> { + fn uncle_by_block_number_and_index(&self, num: BlockNumber, index: Index) -> Result, Error> { try!(self.active()); self.uncle(UncleID { block: num.into(), position: index.value() }) diff --git a/rpc/src/v1/traits/eth.rs b/rpc/src/v1/traits/eth.rs index 7d9aa47f3..49f433ffd 100644 --- a/rpc/src/v1/traits/eth.rs +++ b/rpc/src/v1/traits/eth.rs @@ -17,7 +17,7 @@ //! Eth rpc interface. use jsonrpc_core::*; -use v1::types::{Block, BlockNumber, Bytes, CallRequest, Filter, FilterChanges, Index}; +use v1::types::{RichBlock, BlockNumber, Bytes, CallRequest, Filter, FilterChanges, Index}; use v1::types::{Log, Receipt, SyncStatus, Transaction, Work}; use v1::types::{H64, H160, H256, U256}; @@ -68,11 +68,11 @@ build_rpc_trait! { /// Returns block with given hash. #[rpc(name = "eth_getBlockByHash")] - fn block_by_hash(&self, H256, bool) -> Result, Error>; + fn block_by_hash(&self, H256, bool) -> Result, Error>; /// Returns block with given number. #[rpc(name = "eth_getBlockByNumber")] - fn block_by_number(&self, BlockNumber, bool) -> Result, Error>; + fn block_by_number(&self, BlockNumber, bool) -> Result, Error>; /// Returns the number of transactions sent from given address at given time (block number). #[rpc(name = "eth_getTransactionCount")] @@ -128,11 +128,11 @@ build_rpc_trait! { /// Returns an uncles at given block and index. #[rpc(name = "eth_getUncleByBlockHashAndIndex")] - fn uncle_by_block_hash_and_index(&self, H256, Index) -> Result, Error>; + fn uncle_by_block_hash_and_index(&self, H256, Index) -> Result, Error>; /// Returns an uncles at given block and index. #[rpc(name = "eth_getUncleByBlockNumberAndIndex")] - fn uncle_by_block_number_and_index(&self, BlockNumber, Index) -> Result, Error>; + fn uncle_by_block_number_and_index(&self, BlockNumber, Index) -> Result, Error>; /// Returns available compilers. #[rpc(name = "eth_getCompilers")] diff --git a/rpc/src/v1/types/block.rs b/rpc/src/v1/types/block.rs index 53d8c3583..f52785e90 100644 --- a/rpc/src/v1/types/block.rs +++ b/rpc/src/v1/types/block.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::ops::Deref; +use std::collections::BTreeMap; use serde::{Serialize, Serializer}; use v1::types::{Bytes, Transaction, H160, H256, H2048, U256}; @@ -93,11 +95,45 @@ pub struct Block { pub size: Option, } +/// Block representation with additional info +#[derive(Debug)] +pub struct RichBlock { + /// Standard block + pub block: Block, + /// Engine-specific fields with additional description. + /// Should be included directly to serialized block object. + #[serde(skip_serializing)] + pub extra_info: BTreeMap, +} + +impl Deref for RichBlock { + type Target = Block; + fn deref(&self) -> &Self::Target { + &self.block + } +} + +impl Serialize for RichBlock { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer { + use serde_json::{to_value, Value}; + + let serialized = (to_value(&self.block), to_value(&self.extra_info)); + if let (Value::Object(mut block), Value::Object(extras)) = serialized { + // join two objects + block.extend(extras); + // and serialize + try!(block.serialize(serializer)); + } + Ok(()) + } +} + #[cfg(test)] mod tests { + use std::collections::BTreeMap; use serde_json; - use v1::types::{Transaction, H160, H256, H2048, Bytes, U256}; - use super::{Block, BlockTransactions}; + use v1::types::{Transaction, H64, H160, H256, H2048, Bytes, U256}; + use super::{Block, RichBlock, BlockTransactions}; #[test] fn test_serialize_block_transactions() { @@ -134,8 +170,17 @@ mod tests { transactions: BlockTransactions::Hashes(vec![].into()), size: Some(69.into()), }; + let serialized_block = serde_json::to_string(&block).unwrap(); + let rich_block = RichBlock { + block: block, + extra_info: map![ + "mixHash".into() => format!("0x{:?}", H256::default()), + "nonce".into() => format!("0x{:?}", H64::default()) + ], + }; + let serialized_rich_block = serde_json::to_string(&rich_block).unwrap(); - let serialized = serde_json::to_string(&block).unwrap(); - assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","author":"0x0000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","number":"0x0","gasUsed":"0x0","gasLimit":"0x0","extraData":"0x","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","difficulty":"0x0","totalDifficulty":"0x0","sealFields":["0x","0x"],"uncles":[],"transactions":[],"size":"0x45"}"#); + assert_eq!(serialized_block, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","author":"0x0000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","number":"0x0","gasUsed":"0x0","gasLimit":"0x0","extraData":"0x","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","difficulty":"0x0","totalDifficulty":"0x0","sealFields":["0x","0x"],"uncles":[],"transactions":[],"size":"0x45"}"#); + assert_eq!(serialized_rich_block, r#"{"author":"0x0000000000000000000000000000000000000000","difficulty":"0x0","extraData":"0x","gasLimit":"0x0","gasUsed":"0x0","hash":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","sealFields":["0x","0x"],"sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","size":"0x45","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","totalDifficulty":"0x0","transactions":[],"transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","uncles":[]}"#); } } diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index 002fcecca..cba7487fc 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -35,7 +35,7 @@ mod work; mod histogram; pub use self::bytes::Bytes; -pub use self::block::{Block, BlockTransactions}; +pub use self::block::{RichBlock, Block, BlockTransactions}; pub use self::block_number::BlockNumber; pub use self::call_request::CallRequest; pub use self::confirmations::{ConfirmationPayload, ConfirmationRequest, TransactionModification};