Merge pull request #312 from ethcore/rpc_blockchain

`gettxout` implemented
This commit is contained in:
Nikolay Volf 2016-12-13 13:18:42 +01:00 committed by GitHub
commit f087b795f6
16 changed files with 326 additions and 55 deletions

1
Cargo.lock generated
View File

@ -813,6 +813,7 @@ dependencies = [
"jsonrpc-core 4.0.0 (git+https://github.com/ethcore/jsonrpc.git)", "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-http-server 6.1.1 (git+https://github.com/ethcore/jsonrpc.git)",
"jsonrpc-macros 0.1.0 (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)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"miner 0.1.0", "miner 0.1.0",
"network 0.1.0", "network 0.1.0",

View File

@ -35,6 +35,7 @@ pub use self::error::Error;
pub use self::private::Private; pub use self::private::Private;
pub use self::public::Public; pub use self::public::Public;
pub use self::signature::{Signature, CompactSignature}; pub use self::signature::{Signature, CompactSignature};
pub use self::network::Network;
use hash::{H160, H256}; use hash::{H160, H256};

View File

@ -34,11 +34,13 @@ pub fn start(cfg: config::Config) -> Result<(), String> {
}; };
let sync_handle = el.handle(); 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 sync_connection_factory = create_sync_connection_factory(local_sync_node.clone());
let p2p = try!(p2p::P2P::new(p2p_cfg, sync_connection_factory, el.handle()).map_err(|x| x.to_string())); let p2p = try!(p2p::P2P::new(p2p_cfg, sync_connection_factory, el.handle()).map_err(|x| x.to_string()));
let rpc_deps = rpc::Dependencies { let rpc_deps = rpc::Dependencies {
network: cfg.magic,
storage: db,
local_sync_node: local_sync_node, local_sync_node: local_sync_node,
p2p_context: p2p.context().clone(), p2p_context: p2p.context().clone(),
}; };

View File

@ -2,12 +2,16 @@ use std::net::SocketAddr;
use std::sync::Arc; use std::sync::Arc;
use rpc_apis::{self, ApiSet}; use rpc_apis::{self, ApiSet};
use ethcore_rpc::{Server, RpcServer, RpcServerError}; use ethcore_rpc::{Server, RpcServer, RpcServerError};
use network::Magic;
use std::io; use std::io;
use sync; use sync;
use db;
use p2p; use p2p;
pub struct Dependencies { pub struct Dependencies {
pub network: Magic,
pub local_sync_node: sync::LocalNodeRef, pub local_sync_node: sync::LocalNodeRef,
pub storage: db::SharedStore,
pub p2p_context: Arc<p2p::Context>, pub p2p_context: Arc<p2p::Context>,
} }

View File

@ -5,10 +5,12 @@ use ethcore_rpc::Extendable;
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
pub enum Api { pub enum Api {
/// Raw /// Raw methods
Raw, Raw,
/// Miner /// Miner-related methods
Miner, Miner,
/// BlockChain-related methods
BlockChain,
/// Network /// Network
Network, Network,
} }
@ -20,7 +22,7 @@ pub enum ApiSet {
impl Default for ApiSet { impl Default for ApiSet {
fn default() -> Self { fn default() -> Self {
ApiSet::List(vec![Api::Raw, Api::Network].into_iter().collect()) ApiSet::List(vec![Api::Raw, Api::Miner, Api::BlockChain, Api::Network].into_iter().collect())
} }
} }
@ -31,6 +33,7 @@ impl FromStr for Api {
match s { match s {
"raw" => Ok(Api::Raw), "raw" => Ok(Api::Raw),
"miner" => Ok(Api::Miner), "miner" => Ok(Api::Miner),
"blockchain" => Ok(Api::BlockChain),
"network" => Ok(Api::Network), "network" => Ok(Api::Network),
api => Err(format!("Unknown api: {}", api)), api => Err(format!("Unknown api: {}", api)),
} }
@ -52,6 +55,7 @@ pub fn setup_rpc<T: Extendable>(server: T, apis: ApiSet, deps: Dependencies) ->
match api { match api {
Api::Raw => server.add_delegate(RawClient::new(RawClientCore::new(deps.local_sync_node.clone())).to_delegate()), 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::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()),
Api::Network => server.add_delegate(NetworkClient::new(NetworkClientCore::new(deps.p2p_context.clone())).to_delegate()), Api::Network => server.add_delegate(NetworkClient::new(NetworkClientCore::new(deps.p2p_context.clone())).to_delegate()),
} }
} }

View File

@ -29,6 +29,7 @@ test-data = { path = "../test-data" }
miner = { path = "../miner" } miner = { path = "../miner" }
verification = { path = "../verification" } verification = { path = "../verification" }
script = { path = "../script" } script = { path = "../script" }
keys = { path = "../keys" }
[build-dependencies] [build-dependencies]
serde_codegen = { version = "0.8.0", optional = true } serde_codegen = { version = "0.8.0", optional = true }

View File

@ -23,7 +23,8 @@ extern crate miner;
extern crate verification; extern crate verification;
#[cfg(test)] #[cfg(test)]
extern crate ethcore_devtools as devtools; extern crate ethcore_devtools as devtools;
extern crate script; extern crate script as global_script;
extern crate keys;
pub mod v1; pub mod v1;
pub mod rpc_server; pub mod rpc_server;

View File

@ -4,20 +4,19 @@ use v1::types::{GetTxOutResponse, TxOutScriptPubKey};
use v1::types::GetTxOutSetInfoResponse; use v1::types::GetTxOutSetInfoResponse;
use v1::types::H256; use v1::types::H256;
use v1::types::U256; 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, use v1::helpers::errors::{block_not_found, block_at_height_not_found, transaction_not_found,
transaction_output_not_found, transaction_of_side_branch}; transaction_output_not_found, transaction_of_side_branch};
use jsonrpc_macros::Trailing; use jsonrpc_macros::Trailing;
use jsonrpc_core::Error; use jsonrpc_core::Error;
use db; use db;
use script::Script; use global_script::Script;
use chain::OutPoint; use chain::OutPoint;
use verification; use verification;
use ser::serialize; use ser::serialize;
use network::Magic; use network::Magic;
use primitives::hash::H256 as GlobalH256; use primitives::hash::H256 as GlobalH256;
pub struct BlockChainClient<T: BlockChainClientCoreApi> { pub struct BlockChainClient<T: BlockChainClientCoreApi> {
core: T, core: T,
} }
@ -32,14 +31,16 @@ pub trait BlockChainClientCoreApi: Send + Sync + 'static {
} }
pub struct BlockChainClientCore { pub struct BlockChainClientCore {
network: Magic,
storage: db::SharedStore, storage: db::SharedStore,
} }
impl BlockChainClientCore { impl BlockChainClientCore {
pub fn new(storage: db::SharedStore) -> Self { pub fn new(network: Magic, storage: db::SharedStore) -> Self {
assert!(storage.best_block().is_some()); assert!(storage.best_block().is_some());
BlockChainClientCore { BlockChainClientCore {
network: network,
storage: storage, storage: storage,
} }
} }
@ -137,6 +138,7 @@ impl BlockChainClientCoreApi for BlockChainClientCore {
let ref script_bytes = transaction.outputs[prev_out.index as usize].script_pubkey; let ref script_bytes = transaction.outputs[prev_out.index as usize].script_pubkey;
let script: Script = script_bytes.clone().into(); let script: Script = script_bytes.clone().into();
let script_asm = format!("{}", script); let script_asm = format!("{}", script);
let script_addresses = script.extract_destinations().unwrap_or(vec![]);
Ok(GetTxOutResponse { Ok(GetTxOutResponse {
bestblock: block_header.hash().into(), bestblock: block_header.hash().into(),
@ -145,9 +147,9 @@ impl BlockChainClientCoreApi for BlockChainClientCore {
script_pub_key: TxOutScriptPubKey { script_pub_key: TxOutScriptPubKey {
asm: script_asm, asm: script_asm,
hex: script_bytes.clone().into(), hex: script_bytes.clone().into(),
req_sigs: 0, // TODO req_sigs: script.num_signatures_required() as u32,
script_type: ScriptType::NonStandard, // TODO script_type: script.script_type().into(),
addresses: vec![], addresses: script_addresses.into_iter().map(|a| Address::new(self.network, a)).collect(),
}, },
version: transaction.version, version: transaction.version,
coinbase: transaction.is_coinbase(), coinbase: transaction.is_coinbase(),
@ -200,7 +202,9 @@ impl<T> BlockChain for BlockChainClient<T> where T: BlockChainClientCoreApi {
} }
fn transaction_out(&self, transaction_hash: H256, out_index: u32, _include_mempool: Trailing<bool>) -> Result<GetTxOutResponse, Error> { fn transaction_out(&self, transaction_hash: H256, out_index: u32, _include_mempool: Trailing<bool>) -> Result<GetTxOutResponse, Error> {
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| { .map(|mut response| {
response.bestblock = response.bestblock.reversed(); response.bestblock = response.bestblock.reversed();
response response
@ -223,10 +227,14 @@ pub mod tests {
use primitives::hash::H256 as GlobalH256; use primitives::hash::H256 as GlobalH256;
use v1::types::{VerboseBlock, RawBlock}; use v1::types::{VerboseBlock, RawBlock};
use v1::traits::BlockChain; use v1::traits::BlockChain;
use v1::types::GetTxOutResponse; use v1::types::{GetTxOutResponse, TxOutScriptPubKey};
use v1::helpers::errors::block_not_found; use v1::helpers::errors::block_not_found;
use v1::types::Bytes;
use v1::types::H256;
use v1::types::ScriptType;
use chain::OutPoint; use chain::OutPoint;
use test_data; use test_data;
use network::Magic;
use super::*; use super::*;
#[derive(Default)] #[derive(Default)]
@ -279,7 +287,20 @@ pub mod tests {
} }
fn verbose_transaction_out(&self, _prev_out: OutPoint) -> Result<GetTxOutResponse, Error> { fn verbose_transaction_out(&self, _prev_out: OutPoint) -> Result<GetTxOutResponse, Error> {
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,
})
} }
} }
@ -389,7 +410,7 @@ pub mod tests {
storage.insert_block(&test_data::block_h1()).expect("no error"); storage.insert_block(&test_data::block_h1()).expect("no error");
storage.insert_block(&test_data::block_h2()).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: // get info on block #1:
// https://blockexplorer.com/block/00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048 // https://blockexplorer.com/block/00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048
@ -522,4 +543,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}"#); 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}"#);
}
} }

View File

@ -6,7 +6,9 @@ pub mod types;
pub use self::traits::Raw; pub use self::traits::Raw;
pub use self::traits::Miner; pub use self::traits::Miner;
pub use self::traits::BlockChain;
pub use self::traits::Network; pub use self::traits::Network;
pub use self::impls::{RawClient, RawClientCore}; pub use self::impls::{RawClient, RawClientCore};
pub use self::impls::{MinerClient, MinerClientCore}; pub use self::impls::{MinerClient, MinerClientCore};
pub use self::impls::{BlockChainClient, BlockChainClientCore};
pub use self::impls::{NetworkClient, NetworkClientCore}; pub use self::impls::{NetworkClient, NetworkClientCore};

View File

@ -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<S>(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer {
serializer.serialize_str(&self.0.to_string())
}
}
impl Deserialize for Address {
fn deserialize<D>(deserializer: &mut D) -> Result<Self, D::Error> where D: Deserializer {
use serde::de::Visitor;
struct AddressVisitor;
impl Visitor for AddressVisitor {
type Value = Address;
fn visit_str<E>(&mut self, value: &str) -> Result<Address, E> 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<T> From<T> for Address where GlobalAddress: From<T> {
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::<Address>(r#""1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa""#).unwrap(), address);
assert!(serde_json::from_str::<Address>(r#""DEADBEEF""#).is_err());
}
}

View File

@ -20,15 +20,9 @@ impl Bytes {
} }
} }
impl From<GlobalBytes> for Bytes { impl<T> From<T> for Bytes where GlobalBytes: From<T> {
fn from(v: GlobalBytes) -> Self { fn from(other: T) -> Self {
Bytes(v.take()) Bytes(GlobalBytes::from(other).take())
}
}
impl From<Vec<u8>> for Bytes {
fn from(bytes: Vec<u8>) -> Bytes {
Bytes(bytes)
} }
} }

View File

@ -1,9 +1,10 @@
use super::address::Address;
use super::bytes::Bytes; use super::bytes::Bytes;
use super::hash::{H160, H256}; use super::hash::H256;
use super::script::ScriptType; use super::script::ScriptType;
/// gettxout response /// gettxout response
#[derive(Debug, Default, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct GetTxOutResponse { pub struct GetTxOutResponse {
/// Hash of the block this transaction output is included into. /// Hash of the block this transaction output is included into.
/// Why it's called 'best'? Who knows /// Why it's called 'best'? Who knows
@ -22,7 +23,7 @@ pub struct GetTxOutResponse {
} }
/// Script pub key information /// Script pub key information
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct TxOutScriptPubKey { pub struct TxOutScriptPubKey {
/// Script code /// Script code
pub asm: String, pub asm: String,
@ -35,18 +36,81 @@ pub struct TxOutScriptPubKey {
#[serde(rename = "type")] #[serde(rename = "type")]
pub script_type: ScriptType, pub script_type: ScriptType,
/// Array of bitcoin addresses /// Array of bitcoin addresses
pub addresses: Vec<H160>, pub addresses: Vec<Address>,
} }
// TODO: remove me #[cfg(test)]
impl Default for TxOutScriptPubKey { mod tests {
fn default() -> Self { use serde_json;
TxOutScriptPubKey { use super::super::bytes::Bytes;
asm: String::default(), use super::super::hash::H256;
hex: Bytes::default(), use super::super::script::ScriptType;
req_sigs: u32::default(), use super::*;
script_type: ScriptType::NonStandard,
addresses: vec![], #[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::<GetTxOutResponse>(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::<TxOutScriptPubKey>(r#"{"asm":"Hello, world!!!","hex":"01020304","reqSigs":777,"type":"multisig","addresses":["1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa","1H5m1XzvHsjWX3wwU781ubctznEpNACrNC"]}"#).unwrap(),
txout);
} }
} }

View File

@ -1,3 +1,4 @@
mod address;
mod block_template; mod block_template;
mod block_template_request; mod block_template_request;
mod bytes; mod bytes;
@ -11,13 +12,14 @@ mod script;
mod uint; mod uint;
mod nodes; mod nodes;
pub use self::address::Address;
pub use self::block_template::{BlockTemplate, BlockTemplateTransaction}; pub use self::block_template::{BlockTemplate, BlockTemplateTransaction};
pub use self::block_template_request::{BlockTemplateRequest, BlockTemplateRequestMode}; pub use self::block_template_request::{BlockTemplateRequest, BlockTemplateRequestMode};
pub use self::bytes::Bytes; pub use self::bytes::Bytes;
pub use self::get_block_response::{GetBlockResponse, VerboseBlock}; pub use self::get_block_response::{GetBlockResponse, VerboseBlock};
pub use self::get_tx_out_response::{GetTxOutResponse, TxOutScriptPubKey}; pub use self::get_tx_out_response::{GetTxOutResponse, TxOutScriptPubKey};
pub use self::get_tx_out_set_info_response::GetTxOutSetInfoResponse; 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_block::RawBlock;
pub use self::raw_transaction::RawTransaction; pub use self::raw_transaction::RawTransaction;
pub use self::script::ScriptType; pub use self::script::ScriptType;

View File

@ -1,5 +1,5 @@
use serde::{Serialize, Deserialize, Serializer, Deserializer}; use serde::{Serialize, Deserialize, Serializer, Deserializer};
use script::ScriptType as GlobalScriptType; use global_script::ScriptType as GlobalScriptType;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum ScriptType { pub enum ScriptType {

View File

@ -25,7 +25,7 @@ pub use self::flags::VerificationFlags;
pub use self::interpreter::{eval_script, verify_script}; pub use self::interpreter::{eval_script, verify_script};
pub use self::opcode::Opcode; pub use self::opcode::Opcode;
pub use self::num::Num; pub use self::num::Num;
pub use self::script::{Script, ScriptType}; pub use self::script::{Script, ScriptType, ScriptAddress};
pub use self::sign::{ pub use self::sign::{
TransactionInputSigner, UnsignedTransactionInput, TransactionInputSigner, UnsignedTransactionInput,
Sighash, SighashBase, SignatureVersion Sighash, SighashBase, SignatureVersion

View File

@ -29,6 +29,33 @@ pub enum ScriptType {
WitnessKey, 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. /// Serialized script, used inside transaction inputs and outputs.
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub struct Script { pub struct Script {
@ -350,7 +377,7 @@ impl Script {
return 1; return 1;
} }
pub fn extract_destinations(&self) -> Result<Vec<AddressHash>, keys::Error> { pub fn extract_destinations(&self) -> Result<Vec<ScriptAddress>, keys::Error> {
match self.script_type() { match self.script_type() {
ScriptType::NonStandard => { ScriptType::NonStandard => {
Ok(vec![]) Ok(vec![])
@ -361,26 +388,26 @@ impl Script {
x if x == Opcode::OP_PUSHBYTES_65 as u8 => &self.data[1..66], x if x == Opcode::OP_PUSHBYTES_65 as u8 => &self.data[1..66],
_ => unreachable!(), // because we are relying on script_type() checks here _ => 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 => { ScriptType::PubKeyHash => {
Ok(vec![ Ok(vec![
self.data[3..23].into() ScriptAddress::new_p2pkh(self.data[3..23].into()),
]) ])
}, },
ScriptType::ScriptHash => { ScriptType::ScriptHash => {
Ok(vec![ Ok(vec![
self.data[2..22].into() ScriptAddress::new_p2sh(self.data[2..22].into()),
]) ])
}, },
ScriptType::Multisig => { ScriptType::Multisig => {
let mut addresses: Vec<AddressHash> = Vec::new(); let mut addresses: Vec<ScriptAddress> = Vec::new();
let mut pc = 1; let mut pc = 1;
while pc < self.len() - 2 { while pc < self.len() - 2 {
let instruction = self.get_instruction(pc).expect("this method depends on previous check in script_type()"); 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 data = instruction.data.expect("this method depends on previous check in script_type()");
let address = try!(Public::from_slice(data)).address_hash(); let address = try!(Public::from_slice(data)).address_hash();
addresses.push(address); addresses.push(ScriptAddress::new_p2pkh(address));
pc += instruction.step; pc += instruction.step;
} }
Ok(addresses) Ok(addresses)
@ -516,7 +543,7 @@ impl fmt::Display for Script {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use {Builder, Opcode}; use {Builder, Opcode};
use super::{Script, ScriptType, MAX_SCRIPT_ELEMENT_SIZE}; use super::{Script, ScriptType, ScriptAddress, MAX_SCRIPT_ELEMENT_SIZE};
use keys::{Address, Public}; use keys::{Address, Public};
#[test] #[test]
@ -650,7 +677,9 @@ OP_ADD
.push_opcode(Opcode::OP_CHECKSIG) .push_opcode(Opcode::OP_CHECKSIG)
.into_script(); .into_script();
assert_eq!(script.script_type(), ScriptType::PubKey); 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] #[test]
@ -662,7 +691,9 @@ OP_ADD
.push_opcode(Opcode::OP_CHECKSIG) .push_opcode(Opcode::OP_CHECKSIG)
.into_script(); .into_script();
assert_eq!(script.script_type(), ScriptType::PubKey); 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] #[test]
@ -676,7 +707,9 @@ OP_ADD
.push_opcode(Opcode::OP_CHECKSIG) .push_opcode(Opcode::OP_CHECKSIG)
.into_script(); .into_script();
assert_eq!(script.script_type(), ScriptType::PubKeyHash); 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] #[test]
@ -688,7 +721,9 @@ OP_ADD
.push_opcode(Opcode::OP_EQUAL) .push_opcode(Opcode::OP_EQUAL)
.into_script(); .into_script();
assert_eq!(script.script_type(), ScriptType::ScriptHash); 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] #[test]
@ -705,7 +740,10 @@ OP_ADD
.push_opcode(Opcode::OP_CHECKMULTISIG) .push_opcode(Opcode::OP_CHECKMULTISIG)
.into_script(); .into_script();
assert_eq!(script.script_type(), ScriptType::Multisig); 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] #[test]
@ -729,6 +767,5 @@ OP_ADD
.into_script(); .into_script();
assert_eq!(script.script_type(), ScriptType::ScriptHash); assert_eq!(script.script_type(), ScriptType::ScriptHash);
assert_eq!(script.num_signatures_required(), 1); assert_eq!(script.num_signatures_required(), 1);
} }
} }