Skip to content
This repository has been archived by the owner on Mar 23, 2021. It is now read-only.

Commit

Permalink
bitcoind HTTP API (#1371)
Browse files Browse the repository at this point in the history
bitcoind HTTP API
  • Loading branch information
da-kami authored Sep 13, 2019
2 parents 24d4209 + b005c64 commit bd7d4d3
Show file tree
Hide file tree
Showing 25 changed files with 306 additions and 368 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ commands:
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
echo "deb https://deb.nodesource.com/node_10.x/ trusty main" | sudo tee /etc/apt/sources.list.d/node_10.list
sudo apt-get update
sudo apt-get install -y nodejs=10.* yarn libzmq3-dev
sudo apt-get install -y nodejs=10.* yarn
install_rust:
steps:
- run:
Expand Down
22 changes: 0 additions & 22 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,7 @@ See [#626](https://github.com/comit-network/comit-rs/issues/626) for tracking.
## Setup build environment

1. Install `rustup`: `curl https://sh.rustup.rs -sSf | sh`
2. Install libzmq:
- Ubuntu/Debian: `apt install libzmq3-dev`
- Mac ([Homebrew](https://brew.sh/)) `brew install zeromq`
3. Install OpenSSL:
2. Install OpenSSL:
- Ubuntu/Debian: `apt install libssl-dev pkg-config`

## Build & Run
Expand Down
4 changes: 2 additions & 2 deletions api_tests/lib/bitcoin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import sb from "satoshi-bitcoin";
import { test_rng } from "./util";

export interface BitcoinNodeConfig {
network: string;
username: string;
password: string;
host: string;
rpcPort: number;
zmqPort: number;
}

interface GetBlockchainInfoResponse {
Expand Down Expand Up @@ -79,7 +79,7 @@ function createBitcoinRpcClient(btcConfig?: BitcoinNodeConfig) {

if (!bitcoinRpcClient || btcConfig !== bitcoinConfig) {
bitcoinRpcClient = new BitcoinRpcClient({
network: "regtest",
network: btcConfig.network,
port: btcConfig.rpcPort,
host: btcConfig.host,
username: btcConfig.username,
Expand Down
16 changes: 2 additions & 14 deletions api_tests/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,7 @@ export interface CndConfigFile {

interface BtsieveBitcoin {
node_url: string;
zmq_endpoint: string;
authentication: {
basic: {
node_username: string;
node_password: string;
};
};
network: string;
}

interface BtsieveEthereum {
Expand Down Expand Up @@ -143,14 +137,8 @@ export function btsieveBitcoinConfig(
nodeConfig: BitcoinNodeConfig
): BtsieveBitcoin {
return {
authentication: {
basic: {
node_password: nodeConfig.password,
node_username: nodeConfig.username,
},
},
node_url: `http://${nodeConfig.host}:${nodeConfig.rpcPort}`,
zmq_endpoint: `tcp://${nodeConfig.host}:${nodeConfig.zmqPort}`,
network: nodeConfig.network,
};
}

Expand Down
7 changes: 3 additions & 4 deletions api_tests/lib/ledger_runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,9 @@ export class LedgerRunner {
const [, password] = result.output.split(":");

return {
network: "regtest",
host: container.getContainerIpAddress(),
rpcPort: container.getMappedPort(18443),
zmqPort: container.getMappedPort(28332),
username: "__cookie__",
password,
};
Expand Down Expand Up @@ -162,11 +162,10 @@ async function startBitcoinContainer(): Promise<StartedTestContainer> {
"-rpcbind=0.0.0.0:18443",
"-rpcallowip=0.0.0.0/0",
"-debug=1",
"-zmqpubrawblock=tcp://*:28332",
"-zmqpubrawtx=tcp://*:28333",
"-acceptnonstdtxn=0",
"-rest",
])
.withExposedPorts(18443, 28332)
.withExposedPorts(18443)
.withWaitStrategy(new LogWaitStrategy("Flushed wallet.dat"))
.start();
}
Expand Down
2 changes: 0 additions & 2 deletions btsieve/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ version = "0.1.0"
[dependencies]

bitcoin_support = { path = "../internal/bitcoin_support" }
bitcoincore-rpc = "0.8.0-rc1"
byteorder = "1.3"
chrono = { version = "0.4", features = ["serde"] }
config = { version = "0.9", features = ["toml"] }
Expand All @@ -34,7 +33,6 @@ structopt = "0.3"
tokio = "0.1"
url = { version = "2.1", features = ["serde"] }
warp = { version = "0.1", default-features = false }
zmq-rs = "0.1"

[dev-dependencies]
env_logger = "0.6"
Expand Down
12 changes: 0 additions & 12 deletions btsieve/config/bitcoin_basicauth.toml

This file was deleted.

11 changes: 0 additions & 11 deletions btsieve/config/bitcoin_cookieauth.toml

This file was deleted.

6 changes: 1 addition & 5 deletions btsieve/config/bitcoin_only.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
[bitcoin]
network = "regtest"
node_url = "http://localhost:18443"
zmq_endpoint = "tcp://127.0.0.1:28332"

[bitcoin.authentication.basic]
node_username = "bitcoin"
node_password = "54pLR_f7-G6is32LP-7nbhzZSbJs_2zSATtZV_r05yg="

[http_api]
address_bind="0.0.0.0"
Expand Down
6 changes: 1 addition & 5 deletions btsieve/config/btsieve.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
[bitcoin]
network = "testnet"
node_url = "http://localhost:18443"
zmq_endpoint = "tcp://127.0.0.1:28332"

[bitcoin.authentication.basic]
node_username = "bitcoin"
node_password = "54pLR_f7-G6is32LP-7nbhzZSbJs_2zSATtZV_r05yg="

[ethereum]
node_url = "http://localhost:8545"
Expand Down
169 changes: 169 additions & 0 deletions btsieve/src/bitcoin/bitcoind_http_blocksource.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
use crate::blocksource::{self, BlockSource};
use bitcoin_support::{deserialize, Block, Network, Transaction};
use futures::{Future, Stream};
use reqwest::r#async::Client;
use serde::Deserialize;
use std::time::Duration;
use tokio::timer::Interval;

#[derive(Deserialize)]
struct ChainInfo {
bestblockhash: String,
}

#[derive(Debug)]
pub enum Error {
Reqwest(reqwest::Error),
Hex(hex::FromHexError),
BlockDeserialization(bitcoin_support::consensus::encode::Error),
TransactionDeserialization(String),
}

#[derive(Clone)]
pub struct BitcoindHttpBlockSource {
network: Network,
base_url: String,
client: Client,
}

impl BitcoindHttpBlockSource {
pub fn new(url: String, network: Network) -> Self {
Self {
network,
base_url: url,
client: Client::new(),
}
}

fn latest_block(&self) -> impl Future<Item = Block, Error = Error> + Send + 'static {
let cloned_self = self.clone();

self.latest_block_hash()
.and_then(move |latest_block_hash| cloned_self.block_by_hash(latest_block_hash))
}

fn latest_block_hash(&self) -> impl Future<Item = String, Error = Error> + Send + 'static {
let bitcoind_blockchain_info_url = format!("{}/rest/chaininfo.json", self.base_url);

self.client
.get(bitcoind_blockchain_info_url.as_str())
.send()
.map_err(|e| {
log::error!("Error when sending request to bitcoind");
Error::Reqwest(e)
})
.and_then(move |mut response| {
response.json::<ChainInfo>().map_err(|e| {
log::error!("Error when deserialising the response from bitcoind");
Error::Reqwest(e)
})
})
.map(move |blockchain_info| blockchain_info.bestblockhash)
}

fn block_by_hash(
&self,
block_hash: String,
) -> impl Future<Item = Block, Error = Error> + Send + 'static {
let raw_block_by_hash_url = format!("{}/rest/block/{}.hex", self.base_url, block_hash);

self.client
.get(raw_block_by_hash_url.as_str())
.send()
.map_err(Error::Reqwest)
.and_then(|mut response| response.text().map_err(Error::Reqwest))
.and_then(move |mut response_text| {
response_text = response_text.as_str().trim().to_string();
hex::decode(response_text).map_err(Error::Hex)
})
.and_then(|bytes| deserialize(bytes.as_ref()).map_err(Error::BlockDeserialization))
.map(move |block| {
log::trace!("Got {:?}", block);
block
})
}

pub fn transaction_by_hash(
&self,
transaction_hash: String,
) -> impl Future<Item = Transaction, Error = Error> + Send + 'static {
let raw_transaction_by_hash_url =
format!("{}/rest/tx/{}.hex", self.base_url, transaction_hash);

self.client
.get(raw_transaction_by_hash_url.as_str())
.send()
.map_err(Error::Reqwest)
.and_then(|mut response| response.text().map_err(Error::Reqwest))
.and_then(move |mut response_text| {
response_text = response_text.as_str().trim().to_string();
hex::decode(response_text).map_err(Error::Hex)
})
.and_then(|bytes| {
deserialize(bytes.as_ref()).map_err(|e| {
log::error!(
"Got new transaction but failed to deserialize it because {:?}",
e
);
Error::TransactionDeserialization(format!(
"Failed to deserialize the response from bitcoind into a transaction: {}",
e
))
})
})
.inspect(move |transaction| {
log::debug!("Fetched transaction {:?}", transaction);
})
}
}

impl BlockSource for BitcoindHttpBlockSource {
type Block = Block;
type Error = Error;

fn blocks(
&self,
) -> Box<dyn Stream<Item = Self::Block, Error = blocksource::Error<Error>> + Send> {
// The Bitcoin blockchain has a mining interval of about 10 minutes.
// The poll interval is configured to once every 2 minutes for mainnet and
// testnet so we don't have to wait to long to see a new block.
let poll_interval = match self.network {
Network::Mainnet => 120_000,
Network::Testnet => 120_000,
Network::Regtest => 300,
};

log::info!(target: "bitcoin::blocksource", "polling for new blocks from bitcoind on {} every {} seconds", self.network, poll_interval);

let cloned_self = self.clone();

let stream = Interval::new_interval(Duration::from_millis(poll_interval))
.map_err(blocksource::Error::Timer)
.and_then(move |_| {
cloned_self
.latest_block()
.map(Some)
.or_else(|error| {
match error {
Error::Reqwest(e) => {
log::warn!(target: "bitcoin::blocksource", "reqwest error encountered during polling: {:?}", e);
Ok(None)
}
Error::Hex(e) => {
log::warn!(target: "bitcoin::blocksource", "hex-decode error encountered during polling: {:?}", e);
Ok(None)
}
Error::BlockDeserialization(e) => {
log::warn!(target: "bitcoin::blocksource", "block-deserialization error encountered during polling: {:?}", e);
Ok(None)
}
_ => Err(error)
}
})
.map_err(blocksource::Error::Source)
})
.filter_map(|maybe_block| maybe_block);

Box::new(stream)
}
}
Loading

0 comments on commit bd7d4d3

Please sign in to comment.