├── rust-toolchain ├── src ├── types │ ├── serde │ │ ├── mod.rs │ │ └── network.rs │ ├── mod.rs │ ├── keys.rs │ ├── script.rs │ ├── block.rs │ ├── blockchain.rs │ ├── address.rs │ └── transaction.rs ├── stub_rpc_client.rs ├── lib.rs ├── bitcoin_rpc_api.rs └── bitcoincore.rs ├── tests ├── common │ ├── mod.rs │ ├── test_lifecycle.rs │ ├── assert.rs │ └── test_client.rs └── rpc_calls.rs ├── .travis.yml ├── examples └── get_new_address.rs ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── .gitignore └── LICENSE-Apache-2.0 /rust-toolchain: -------------------------------------------------------------------------------- 1 | stable 2 | -------------------------------------------------------------------------------- /src/types/serde/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod network; 2 | -------------------------------------------------------------------------------- /tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod assert; 2 | pub mod test_client; 3 | pub mod test_lifecycle; 4 | -------------------------------------------------------------------------------- /tests/common/test_lifecycle.rs: -------------------------------------------------------------------------------- 1 | extern crate env_logger; 2 | 3 | pub fn setup() -> () { 4 | let _ = env_logger::try_init(); 5 | } 6 | -------------------------------------------------------------------------------- /src/stub_rpc_client.rs: -------------------------------------------------------------------------------- 1 | use bitcoin_rpc_api::BitcoinRpcApi; 2 | 3 | pub struct BitcoinStubClient {} 4 | 5 | impl BitcoinStubClient { 6 | pub fn new() -> Self { 7 | Self {} 8 | } 9 | } 10 | 11 | impl BitcoinRpcApi for BitcoinStubClient {} 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | cache: 3 | directories: 4 | - "$HOME/.cargo" 5 | - "$HOME/.cache/sccache" 6 | rust: stable 7 | before_script: 8 | - rustup component add rustfmt-preview 9 | - docker pull coblox/bitcoin-core:0.16.1-r2 10 | - which sccache || cargo install sccache 11 | script: 12 | - cargo fmt -- --check 13 | - RUSTC_WRAPPER=~/.cargo/bin/sccache cargo build --all 14 | - RUSTC_WRAPPER=~/.cargo/bin/sccache cargo test --all 15 | -------------------------------------------------------------------------------- /examples/get_new_address.rs: -------------------------------------------------------------------------------- 1 | #![allow(deprecated)] 2 | extern crate bitcoin_rpc_client; 3 | 4 | use bitcoin_rpc_client::BitcoinCoreClient; 5 | use bitcoin_rpc_client::BitcoinRpcApi; 6 | use std::env::var; 7 | 8 | fn main() { 9 | let url = var("BITCOIN_CORE_URL").unwrap(); 10 | let user = var("BITCOIN_CORE_USER").unwrap(); 11 | let password = var("BITCOIN_CORE_PASSWORD").unwrap(); 12 | 13 | let client = BitcoinCoreClient::new(&url, &user, &password); 14 | 15 | let address = client 16 | .get_new_address() 17 | .unwrap() // Handle network error here 18 | .unwrap(); // Handle RpcError 19 | 20 | println!("Generated address: {:?}", address); 21 | } 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitcoin_rpc_client" 3 | version = "0.6.1" 4 | authors = [ "CoBloX developers " ] 5 | description = "Rust client library for talking to Bitcoin Core nodes using JsonRPC." 6 | license = "MIT OR Apache-2.0" 7 | repository = "https://github.com/coblox/bitcoinrpc_rust_client" 8 | categories = ["api-bindings"] 9 | keywords = ["bitcoin", "rpc", "client"] 10 | 11 | [dependencies] 12 | base64 = "0.9" 13 | bitcoin = { version = "0.18", features = ["use-serde"] } 14 | bitcoin_hashes = "0.3.2" 15 | hex = "0.3" 16 | jsonrpc_client = "0.2" 17 | log = "0.4" 18 | serde = "1" 19 | serde_derive = "1" 20 | serde_json = "1" 21 | 22 | [dev-dependencies] 23 | env_logger = "0.6" 24 | testcontainers = "0.7" 25 | -------------------------------------------------------------------------------- /src/types/mod.rs: -------------------------------------------------------------------------------- 1 | mod serde; 2 | 3 | pub mod address; 4 | pub mod block; 5 | pub mod blockchain; 6 | pub mod keys; 7 | pub mod script; 8 | pub mod transaction; 9 | 10 | #[derive(Deserialize, Serialize, Debug)] 11 | pub struct Account(pub String); 12 | 13 | #[allow(non_camel_case_types)] 14 | // TODO: This enum is a bit weird. Clear it up once we have a better understanding of it 15 | #[derive(Deserialize, Serialize, Debug)] 16 | pub enum SigHashType { 17 | #[serde(rename = "ALL")] 18 | All, 19 | #[serde(rename = "NONE")] 20 | None, 21 | #[serde(rename = "SINGLE")] 22 | Single, 23 | #[serde(rename = "ALL|ANYONECANPAY")] 24 | All_AnyoneCanPay, 25 | #[serde(rename = "NONE|ANYONECANPAY")] 26 | None_AnyoneCanPay, 27 | #[serde(rename = "SINGLE|ANYONECANPAY")] 28 | Single_AnyoneCanPay, 29 | } 30 | 31 | pub enum TxOutConfirmations { 32 | Unconfirmed, 33 | AtLeast(i32), 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2018 CoBloX 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /src/types/serde/network.rs: -------------------------------------------------------------------------------- 1 | use bitcoin::network::constants::Network; 2 | use serde::{de, Deserializer}; 3 | use std::fmt; 4 | 5 | pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result 6 | where 7 | D: Deserializer<'de>, 8 | { 9 | struct Visitor; 10 | 11 | impl<'de> de::Visitor<'de> for Visitor { 12 | type Value = Network; 13 | 14 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 15 | formatter.write_str("Bitcoin network: `main`, `test` or `regtest`") 16 | } 17 | 18 | fn visit_str(self, value: &str) -> Result 19 | where 20 | E: de::Error, 21 | { 22 | match value { 23 | "test" => Ok(Network::Testnet), 24 | "regtest" => Ok(Network::Regtest), 25 | "main" => Ok(Network::Bitcoin), 26 | _ => Err(E::custom(format!("Unexpect value for Network: {}", value))), 27 | } 28 | } 29 | } 30 | 31 | deserializer.deserialize_str(Visitor) 32 | } 33 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(test, deny(warnings))] 2 | #![allow(deprecated)] 3 | 4 | extern crate base64; 5 | extern crate bitcoin; 6 | extern crate bitcoin_hashes; 7 | extern crate hex as std_hex; 8 | extern crate jsonrpc_client; 9 | #[macro_use] 10 | extern crate log; 11 | extern crate serde; 12 | #[macro_use] 13 | extern crate serde_derive; 14 | extern crate serde_json; 15 | 16 | mod bitcoin_rpc_api; 17 | mod bitcoincore; 18 | mod stub_rpc_client; 19 | mod types; 20 | 21 | // Re-export types from rust-bitcoin crates so explicit dependency is not needed 22 | pub type TransactionId = bitcoin_hashes::sha256d::Hash; 23 | pub type BlockHash = bitcoin_hashes::sha256d::Hash; 24 | 25 | pub use bitcoin::network::constants::Network; 26 | pub use bitcoin::util::key::PrivateKey; 27 | pub use bitcoin::Address; 28 | pub use bitcoin::Script; 29 | 30 | pub use bitcoin_rpc_api::BitcoinRpcApi; 31 | pub use bitcoincore::BitcoinCoreClient; 32 | pub use stub_rpc_client::BitcoinStubClient; 33 | 34 | pub use jsonrpc_client::{ClientError, RpcError}; 35 | 36 | pub mod rpc { 37 | pub use types::address::*; 38 | pub use types::block::*; 39 | pub use types::blockchain::*; 40 | pub use types::keys::*; 41 | pub use types::script::*; 42 | pub use types::transaction::*; 43 | pub use types::{Account, SigHashType, TxOutConfirmations}; 44 | } 45 | -------------------------------------------------------------------------------- /tests/common/assert.rs: -------------------------------------------------------------------------------- 1 | use bitcoin_rpc_client::BitcoinCoreClient; 2 | use jsonrpc_client::ClientError; 3 | use jsonrpc_client::RpcError; 4 | use std::fmt::Debug; 5 | use testcontainers::{clients::Cli, images::coblox_bitcoincore::BitcoinCore, Docker}; 6 | 7 | pub fn assert_successful_result(invocation: I) 8 | where 9 | R: Debug, 10 | I: Fn(&BitcoinCoreClient) -> Result, ClientError>, 11 | { 12 | let docker = Cli::default(); 13 | let container = docker.run(BitcoinCore::default()); 14 | let client = { 15 | let host_port = container.get_host_port(18443).unwrap(); 16 | 17 | let url = format!("http://localhost:{}", host_port); 18 | 19 | let auth = container.image().auth(); 20 | 21 | BitcoinCoreClient::new(url.as_str(), auth.username(), auth.password()) 22 | }; 23 | 24 | match invocation(&client) { 25 | Ok(Ok(result)) => { 26 | // Having a successful result means: 27 | // - No HTTP Error occured 28 | // - No deserialization error occured 29 | debug!("Returned result: {:?}", result) 30 | } 31 | Ok(Err(rpc_error)) => panic!( 32 | "Network call was successful but node returned rpc-error: {:?}", 33 | rpc_error 34 | ), 35 | Err(http_error) => panic!("Failed to connect to node: {:?}", http_error), 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bitcoin RPC Client 2 | 3 | [![Build Status](https://travis-ci.com/coblox/bitcoinrpc-rust-client.svg?branch=master)](https://travis-ci.com/coblox/bitcoinrpc-rust-client) 4 | [![Crates.io](https://img.shields.io/crates/v/bitcoin_rpc_client.svg)](https://crates.io/crates/bitcoin_rpc_client) 5 | 6 | ## Deprecated. This library is unmaintained in favor of https://github.com/rust-bitcoin/rust-bitcoincore-rpc 7 | 8 | This crate provides a Rust interface to the Bitcoin JSON-RPC API. 9 | 10 | It is currently work-in-progress as not all RPC calls are implemented. 11 | 12 | ## Features 13 | 14 | - Does not use macros 15 | - Automatic retry mechanism if bitcoin-core is not yet ready 16 | - Provides trait of all RPC methods for easy mocking (`BitcoinRpcApi`) 17 | 18 | ## Usage 19 | 20 | Check `examples/` but basically, given a URL and the username/password for the node, you can construct a client and call the desired RPC method. 21 | 22 | ## License 23 | 24 | Licensed under either of 25 | 26 | * Apache License, Version 2.0 27 | ([LICENSE-APACHE](LICENSE-Apache-2.0) or http://www.apache.org/licenses/LICENSE-2.0) 28 | * MIT license 29 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 30 | 31 | at your option. 32 | 33 | ## Contribution 34 | 35 | Unless you explicitly state otherwise, any contribution intentionally submitted 36 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 37 | dual licensed as above, without any additional terms or conditions. 38 | -------------------------------------------------------------------------------- /tests/common/test_client.rs: -------------------------------------------------------------------------------- 1 | use bitcoin::Address; 2 | use bitcoin_rpc_client::*; 3 | 4 | pub struct BitcoinCoreTestClient<'a> { 5 | pub client: &'a BitcoinCoreClient, 6 | } 7 | 8 | impl<'a> BitcoinCoreTestClient<'a> { 9 | pub fn new(client: &'a BitcoinCoreClient) -> BitcoinCoreTestClient { 10 | BitcoinCoreTestClient { client } 11 | } 12 | 13 | pub fn a_utxo(&self) -> rpc::UnspentTransactionOutput { 14 | let _ = self.a_block(); // Need to generate a block first 15 | 16 | let mut utxos = self 17 | .client 18 | .list_unspent(rpc::TxOutConfirmations::AtLeast(6), None, None) 19 | .unwrap() 20 | .unwrap(); 21 | 22 | utxos.remove(0) 23 | } 24 | 25 | pub fn a_transaction_id(&self) -> TransactionId { 26 | let mut block = self.a_block(); 27 | 28 | block.tx.remove(0) 29 | } 30 | 31 | pub fn a_block_hash(&self) -> BlockHash { 32 | self.a_block().hash 33 | } 34 | 35 | pub fn an_address(&self) -> Address { 36 | self.client.get_new_address().unwrap().unwrap() 37 | } 38 | 39 | pub fn a_block(&self) -> rpc::Block { 40 | self.client 41 | .generate(101) 42 | .and_then(|response| { 43 | let blocks = response.unwrap(); 44 | let block = blocks.get(50).unwrap(); 45 | self.client.get_block(block) 46 | }) 47 | .unwrap() 48 | .unwrap() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Rust template 3 | # Generated by Cargo 4 | # will have compiled files and executables 5 | /target/ 6 | 7 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 8 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 9 | Cargo.lock 10 | 11 | # These are backup files generated by rustfmt 12 | **/*.rs.bk 13 | ### JetBrains template 14 | 15 | .idea 16 | 17 | ### Emacs template 18 | # -*- mode: gitignore; -*- 19 | *~ 20 | \#*\# 21 | /.emacs.desktop 22 | /.emacs.desktop.lock 23 | *.elc 24 | auto-save-list 25 | tramp 26 | .\#* 27 | 28 | # Org-mode 29 | .org-id-locations 30 | *_archive 31 | 32 | # flymake-mode 33 | *_flymake.* 34 | 35 | # eshell files 36 | /eshell/history 37 | /eshell/lastdir 38 | 39 | # elpa packages 40 | /elpa/ 41 | 42 | # reftex files 43 | *.rel 44 | 45 | # AUCTeX auto folder 46 | /auto/ 47 | 48 | # cask packages 49 | .cask/ 50 | dist/ 51 | 52 | # Flycheck 53 | flycheck_*.el 54 | 55 | # server auth directory 56 | /server/ 57 | 58 | # projectiles files 59 | .projectile 60 | 61 | # directory configuration 62 | .dir-locals.el 63 | ### Linux template 64 | # temporary files which can be created if a process still has a handle open of a deleted file 65 | .fuse_hidden* 66 | 67 | # KDE directory preferences 68 | .directory 69 | 70 | # Linux trash folder which might appear on any partition or disk 71 | .Trash-* 72 | 73 | # .nfs files are created when an open file is removed but is still being accessed 74 | .nfs* 75 | ### macOS template 76 | # General 77 | .DS_Store 78 | .AppleDouble 79 | .LSOverride 80 | 81 | # Icon must end with two \r 82 | Icon 83 | 84 | # Thumbnails 85 | ._* 86 | 87 | # Files that might appear in the root of a volume 88 | .DocumentRevisions-V100 89 | .fseventsd 90 | .Spotlight-V100 91 | .TemporaryItems 92 | .Trashes 93 | .VolumeIcon.icns 94 | .com.apple.timemachine.donotpresent 95 | 96 | # Directories potentially created on remote AFP share 97 | .AppleDB 98 | .AppleDesktop 99 | Network Trash Folder 100 | Temporary Items 101 | .apdisk 102 | -------------------------------------------------------------------------------- /src/types/keys.rs: -------------------------------------------------------------------------------- 1 | use bitcoin::util::key; 2 | use serde::{de, export::fmt, Deserialize, Deserializer, Serialize, Serializer}; 3 | use std::{fmt as std_fmt, str::FromStr}; 4 | 5 | #[derive(PartialEq)] 6 | pub struct PrivateKey(key::PrivateKey); 7 | 8 | impl From for PrivateKey { 9 | fn from(p: key::PrivateKey) -> Self { 10 | PrivateKey(p) 11 | } 12 | } 13 | 14 | impl From for key::PrivateKey { 15 | fn from(p: PrivateKey) -> Self { 16 | p.0 17 | } 18 | } 19 | 20 | impl Serialize for PrivateKey { 21 | fn serialize(&self, serializer: S) -> Result 22 | where 23 | S: Serializer, 24 | { 25 | serializer.serialize_str(self.0.to_string().as_str()) 26 | } 27 | } 28 | 29 | impl<'de> Deserialize<'de> for PrivateKey { 30 | fn deserialize(deserializer: D) -> Result>::Error> 31 | where 32 | D: Deserializer<'de>, 33 | { 34 | struct Visitor; 35 | 36 | impl<'vde> de::Visitor<'vde> for Visitor { 37 | type Value = PrivateKey; 38 | 39 | fn expecting(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> { 40 | formatter.write_str("a Wallet-Import-Format encoded value") 41 | } 42 | 43 | fn visit_str(self, v: &str) -> Result 44 | where 45 | E: de::Error, 46 | { 47 | let privkey = 48 | key::PrivateKey::from_str(v).map_err(|err| E::custom(format!("{}", err)))?; 49 | Ok(PrivateKey(privkey)) 50 | } 51 | } 52 | 53 | deserializer.deserialize_str(Visitor) 54 | } 55 | } 56 | 57 | impl std_fmt::Debug for PrivateKey { 58 | fn fmt(&self, f: &mut std_fmt::Formatter) -> std_fmt::Result { 59 | write!(f, "PrivateKey {{ #REDACTED# }}") 60 | } 61 | } 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | use super::*; 66 | use serde_json; 67 | 68 | #[test] 69 | fn serialize_private_key() { 70 | let private_key = PrivateKey( 71 | key::PrivateKey::from_str("cQ1DDxScq1rsYDdCUBywawwNVWTMwnLzCKCwGndC6MgdNtKPQ5Hz") 72 | .unwrap(), 73 | ); 74 | 75 | let se_private_key = serde_json::to_string(&private_key).unwrap(); 76 | let de_private_key = serde_json::from_str::(se_private_key.as_str()).unwrap(); 77 | 78 | let priv_key: key::PrivateKey = private_key.into(); 79 | let de_priv_key: key::PrivateKey = de_private_key.into(); 80 | 81 | assert_eq!(priv_key.key, de_priv_key.key); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/types/script.rs: -------------------------------------------------------------------------------- 1 | use bitcoin::{Address, Script}; 2 | 3 | #[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] 4 | pub struct ScriptPubKey { 5 | pub asm: String, 6 | pub hex: Script, 7 | #[serde(rename = "reqSigs")] 8 | pub req_sigs: Option, 9 | #[serde(rename = "type")] 10 | pub script_type: ScriptType, 11 | pub addresses: Option>, 12 | } 13 | 14 | #[derive(Deserialize, Serialize, Debug, PartialEq, Clone)] 15 | pub enum ScriptType { 16 | #[serde(rename = "pubkey")] 17 | PubKey, 18 | #[serde(rename = "pubkeyhash")] 19 | PubKeyHash, 20 | #[serde(rename = "multisig")] 21 | MultiSig, 22 | #[serde(rename = "nonstandard")] 23 | NonStandard, 24 | #[serde(rename = "scripthash")] 25 | ScriptHash, 26 | #[serde(rename = "witness_v0_keyhash")] 27 | WitnessPubKeyHash, 28 | #[serde(rename = "witness_unknown")] 29 | WitnessUnknown, 30 | /// Appears for generated transactions 31 | #[serde(rename = "nulldata")] 32 | NullData, 33 | #[serde(rename = "witness_v0_scripthash")] 34 | WitnessScriptHash, 35 | } 36 | 37 | #[derive(Deserialize, Serialize, Debug, PartialEq)] 38 | pub struct DecodedScript { 39 | pub asm: String, 40 | #[serde(rename = "type")] 41 | pub script_type: Option, 42 | #[serde(rename = "reqSigs")] 43 | pub req_sigs: Option, 44 | pub addresses: Option>, 45 | pub p2sh: Address, 46 | } 47 | 48 | #[cfg(test)] 49 | mod tests { 50 | 51 | use super::*; 52 | use serde_json; 53 | use std::str::FromStr; 54 | 55 | #[test] 56 | fn can_deserialize_decoded_script_type() { 57 | let json = r#" 58 | { 59 | "asm" : "2 03ede722780d27b05f0b1169efc90fa15a601a32fc6c3295114500c586831b6aaf 02ecd2d250a76d204011de6bc365a56033b9b3a149f679bc17205555d3c2b2854f 022d609d2f0d359e5bc0e5d0ea20ff9f5d3396cb5b1906aa9c56a0e7b5edc0c5d5 3 OP_CHECKMULTISIG", 60 | "reqSigs" : 2, 61 | "type" : "multisig", 62 | "addresses" : [ 63 | "mjbLRSidW1MY8oubvs4SMEnHNFXxCcoehQ", 64 | "mo1vzGwCzWqteip29vGWWW6MsEBREuzW94", 65 | "mt17cV37fBqZsnMmrHnGCm9pM28R1kQdMG" 66 | ], 67 | "p2sh" : "2MyVxxgNBk5zHRPRY2iVjGRJHYZEp1pMCSq" 68 | }"#; 69 | 70 | let script: DecodedScript = serde_json::from_str(json).unwrap(); 71 | 72 | assert_eq!(script, DecodedScript { 73 | asm: "2 03ede722780d27b05f0b1169efc90fa15a601a32fc6c3295114500c586831b6aaf 02ecd2d250a76d204011de6bc365a56033b9b3a149f679bc17205555d3c2b2854f 022d609d2f0d359e5bc0e5d0ea20ff9f5d3396cb5b1906aa9c56a0e7b5edc0c5d5 3 OP_CHECKMULTISIG".to_string(), 74 | script_type: Some(ScriptType::MultiSig), 75 | req_sigs: Some(2), 76 | addresses: Some(vec![ 77 | Address::from_str("mjbLRSidW1MY8oubvs4SMEnHNFXxCcoehQ").unwrap(), 78 | Address::from_str("mo1vzGwCzWqteip29vGWWW6MsEBREuzW94").unwrap(), 79 | Address::from_str("mt17cV37fBqZsnMmrHnGCm9pM28R1kQdMG").unwrap(), 80 | ]), 81 | p2sh: Address::from_str("2MyVxxgNBk5zHRPRY2iVjGRJHYZEp1pMCSq").unwrap(), 82 | }) 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/types/block.rs: -------------------------------------------------------------------------------- 1 | use BlockHash; 2 | 3 | #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] 4 | pub struct BlockHeight(u32); 5 | 6 | impl BlockHeight { 7 | pub fn new(h: u32) -> BlockHeight { 8 | BlockHeight(h) 9 | } 10 | 11 | pub fn as_i64(&self) -> i64 { 12 | i64::from(self.0) 13 | } 14 | } 15 | 16 | impl From for u32 { 17 | fn from(block_height: BlockHeight) -> Self { 18 | block_height.0 19 | } 20 | } 21 | 22 | #[derive(Deserialize, Serialize, Debug, PartialEq)] 23 | pub struct Block { 24 | pub hash: BlockHash, 25 | pub confirmations: i32, 26 | pub size: u32, 27 | pub strippedsize: u32, 28 | pub weight: u32, 29 | pub height: u32, 30 | pub version: u32, 31 | #[serde(rename = "versionHex")] 32 | pub version_hex: String, 33 | pub merkleroot: String, 34 | pub tx: Vec, 35 | pub time: u64, 36 | pub mediantime: u64, 37 | pub nonce: u32, 38 | pub bits: String, 39 | pub difficulty: f64, 40 | pub chainwork: String, 41 | pub previousblockhash: Option, 42 | pub nextblockhash: Option, 43 | } 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | use super::*; 48 | use bitcoin_hashes::hex::FromHex; 49 | use serde_json; 50 | use TransactionId; 51 | 52 | #[test] 53 | fn can_deserialize_block_struct() { 54 | let json = r#"{ 55 | "hash": "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048", 56 | "confirmations": 447014, 57 | "strippedsize": 215, 58 | "size": 215, 59 | "weight": 860, 60 | "height": 1, 61 | "version": 1, 62 | "versionHex": "00000001", 63 | "merkleroot": "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098", 64 | "tx": [ 65 | "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098" 66 | ], 67 | "time": 1231469665, 68 | "mediantime": 1231469665, 69 | "nonce": 2573394689, 70 | "bits": "1d00ffff", 71 | "difficulty": 1, 72 | "chainwork": "0000000000000000000000000000000000000000000000000000000200020002", 73 | "previousblockhash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", 74 | "nextblockhash": "000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd" 75 | }"#; 76 | let block: Block = serde_json::from_str(json).unwrap(); 77 | 78 | assert_eq!( 79 | block, 80 | Block { 81 | hash: BlockHash::from_hex( 82 | "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048" 83 | ) 84 | .unwrap(), 85 | confirmations: 447014, 86 | size: 215, 87 | strippedsize: 215, 88 | weight: 860, 89 | height: 1, 90 | version: 1, 91 | version_hex: "00000001".to_string(), 92 | merkleroot: "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098" 93 | .to_string(), 94 | tx: vec![TransactionId::from_hex( 95 | "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098", 96 | ) 97 | .unwrap(),], 98 | time: 1231469665, 99 | mediantime: 1231469665, 100 | nonce: 2573394689, 101 | bits: "1d00ffff".to_string(), 102 | difficulty: 1.0, 103 | chainwork: "0000000000000000000000000000000000000000000000000000000200020002" 104 | .to_string(), 105 | previousblockhash: Some( 106 | BlockHash::from_hex( 107 | "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", 108 | ) 109 | .unwrap() 110 | ), 111 | nextblockhash: Some( 112 | BlockHash::from_hex( 113 | "000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd", 114 | ) 115 | .unwrap() 116 | ), 117 | } 118 | ) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/types/blockchain.rs: -------------------------------------------------------------------------------- 1 | use bitcoin::network::constants::Network; 2 | 3 | #[derive(Deserialize, Debug, PartialEq)] 4 | pub struct SoftFork { 5 | pub id: String, 6 | pub version: u32, 7 | pub reject: Reject, 8 | } 9 | 10 | #[derive(Deserialize, Debug, PartialEq)] 11 | pub struct Reject { 12 | pub status: bool, 13 | } 14 | 15 | #[derive(Deserialize, Debug, PartialEq)] 16 | pub struct Bip9SoftFork { 17 | pub csv: Bip9SoftForkDetails, 18 | pub segwit: Bip9SoftForkDetails, 19 | } 20 | 21 | #[derive(Deserialize, Debug, PartialEq)] 22 | pub struct Bip9SoftForkDetails { 23 | pub status: String, 24 | pub bit: Option, 25 | #[serde(rename = "startTime")] 26 | // In regtest, startTime is -1 27 | pub start_time: i64, 28 | pub timeout: u64, 29 | pub since: u64, 30 | // TODO: implement before new BIP9 31 | /* 32 | "statistics": { (object) numeric statistics about BIP9 signalling for a softfork (only for "started" status) 33 | "period": xx, (numeric) the length in blocks of the BIP9 signalling period 34 | "threshold": xx, (numeric) the number of blocks with the version bit set required to activate the feature 35 | "elapsed": xx, (numeric) the number of blocks elapsed since the beginning of the current period 36 | "count": xx, (numeric) the number of blocks with the version bit set in the current period 37 | "possible": xx (boolean) returns false if there are not enough blocks left in this period to pass activation threshold 38 | */ 39 | } 40 | 41 | #[derive(Deserialize, Debug, PartialEq)] 42 | pub struct BlockchainInfo { 43 | #[serde(with = "super::serde::network")] 44 | pub chain: Network, 45 | pub blocks: u64, 46 | pub headers: u64, 47 | pub bestblockhash: String, 48 | //TODO: Cannot trust serde - it is not able to deserialise “4.656542373906925e-10" 49 | pub difficulty: f64, 50 | pub mediantime: u64, 51 | pub verificationprogress: f64, 52 | pub initialblockdownload: bool, 53 | pub chainwork: String, 54 | pub size_on_disk: u64, 55 | pub pruned: bool, 56 | pub pruneheight: Option, 57 | pub automatic_pruning: Option, 58 | pub prune_target_size: Option, 59 | pub softforks: Vec, 60 | pub bip9_softforks: Bip9SoftFork, 61 | pub warnings: String, 62 | } 63 | #[cfg(test)] 64 | mod tests { 65 | use super::*; 66 | use serde_json; 67 | 68 | #[test] 69 | fn can_deserialize_blockchain_response() { 70 | let json = r#"{ 71 | "chain": "regtest", 72 | "blocks": 0, 73 | "headers": 0, 74 | "bestblockhash": "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206", 75 | "difficulty": 4.65654237390692e-10, 76 | "mediantime": 1296688602, 77 | "verificationprogress": 1, 78 | "initialblockdownload": true, 79 | "chainwork": "0000000000000000000000000000000000000000000000000000000000000002", 80 | "size_on_disk": 293, 81 | "pruned": false, 82 | "softforks": [ 83 | { 84 | "id": "bip34", 85 | "version": 2, 86 | "reject": { 87 | "status": false 88 | } 89 | }, 90 | { 91 | "id": "bip66", 92 | "version": 3, 93 | "reject": { 94 | "status": false 95 | } 96 | }, 97 | { 98 | "id": "bip65", 99 | "version": 4, 100 | "reject": { 101 | "status": false 102 | } 103 | } 104 | ], 105 | "bip9_softforks": { 106 | "csv": { 107 | "status": "defined", 108 | "startTime": 0, 109 | "timeout": 9223372036854775807, 110 | "since": 0 111 | }, 112 | "segwit": { 113 | "status": "active", 114 | "startTime": -1, 115 | "timeout": 9223372036854775807, 116 | "since": 0 117 | } 118 | }, 119 | "warnings": "" 120 | }"#; 121 | let blockchain: BlockchainInfo = serde_json::from_str(json).unwrap(); 122 | 123 | assert_eq!( 124 | blockchain, 125 | BlockchainInfo { 126 | chain: Network::Regtest, 127 | blocks: 0, 128 | headers: 0, 129 | bestblockhash: String::from( 130 | "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206" 131 | ), 132 | difficulty: 4.65654237390692e-10, 133 | mediantime: 1296688602, 134 | verificationprogress: 1.0, 135 | initialblockdownload: true, 136 | chainwork: String::from( 137 | "0000000000000000000000000000000000000000000000000000000000000002" 138 | ), 139 | size_on_disk: 293, 140 | pruned: false, 141 | pruneheight: None, 142 | automatic_pruning: None, 143 | prune_target_size: None, 144 | softforks: vec![ 145 | SoftFork { 146 | id: String::from("bip34"), 147 | version: 2, 148 | reject: Reject { status: false }, 149 | }, 150 | SoftFork { 151 | id: String::from("bip66"), 152 | version: 3, 153 | reject: Reject { status: false }, 154 | }, 155 | SoftFork { 156 | id: String::from("bip65"), 157 | version: 4, 158 | reject: Reject { status: false }, 159 | }, 160 | ], 161 | bip9_softforks: Bip9SoftFork { 162 | csv: Bip9SoftForkDetails { 163 | status: String::from("defined"), 164 | bit: None, 165 | start_time: 0, 166 | timeout: 9223372036854775807, 167 | since: 0, 168 | }, 169 | segwit: Bip9SoftForkDetails { 170 | status: String::from("active"), 171 | bit: None, 172 | start_time: -1, 173 | timeout: 9223372036854775807, 174 | since: 0, 175 | }, 176 | }, 177 | warnings: String::new(), 178 | }, 179 | ) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/bitcoin_rpc_api.rs: -------------------------------------------------------------------------------- 1 | use bitcoin::Address; 2 | use bitcoin::Script; 3 | use jsonrpc_client::ClientError; 4 | use jsonrpc_client::RpcError; 5 | use rpc; 6 | use types::address::AddressInfoResult; 7 | use BlockHash; 8 | use TransactionId; 9 | 10 | #[deprecated( 11 | since = "0.6.1", 12 | note = "This library is deprecated in favor of bitcoincore-rpc." 13 | )] 14 | #[allow(unused_variables)] 15 | pub trait BitcoinRpcApi: Send + Sync { 16 | // Order as per: https://bitcoin.org/en/developer-reference#rpcs 17 | 18 | fn add_multisig_address( 19 | &self, 20 | number_of_required_signatures: u32, 21 | participants: Vec<&Address>, 22 | ) -> Result, ClientError> { 23 | unimplemented!() 24 | } 25 | 26 | // TODO: abandontransaction 27 | // TODO: addmultisigaddress 28 | // TODO: addnode 29 | // TODO: addwitnessaddress 30 | // TODO: backupwallet 31 | // TODO: bumpfee 32 | // TODO: clearbanned 33 | // TODO: createmultisig 34 | 35 | fn create_raw_transaction( 36 | &self, 37 | inputs: Vec<&rpc::NewTransactionInput>, 38 | output: &rpc::NewTransactionOutput, 39 | ) -> Result, ClientError> { 40 | unimplemented!() 41 | } 42 | 43 | fn decode_rawtransaction( 44 | &self, 45 | tx: rpc::SerializedRawTransaction, 46 | ) -> Result, ClientError> { 47 | unimplemented!() 48 | } 49 | 50 | fn decode_script( 51 | &self, 52 | script: Script, 53 | ) -> Result, ClientError> { 54 | unimplemented!() 55 | } 56 | 57 | // TODO: disconnectnode 58 | 59 | fn dump_privkey( 60 | &self, 61 | address: &Address, 62 | ) -> Result, ClientError> { 63 | unimplemented!() 64 | } 65 | 66 | // TODO: dumpwallet 67 | // TODO: encryptwallet 68 | // TODO: estimatefee 69 | // TODO: estimatepriority 70 | 71 | fn fund_raw_transaction( 72 | &self, 73 | tx: &rpc::SerializedRawTransaction, 74 | options: &rpc::FundingOptions, 75 | ) -> Result, ClientError> { 76 | unimplemented!() 77 | } 78 | 79 | fn generate( 80 | &self, 81 | number_of_blocks: u32, 82 | ) -> Result, RpcError>, ClientError> { 83 | unimplemented!() 84 | } 85 | 86 | // TODO: generatetoaddress 87 | // TODO: getaddednodeinfo 88 | 89 | fn get_address_info( 90 | &self, 91 | address: &Address, 92 | ) -> Result, ClientError> { 93 | unimplemented!() 94 | } 95 | 96 | // TODO: getaddressesbylabel 97 | 98 | fn get_balance(&self) -> Result, ClientError> { 99 | unimplemented!() 100 | } 101 | 102 | fn get_best_block_hash(&self) -> Result, ClientError> { 103 | unimplemented!() 104 | } 105 | 106 | fn get_block( 107 | &self, 108 | header_hash: &BlockHash, 109 | ) -> Result, RpcError>, ClientError> { 110 | unimplemented!() 111 | } 112 | 113 | // TODO(evg): add verbosity param to get_block instead of separate methods? 114 | fn get_block_verbose( 115 | &self, 116 | header_hash: &BlockHash, 117 | ) -> Result, RpcError>, ClientError> { 118 | unimplemented!() 119 | } 120 | 121 | fn get_blockchain_info(&self) -> Result, ClientError> { 122 | unimplemented!() 123 | } 124 | 125 | fn get_block_count(&self) -> Result, ClientError> { 126 | unimplemented!() 127 | } 128 | 129 | fn get_block_hash(&self, height: u32) -> Result, ClientError> { 130 | unimplemented!() 131 | } 132 | 133 | // TODO: getblockheader 134 | // TODO: getblocktemplate 135 | // TODO: getchaintips 136 | // TODO: getconnectioncount 137 | // TODO: getdifficulty 138 | // TODO: getgenerate 139 | // TODO: gethashespersec 140 | // TODO: getinfo 141 | // TODO: getmemoryinfo 142 | // TODO: getmempoolancestors 143 | // TODO: getmempooldescendants 144 | // TODO: getmempoolentry 145 | // TODO: getmempoolinfo 146 | // TODO: getmininginfo 147 | // TODO: getnettotals 148 | // TODO: getnetworkhashesps 149 | // TODO: getnetworkinfo 150 | 151 | fn get_new_address(&self) -> Result, ClientError> { 152 | unimplemented!() 153 | } 154 | 155 | // TODO: getpeerinfo 156 | // TODO: getrawchangeaddress 157 | // TODO: getrawmempool 158 | 159 | fn get_raw_transaction_serialized( 160 | &self, 161 | tx: &TransactionId, 162 | ) -> Result, ClientError> { 163 | unimplemented!() 164 | } 165 | 166 | fn get_raw_transaction_verbose( 167 | &self, 168 | tx: &TransactionId, 169 | ) -> Result, ClientError> { 170 | unimplemented!() 171 | } 172 | 173 | // TODO: getreceivedbylabel 174 | // TODO: getreceivedbyaddress 175 | // TODO: gettxout 176 | // TODO: gettxoutsetinfo 177 | // TODO: getunconfirmedbalance 178 | // TODO: getwalletinfo 179 | // TODO: getwork 180 | // TODO: importaddress 181 | // TODO: importmulti 182 | // TODO: importprivkey 183 | // TODO: importprunedfunds 184 | // TODO: importwallet 185 | // TODO: keypoolrefill 186 | // TODO: invalidateblock 187 | // TODO: keypoolrefill 188 | // TODO: listlabels 189 | // TODO: listaddressgroupings 190 | // TODO: listbanned 191 | // TODO: listlockunspent 192 | // TODO: listreceivedbylabel 193 | // TODO: listreceivedbyaddress 194 | // TODO: listsinceblock 195 | // TODO: listtransactions 196 | 197 | fn list_unspent( 198 | &self, 199 | min_confirmations: rpc::TxOutConfirmations, 200 | max_confirmations: Option, 201 | recipients: Option>, 202 | ) -> Result, RpcError>, ClientError> { 203 | unimplemented!() 204 | } 205 | 206 | // TODO: lockunspent 207 | // TODO: ping 208 | // TODO: preciousblock 209 | // TODO: prioritisetransaction 210 | // TODO: pruneblockchain 211 | // TODO: removeprunedfunds 212 | // TODO: sendmany 213 | 214 | fn send_raw_transaction( 215 | &self, 216 | tx_data: rpc::SerializedRawTransaction, 217 | ) -> Result, ClientError> { 218 | unimplemented!() 219 | } 220 | 221 | fn send_to_address( 222 | &self, 223 | address: &Address, 224 | amount: f64, 225 | ) -> Result, ClientError> { 226 | unimplemented!() 227 | } 228 | // TODO: setlabel 229 | // TODO: setban 230 | // TODO: setgenerate 231 | // TODO: setnetworkactive 232 | // TODO: settxfee 233 | // TODO: signmessage 234 | // TODO: signmessagewithprivkey 235 | 236 | fn sign_raw_transaction_with_key( 237 | &self, 238 | tx: &rpc::SerializedRawTransaction, 239 | private_keys: Option>, 240 | dependencies: Option>, 241 | signature_hash_type: Option, 242 | ) -> Result, ClientError> { 243 | unimplemented!() 244 | } 245 | 246 | // TODO: signrawtransactionwithwallet 247 | // TODO: stop 248 | // TODO: submitblock 249 | 250 | fn validate_address( 251 | &self, 252 | address: &Address, 253 | ) -> Result, ClientError> { 254 | unimplemented!() 255 | } 256 | 257 | // TODO: verifychain 258 | // TODO: verifymessage 259 | // TODO: verifytxoutproof 260 | // TODO: walletlock 261 | // TODO: walletpassphrase 262 | // TODO: walletpassphrasechange 263 | } 264 | -------------------------------------------------------------------------------- /tests/rpc_calls.rs: -------------------------------------------------------------------------------- 1 | #![allow(deprecated)] 2 | extern crate bitcoin_rpc_client; 3 | extern crate jsonrpc_client; 4 | #[macro_use] 5 | extern crate log; 6 | extern crate bitcoin; 7 | extern crate hex; 8 | extern crate testcontainers; 9 | 10 | use bitcoin_rpc_client::*; 11 | use common::{ 12 | assert::assert_successful_result, test_client::BitcoinCoreTestClient, test_lifecycle::setup, 13 | }; 14 | use std::collections::HashMap; 15 | 16 | mod common; 17 | 18 | #[test] 19 | fn get_address_info() { 20 | setup(); 21 | 22 | assert_successful_result(|client| { 23 | let test_client = BitcoinCoreTestClient::new(client); 24 | 25 | let alice = test_client.an_address(); 26 | 27 | client.get_address_info(&alice) 28 | }) 29 | } 30 | 31 | #[test] 32 | fn add_multisig_address() { 33 | setup(); 34 | 35 | assert_successful_result(|client| { 36 | let test_client = BitcoinCoreTestClient::new(client); 37 | 38 | let alice = test_client.an_address(); 39 | let bob = test_client.an_address(); 40 | 41 | client.add_multisig_address(1, vec![&alice, &bob]) 42 | }) 43 | } 44 | 45 | #[test] 46 | fn get_block_count() { 47 | setup(); 48 | assert_successful_result(BitcoinCoreClient::get_block_count) 49 | } 50 | 51 | #[test] 52 | fn get_blockchain_info() { 53 | setup(); 54 | assert_successful_result(BitcoinCoreClient::get_blockchain_info) 55 | } 56 | 57 | #[test] 58 | fn get_new_address() { 59 | setup(); 60 | assert_successful_result(BitcoinCoreClient::get_new_address) 61 | } 62 | 63 | #[test] 64 | fn generate() { 65 | setup(); 66 | assert_successful_result(|client| client.generate(1)) 67 | } 68 | 69 | #[test] 70 | fn get_balance() { 71 | setup(); 72 | assert_successful_result(|client| { 73 | || client.generate(101).unwrap().unwrap(); 74 | client.get_balance() 75 | }) 76 | } 77 | 78 | #[test] 79 | fn list_unspent() { 80 | setup(); 81 | assert_successful_result(|client| { 82 | let _ = client.generate(101); 83 | client.list_unspent(rpc::TxOutConfirmations::Unconfirmed, Some(101), None) 84 | }) 85 | } 86 | 87 | #[test] 88 | fn getblock() { 89 | setup(); 90 | 91 | assert_successful_result(|client| { 92 | let block_hash = BitcoinCoreTestClient::new(client).a_block_hash(); 93 | 94 | client.get_block(&block_hash) 95 | }) 96 | } 97 | 98 | #[test] 99 | fn get_block_verbose() { 100 | setup(); 101 | 102 | assert_successful_result(|client| { 103 | let block_hash = BitcoinCoreTestClient::new(client).a_block_hash(); 104 | client.get_block_verbose(&block_hash) 105 | }) 106 | } 107 | 108 | #[test] 109 | fn get_best_block_hash() { 110 | setup(); 111 | 112 | assert_successful_result(|client| { 113 | BitcoinCoreTestClient::new(client).a_block(); 114 | 115 | client.get_best_block_hash() 116 | }) 117 | } 118 | 119 | #[test] 120 | fn get_block_hash() { 121 | setup(); 122 | 123 | assert_successful_result(|client| { 124 | BitcoinCoreTestClient::new(client).a_block_hash(); 125 | 126 | client.get_block_hash(50) 127 | }) 128 | } 129 | 130 | #[test] 131 | fn validate_address() { 132 | setup(); 133 | 134 | assert_successful_result(|client| { 135 | let address = BitcoinCoreTestClient::new(client).an_address(); 136 | 137 | client.validate_address(&address) 138 | }) 139 | } 140 | 141 | #[test] 142 | fn validate_multisig_address() { 143 | setup(); 144 | 145 | assert_successful_result(|client| { 146 | let test_client = BitcoinCoreTestClient::new(client); 147 | 148 | let alice = test_client.an_address(); 149 | let bob = test_client.an_address(); 150 | 151 | let multi_sig = client 152 | .add_multisig_address(1, vec![&alice, &bob]) 153 | .unwrap() 154 | .unwrap() 155 | .address; 156 | 157 | client.validate_address(&multi_sig) 158 | }) 159 | } 160 | 161 | #[test] 162 | fn get_raw_transaction_serialized() { 163 | setup(); 164 | 165 | assert_successful_result(|client| { 166 | let tx_id = BitcoinCoreTestClient::new(client).a_transaction_id(); 167 | 168 | client.get_raw_transaction_serialized(&tx_id) 169 | }); 170 | } 171 | 172 | #[test] 173 | fn decode_script() { 174 | setup(); 175 | 176 | assert_successful_result(|client| { 177 | client.decode_script(Script::from(hex::decode("522103ede722780d27b05f0b1169efc90fa15a601a32fc6c3295114500c586831b6aaf2102ecd2d250a76d204011de6bc365a56033b9b3a149f679bc17205555d3c2b2854f21022d609d2f0d359e5bc0e5d0ea20ff9f5d3396cb5b1906aa9c56a0e7b5edc0c5d553ae").unwrap())) 178 | }) 179 | } 180 | 181 | #[test] 182 | fn decode_rawtransaction() { 183 | setup(); 184 | 185 | assert_successful_result(|client| { 186 | client.decode_rawtransaction(rpc::SerializedRawTransaction("0100000001bafe2175b9d7b3041ebac529056b393cf2997f7964485aa382ffa449ffdac02a000000008a473044022013d212c22f0b46bb33106d148493b9a9723adb2c3dd3a3ebe3a9c9e3b95d8cb00220461661710202fbab550f973068af45c294667fc4dc526627a7463eb23ab39e9b01410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ffffffff01b0a86a00000000001976a91401b81d5fa1e55e069e3cc2db9c19e2e80358f30688ac00000000".into())) 187 | }) 188 | } 189 | 190 | #[test] 191 | fn create_raw_transaction() { 192 | setup(); 193 | 194 | assert_successful_result(|client| { 195 | let test_client = BitcoinCoreTestClient::new(client); 196 | 197 | let alice = test_client.an_address(); 198 | let _ = test_client.a_block(); 199 | 200 | let utxo = test_client.a_utxo(); 201 | 202 | let input = rpc::NewTransactionInput::from_utxo(&utxo); 203 | let mut map = HashMap::new(); 204 | map.insert(alice, utxo.amount); 205 | 206 | client.create_raw_transaction(vec![&input], &map) 207 | }) 208 | } 209 | 210 | #[test] 211 | fn dump_privkey() { 212 | setup(); 213 | 214 | assert_successful_result(|client| { 215 | let test_client = BitcoinCoreTestClient::new(client); 216 | 217 | let alice = test_client.an_address(); 218 | 219 | client.dump_privkey(&alice) 220 | }) 221 | } 222 | 223 | #[test] 224 | fn sign_raw_transaction() { 225 | setup(); 226 | 227 | // Note: The signing actually fails but this way, we get to test the deserialization of the datastructures 228 | assert_successful_result(|client| { 229 | let test_client = BitcoinCoreTestClient::new(client); 230 | 231 | let alice = test_client.an_address(); 232 | let alice_private_key = test_client.client.dump_privkey(&alice).unwrap().unwrap(); 233 | 234 | let utxo = test_client.a_utxo(); 235 | 236 | let input = rpc::NewTransactionInput::from_utxo(&utxo); 237 | let mut map = HashMap::new(); 238 | map.insert(alice, utxo.amount); 239 | 240 | let tx = test_client 241 | .client 242 | .create_raw_transaction(vec![&input], &map) 243 | .unwrap() 244 | .unwrap(); 245 | 246 | client.sign_raw_transaction_with_key( 247 | &tx, 248 | Some(vec![&alice_private_key]), 249 | None, 250 | Some(rpc::SigHashType::Single_AnyoneCanPay), 251 | ) 252 | }) 253 | } 254 | 255 | #[test] 256 | fn send_to_address() { 257 | setup(); 258 | 259 | assert_successful_result(|client| { 260 | let test_client = BitcoinCoreTestClient::new(client); 261 | test_client.a_block(); 262 | let alice = test_client.an_address(); 263 | 264 | client.send_to_address(&alice, 1.0) 265 | }) 266 | } 267 | 268 | #[test] 269 | fn fund_raw_transaction() { 270 | setup(); 271 | 272 | assert_successful_result(|client| { 273 | let test_client = BitcoinCoreTestClient::new(client); 274 | 275 | test_client.a_block(); 276 | 277 | let alice = test_client.an_address(); 278 | 279 | let mut outputs = HashMap::new(); 280 | outputs.insert(alice, 10f64); 281 | 282 | let raw_tx = test_client 283 | .client 284 | .create_raw_transaction(Vec::new(), &outputs) 285 | .unwrap() 286 | .unwrap(); 287 | let options = rpc::FundingOptions::new(); 288 | 289 | client.fund_raw_transaction(&raw_tx, &options) 290 | }) 291 | } 292 | -------------------------------------------------------------------------------- /src/types/address.rs: -------------------------------------------------------------------------------- 1 | use bitcoin::Address; 2 | use bitcoin::Script; 3 | use types::script::ScriptType; 4 | 5 | #[derive(Deserialize, Serialize, Debug, PartialEq)] 6 | pub struct MultiSigAddress { 7 | pub address: Address, 8 | #[serde(rename = "redeemScript")] 9 | pub redeem_script: Script, 10 | } 11 | 12 | #[derive(Deserialize, Serialize, Debug, PartialEq)] 13 | pub struct Label { 14 | pub name: String, 15 | pub purpose: String, 16 | } 17 | 18 | /// Most of the Option are due to different address formats 19 | /// Different fields are returned for P2PKH and P2SH addresses. 20 | #[derive(Deserialize, Serialize, Debug, PartialEq)] 21 | pub struct AddressValidationResult { 22 | #[serde(rename = "isvalid")] 23 | is_valid: bool, 24 | address: Option
, 25 | #[serde(rename = "scriptPubKey")] 26 | script_pub_key: Option, 27 | #[serde(rename = "ismine")] 28 | is_mine: Option, 29 | #[serde(rename = "iswatchonly")] 30 | is_watch_only: Option, 31 | #[serde(rename = "isscript")] 32 | is_script: Option, 33 | #[serde(rename = "script")] 34 | script_type: Option, 35 | #[serde(rename = "hex")] 36 | redeem_script: Option