From 9e1932e7f9a16ea30550fa2c5d5469b5034870e4 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 12 Dec 2016 21:49:01 +0300 Subject: [PATCH] finished gettxout implementation --- Cargo.lock | 1 + keys/src/lib.rs | 1 + pbtc/commands/start.rs | 4 +- pbtc/rpc.rs | 4 + pbtc/rpc_apis.rs | 10 ++- rpc/Cargo.toml | 1 + rpc/src/lib.rs | 3 +- rpc/src/v1/impls/blockchain.rs | 102 +++++++++++++++++++++--- rpc/src/v1/mod.rs | 2 + rpc/src/v1/types/address.rs | 76 ++++++++++++++++++ rpc/src/v1/types/bytes.rs | 12 +-- rpc/src/v1/types/get_tx_out_response.rs | 92 +++++++++++++++++---- rpc/src/v1/types/mod.rs.in | 4 +- rpc/src/v1/types/script.rs | 2 +- script/src/lib.rs | 2 +- script/src/script.rs | 63 ++++++++++++--- 16 files changed, 325 insertions(+), 54 deletions(-) create mode 100644 rpc/src/v1/types/address.rs diff --git a/Cargo.lock b/Cargo.lock index 613d898e..5f3c1b54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -804,6 +804,7 @@ dependencies = [ "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)", + "keys 0.1.0", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "miner 0.1.0", "network 0.1.0", diff --git a/keys/src/lib.rs b/keys/src/lib.rs index 40d8d023..0436c4ef 100644 --- a/keys/src/lib.rs +++ b/keys/src/lib.rs @@ -35,6 +35,7 @@ pub use self::error::Error; pub use self::private::Private; pub use self::public::Public; pub use self::signature::{Signature, CompactSignature}; +pub use self::network::Network; use hash::{H160, H256}; diff --git a/pbtc/commands/start.rs b/pbtc/commands/start.rs index 3e865ca2..e5462580 100644 --- a/pbtc/commands/start.rs +++ b/pbtc/commands/start.rs @@ -34,10 +34,12 @@ pub fn start(cfg: config::Config) -> Result<(), String> { }; let sync_handle = el.handle(); - let local_sync_node = create_local_sync_node(&sync_handle, cfg.magic, db); + let local_sync_node = create_local_sync_node(&sync_handle, cfg.magic, db.clone()); let sync_connection_factory = create_sync_connection_factory(local_sync_node.clone()); let rpc_deps = rpc::Dependencies { + network: cfg.magic, + storage: db, local_sync_node: local_sync_node, }; let _rpc_server = try!(rpc::new_http(cfg.rpc_config, rpc_deps)); diff --git a/pbtc/rpc.rs b/pbtc/rpc.rs index e5fe5812..59cd5026 100644 --- a/pbtc/rpc.rs +++ b/pbtc/rpc.rs @@ -1,11 +1,15 @@ use std::net::SocketAddr; use rpc_apis::{self, ApiSet}; use ethcore_rpc::{Server, RpcServer, RpcServerError}; +use network::Magic; use std::io; use sync; +use db; pub struct Dependencies { + pub network: Magic, pub local_sync_node: sync::LocalNodeRef, + pub storage: db::SharedStore, } #[derive(Debug, PartialEq)] diff --git a/pbtc/rpc_apis.rs b/pbtc/rpc_apis.rs index af3682d6..29d03ee2 100644 --- a/pbtc/rpc_apis.rs +++ b/pbtc/rpc_apis.rs @@ -5,10 +5,12 @@ use ethcore_rpc::Extendable; #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] pub enum Api { - /// Raw + /// Raw methods Raw, - /// Miner + /// Miner-related methods Miner, + /// BlockChain-related methods + BlockChain, } #[derive(Debug, PartialEq, Eq)] @@ -18,7 +20,7 @@ pub enum ApiSet { impl Default for ApiSet { fn default() -> Self { - ApiSet::List(vec![Api::Raw].into_iter().collect()) + ApiSet::List(vec![Api::Raw, Api::Miner, Api::BlockChain].into_iter().collect()) } } @@ -29,6 +31,7 @@ impl FromStr for Api { match s { "raw" => Ok(Api::Raw), "miner" => Ok(Api::Miner), + "blockchain" => Ok(Api::BlockChain), api => Err(format!("Unknown api: {}", api)), } } @@ -49,6 +52,7 @@ pub fn setup_rpc(server: T, apis: ApiSet, deps: Dependencies) -> match api { Api::Raw => server.add_delegate(RawClient::new(RawClientCore::new(deps.local_sync_node.clone())).to_delegate()), Api::Miner => server.add_delegate(MinerClient::new(MinerClientCore::new(deps.local_sync_node.clone())).to_delegate()), + Api::BlockChain => server.add_delegate(BlockChainClient::new(BlockChainClientCore::new(deps.network, deps.storage.clone())).to_delegate()), } } server diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index d1d14c0d..cd7f0c09 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -29,6 +29,7 @@ test-data = { path = "../test-data" } miner = { path = "../miner" } verification = { path = "../verification" } script = { path = "../script" } +keys = { path = "../keys" } [build-dependencies] serde_codegen = { version = "0.8.0", optional = true } diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 816d4e4c..c3f20ff9 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -23,7 +23,8 @@ extern crate miner; extern crate verification; #[cfg(test)] extern crate ethcore_devtools as devtools; -extern crate script; +extern crate script as global_script; +extern crate keys; pub mod v1; pub mod rpc_server; diff --git a/rpc/src/v1/impls/blockchain.rs b/rpc/src/v1/impls/blockchain.rs index bfac0b40..84adc0d0 100644 --- a/rpc/src/v1/impls/blockchain.rs +++ b/rpc/src/v1/impls/blockchain.rs @@ -4,18 +4,18 @@ use v1::types::{GetTxOutResponse, TxOutScriptPubKey}; use v1::types::GetTxOutSetInfoResponse; use v1::types::H256; use v1::types::U256; -use v1::types::ScriptType; +use v1::types::Address; 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 global_script::Script; use chain::OutPoint; use verification; use ser::serialize; use primitives::hash::H256 as GlobalH256; - +use network::Magic; pub struct BlockChainClient { core: T, @@ -31,14 +31,16 @@ pub trait BlockChainClientCoreApi: Send + Sync + 'static { } pub struct BlockChainClientCore { + network: Magic, storage: db::SharedStore, } impl BlockChainClientCore { - pub fn new(storage: db::SharedStore) -> Self { + pub fn new(network: Magic, storage: db::SharedStore) -> Self { assert!(storage.best_block().is_some()); BlockChainClientCore { + network: network, storage: storage, } } @@ -130,6 +132,7 @@ impl BlockChainClientCoreApi for BlockChainClientCore { 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); + let script_addresses = script.extract_destinations().unwrap_or(vec![]); Ok(GetTxOutResponse { bestblock: block_header.hash().into(), @@ -138,9 +141,9 @@ impl BlockChainClientCoreApi for BlockChainClientCore { script_pub_key: TxOutScriptPubKey { asm: script_asm, hex: script_bytes.clone().into(), - req_sigs: 0, // TODO - script_type: ScriptType::NonStandard, // TODO - addresses: vec![], + req_sigs: script.num_signatures_required() as u32, + script_type: script.script_type().into(), + addresses: script_addresses.into_iter().map(|a| Address::new(self.network, a)).collect(), }, version: transaction.version, coinbase: transaction.is_coinbase(), @@ -193,7 +196,9 @@ impl BlockChain for BlockChainClient where T: BlockChainClientCoreApi { } 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 }) + // TODO: include_mempool + let transaction_hash: GlobalH256 = transaction_hash.into(); + self.core.verbose_transaction_out(OutPoint { hash: transaction_hash.reversed(), index: out_index }) .map(|mut response| { response.bestblock = response.bestblock.reversed(); response @@ -218,8 +223,11 @@ pub mod tests { use v1::traits::BlockChain; use v1::types::GetTxOutResponse; use v1::helpers::errors::block_not_found; + use v1::types::Bytes; + use v1::types::ScriptType; use chain::OutPoint; use test_data; + use network::Magic; use super::*; #[derive(Default)] @@ -272,7 +280,20 @@ pub mod tests { } fn verbose_transaction_out(&self, _prev_out: OutPoint) -> Result { - Ok(GetTxOutResponse::default()) // TODO: non-default + Ok(GetTxOutResponse { + bestblock: H256::from(0x56), + confirmations: 777, + value: 100000.56, + script_pub_key: TxOutScriptPubKey { + asm: "Hello, world!!!".to_owned(), + hex: Bytes::new(vec![1, 2, 3, 4]), + req_sigs: 777, + script_type: ScriptType::Multisig, + addresses: vec!["1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa".into(), "1H5m1XzvHsjWX3wwU781ubctznEpNACrNC".into()], + }, + version: 33, + coinbase: false, + }) } } @@ -382,7 +403,7 @@ pub mod tests { 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); + let core = BlockChainClientCore::new(Magic::Mainnet, storage); // get info on block #1: // https://blockexplorer.com/block/00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048 @@ -515,4 +536,65 @@ pub mod tests { 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_transaction_out_contents() { + let storage = Arc::new(db::TestStorage::with_genesis_block()); + let core = BlockChainClientCore::new(Magic::Mainnet, storage); + + // get info on tx from genesis block: + // https://blockchain.info/ru/tx/4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b + let verbose_transaction_out = core.verbose_transaction_out(OutPoint { + hash: "3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a".into(), + index: 0, + }); + assert_eq!(verbose_transaction_out, Ok(GetTxOutResponse { + bestblock: "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000".into(), + confirmations: 1, + value: 50.0, + script_pub_key: TxOutScriptPubKey { + asm: "OP_PUSHBYTES_65 0x04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f\nOP_CHECKSIG\n".to_owned(), + hex: Bytes::from("4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac"), + req_sigs: 1, + script_type: ScriptType::PubKey, + addresses: vec!["1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa".into()] + }, + version: 1, + coinbase: true + })); + } + + #[test] + fn transaction_out_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": "gettxout", + "params": ["4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", 0], + "id": 1 + }"#)).unwrap(); + + assert_eq!(&sample, r#"{"jsonrpc":"2.0","result":{"bestblock":"0000000000000000000000000000000000000000000000000000000000000056","coinbase":false,"confirmations":777,"scriptPubKey":{"addresses":["1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa","1H5m1XzvHsjWX3wwU781ubctznEpNACrNC"],"asm":"Hello, world!!!","hex":"01020304","reqSigs":777,"type":"multisig"},"value":100000.56,"version":33},"id":1}"#); + } + + #[test] + fn transaction_out_failure() { + 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": "gettxout", + "params": ["4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", 0], + "id": 1 + }"#)).unwrap(); + + assert_eq!(&sample, r#"{"jsonrpc":"2.0","error":{"code":-32099,"message":"Block with given hash is not found","data":"3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a"},"id":1}"#); + } } diff --git a/rpc/src/v1/mod.rs b/rpc/src/v1/mod.rs index e63f6d7b..14512a6d 100644 --- a/rpc/src/v1/mod.rs +++ b/rpc/src/v1/mod.rs @@ -6,5 +6,7 @@ pub mod types; pub use self::traits::Raw; pub use self::traits::Miner; +pub use self::traits::BlockChain; pub use self::impls::{RawClient, RawClientCore}; pub use self::impls::{MinerClient, MinerClientCore}; +pub use self::impls::{BlockChainClient, BlockChainClientCore}; \ No newline at end of file diff --git a/rpc/src/v1/types/address.rs b/rpc/src/v1/types/address.rs new file mode 100644 index 00000000..f5e5101b --- /dev/null +++ b/rpc/src/v1/types/address.rs @@ -0,0 +1,76 @@ +use std::str::FromStr; +use serde::{Serialize, Deserialize, Serializer, Deserializer}; +use global_script::ScriptAddress; +use keys::Address as GlobalAddress; +use keys::Network as KeysNetwork; +use network::Magic; + +/// Bitcoin address +#[derive(Debug, PartialEq)] +pub struct Address(GlobalAddress); + +impl Address { + pub fn new(network: Magic, address: ScriptAddress) -> Self { + Address(GlobalAddress { + network: match network { + Magic::Mainnet => KeysNetwork::Mainnet, + // there's no correct choices for Regtests && Other networks + // => let's just make Testnet key + _ => KeysNetwork::Testnet, + }, + hash: address.hash, + kind: address.kind, + }) + } +} + +impl Serialize for Address { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer { + serializer.serialize_str(&self.0.to_string()) + } +} + +impl Deserialize for Address { + fn deserialize(deserializer: &mut D) -> Result where D: Deserializer { + use serde::de::Visitor; + + struct AddressVisitor; + + impl Visitor for AddressVisitor { + type Value = Address; + + fn visit_str(&mut self, value: &str) -> Result where E: ::serde::de::Error { + GlobalAddress::from_str(value) + .map_err(|err| E::invalid_value(&format!("error {} parsing address {}", err, value))) + .map(|address| Address(address)) + } + } + + deserializer.deserialize(AddressVisitor) + } +} + +impl From for Address where GlobalAddress: From { + fn from(o: T) -> Self { + Address(GlobalAddress::from(o)) + } +} + +#[cfg(test)] +mod tests { + use serde_json; + use super::Address; + + #[test] + fn address_serialize() { + let address: Address = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa".into(); + assert_eq!(serde_json::to_string(&address).unwrap(), r#""1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa""#); + } + + #[test] + fn address_deserialize() { + let address: Address = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa".into(); + assert_eq!(serde_json::from_str::
(r#""1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa""#).unwrap(), address); + assert!(serde_json::from_str::
(r#""DEADBEEF""#).is_err()); + } +} diff --git a/rpc/src/v1/types/bytes.rs b/rpc/src/v1/types/bytes.rs index b7bd65d8..f0bd94b5 100644 --- a/rpc/src/v1/types/bytes.rs +++ b/rpc/src/v1/types/bytes.rs @@ -20,15 +20,9 @@ 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) +impl From for Bytes where GlobalBytes: From { + fn from(other: T) -> Self { + Bytes(GlobalBytes::from(other).take()) } } diff --git a/rpc/src/v1/types/get_tx_out_response.rs b/rpc/src/v1/types/get_tx_out_response.rs index 9f204e87..5cfa4a81 100644 --- a/rpc/src/v1/types/get_tx_out_response.rs +++ b/rpc/src/v1/types/get_tx_out_response.rs @@ -1,9 +1,10 @@ +use super::address::Address; use super::bytes::Bytes; -use super::hash::{H160, H256}; +use super::hash::H256; use super::script::ScriptType; /// gettxout response -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct GetTxOutResponse { /// Hash of the block this transaction output is included into. /// Why it's called 'best'? Who knows @@ -22,7 +23,7 @@ pub struct GetTxOutResponse { } /// Script pub key information -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] pub struct TxOutScriptPubKey { /// Script code pub asm: String, @@ -35,18 +36,81 @@ pub struct TxOutScriptPubKey { #[serde(rename = "type")] pub script_type: ScriptType, /// Array of bitcoin addresses - pub addresses: Vec, + 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![], - } +#[cfg(test)] +mod tests { + use serde_json; + use super::super::bytes::Bytes; + use super::super::hash::H256; + use super::super::script::ScriptType; + use super::*; + + #[test] + fn tx_out_response_serialize() { + let txout = GetTxOutResponse { + bestblock: H256::from(0x56), + confirmations: 777, + value: 100000.56, + script_pub_key: TxOutScriptPubKey { + asm: "Hello, world!!!".to_owned(), + hex: Bytes::new(vec![1, 2, 3, 4]), + req_sigs: 777, + script_type: ScriptType::Multisig, + addresses: vec!["1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa".into(), "1H5m1XzvHsjWX3wwU781ubctznEpNACrNC".into()], + }, + version: 33, + coinbase: false, + }; + assert_eq!(serde_json::to_string(&txout).unwrap(), r#"{"bestblock":"5600000000000000000000000000000000000000000000000000000000000000","confirmations":777,"value":100000.56,"scriptPubKey":{"asm":"Hello, world!!!","hex":"01020304","reqSigs":777,"type":"multisig","addresses":["1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa","1H5m1XzvHsjWX3wwU781ubctznEpNACrNC"]},"version":33,"coinbase":false}"#); + } + + #[test] + fn tx_out_response_deserialize() { + let txout = GetTxOutResponse { + bestblock: H256::from(0x56), + confirmations: 777, + value: 100000.56, + script_pub_key: TxOutScriptPubKey { + asm: "Hello, world!!!".to_owned(), + hex: Bytes::new(vec![1, 2, 3, 4]), + req_sigs: 777, + script_type: ScriptType::Multisig, + addresses: vec!["1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa".into(), "1H5m1XzvHsjWX3wwU781ubctznEpNACrNC".into()], + }, + version: 33, + coinbase: false, + }; + assert_eq!( + serde_json::from_str::(r#"{"bestblock":"5600000000000000000000000000000000000000000000000000000000000000","confirmations":777,"value":100000.56,"scriptPubKey":{"asm":"Hello, world!!!","hex":"01020304","reqSigs":777,"type":"multisig","addresses":["1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa","1H5m1XzvHsjWX3wwU781ubctznEpNACrNC"]},"version":33,"coinbase":false}"#).unwrap(), + txout); + } + + #[test] + fn tx_out_script_pubkey_serialize() { + let txout = TxOutScriptPubKey { + asm: "Hello, world!!!".to_owned(), + hex: Bytes::new(vec![1, 2, 3, 4]), + req_sigs: 777, + script_type: ScriptType::Multisig, + addresses: vec!["1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa".into(), "1H5m1XzvHsjWX3wwU781ubctznEpNACrNC".into()], + }; + assert_eq!(serde_json::to_string(&txout).unwrap(), r#"{"asm":"Hello, world!!!","hex":"01020304","reqSigs":777,"type":"multisig","addresses":["1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa","1H5m1XzvHsjWX3wwU781ubctznEpNACrNC"]}"#); + } + + #[test] + fn tx_out_script_pubkey_deserialize() { + let txout = TxOutScriptPubKey { + asm: "Hello, world!!!".to_owned(), + hex: Bytes::new(vec![1, 2, 3, 4]), + req_sigs: 777, + script_type: ScriptType::Multisig, + addresses: vec!["1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa".into(), "1H5m1XzvHsjWX3wwU781ubctznEpNACrNC".into()], + }; + + assert_eq!( + serde_json::from_str::(r#"{"asm":"Hello, world!!!","hex":"01020304","reqSigs":777,"type":"multisig","addresses":["1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa","1H5m1XzvHsjWX3wwU781ubctznEpNACrNC"]}"#).unwrap(), + txout); } } diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index 0dffeec5..75f7d7fd 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -1,3 +1,4 @@ +mod address; mod block_template; mod block_template_request; mod bytes; @@ -10,13 +11,14 @@ mod raw_transaction; mod script; mod uint; +pub use self::address::Address; 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::hash::{H160, H256}; pub use self::raw_block::RawBlock; pub use self::raw_transaction::RawTransaction; pub use self::script::ScriptType; diff --git a/rpc/src/v1/types/script.rs b/rpc/src/v1/types/script.rs index bf9fbe13..51e645d4 100644 --- a/rpc/src/v1/types/script.rs +++ b/rpc/src/v1/types/script.rs @@ -1,5 +1,5 @@ use serde::{Serialize, Deserialize, Serializer, Deserializer}; -use script::ScriptType as GlobalScriptType; +use global_script::ScriptType as GlobalScriptType; #[derive(Debug, PartialEq)] pub enum ScriptType { diff --git a/script/src/lib.rs b/script/src/lib.rs index e90f8bf3..9ba536fd 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, ScriptType}; +pub use self::script::{Script, ScriptType, ScriptAddress}; pub use self::sign::{ TransactionInputSigner, UnsignedTransactionInput, Sighash, SighashBase, SignatureVersion diff --git a/script/src/script.rs b/script/src/script.rs index 4dccdec0..e9933128 100644 --- a/script/src/script.rs +++ b/script/src/script.rs @@ -29,6 +29,33 @@ pub enum ScriptType { WitnessKey, } +/// Address from Script +#[derive(PartialEq, Debug)] +pub struct ScriptAddress { + /// The type of the address. + pub kind: keys::Type, + /// Public key hash. + pub hash: AddressHash, +} + +impl ScriptAddress { + /// Creates P2PKH-type ScriptAddress + pub fn new_p2pkh(hash: AddressHash) -> Self { + ScriptAddress { + kind: keys::Type::P2PKH, + hash: hash, + } + } + + /// Creates P2SH-type ScriptAddress + pub fn new_p2sh(hash: AddressHash) -> Self { + ScriptAddress { + kind: keys::Type::P2SH, + hash: hash, + } + } +} + /// Serialized script, used inside transaction inputs and outputs. #[derive(PartialEq, Debug)] pub struct Script { @@ -350,7 +377,7 @@ impl Script { return 1; } - pub fn extract_destinations(&self) -> Result, keys::Error> { + pub fn extract_destinations(&self) -> Result, keys::Error> { match self.script_type() { ScriptType::NonStandard => { Ok(vec![]) @@ -361,26 +388,26 @@ impl Script { x if x == Opcode::OP_PUSHBYTES_65 as u8 => &self.data[1..66], _ => unreachable!(), // because we are relying on script_type() checks here }) - .map(|public| vec![public.address_hash()]) + .map(|public| vec![ScriptAddress::new_p2pkh(public.address_hash())]) }, ScriptType::PubKeyHash => { Ok(vec![ - self.data[3..23].into() + ScriptAddress::new_p2pkh(self.data[3..23].into()), ]) }, ScriptType::ScriptHash => { Ok(vec![ - self.data[2..22].into() + ScriptAddress::new_p2sh(self.data[2..22].into()), ]) }, ScriptType::Multisig => { - let mut addresses: Vec = Vec::new(); + let mut addresses: Vec = Vec::new(); let mut pc = 1; while pc < self.len() - 2 { let instruction = self.get_instruction(pc).expect("this method depends on previous check in script_type()"); let data = instruction.data.expect("this method depends on previous check in script_type()"); let address = try!(Public::from_slice(data)).address_hash(); - addresses.push(address); + addresses.push(ScriptAddress::new_p2pkh(address)); pc += instruction.step; } Ok(addresses) @@ -516,7 +543,7 @@ impl fmt::Display for Script { #[cfg(test)] mod tests { use {Builder, Opcode}; - use super::{Script, ScriptType, MAX_SCRIPT_ELEMENT_SIZE}; + use super::{Script, ScriptType, ScriptAddress, MAX_SCRIPT_ELEMENT_SIZE}; use keys::{Address, Public}; #[test] @@ -650,7 +677,9 @@ OP_ADD .push_opcode(Opcode::OP_CHECKSIG) .into_script(); assert_eq!(script.script_type(), ScriptType::PubKey); - assert_eq!(script.extract_destinations(), Ok(vec![address])); + assert_eq!(script.extract_destinations(), Ok(vec![ + ScriptAddress::new_p2pkh(address), + ])); } #[test] @@ -662,7 +691,9 @@ OP_ADD .push_opcode(Opcode::OP_CHECKSIG) .into_script(); assert_eq!(script.script_type(), ScriptType::PubKey); - assert_eq!(script.extract_destinations(), Ok(vec![address])); + assert_eq!(script.extract_destinations(), Ok(vec![ + ScriptAddress::new_p2pkh(address), + ])); } #[test] @@ -676,7 +707,9 @@ OP_ADD .push_opcode(Opcode::OP_CHECKSIG) .into_script(); assert_eq!(script.script_type(), ScriptType::PubKeyHash); - assert_eq!(script.extract_destinations(), Ok(vec![address])); + assert_eq!(script.extract_destinations(), Ok(vec![ + ScriptAddress::new_p2pkh(address), + ])); } #[test] @@ -688,7 +721,9 @@ OP_ADD .push_opcode(Opcode::OP_EQUAL) .into_script(); assert_eq!(script.script_type(), ScriptType::ScriptHash); - assert_eq!(script.extract_destinations(), Ok(vec![address])); + assert_eq!(script.extract_destinations(), Ok(vec![ + ScriptAddress::new_p2sh(address), + ])); } #[test] @@ -705,7 +740,10 @@ OP_ADD .push_opcode(Opcode::OP_CHECKMULTISIG) .into_script(); assert_eq!(script.script_type(), ScriptType::Multisig); - assert_eq!(script.extract_destinations(), Ok(vec![address1, address2])); + assert_eq!(script.extract_destinations(), Ok(vec![ + ScriptAddress::new_p2pkh(address1), + ScriptAddress::new_p2pkh(address2), + ])); } #[test] @@ -729,6 +767,5 @@ OP_ADD .into_script(); assert_eq!(script.script_type(), ScriptType::ScriptHash); assert_eq!(script.num_signatures_required(), 1); - } }