diff --git a/Cargo.lock b/Cargo.lock index f4df0b2a..d7849c79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -329,7 +329,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "jsonrpc-core" version = "4.0.0" -source = "git+https://github.com/ethcore/jsonrpc.git#140257f1a726e9190bdaafeb4625b2a5400de4da" +source = "git+https://github.com/ethcore/jsonrpc.git#ce49b762bc3e005f0cf549e1d98fc51b47f2aa54" dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -341,7 +341,7 @@ dependencies = [ [[package]] name = "jsonrpc-http-server" version = "6.1.1" -source = "git+https://github.com/ethcore/jsonrpc.git#140257f1a726e9190bdaafeb4625b2a5400de4da" +source = "git+https://github.com/ethcore/jsonrpc.git#ce49b762bc3e005f0cf549e1d98fc51b47f2aa54" dependencies = [ "hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)", "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", @@ -352,7 +352,7 @@ dependencies = [ [[package]] name = "jsonrpc-macros" version = "0.1.0" -source = "git+https://github.com/ethcore/jsonrpc.git#140257f1a726e9190bdaafeb4625b2a5400de4da" +source = "git+https://github.com/ethcore/jsonrpc.git#ce49b762bc3e005f0cf549e1d98fc51b47f2aa54" dependencies = [ "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -809,6 +809,7 @@ version = "0.1.0" dependencies = [ "chain 0.1.0", "db 0.1.0", + "ethcore-devtools 1.3.0", "jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)", "jsonrpc-macros 0.1.0 (git+https://github.com/ethcore/jsonrpc.git)", @@ -818,6 +819,7 @@ dependencies = [ "p2p 0.1.0", "primitives 0.1.0", "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "script 0.1.0", "serde 0.8.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen 0.8.9 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -826,6 +828,7 @@ dependencies = [ "sync 0.1.0", "test-data 0.1.0", "tokio-core 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "verification 0.1.0", ] [[package]] diff --git a/db/src/storage.rs b/db/src/storage.rs index 78f51ca1..a7c5d6d5 100644 --- a/db/src/storage.rs +++ b/db/src/storage.rs @@ -49,6 +49,9 @@ pub trait Store: AsSubstore { /// get best header fn best_header(&self) -> Option; + + /// get blockchain difficulty + fn difficulty(&self) -> f64; } /// Allows casting Arc to reference to any substore type @@ -457,14 +460,6 @@ impl Storage { } } - - pub fn difficulty(&self) -> f64 { - self.best_hash() - .and_then(|h| self.block_header_by_hash(&h)) - .map(|header| header.bits.to_f64()) - .unwrap_or(1.0f64) - } - } impl BlockHeaderProvider for Storage { @@ -747,6 +742,13 @@ impl Store for Storage { |bb| Some(self.block_header_by_hash(&bb.hash).expect("Best block exists but no such header. Race condition?")), ) } + + fn difficulty(&self) -> f64 { + self.best_hash() + .and_then(|h| self.block_header_by_hash(&h)) + .map(|header| header.bits.to_f64()) + .unwrap_or(1.0f64) + } } #[cfg(test)] diff --git a/db/src/test_storage.rs b/db/src/test_storage.rs index c9aab2ce..c5b3928f 100644 --- a/db/src/test_storage.rs +++ b/db/src/test_storage.rs @@ -212,5 +212,9 @@ impl Store for TestStorage { |bb| Some(self.block_header(BlockRef::Hash(bb.hash.clone())).expect("Best block exists but no such header. Race condition?")) ) } + + fn difficulty(&self) -> f64 { + unimplemented!() + } } diff --git a/primitives/src/bytes.rs b/primitives/src/bytes.rs index 37958e75..cccfb837 100644 --- a/primitives/src/bytes.rs +++ b/primitives/src/bytes.rs @@ -12,6 +12,10 @@ impl Bytes { pub fn new_with_len(len: usize) -> Self { Bytes(vec![0; len]) } + + pub fn take(self) -> Vec { + self.0 + } } impl<'a> From<&'a [u8]> for Bytes { diff --git a/primitives/src/uint.rs b/primitives/src/uint.rs index 9ae77bc8..1e541629 100644 --- a/primitives/src/uint.rs +++ b/primitives/src/uint.rs @@ -884,7 +884,6 @@ macro_rules! construct_uint { impl fmt::LowerHex for $name { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let &$name(ref data) = self; - try!(write!(f, "0x")); let mut latch = false; for ch in data.iter().rev() { for x in 0..16 { diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 0f4a1607..d1d14c0d 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -17,6 +17,7 @@ jsonrpc-core = { git = "https://github.com/ethcore/jsonrpc.git" } jsonrpc-macros = { git = "https://github.com/ethcore/jsonrpc.git" } jsonrpc-http-server = { git = "https://github.com/ethcore/jsonrpc.git" } +ethcore-devtools = { path = "../devtools" } sync = { path = "../sync" } serialization = { path = "../serialization" } chain = { path = "../chain" } @@ -26,6 +27,8 @@ network = { path = "../network" } db = { path = "../db" } test-data = { path = "../test-data" } miner = { path = "../miner" } +verification = { path = "../verification" } +script = { path = "../script" } [build-dependencies] serde_codegen = { version = "0.8.0", optional = true } diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index a74c2be7..816d4e4c 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -20,6 +20,10 @@ extern crate db; #[cfg(test)] extern crate test_data; extern crate miner; +extern crate verification; +#[cfg(test)] +extern crate ethcore_devtools as devtools; +extern crate script; pub mod v1; pub mod rpc_server; diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index 74ac6c57..cddd2f77 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -3,11 +3,27 @@ mod codes { // NOTE [ToDr] Codes from [-32099, -32000] pub const EXECUTION_ERROR: i64 = -32015; + pub const TRANSACTION_NOT_FOUND: i64 = -32096; + pub const TRANSACTION_OUTPUT_NOT_FOUND: i64 = -32097; + pub const TRANSACTION_OF_SIDE_BRANCH: i64 = -32098; + pub const BLOCK_NOT_FOUND: i64 = -32099; } use std::fmt; use jsonrpc_core::{Error, ErrorCode, Value}; +macro_rules! rpc_unimplemented { + () => (Err(::v1::helpers::errors::unimplemented(None))) +} + +pub fn unimplemented(details: Option) -> Error { + Error { + code: ErrorCode::InternalError, + message: "This request is not implemented yet. Please create an issue on Github repo.".into(), + data: details.map(Value::String), + } +} + pub fn invalid_params(param: &str, details: T) -> Error { Error { code: ErrorCode::InvalidParams, @@ -24,3 +40,42 @@ pub fn execution(data: T) -> Error { } } +pub fn block_not_found(data: T) -> Error { + Error { + code: ErrorCode::ServerError(codes::BLOCK_NOT_FOUND), + message: "Block with given hash is not found".into(), + data: Some(Value::String(format!("{:?}", data))), + } +} + +pub fn block_at_height_not_found(data: T) -> Error { + Error { + code: ErrorCode::ServerError(codes::BLOCK_NOT_FOUND), + message: "Block at given height is not found".into(), + data: Some(Value::String(format!("{:?}", data))), + } +} + +pub fn transaction_not_found(data: T) -> Error { + Error { + code: ErrorCode::ServerError(codes::TRANSACTION_NOT_FOUND), + message: "Transaction with given hash is not found".into(), + data: Some(Value::String(format!("{:?}", data))), + } +} + +pub fn transaction_output_not_found(data: T) -> Error { + Error { + code: ErrorCode::ServerError(codes::TRANSACTION_OUTPUT_NOT_FOUND), + message: "Transaction output is not found".into(), + data: Some(Value::String(format!("{:?}", data))), + } +} + +pub fn transaction_of_side_branch(data: T) -> Error { + Error { + code: ErrorCode::ServerError(codes::TRANSACTION_OF_SIDE_BRANCH), + message: "Transaction is of side branch".into(), + data: Some(Value::String(format!("{:?}", data))), + } +} diff --git a/rpc/src/v1/helpers/mod.rs b/rpc/src/v1/helpers/mod.rs index 629e98fb..45112cb7 100644 --- a/rpc/src/v1/helpers/mod.rs +++ b/rpc/src/v1/helpers/mod.rs @@ -1 +1,2 @@ +#[macro_use] pub mod errors; diff --git a/rpc/src/v1/impls/blockchain.rs b/rpc/src/v1/impls/blockchain.rs new file mode 100644 index 00000000..bfac0b40 --- /dev/null +++ b/rpc/src/v1/impls/blockchain.rs @@ -0,0 +1,518 @@ +use v1::traits::BlockChain; +use v1::types::{GetBlockResponse, VerboseBlock, RawBlock}; +use v1::types::{GetTxOutResponse, TxOutScriptPubKey}; +use v1::types::GetTxOutSetInfoResponse; +use v1::types::H256; +use v1::types::U256; +use v1::types::ScriptType; +use v1::helpers::errors::{block_not_found, block_at_height_not_found, transaction_not_found, + transaction_output_not_found, transaction_of_side_branch}; +use jsonrpc_macros::Trailing; +use jsonrpc_core::Error; +use db; +use script::Script; +use chain::OutPoint; +use verification; +use ser::serialize; +use primitives::hash::H256 as GlobalH256; + + +pub struct BlockChainClient { + core: T, +} + +pub trait BlockChainClientCoreApi: Send + Sync + 'static { + fn best_block_hash(&self) -> GlobalH256; + fn block_hash(&self, height: u32) -> Option; + fn difficulty(&self) -> f64; + fn raw_block(&self, hash: GlobalH256) -> Option; + fn verbose_block(&self, hash: GlobalH256) -> Option; + fn verbose_transaction_out(&self, prev_out: OutPoint) -> Result; +} + +pub struct BlockChainClientCore { + storage: db::SharedStore, +} + +impl BlockChainClientCore { + pub fn new(storage: db::SharedStore) -> Self { + assert!(storage.best_block().is_some()); + + BlockChainClientCore { + storage: storage, + } + } +} + +impl BlockChainClientCoreApi for BlockChainClientCore { + fn best_block_hash(&self) -> GlobalH256 { + self.storage.best_block().expect("storage with genesis block required").hash + } + + fn block_hash(&self, height: u32) -> Option { + self.storage.block_hash(height) + } + + fn difficulty(&self) -> f64 { + self.storage.difficulty() + } + + fn raw_block(&self, hash: GlobalH256) -> Option { + self.storage.block(hash.into()) + .map(|block| { + serialize(&block).into() + }) + } + + fn verbose_block(&self, hash: GlobalH256) -> Option { + self.storage.block(hash.into()) + .map(|block| { + let block: db::IndexedBlock = block.into(); + let height = self.storage.block_number(block.hash()); + let confirmations = match height { + Some(block_number) => (self.storage.best_block().expect("genesis block is required").number - block_number + 1) as i64, + None => -1, + }; + let block_size = block.size(); + let median_time = verification::ChainVerifier::median_timestamp(self.storage.as_block_header_provider(), &block.header.raw); + VerboseBlock { + confirmations: confirmations, + size: block_size as u32, + strippedsize: block_size as u32, // TODO: segwit + weight: block_size as u32, // TODO: segwit + height: height, + mediantime: median_time, + difficulty: block.header.raw.bits.to_f64(), + chainwork: U256::default(), // TODO: read from storage + previousblockhash: Some(block.header.raw.previous_header_hash.clone().into()), + nextblockhash: height.and_then(|h| self.storage.block_hash(h + 1).map(|h| h.into())), + bits: block.header.raw.bits.into(), + hash: block.hash().clone().into(), + merkleroot: block.header.raw.merkle_root_hash.clone().into(), + nonce: block.header.raw.nonce, + time: block.header.raw.time, + tx: block.transactions.into_iter().map(|t| t.hash.into()).collect(), + version: block.header.raw.version, + version_hex: format!("{:x}", &block.header.raw.version), + } + }) + } + + fn verbose_transaction_out(&self, prev_out: OutPoint) -> Result { + let transaction = match self.storage.transaction(&prev_out.hash) { + Some(transaction) => transaction, + // no transaction => no response + None => return Err(transaction_not_found(prev_out.hash)), + }; + + if prev_out.index >= transaction.outputs.len() as u32 { + return Err(transaction_output_not_found(prev_out)); + } + + let meta = match self.storage.transaction_meta(&prev_out.hash) { + Some(meta) => meta, + // not in the main branch => no response + None => return Err(transaction_of_side_branch(prev_out.hash)), + }; + + let block_header = match self.storage.block_header(meta.height().into()) { + Some(block_header) => block_header, + // this is possible during reorgs + None => return Err(transaction_not_found(prev_out.hash)), + }; + + let best_block = self.storage.best_block().expect("storage with genesis block is required"); + if best_block.number < meta.height() { + // this is possible during reorgs + return Err(transaction_not_found(prev_out.hash)); + } + + let ref script_bytes = transaction.outputs[prev_out.index as usize].script_pubkey; + let script: Script = script_bytes.clone().into(); + let script_asm = format!("{}", script); + + Ok(GetTxOutResponse { + bestblock: block_header.hash().into(), + confirmations: best_block.number - meta.height() + 1, + value: 0.00000001f64 * (transaction.outputs[prev_out.index as usize].value as f64), + script_pub_key: TxOutScriptPubKey { + asm: script_asm, + hex: script_bytes.clone().into(), + req_sigs: 0, // TODO + script_type: ScriptType::NonStandard, // TODO + addresses: vec![], + }, + version: transaction.version, + coinbase: transaction.is_coinbase(), + }) + } +} + +impl BlockChainClient where T: BlockChainClientCoreApi { + pub fn new(core: T) -> Self { + BlockChainClient { + core: core, + } + } +} + +impl BlockChain for BlockChainClient where T: BlockChainClientCoreApi { + fn best_block_hash(&self) -> Result { + Ok(self.core.best_block_hash().reversed().into()) + } + + fn block_hash(&self, height: u32) -> Result { + self.core.block_hash(height) + .map(|h| h.reversed().into()) + .ok_or(block_at_height_not_found(height)) + } + + fn difficulty(&self) -> Result { + Ok(self.core.difficulty()) + } + + fn block(&self, hash: H256, verbose: Trailing) -> Result { + let global_hash: GlobalH256 = hash.clone().into(); + if verbose.0 { + let verbose_block = self.core.verbose_block(global_hash.reversed()); + if let Some(mut verbose_block) = verbose_block { + verbose_block.previousblockhash = verbose_block.previousblockhash.map(|h| h.reversed()); + verbose_block.nextblockhash = verbose_block.nextblockhash.map(|h| h.reversed()); + verbose_block.hash = verbose_block.hash.reversed(); + verbose_block.merkleroot = verbose_block.merkleroot.reversed(); + verbose_block.tx = verbose_block.tx.into_iter().map(|h| h.reversed()).collect(); + Some(GetBlockResponse::Verbose(verbose_block)) + } else { + None + } + } else { + self.core.raw_block(global_hash.reversed()) + .map(|block| GetBlockResponse::Raw(block)) + } + .ok_or(block_not_found(hash)) + } + + fn transaction_out(&self, transaction_hash: H256, out_index: u32, _include_mempool: Trailing) -> Result { + self.core.verbose_transaction_out(OutPoint { hash: transaction_hash.into(), index: out_index }) + .map(|mut response| { + response.bestblock = response.bestblock.reversed(); + response + }) + } + + fn transaction_out_set_info(&self) -> Result { + rpc_unimplemented!() + } +} + +#[cfg(test)] +pub mod tests { + use std::sync::Arc; + use devtools::RandomTempPath; + use jsonrpc_core::{IoHandler, GenericIoHandler}; + use jsonrpc_core::Error; + use db::{self, BlockStapler}; + use primitives::bytes::Bytes as GlobalBytes; + use primitives::hash::H256 as GlobalH256; + use v1::types::{VerboseBlock, RawBlock}; + use v1::traits::BlockChain; + use v1::types::GetTxOutResponse; + use v1::helpers::errors::block_not_found; + use chain::OutPoint; + use test_data; + use super::*; + + #[derive(Default)] + struct SuccessBlockChainClientCore; + #[derive(Default)] + struct ErrorBlockChainClientCore; + + impl BlockChainClientCoreApi for SuccessBlockChainClientCore { + fn best_block_hash(&self) -> GlobalH256 { + test_data::genesis().hash() + } + + fn block_hash(&self, _height: u32) -> Option { + Some(test_data::genesis().hash()) + } + + fn difficulty(&self) -> f64 { + 1f64 + } + + fn raw_block(&self, _hash: GlobalH256) -> Option { + let b2_bytes: GlobalBytes = "010000004860eb18bf1b1620e37e9490fc8a427514416fd75159ab86688e9a8300000000d5fdcc541e25de1c7a5addedf24858b8bb665c9f36ef744ee42c316022c90f9bb0bc6649ffff001d08d2bd610101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d010bffffffff0100f2052a010000004341047211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073dee6c89064984f03385237d92167c13e236446b417ab79a0fcae412ae3316b77ac00000000".into(); + Some(RawBlock::from(b2_bytes)) + } + + fn verbose_block(&self, _hash: GlobalH256) -> Option { + // https://blockexplorer.com/block/000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd + // https://blockchain.info/ru/block/000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd + // https://webbtc.com/block/000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd.json + Some(VerboseBlock { + hash: "bddd99ccfda39da1b108ce1a5d70038d0a967bacb68b6b63065f626a00000000".into(), + confirmations: 1, // h2 + size: 215, + strippedsize: 215, + weight: 215, + height: Some(2), + version: 1, + version_hex: "1".to_owned(), + merkleroot: "d5fdcc541e25de1c7a5addedf24858b8bb665c9f36ef744ee42c316022c90f9b".into(), + tx: vec!["d5fdcc541e25de1c7a5addedf24858b8bb665c9f36ef744ee42c316022c90f9b".into()], + time: 1231469744, + mediantime: None, + nonce: 1639830024, + bits: 486604799, + difficulty: 1.0, + chainwork: 0.into(), + previousblockhash: Some("4860eb18bf1b1620e37e9490fc8a427514416fd75159ab86688e9a8300000000".into()), + nextblockhash: None, + }) + } + + fn verbose_transaction_out(&self, _prev_out: OutPoint) -> Result { + Ok(GetTxOutResponse::default()) // TODO: non-default + } + } + + impl BlockChainClientCoreApi for ErrorBlockChainClientCore { + fn best_block_hash(&self) -> GlobalH256 { + test_data::genesis().hash() + } + + fn block_hash(&self, _height: u32) -> Option { + None + } + + fn difficulty(&self) -> f64 { + 1f64 + } + + fn raw_block(&self, _hash: GlobalH256) -> Option { + None + } + + fn verbose_block(&self, _hash: GlobalH256) -> Option { + None + } + + fn verbose_transaction_out(&self, prev_out: OutPoint) -> Result { + Err(block_not_found(prev_out.hash)) + } + } + + #[test] + fn best_block_hash_success() { + let client = BlockChainClient::new(SuccessBlockChainClientCore::default()); + let handler = IoHandler::new(); + handler.add_delegate(client.to_delegate()); + + let sample = handler.handle_request_sync(&(r#" + { + "jsonrpc": "2.0", + "method": "getbestblockhash", + "params": [], + "id": 1 + }"#)).unwrap(); + + // direct hash is 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000 + // but client expects reverse hash + assert_eq!(&sample, r#"{"jsonrpc":"2.0","result":"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f","id":1}"#); + } + + #[test] + fn block_hash_success() { + let client = BlockChainClient::new(SuccessBlockChainClientCore::default()); + let handler = IoHandler::new(); + handler.add_delegate(client.to_delegate()); + + let sample = handler.handle_request_sync(&(r#" + { + "jsonrpc": "2.0", + "method": "getblockhash", + "params": [0], + "id": 1 + }"#)).unwrap(); + + // direct hash is 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000 + // but client expects reverse hash + assert_eq!(&sample, r#"{"jsonrpc":"2.0","result":"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f","id":1}"#); + } + + #[test] + fn block_hash_error() { + let client = BlockChainClient::new(ErrorBlockChainClientCore::default()); + let handler = IoHandler::new(); + handler.add_delegate(client.to_delegate()); + + let sample = handler.handle_request_sync(&(r#" + { + "jsonrpc": "2.0", + "method": "getblockhash", + "params": [0], + "id": 1 + }"#)).unwrap(); + + assert_eq!(&sample, r#"{"jsonrpc":"2.0","error":{"code":-32099,"message":"Block at given height is not found","data":"0"},"id":1}"#); + } + + #[test] + fn difficulty_success() { + let client = BlockChainClient::new(SuccessBlockChainClientCore::default()); + let handler = IoHandler::new(); + handler.add_delegate(client.to_delegate()); + + let sample = handler.handle_request_sync(&(r#" + { + "jsonrpc": "2.0", + "method": "getdifficulty", + "params": [], + "id": 1 + }"#)).unwrap(); + + assert_eq!(&sample, r#"{"jsonrpc":"2.0","result":1.0,"id":1}"#); + } + + #[test] + fn verbose_block_contents() { + let path = RandomTempPath::create_dir(); + let storage = Arc::new(db::Storage::new(path.as_path()).unwrap()); + storage.insert_block(&test_data::genesis()).expect("no error"); + storage.insert_block(&test_data::block_h1()).expect("no error"); + storage.insert_block(&test_data::block_h2()).expect("no error"); + + let core = BlockChainClientCore::new(storage); + + // get info on block #1: + // https://blockexplorer.com/block/00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048 + // https://blockchain.info/block/00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048 + // https://webbtc.com/block/00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048.json + let verbose_block = core.verbose_block("4860eb18bf1b1620e37e9490fc8a427514416fd75159ab86688e9a8300000000".into()); + assert_eq!(verbose_block, Some(VerboseBlock { + hash: "4860eb18bf1b1620e37e9490fc8a427514416fd75159ab86688e9a8300000000".into(), + confirmations: 2, // h1 + h2 + size: 215, + strippedsize: 215, + weight: 215, + height: Some(1), + version: 1, + version_hex: "1".to_owned(), + merkleroot: "982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e".into(), + tx: vec!["982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e".into()], + time: 1231469665, + mediantime: None, + nonce: 2573394689, + bits: 486604799, + difficulty: 1.0, + chainwork: 0.into(), + previousblockhash: Some("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000".into()), + nextblockhash: Some("bddd99ccfda39da1b108ce1a5d70038d0a967bacb68b6b63065f626a00000000".into()), + })); + + // get info on block #2: + // https://blockexplorer.com/block/000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd + // https://blockchain.info/ru/block/000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd + // https://webbtc.com/block/000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd.json + let verbose_block = core.verbose_block("bddd99ccfda39da1b108ce1a5d70038d0a967bacb68b6b63065f626a00000000".into()); + assert_eq!(verbose_block, Some(VerboseBlock { + hash: "bddd99ccfda39da1b108ce1a5d70038d0a967bacb68b6b63065f626a00000000".into(), + confirmations: 1, // h2 + size: 215, + strippedsize: 215, + weight: 215, + height: Some(2), + version: 1, + version_hex: "1".to_owned(), + merkleroot: "d5fdcc541e25de1c7a5addedf24858b8bb665c9f36ef744ee42c316022c90f9b".into(), + tx: vec!["d5fdcc541e25de1c7a5addedf24858b8bb665c9f36ef744ee42c316022c90f9b".into()], + time: 1231469744, + mediantime: None, + nonce: 1639830024, + bits: 486604799, + difficulty: 1.0, + chainwork: 0.into(), + previousblockhash: Some("4860eb18bf1b1620e37e9490fc8a427514416fd75159ab86688e9a8300000000".into()), + nextblockhash: None, + })); + } + + #[test] + fn raw_block_success() { + let client = BlockChainClient::new(SuccessBlockChainClientCore::default()); + let handler = IoHandler::new(); + handler.add_delegate(client.to_delegate()); + + let expected = r#"{"jsonrpc":"2.0","result":"010000004860eb18bf1b1620e37e9490fc8a427514416fd75159ab86688e9a8300000000d5fdcc541e25de1c7a5addedf24858b8bb665c9f36ef744ee42c316022c90f9bb0bc6649ffff001d08d2bd610101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d010bffffffff0100f2052a010000004341047211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073dee6c89064984f03385237d92167c13e236446b417ab79a0fcae412ae3316b77ac00000000","id":1}"#; + + let sample = handler.handle_request_sync(&(r#" + { + "jsonrpc": "2.0", + "method": "getblock", + "params": ["000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd", false], + "id": 1 + }"#)).unwrap(); + assert_eq!(&sample, expected); + + // try without optional parameter + let sample = handler.handle_request_sync(&(r#" + { + "jsonrpc": "2.0", + "method": "getblock", + "params": ["000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd"], + "id": 1 + }"#)).unwrap(); + assert_eq!(&sample, expected); + } + + #[test] + fn raw_block_error() { + let client = BlockChainClient::new(ErrorBlockChainClientCore::default()); + let handler = IoHandler::new(); + handler.add_delegate(client.to_delegate()); + + let sample = handler.handle_request_sync(&(r#" + { + "jsonrpc": "2.0", + "method": "getblock", + "params": ["000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd", false], + "id": 1 + }"#)).unwrap(); + + assert_eq!(&sample, r#"{"jsonrpc":"2.0","error":{"code":-32099,"message":"Block with given hash is not found","data":"000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd"},"id":1}"#); + } + + #[test] + fn verbose_block_success() { + let client = BlockChainClient::new(SuccessBlockChainClientCore::default()); + let handler = IoHandler::new(); + handler.add_delegate(client.to_delegate()); + + let sample = handler.handle_request_sync(&(r#" + { + "jsonrpc": "2.0", + "method": "getblock", + "params": ["000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd",true], + "id": 1 + }"#)).unwrap(); + + assert_eq!(&sample, r#"{"jsonrpc":"2.0","result":{"bits":486604799,"chainwork":"","confirmations":1,"difficulty":1.0,"hash":"000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd","height":2,"mediantime":null,"merkleroot":"9b0fc92260312ce44e74ef369f5c66bbb85848f2eddd5a7a1cde251e54ccfdd5","nextblockhash":null,"nonce":1639830024,"previousblockhash":"00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048","size":215,"strippedsize":215,"time":1231469744,"tx":["9b0fc92260312ce44e74ef369f5c66bbb85848f2eddd5a7a1cde251e54ccfdd5"],"version":1,"versionHex":"1","weight":215},"id":1}"#); + } + + #[test] + fn verbose_block_error() { + let client = BlockChainClient::new(ErrorBlockChainClientCore::default()); + let handler = IoHandler::new(); + handler.add_delegate(client.to_delegate()); + + let sample = handler.handle_request_sync(&(r#" + { + "jsonrpc": "2.0", + "method": "getblock", + "params": ["000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd", true], + "id": 1 + }"#)).unwrap(); + + assert_eq!(&sample, r#"{"jsonrpc":"2.0","error":{"code":-32099,"message":"Block with given hash is not found","data":"000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd"},"id":1}"#); + } +} diff --git a/rpc/src/v1/impls/mod.rs b/rpc/src/v1/impls/mod.rs index d079c545..724d4722 100644 --- a/rpc/src/v1/impls/mod.rs +++ b/rpc/src/v1/impls/mod.rs @@ -1,5 +1,7 @@ +mod blockchain; mod miner; mod raw; +pub use self::blockchain::{BlockChainClient, BlockChainClientCore}; pub use self::miner::{MinerClient, MinerClientCore}; pub use self::raw::{RawClient, RawClientCore}; diff --git a/rpc/src/v1/traits/blockchain.rs b/rpc/src/v1/traits/blockchain.rs new file mode 100644 index 00000000..2b75ddd0 --- /dev/null +++ b/rpc/src/v1/traits/blockchain.rs @@ -0,0 +1,32 @@ +use jsonrpc_macros::Trailing; +use jsonrpc_core::Error; + +use v1::types::H256; +use v1::types::GetBlockResponse; +use v1::types::GetTxOutResponse; +use v1::types::GetTxOutSetInfoResponse; + + +build_rpc_trait! { + /// Parity-bitcoin blockchain data interface. + pub trait BlockChain { + /// Get hash of best block. + #[rpc(name = "getbestblockhash")] + fn best_block_hash(&self) -> Result; + /// Get hash of block at given height. + #[rpc(name = "getblockhash")] + fn block_hash(&self, u32) -> Result; + /// Get proof-of-work difficulty as a multiple of the minimum difficulty + #[rpc(name = "getdifficulty")] + fn difficulty(&self) -> Result; + /// Get information on given block. + #[rpc(name = "getblock")] + fn block(&self, H256, Trailing) -> Result; + /// Get details about an unspent transaction output. + #[rpc(name = "gettxout")] + fn transaction_out(&self, H256, u32, Trailing) -> Result; + /// Get statistics about the unspent transaction output set. + #[rpc(name = "gettxoutsetinfo")] + fn transaction_out_set_info(&self) -> Result; + } +} diff --git a/rpc/src/v1/traits/mod.rs b/rpc/src/v1/traits/mod.rs index dca6e7db..6311c282 100644 --- a/rpc/src/v1/traits/mod.rs +++ b/rpc/src/v1/traits/mod.rs @@ -1,5 +1,7 @@ +mod blockchain; mod miner; mod raw; +pub use self::blockchain::BlockChain; pub use self::miner::Miner; pub use self::raw::Raw; \ No newline at end of file diff --git a/rpc/src/v1/types/bytes.rs b/rpc/src/v1/types/bytes.rs index 2cb845ed..b7bd65d8 100644 --- a/rpc/src/v1/types/bytes.rs +++ b/rpc/src/v1/types/bytes.rs @@ -2,6 +2,7 @@ use rustc_serialize::hex::{ToHex, FromHex}; use serde::{Serialize, Serializer, Deserialize, Deserializer, Error}; use serde::de::Visitor; +use primitives::bytes::Bytes as GlobalBytes; /// Wrapper structure around vector of bytes. #[derive(Debug, PartialEq, Eq, Default, Hash, Clone)] @@ -19,6 +20,12 @@ impl Bytes { } } +impl From for Bytes { + fn from(v: GlobalBytes) -> Self { + Bytes(v.take()) + } +} + impl From> for Bytes { fn from(bytes: Vec) -> Bytes { Bytes(bytes) diff --git a/rpc/src/v1/types/get_block_response.rs b/rpc/src/v1/types/get_block_response.rs new file mode 100644 index 00000000..864e9d0b --- /dev/null +++ b/rpc/src/v1/types/get_block_response.rs @@ -0,0 +1,148 @@ +use serde::{Serialize, Serializer}; +use super::hash::H256; +use super::uint::U256; +use super::raw_block::RawBlock; + +/// Response to getblock RPC request +#[derive(Debug)] +pub enum GetBlockResponse { + /// When asking for short response + Raw(RawBlock), + /// When asking for verbose response + Verbose(VerboseBlock), +} + +/// Verbose block information +#[derive(Debug, Default, Serialize, Deserialize, PartialEq)] +pub struct VerboseBlock { + /// Block hash + pub hash: H256, + /// Number of confirmations. -1 if block is on the side chain + pub confirmations: i64, + /// Block size + pub size: u32, + /// Block size, excluding witness data + pub strippedsize: u32, + /// Block weight + pub weight: u32, + /// Block height + /// TODO: bitcoind always returns value, but we hold this value for main chain blocks only + pub height: Option, + /// Block version + pub version: u32, + /// Block version as hex + #[serde(rename = "versionHex")] + pub version_hex: String, + /// Merkle root of this block + pub merkleroot: H256, + /// Transactions ids + pub tx: Vec, + /// Block time in seconds since epoch (Jan 1 1970 GMT) + pub time: u32, + /// Median block time in seconds since epoch (Jan 1 1970 GMT) + /// TODO: bitcoind always returns value, but we can calculate this only if height(block) > 2 + pub mediantime: Option, + /// Block nonce + pub nonce: u32, + /// Block nbits + pub bits: u32, + /// Block difficulty + pub difficulty: f64, + /// Expected number of hashes required to produce the chain up to this block (in hex) + pub chainwork: U256, + /// Hash of previous block + pub previousblockhash: Option, + /// Hash of next block + pub nextblockhash: Option, +} + +impl Serialize for GetBlockResponse { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer { + match *self { + GetBlockResponse::Raw(ref raw_block) => raw_block.serialize(serializer), + GetBlockResponse::Verbose(ref verbose_block) => verbose_block.serialize(serializer), + } + } +} + +#[cfg(test)] +mod tests { + use super::super::bytes::Bytes; + use super::super::hash::H256; + use super::super::uint::U256; + use serde_json; + use super::*; + + #[test] + fn verbose_block_serialize() { + let block = VerboseBlock::default(); + assert_eq!(serde_json::to_string(&block).unwrap(), r#"{"hash":"0000000000000000000000000000000000000000000000000000000000000000","confirmations":0,"size":0,"strippedsize":0,"weight":0,"height":null,"version":0,"versionHex":"","merkleroot":"0000000000000000000000000000000000000000000000000000000000000000","tx":[],"time":0,"mediantime":null,"nonce":0,"bits":0,"difficulty":0.0,"chainwork":"","previousblockhash":null,"nextblockhash":null}"#); + + let block = VerboseBlock { + hash: H256::from(1), + confirmations: -1, + size: 500000, + strippedsize: 444444, + weight: 5236235, + height: Some(3513513), + version: 1, + version_hex: "01".to_owned(), + merkleroot: H256::from(2), + tx: vec![H256::from(3), H256::from(4)], + time: 111, + mediantime: Some(100), + nonce: 124, + bits: 13513, + difficulty: 555.555, + chainwork: U256::from(3), + previousblockhash: Some(H256::from(4)), + nextblockhash: Some(H256::from(5)), + }; + assert_eq!(serde_json::to_string(&block).unwrap(), r#"{"hash":"0100000000000000000000000000000000000000000000000000000000000000","confirmations":-1,"size":500000,"strippedsize":444444,"weight":5236235,"height":3513513,"version":1,"versionHex":"01","merkleroot":"0200000000000000000000000000000000000000000000000000000000000000","tx":["0300000000000000000000000000000000000000000000000000000000000000","0400000000000000000000000000000000000000000000000000000000000000"],"time":111,"mediantime":100,"nonce":124,"bits":13513,"difficulty":555.555,"chainwork":"3","previousblockhash":"0400000000000000000000000000000000000000000000000000000000000000","nextblockhash":"0500000000000000000000000000000000000000000000000000000000000000"}"#); + } + + #[test] + fn verbose_block_deserialize() { + let block = VerboseBlock::default(); + assert_eq!( + serde_json::from_str::(r#"{"hash":"0000000000000000000000000000000000000000000000000000000000000000","confirmations":0,"size":0,"strippedsize":0,"weight":0,"height":null,"version":0,"versionHex":"","merkleroot":"0000000000000000000000000000000000000000000000000000000000000000","tx":[],"time":0,"mediantime":null,"nonce":0,"bits":0,"difficulty":0.0,"chainwork":"0","previousblockhash":null,"nextblockhash":null}"#).unwrap(), + block); + + let block = VerboseBlock { + hash: H256::from(1), + confirmations: -1, + size: 500000, + strippedsize: 444444, + weight: 5236235, + height: Some(3513513), + version: 1, + version_hex: "01".to_owned(), + merkleroot: H256::from(2), + tx: vec![H256::from(3), H256::from(4)], + time: 111, + mediantime: Some(100), + nonce: 124, + bits: 13513, + difficulty: 555.555, + chainwork: U256::from(3), + previousblockhash: Some(H256::from(4)), + nextblockhash: Some(H256::from(5)), + }; + assert_eq!( + serde_json::from_str::(r#"{"hash":"0100000000000000000000000000000000000000000000000000000000000000","confirmations":-1,"size":500000,"strippedsize":444444,"weight":5236235,"height":3513513,"version":1,"versionHex":"01","merkleroot":"0200000000000000000000000000000000000000000000000000000000000000","tx":["0300000000000000000000000000000000000000000000000000000000000000","0400000000000000000000000000000000000000000000000000000000000000"],"time":111,"mediantime":100,"nonce":124,"bits":13513,"difficulty":555.555,"chainwork":"3","previousblockhash":"0400000000000000000000000000000000000000000000000000000000000000","nextblockhash":"0500000000000000000000000000000000000000000000000000000000000000"}"#).unwrap(), + block); + } + + #[test] + fn get_block_response_raw_serialize() { + let raw_response = GetBlockResponse::Raw(Bytes::new(vec![0])); + assert_eq!(serde_json::to_string(&raw_response).unwrap(), r#""00""#); + } + + #[test] + fn get_block_response_verbose_serialize() { + let block = VerboseBlock::default(); + let verbose_response = GetBlockResponse::Verbose(block); + assert_eq!(serde_json::to_string(&verbose_response).unwrap(), r#"{"hash":"0000000000000000000000000000000000000000000000000000000000000000","confirmations":0,"size":0,"strippedsize":0,"weight":0,"height":null,"version":0,"versionHex":"","merkleroot":"0000000000000000000000000000000000000000000000000000000000000000","tx":[],"time":0,"mediantime":null,"nonce":0,"bits":0,"difficulty":0.0,"chainwork":"","previousblockhash":null,"nextblockhash":null}"#); + } +} diff --git a/rpc/src/v1/types/get_tx_out_response.rs b/rpc/src/v1/types/get_tx_out_response.rs new file mode 100644 index 00000000..9f204e87 --- /dev/null +++ b/rpc/src/v1/types/get_tx_out_response.rs @@ -0,0 +1,52 @@ +use super::bytes::Bytes; +use super::hash::{H160, H256}; +use super::script::ScriptType; + +/// gettxout response +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct GetTxOutResponse { + /// Hash of the block this transaction output is included into. + /// Why it's called 'best'? Who knows + pub bestblock: H256, + /// Number of confirmations of this transaction + pub confirmations: u32, + /// Transaction value in BTC + pub value: f64, + /// Script info + #[serde(rename = "scriptPubKey")] + pub script_pub_key: TxOutScriptPubKey, + /// This transaction version + pub version: i32, + /// Is this transactio a coinbase transaction? + pub coinbase: bool, +} + +/// Script pub key information +#[derive(Debug, Serialize, Deserialize)] +pub struct TxOutScriptPubKey { + /// Script code + pub asm: String, + /// Script hex + pub hex: Bytes, + /// Number of required signatures + #[serde(rename = "reqSigs")] + pub req_sigs: u32, + /// Type of script + #[serde(rename = "type")] + pub script_type: ScriptType, + /// Array of bitcoin addresses + pub addresses: Vec, +} + +// TODO: remove me +impl Default for TxOutScriptPubKey { + fn default() -> Self { + TxOutScriptPubKey { + asm: String::default(), + hex: Bytes::default(), + req_sigs: u32::default(), + script_type: ScriptType::NonStandard, + addresses: vec![], + } + } +} diff --git a/rpc/src/v1/types/get_tx_out_set_info_response.rs b/rpc/src/v1/types/get_tx_out_set_info_response.rs new file mode 100644 index 00000000..b7afb50d --- /dev/null +++ b/rpc/src/v1/types/get_tx_out_set_info_response.rs @@ -0,0 +1,3 @@ +#[derive(Debug, Serialize, Deserialize)] +pub struct GetTxOutSetInfoResponse { +} diff --git a/rpc/src/v1/types/hash.rs b/rpc/src/v1/types/hash.rs index 79e388e2..428765bc 100644 --- a/rpc/src/v1/types/hash.rs +++ b/rpc/src/v1/types/hash.rs @@ -5,6 +5,7 @@ use std::hash::{Hash, Hasher}; use serde; use rustc_serialize::hex::{ToHex, FromHex}; use primitives::hash::H256 as GlobalH256; +use primitives::hash::H160 as GlobalH160; macro_rules! impl_hash { ($name: ident, $other: ident, $size: expr) => { @@ -127,6 +128,15 @@ macro_rules! impl_hash { } impl_hash!(H256, GlobalH256, 32); +impl_hash!(H160, GlobalH160, 20); + +impl H256 { + pub fn reversed(&self) -> Self { + let mut result = self.clone(); + result.0.reverse(); + result + } +} #[cfg(test)] mod tests { diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index f0e0f6a1..0dffeec5 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -1,11 +1,23 @@ mod block_template; mod block_template_request; mod bytes; +mod get_block_response; +mod get_tx_out_response; +mod get_tx_out_set_info_response; mod hash; +mod raw_block; mod raw_transaction; +mod script; +mod uint; pub use self::block_template::{BlockTemplate, BlockTemplateTransaction}; pub use self::block_template_request::{BlockTemplateRequest, BlockTemplateRequestMode}; pub use self::bytes::Bytes; +pub use self::get_block_response::{GetBlockResponse, VerboseBlock}; +pub use self::get_tx_out_response::{GetTxOutResponse, TxOutScriptPubKey}; +pub use self::get_tx_out_set_info_response::GetTxOutSetInfoResponse; pub use self::hash::H256; +pub use self::raw_block::RawBlock; pub use self::raw_transaction::RawTransaction; +pub use self::script::ScriptType; +pub use self::uint::U256; diff --git a/rpc/src/v1/types/raw_block.rs b/rpc/src/v1/types/raw_block.rs new file mode 100644 index 00000000..3ac34003 --- /dev/null +++ b/rpc/src/v1/types/raw_block.rs @@ -0,0 +1,3 @@ +use super::bytes::Bytes; + +pub type RawBlock = Bytes; diff --git a/rpc/src/v1/types/script.rs b/rpc/src/v1/types/script.rs new file mode 100644 index 00000000..bf9fbe13 --- /dev/null +++ b/rpc/src/v1/types/script.rs @@ -0,0 +1,102 @@ +use serde::{Serialize, Deserialize, Serializer, Deserializer}; +use script::ScriptType as GlobalScriptType; + +#[derive(Debug, PartialEq)] +pub enum ScriptType { + NonStandard, + PubKey, + PubKeyHash, + ScriptHash, + Multisig, + NullData, + WitnessScript, + WitnessKey, +} + +impl From for ScriptType { + fn from(script_type: GlobalScriptType) -> Self { + match script_type { + GlobalScriptType::NonStandard => ScriptType::NonStandard, + GlobalScriptType::PubKey => ScriptType::PubKey, + GlobalScriptType::PubKeyHash => ScriptType::PubKeyHash, + GlobalScriptType::ScriptHash => ScriptType::ScriptHash, + GlobalScriptType::Multisig => ScriptType::Multisig, + GlobalScriptType::NullData => ScriptType::NullData, + GlobalScriptType::WitnessScript => ScriptType::WitnessScript, + GlobalScriptType::WitnessKey => ScriptType::WitnessKey, + } + } +} + +impl Serialize for ScriptType { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer { + match *self { + ScriptType::NonStandard => "nonstandard".serialize(serializer), + ScriptType::PubKey => "pubkey".serialize(serializer), + ScriptType::PubKeyHash => "pubkeyhash".serialize(serializer), + ScriptType::ScriptHash => "scripthash".serialize(serializer), + ScriptType::Multisig => "multisig".serialize(serializer), + ScriptType::NullData => "nulldata".serialize(serializer), + ScriptType::WitnessScript => "witness_v0_scripthash".serialize(serializer), + ScriptType::WitnessKey => "witness_v0_keyhash".serialize(serializer), + } + } +} + +impl Deserialize for ScriptType { + fn deserialize(deserializer: &mut D) -> Result where D: Deserializer { + use serde::de::Visitor; + + struct ScriptTypeVisitor; + + impl Visitor for ScriptTypeVisitor { + type Value = ScriptType; + + fn visit_str(&mut self, value: &str) -> Result where E: ::serde::de::Error { + match value { + "nonstandard" => Ok(ScriptType::NonStandard), + "pubkey" => Ok(ScriptType::PubKey), + "pubkeyhash" => Ok(ScriptType::PubKeyHash), + "scripthash" => Ok(ScriptType::ScriptHash), + "multisig" => Ok(ScriptType::Multisig), + "nulldata" => Ok(ScriptType::NullData), + "witness_v0_scripthash" => Ok(ScriptType::WitnessScript), + "witness_v0_keyhash" => Ok(ScriptType::WitnessKey), + _ => Err(E::invalid_value(&format!("unknown ScriptType variant: {}", value))), + } + } + } + + deserializer.deserialize(ScriptTypeVisitor) + } +} + +#[cfg(test)] +mod tests { + use super::ScriptType; + use serde_json; + + #[test] + fn script_type_serialize() { + assert_eq!(serde_json::to_string(&ScriptType::NonStandard).unwrap(), r#""nonstandard""#); + assert_eq!(serde_json::to_string(&ScriptType::PubKey).unwrap(), r#""pubkey""#); + assert_eq!(serde_json::to_string(&ScriptType::PubKeyHash).unwrap(), r#""pubkeyhash""#); + assert_eq!(serde_json::to_string(&ScriptType::ScriptHash).unwrap(), r#""scripthash""#); + assert_eq!(serde_json::to_string(&ScriptType::Multisig).unwrap(), r#""multisig""#); + assert_eq!(serde_json::to_string(&ScriptType::NullData).unwrap(), r#""nulldata""#); + assert_eq!(serde_json::to_string(&ScriptType::WitnessScript).unwrap(), r#""witness_v0_scripthash""#); + assert_eq!(serde_json::to_string(&ScriptType::WitnessKey).unwrap(), r#""witness_v0_keyhash""#); + } + + #[test] + fn script_type_deserialize() { + assert_eq!(serde_json::from_str::(r#""nonstandard""#).unwrap(), ScriptType::NonStandard); + assert_eq!(serde_json::from_str::(r#""pubkey""#).unwrap(), ScriptType::PubKey); + assert_eq!(serde_json::from_str::(r#""pubkeyhash""#).unwrap(), ScriptType::PubKeyHash); + assert_eq!(serde_json::from_str::(r#""scripthash""#).unwrap(), ScriptType::ScriptHash); + assert_eq!(serde_json::from_str::(r#""multisig""#).unwrap(), ScriptType::Multisig); + assert_eq!(serde_json::from_str::(r#""nulldata""#).unwrap(), ScriptType::NullData); + assert_eq!(serde_json::from_str::(r#""witness_v0_scripthash""#).unwrap(), ScriptType::WitnessScript); + assert_eq!(serde_json::from_str::(r#""witness_v0_keyhash""#).unwrap(), ScriptType::WitnessKey); + } +} diff --git a/rpc/src/v1/types/uint.rs b/rpc/src/v1/types/uint.rs new file mode 100644 index 00000000..9b38d0c9 --- /dev/null +++ b/rpc/src/v1/types/uint.rs @@ -0,0 +1,87 @@ +use std::str::FromStr; +use serde; +use primitives::uint::U256 as GlobalU256; + +macro_rules! impl_uint { + ($name: ident, $other: ident, $size: expr) => { + /// Uint serialization. + #[derive(Debug, Default, Clone, Copy, PartialEq, Hash)] + pub struct $name($other); + + impl Eq for $name { } + + impl From for $name where $other: From { + fn from(o: T) -> Self { + $name($other::from(o)) + } + } + + impl FromStr for $name { + type Err = <$other as FromStr>::Err; + + fn from_str(s: &str) -> Result { + $other::from_str(s).map($name) + } + } + + impl Into<$other> for $name { + fn into(self) -> $other { + self.0 + } + } + + impl serde::Serialize for $name { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> where S: serde::Serializer { + let as_hex = format!("{:x}", self.0); + serializer.serialize_str(&as_hex) + } + } + + impl serde::Deserialize for $name { + fn deserialize(deserializer: &mut D) -> Result<$name, D::Error> where D: serde::Deserializer { + struct UintVisitor; + + impl serde::de::Visitor for UintVisitor { + type Value = $name; + + fn visit_str(&mut self, value: &str) -> Result where E: serde::Error { + if value.len() > $size * 16 { + return Err(serde::Error::custom("Invalid length.")); + } + + $other::from_str(value).map($name).map_err(|_| serde::Error::custom("Invalid hex value.")) + } + + fn visit_string(&mut self, value: String) -> Result where E: serde::Error { + self.visit_str(&value) + } + } + + deserializer.deserialize(UintVisitor) + } + } + } +} + +impl_uint!(U256, GlobalU256, 4); + + +#[cfg(test)] +mod tests { + use super::U256; + use serde_json; + + #[test] + fn u256_serialize() { + let u256 = U256::from(256); + let serialized = serde_json::to_string(&u256).unwrap(); + assert_eq!(serialized, r#""100""#); + } + + #[test] + fn u256_deserialize() { + let u256 = U256::from(256); + let deserialized = serde_json::from_str::(r#""100""#).unwrap(); + assert_eq!(deserialized, u256); + } +} diff --git a/script/src/lib.rs b/script/src/lib.rs index 09ba56c6..e90f8bf3 100644 --- a/script/src/lib.rs +++ b/script/src/lib.rs @@ -25,7 +25,7 @@ pub use self::flags::VerificationFlags; pub use self::interpreter::{eval_script, verify_script}; pub use self::opcode::Opcode; pub use self::num::Num; -pub use self::script::Script; +pub use self::script::{Script, ScriptType}; pub use self::sign::{ TransactionInputSigner, UnsignedTransactionInput, Sighash, SighashBase, SignatureVersion diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 83866c02..aebaefbd 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -85,7 +85,7 @@ pub fn create_local_sync_node(handle: &Handle, network: Magic, db: db::SharedSto }; let sync_chain = Arc::new(RwLock::new(SyncChain::new(db.clone()))); - let chain_verifier = Arc::new(ChainVerifier::new(db, network)); + let chain_verifier = Arc::new(ChainVerifier::new(db.clone(), network)); let sync_executor = SyncExecutor::new(sync_chain.clone()); let sync_server = Arc::new(SynchronizationServer::new(sync_chain.clone(), sync_executor.clone())); let sync_client_core = SynchronizationClientCore::new(sync_client_config, handle, sync_executor.clone(), sync_chain.clone(), chain_verifier.clone());