Merge pull request #312 from ethcore/rpc_blockchain
`gettxout` implemented
This commit is contained in:
commit
f087b795f6
|
@ -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",
|
||||||
|
|
|
@ -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};
|
||||||
|
|
||||||
|
|
|
@ -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(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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}"#);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue