Browse Source

Run 'cargo +stable fmt --all'

liquid_e
Roman Zeyde 6 years ago
parent
commit
ff32483a09
  1. 5
      src/bin/electrs.rs
  2. 2
      src/bulk.rs
  3. 2
      src/config.rs
  4. 25
      src/daemon.rs
  5. 18
      src/index.rs
  6. 10
      src/lib.rs
  7. 2
      src/mempool.rs
  8. 6
      src/metrics.rs
  9. 126
      src/query.rs
  10. 428
      src/rest.rs
  11. 14
      src/rpc.rs
  12. 90
      src/util.rs
  13. 159
      src/utils/address.rs

5
src/bin/electrs.rs

@ -4,10 +4,10 @@ extern crate error_chain;
#[macro_use]
extern crate log;
use electrs::rest;
use error_chain::ChainedError;
use std::process;
use std::time::Duration;
use electrs::rest;
use electrs::{
app::App,
@ -81,11 +81,8 @@ fn run_server(config: &Config) -> Result<()> {
fn main() {
let config = Config::from_args();
if let Err(e) = run_server(&config) {
error!("server failed: {}", e.display_chain());
process::exit(1);
}
}

2
src/bulk.rs

@ -1,6 +1,6 @@
use elements::Block;
use bitcoin::consensus::encode::{deserialize, Decodable};
use bitcoin::util::hash::{BitcoinHash, Sha256dHash};
use elements::Block;
use libc;
use std::collections::HashSet;
use std::fs;

2
src/config.rs

@ -9,8 +9,8 @@ use stderrlog;
use daemon::CookieGetter;
use errors::*;
use daemon::Network;
use errors::*;
#[derive(Debug, Clone)]
pub struct Config {

25
src/daemon.rs

@ -1,11 +1,11 @@
use base64;
use elements::{Block, BlockHeader, Transaction};
use bitcoin::blockdata::constants::genesis_block;
use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::network::constants::Network as BNetwork;
use bitcoin::util::hash::BitcoinHash;
use bitcoin::util::hash::Sha256dHash;
use bitcoin::blockdata::constants::genesis_block;
use bitcoin::network::constants::Network as BNetwork;
use bitcoin_bech32::constants::Network as B32Network;
use elements::{Block, BlockHeader, Transaction};
use glob;
use hex;
use serde_json::{from_str, from_value, Value};
@ -59,7 +59,7 @@ impl<'a> From<&'a Network> for BNetwork {
Network::Bitcoin => BNetwork::Bitcoin,
Network::Testnet => BNetwork::Testnet,
Network::Regtest => BNetwork::Regtest,
Network::Liquid => BNetwork::Bitcoin, // @FIXME
Network::Liquid => BNetwork::Bitcoin, // @FIXME
Network::LiquidV1 => BNetwork::Bitcoin, // @FIXME
Network::LiquidRegtest => BNetwork::Regtest, // @FIXME
}
@ -82,9 +82,9 @@ impl<'a> From<&'a Network> for B32Network {
impl<'a> From<&'a BNetwork> for Network {
fn from(network: &'a BNetwork) -> Self {
match network {
BNetwork::Bitcoin => Network::Liquid, // @FIXME
BNetwork::Bitcoin => Network::Liquid, // @FIXME
BNetwork::Regtest => Network::LiquidRegtest, // @FIXME
BNetwork::Testnet => Network::Testnet, // @FIXME
BNetwork::Testnet => Network::Testnet, // @FIXME
}
}
}
@ -556,19 +556,12 @@ impl Daemon {
Ok(blocks)
}
pub fn gettransaction(
&self,
txhash: &Sha256dHash
) -> Result<Transaction> {
let args = json!([txhash.be_hex_string(), /*verbose=*/ false]);
pub fn gettransaction(&self, txhash: &Sha256dHash) -> Result<Transaction> {
let args = json!([txhash.be_hex_string(), /*verbose=*/ false]);
tx_from_value(self.request("getrawtransaction", args)?)
}
pub fn gettransaction_raw(
&self,
txhash: &Sha256dHash,
verbose: bool,
) -> Result<Value> {
pub fn gettransaction_raw(&self, txhash: &Sha256dHash, verbose: bool) -> Result<Value> {
let args = json!([txhash.be_hex_string(), verbose]);
debug!("gettransaction_raw args {:?}", args);
Ok(self.request("getrawtransaction", args)?)

18
src/index.rs

@ -1,10 +1,10 @@
use elements::{Block, BlockHeader, Transaction, TxIn, TxOut};
use bincode;
use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::util::hash::BitcoinHash;
use bitcoin::util::hash::Sha256dHash;
use crypto::digest::Digest;
use crypto::sha2::Sha256;
use elements::{Block, BlockHeader, Transaction, TxIn, TxOut};
use std::collections::{HashMap, HashSet};
use std::iter::FromIterator;
use std::sync::RwLock;
@ -14,9 +14,8 @@ use metrics::{Counter, Gauge, HistogramOpts, HistogramTimer, HistogramVec, Metri
use signal::Waiter;
use store::{ReadStore, Row, WriteStore};
use util::{
full_hash, hash_prefix, spawn_thread, Bytes, FullHash, HashPrefix, HeaderEntry, HeaderList,
HeaderMap, SyncChannel, HASH_PREFIX_LEN,
BlockMeta,
full_hash, hash_prefix, spawn_thread, BlockMeta, Bytes, FullHash, HashPrefix, HeaderEntry,
HeaderList, HeaderMap, SyncChannel, HASH_PREFIX_LEN,
};
use errors::*;
@ -149,7 +148,8 @@ impl TxRow {
}
pub fn from_row(row: &Row) -> TxRow {
let (height, blockhash): (u32, Sha256dHash) = bincode::deserialize(&row.value).expect("failed to parse tx row");
let (height, blockhash): (u32, Sha256dHash) =
bincode::deserialize(&row.value).expect("failed to parse tx row");
TxRow {
key: bincode::deserialize(&row.key).expect("failed to parse TxKey"),
height: height,
@ -197,7 +197,6 @@ impl RawTxRow {
}
}
#[derive(Serialize, Deserialize)]
struct BlockKey {
code: u8,
@ -212,7 +211,12 @@ pub fn compute_script_hash(data: &[u8]) -> FullHash {
hash
}
pub fn index_transaction(txn: &Transaction, height: usize, blockhash: &Sha256dHash, rows: &mut Vec<Row>) {
pub fn index_transaction(
txn: &Transaction,
height: usize,
blockhash: &Sha256dHash,
rows: &mut Vec<Row>,
) {
let null_hash = Sha256dHash::default();
let txid: Sha256dHash = txn.txid();
for input in &txn.input {

10
src/lib.rs

@ -4,14 +4,16 @@ extern crate base64;
extern crate bincode;
extern crate bitcoin;
extern crate bitcoin_bech32;
extern crate elements;
extern crate chan_signal;
extern crate crypto;
extern crate dirs;
extern crate elements;
extern crate glob;
extern crate hex;
extern crate hyper;
extern crate libc;
extern crate lru;
extern crate lru_cache;
extern crate num_cpus;
extern crate page_size;
extern crate prometheus;
@ -23,8 +25,6 @@ extern crate sysconf;
extern crate time;
extern crate tiny_http;
extern crate url;
extern crate hyper;
extern crate lru_cache;
#[macro_use]
extern crate chan;
@ -41,8 +41,6 @@ extern crate serde_derive;
#[macro_use]
extern crate serde_json;
pub mod app;
pub mod bulk;
pub mod config;
@ -53,9 +51,9 @@ pub mod index;
pub mod mempool;
pub mod metrics;
pub mod query;
pub mod rest;
pub mod rpc;
pub mod signal;
pub mod store;
pub mod util;
pub mod rest;
pub mod utils;

2
src/mempool.rs

@ -1,5 +1,5 @@
use elements::Transaction;
use bitcoin::util::hash::Sha256dHash;
use elements::Transaction;
use hex;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::iter::FromIterator;

6
src/metrics.rs

@ -102,7 +102,11 @@ struct Stats {
fn parse_stats() -> Result<Stats> {
if cfg!(target_os = "macos") {
return Ok(Stats { utime: 0f64, rss: 0u64, fds: 0usize });
return Ok(Stats {
utime: 0f64,
rss: 0u64,
fds: 0usize,
});
}
let value = fs::read_to_string("/proc/self/stat").chain_err(|| "failed to read stats")?;
let parts: Vec<&str> = value.split_whitespace().collect();

126
src/query.rs

@ -1,21 +1,24 @@
use elements::{confidential, Block, Transaction};
use bitcoin::consensus::encode::{serialize, deserialize};
use bincode;
use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::util::hash::Sha256dHash;
use crypto::digest::Digest;
use crypto::sha2::Sha256;
use elements::{confidential, Block, Transaction};
use lru::LruCache;
use std::collections::{HashMap, BTreeMap};
use std::sync::{Arc, Mutex, RwLock};
use std::cmp::Ordering;
use bincode;
use std::collections::{BTreeMap, HashMap};
use std::sync::{Arc, Mutex, RwLock};
use app::App;
use index::{compute_script_hash, TxInRow, TxOutRow, TxRow, RawTxRow};
use index::{compute_script_hash, RawTxRow, TxInRow, TxOutRow, TxRow};
use mempool::Tracker;
use metrics::Metrics;
use serde_json::Value;
use store::{ReadStore, Row};
use util::{FullHash, HashPrefix, HeaderEntry, Bytes, BlockMeta, BlockHeaderMeta, BlockStatus, TransactionStatus};
use util::{
BlockHeaderMeta, BlockMeta, BlockStatus, Bytes, FullHash, HashPrefix, HeaderEntry,
TransactionStatus,
};
use errors::*;
@ -105,9 +108,15 @@ impl Status {
txns_map.insert(s.txn_id, &s.txn.as_ref().unwrap());
}
let mut txns: Vec<&TxnHeight> = txns_map.into_iter().map(|item| item.1).collect();
txns.sort_by(|a, b| if a.height == 0 { Ordering::Less }
else if b.height == 0 { Ordering::Greater }
else { b.height.cmp(&a.height) });
txns.sort_by(|a, b| {
if a.height == 0 {
Ordering::Less
} else if b.height == 0 {
Ordering::Greater
} else {
b.height.cmp(&a.height)
}
});
txns
}
@ -146,7 +155,6 @@ impl Status {
}
}
#[derive(Clone)]
pub struct TxnHeight {
pub txn: Transaction,
@ -159,7 +167,6 @@ fn merklize(left: Sha256dHash, right: Sha256dHash) -> Sha256dHash {
Sha256dHash::from_data(&data)
}
fn txrow_by_txid(store: &ReadStore, txid: &Sha256dHash) -> Option<TxRow> {
let key = TxRow::filter_full(&txid);
let value = store.get(&key)?;
@ -184,7 +191,7 @@ fn txids_by_script_hash(store: &ReadStore, script_hash: &[u8]) -> Vec<HashPrefix
store
.scan(&TxOutRow::filter(script_hash))
.iter()
.take(FUNDING_TXN_LIMIT+1)
.take(FUNDING_TXN_LIMIT + 1)
.map(|row| TxOutRow::from_row(row).txid_prefix)
.collect()
}
@ -259,7 +266,6 @@ impl Query {
store: &ReadStore,
prefixes: Vec<HashPrefix>,
) -> Result<Vec<TxnHeight>> {
if prefixes.len() > FUNDING_TXN_LIMIT {
bail!("Too many txs");
}
@ -381,12 +387,10 @@ impl Query {
}
pub fn status(&self, script_hash: &[u8]) -> Result<Status> {
let confirmed = self
.confirmed_status(script_hash)?;
//.chain_err(|| "failed to get confirmed status")?;
let mempool = self
.mempool_status(script_hash, &confirmed.0)?;
//.chain_err(|| "failed to get mempool status")?;
let confirmed = self.confirmed_status(script_hash)?;
//.chain_err(|| "failed to get confirmed status")?;
let mempool = self.mempool_status(script_hash, &confirmed.0)?;
//.chain_err(|| "failed to get mempool status")?;
Ok(Status { confirmed, mempool })
}
@ -394,24 +398,33 @@ impl Query {
let funding_output = FundingOutput::from(outpoint);
let read_store = self.app.read_store();
let tracker = self.tracker.read().unwrap();
Ok(if let Some(spent) = self.find_spending_input(read_store, &funding_output)? {
Some(spent)
} else if let Some(spent) = self.find_spending_input(tracker.index(), &funding_output)? {
Some(spent)
} else {
None
})
Ok(
if let Some(spent) = self.find_spending_input(read_store, &funding_output)? {
Some(spent)
} else if let Some(spent) =
self.find_spending_input(tracker.index(), &funding_output)?
{
Some(spent)
} else {
None
},
)
}
pub fn find_spending_for_funding_tx(&self, tx: Transaction) -> Result<Vec<Option<SpendingInput>>> {
pub fn find_spending_for_funding_tx(
&self,
tx: Transaction,
) -> Result<Vec<Option<SpendingInput>>> {
let txid = tx.txid();
let mut spends = vec![];
for (output_index, output) in tx.output.iter().enumerate() {
let spend = if !output.is_fee() && !output.script_pubkey.is_provably_unspendable() {
self.find_spending_by_outpoint((txid, output_index))?
} else { None };
spends.push(spend)
}
let spend = if !output.is_fee() && !output.script_pubkey.is_provably_unspendable() {
self.find_spending_by_outpoint((txid, output_index))?
} else {
None
};
spends.push(spend)
}
Ok(spends)
}
@ -422,33 +435,38 @@ impl Query {
// Get transaction from txstore or the in-memory mempool Tracker
pub fn tx_get(&self, txid: &Sha256dHash) -> Option<Transaction> {
rawtxrow_by_txid(self.app.read_store(), txid).map(|row| deserialize(&row.rawtx).expect("cannot parse tx from txstore"))
rawtxrow_by_txid(self.app.read_store(), txid)
.map(|row| deserialize(&row.rawtx).expect("cannot parse tx from txstore"))
.or_else(|| self.tracker.read().unwrap().get_txn(&txid))
}
// Get raw transaction from txstore or the in-memory mempool Tracker
pub fn tx_get_raw(&self, txid: &Sha256dHash) -> Option<Bytes> {
rawtxrow_by_txid(self.app.read_store(), txid).map(|row| row.rawtx)
.or_else(|| self.tracker.read().unwrap().get_txn(&txid).map(|tx| serialize(&tx)))
rawtxrow_by_txid(self.app.read_store(), txid)
.map(|row| row.rawtx)
.or_else(|| {
self.tracker
.read()
.unwrap()
.get_txn(&txid)
.map(|tx| serialize(&tx))
})
}
// Public API for transaction retrieval (for Electrum RPC)
// Fetched from bitcoind, includes tx confirmation information (number of confirmations and block hash)
pub fn get_transaction(&self, tx_hash: &Sha256dHash, verbose: bool) -> Result<Value> {
self.app
.daemon()
.gettransaction_raw(tx_hash, verbose)
self.app.daemon().gettransaction_raw(tx_hash, verbose)
}
pub fn get_block(&self, blockhash: &Sha256dHash) -> Result<Block> {
self.app
.daemon()
.getblock(blockhash)
self.app.daemon().getblock(blockhash)
}
pub fn get_block_header_with_meta(&self, blockhash: &Sha256dHash) -> Result<BlockHeaderMeta> {
let header_entry = self.get_header_by_hash(blockhash)?;
let meta = get_block_meta(self.app.read_store(), blockhash).ok_or("cannot load block meta")?;
let meta =
get_block_meta(self.app.read_store(), blockhash).ok_or("cannot load block meta")?;
Ok(BlockHeaderMeta { header_entry, meta })
}
@ -489,7 +507,11 @@ impl Query {
Some(header) => BlockStatus {
in_best_chain: true,
height: Some(header.height()),
next_best: self.app.index().get_header(header.height() + 1).map(|h| h.hash().clone())
next_best: self
.app
.index()
.get_header(header.height() + 1)
.map(|h| h.hash().clone()),
},
None => BlockStatus {
in_best_chain: false,
@ -507,11 +529,18 @@ impl Query {
};
// fetch the block header at the recorded confirmation height
let header = self.app.index().get_header(height as usize).chain_err(|| "invalid block height for tx")?;
let header = self
.app
.index()
.get_header(height as usize)
.chain_err(|| "invalid block height for tx")?;
// the block at confirmation height is not the one containing the tx, must've reorged!
if header.hash() != &blockhash { Ok(TransactionStatus::unconfirmed()) }
else { Ok(TransactionStatus::confirmed(&header)) }
if header.hash() != &blockhash {
Ok(TransactionStatus::unconfirmed())
} else {
Ok(TransactionStatus::confirmed(&header))
}
}
pub fn get_merkle_proof(
@ -519,7 +548,8 @@ impl Query {
tx_hash: &Sha256dHash,
block_hash: &Sha256dHash,
) -> Result<(Vec<Sha256dHash>, usize)> {
let mut txids = self.get_block_txids(&block_hash)
let mut txids = self
.get_block_txids(&block_hash)
.chain_err(|| format!("missing txids for block #{}", block_hash))?;
let pos = txids
.iter()

428
src/rest.rs

@ -1,26 +1,28 @@
use bitcoin::util::hash::{Sha256dHash,HexError};
use bitcoin::consensus::{self, encode::serialize};
use bitcoin::{Script, BitcoinHash};
use bitcoin::util::hash::{HexError, Sha256dHash};
use bitcoin::{BitcoinHash, Script};
use config::Config;
use elements::{TxIn,TxOut,Transaction,Proof};
use elements::confidential::{Value,Asset};
use utils::address::Address;
use daemon::Network;
use elements::confidential::{Asset, Value};
use elements::{Proof, Transaction, TxIn, TxOut};
use errors;
use hex::{self, FromHexError};
use hyper::{Body, Response, Server, Method, Request, StatusCode};
use hyper::service::service_fn_ok;
use hyper::rt::{self, Future};
use query::{Query, TxnHeight, FundingOutput, SpendingInput};
use serde_json;
use hyper::service::service_fn_ok;
use hyper::{Body, Method, Request, Response, Server, StatusCode};
use index::compute_script_hash;
use query::{FundingOutput, Query, SpendingInput, TxnHeight};
use serde::Serialize;
use serde_json;
use std::collections::BTreeMap;
use std::num::ParseIntError;
use std::str::FromStr;
use std::thread;
use std::sync::Arc;
use daemon::Network;
use util::{FullHash, BlockHeaderMeta, TransactionStatus, PegOutRequest, script_to_address, get_script_asm};
use index::compute_script_hash;
use std::thread;
use util::{
get_script_asm, script_to_address, BlockHeaderMeta, FullHash, PegOutRequest, TransactionStatus,
};
use utils::address::Address;
const TX_LIMIT: usize = 25;
const BLOCK_LIMIT: usize = 10;
@ -40,7 +42,7 @@ struct BlockValue {
weight: u32,
merkle_root: String,
previousblockhash: Option<String>,
proof: Option<BlockProofValue>
proof: Option<BlockProofValue>,
}
impl From<BlockHeaderMeta> for BlockValue {
@ -56,8 +58,11 @@ impl From<BlockHeaderMeta> for BlockValue {
size: blockhm.meta.size,
weight: blockhm.meta.weight,
merkle_root: header.merkle_root.be_hex_string(),
previousblockhash: if &header.prev_blockhash != &Sha256dHash::default() { Some(header.prev_blockhash.be_hex_string()) }
else { None },
previousblockhash: if &header.prev_blockhash != &Sha256dHash::default() {
Some(header.prev_blockhash.be_hex_string())
} else {
None
},
}
}
}
@ -95,11 +100,21 @@ struct TransactionValue {
impl From<Transaction> for TransactionValue {
fn from(tx: Transaction) -> Self {
let vin = tx.input.iter().map(|el| TxInValue::from(el.clone())).collect();
let vout: Vec<TxOutValue> = tx.output.iter().map(|el| TxOutValue::from(el.clone())).collect();
let vin = tx
.input
.iter()
.map(|el| TxInValue::from(el.clone()))
.collect();
let vout: Vec<TxOutValue> = tx
.output
.iter()
.map(|el| TxOutValue::from(el.clone()))
.collect();
let bytes = serialize(&tx);
let fee = vout.iter().find(|vout| vout.scriptpubkey_type == "fee")
.map_or(0, |vout| vout.value.unwrap());
let fee = vout
.iter()
.find(|vout| vout.scriptpubkey_type == "fee")
.map_or(0, |vout| vout.value.unwrap());
TransactionValue {
txid: tx.txid(),
@ -117,18 +132,25 @@ impl From<Transaction> for TransactionValue {
impl From<TxnHeight> for TransactionValue {
fn from(t: TxnHeight) -> Self {
let TxnHeight { txn, height, blockhash } = t;
let TxnHeight {
txn,
height,
blockhash,
} = t;
let mut value = TransactionValue::from(txn);
value.status = Some(if height != 0 {
TransactionStatus { confirmed: true, block_height: Some(height as usize), block_hash: Some(blockhash) }
TransactionStatus {
confirmed: true,
block_height: Some(height as usize),
block_hash: Some(blockhash),
}
} else {
TransactionStatus::unconfirmed()
TransactionStatus::unconfirmed()
});
value
}
}
#[derive(Serialize, Deserialize, Clone)]
struct TxInValue {
txid: Sha256dHash,
@ -146,31 +168,45 @@ impl From<TxIn> for TxInValue {
fn from(txin: TxIn) -> Self {
let is_coinbase = txin.is_coinbase();
let zero = [0u8;32];
let zero = [0u8; 32];
let issuance = txin.asset_issuance;
let is_reissuance = issuance.asset_blinding_nonce != zero;
let issuance_val = if txin.has_issuance() { Some(IssuanceValue {
is_reissuance: is_reissuance,
asset_blinding_nonce: if is_reissuance { Some(hex::encode(issuance.asset_blinding_nonce)) } else { None },
asset_entropy: if issuance.asset_entropy != zero { Some(hex::encode(issuance.asset_entropy)) } else { None },
assetamount: match issuance.amount {
Value::Explicit(value) => Some(value),
_ => None
},
assetamountcommitment: match issuance.amount {
Value::Confidential(..) => Some(hex::encode(serialize(&issuance.amount))),
_ => None
},
tokenamount: match issuance.inflation_keys {
Value::Explicit(value) => Some(value / 100000000), // https://github.com/ElementsProject/rust-elements/issues/7
_ => None,
},
tokenamountcommitment: match issuance.inflation_keys {
Value::Confidential(..) => Some(hex::encode(serialize(&issuance.inflation_keys))),
_ => None
},
}) } else { None };
let issuance_val = if txin.has_issuance() {
Some(IssuanceValue {
is_reissuance: is_reissuance,
asset_blinding_nonce: if is_reissuance {
Some(hex::encode(issuance.asset_blinding_nonce))
} else {
None
},
asset_entropy: if issuance.asset_entropy != zero {
Some(hex::encode(issuance.asset_entropy))
} else {
None
},
assetamount: match issuance.amount {
Value::Explicit(value) => Some(value),
_ => None,
},
assetamountcommitment: match issuance.amount {
Value::Confidential(..) => Some(hex::encode(serialize(&issuance.amount))),
_ => None,
},
tokenamount: match issuance.inflation_keys {
Value::Explicit(value) => Some(value / 100000000), // https://github.com/ElementsProject/rust-elements/issues/7
_ => None,
},
tokenamountcommitment: match issuance.inflation_keys {
Value::Confidential(..) => {
Some(hex::encode(serialize(&issuance.inflation_keys)))
}
_ => None,
},
})
} else {
None
};
let script = txin.script_sig;
@ -184,7 +220,7 @@ impl From<TxIn> for TxInValue {
is_coinbase,
sequence: txin.sequence,
//issuance: if txin.has_issuance() { Some(IssuanceValue::from(txin.asset_issuance)) } else { None },
issuance: issuance_val
issuance: issuance_val,
}
}
}
@ -249,11 +285,11 @@ impl From<TxOut> for TxOutValue {
fn from(txout: TxOut) -> Self {
let asset = match txout.asset {
Asset::Explicit(value) => Some(value.be_hex_string()),
_ => None
_ => None,
};
let assetcommitment = match txout.asset {
Asset::Confidential(..) => Some(hex::encode(serialize(&txout.asset))),
_ => None
_ => None,
};
let value = match txout.value {
Value::Explicit(value) => Some(value),
@ -261,7 +297,7 @@ impl From<TxOut> for TxOutValue {
};
let valuecommitment = match txout.value {
Value::Confidential(..) => Some(hex::encode(serialize(&txout.value))),
_ => None
_ => None,
};
let is_fee = txout.is_fee();
let script = txout.script_pubkey;
@ -312,8 +348,17 @@ struct UtxoValue {
}
impl From<FundingOutput> for UtxoValue {
fn from(out: FundingOutput) -> Self {
let FundingOutput { txn, txn_id, output_index, value, asset, .. } = out;
let TxnHeight { height, blockhash, .. } = txn.unwrap(); // we should never get a FundingOutput without a txn here
let FundingOutput {
txn,
txn_id,
output_index,
value,
asset,
..
} = out;
let TxnHeight {
height, blockhash, ..
} = txn.unwrap(); // we should never get a FundingOutput without a txn here
UtxoValue {
txid: txn_id,
@ -321,10 +366,14 @@ impl From<FundingOutput> for UtxoValue {
value: if value != 0 { Some(value) } else { None },
asset: asset.map(|val| val.be_hex_string()),
status: if height != 0 {
TransactionStatus { confirmed: true, block_height: Some(height as usize), block_hash: Some(blockhash) }
TransactionStatus {
confirmed: true,
block_height: Some(height as usize),
block_hash: Some(blockhash),
}
} else {
TransactionStatus::unconfirmed()
}
TransactionStatus::unconfirmed()
},
}
}
}
@ -338,18 +387,29 @@ struct SpendingValue {
}
impl From<SpendingInput> for SpendingValue {
fn from(out: SpendingInput) -> Self {
let SpendingInput { txn, txn_id, input_index, .. } = out;
let TxnHeight { height, blockhash, .. } = txn.unwrap(); // we should never get a SpendingInput without a txn here
let SpendingInput {
txn,
txn_id,
input_index,
..
} = out;
let TxnHeight {
height, blockhash, ..
} = txn.unwrap(); // we should never get a SpendingInput without a txn here
SpendingValue {
spent: true,
txid: Some(txn_id),
vin: Some(input_index as u32),
status: Some(if height != 0 {
TransactionStatus { confirmed: true, block_height: Some(height as usize), block_hash: Some(blockhash) }
TransactionStatus {
confirmed: true,
block_height: Some(height as usize),
block_hash: Some(blockhash),
}
} else {
TransactionStatus::unconfirmed()
})
TransactionStatus::unconfirmed()
}),
}
}
}
@ -365,8 +425,13 @@ impl Default for SpendingValue {
}
fn ttl_by_depth(height: Option<usize>, query: &Query) -> u32 {
height.map_or(TTL_SHORT, |height| if query.get_best_height() - height >= CONF_FINAL { TTL_LONG }
else { TTL_SHORT })
height.map_or(TTL_SHORT, |height| {
if query.get_best_height() - height >= CONF_FINAL {
TTL_LONG
} else {
TTL_SHORT
}
})
}
fn attach_tx_data(tx: TransactionValue, config: &Config, query: &Arc<Query>) -> TransactionValue {
@ -385,14 +450,21 @@ fn attach_txs_data(txs: &mut Vec<TransactionValue>, config: &Config, query: &Arc
// collect lookups
for mut vin in tx.vin.iter_mut() {
if !vin.is_coinbase && !vin.is_pegin {
lookups.entry(vin.txid).or_insert(vec![]).push((vin.vout, vin));
lookups
.entry(vin.txid)
.or_insert(vec![])
.push((vin.vout, vin));
}
}
// attach encoded address and pegout info (should ideally happen in TxOutValue::from(),
// but it cannot easily access the network)
for mut vout in tx.vout.iter_mut() {
vout.scriptpubkey_address = script_to_address(&vout.scriptpubkey, &config.network_type);
vout.pegout = PegOutRequest::parse(&vout.scriptpubkey, &config.parent_network, &config.parent_genesis_hash);
vout.pegout = PegOutRequest::parse(
&vout.scriptpubkey,
&config.parent_network,
&config.parent_genesis_hash,
);
}
}
@ -401,37 +473,35 @@ fn attach_txs_data(txs: &mut Vec<TransactionValue>, config: &Config, query: &Arc
let prevtx = query.tx_get(&prev_txid).unwrap();
for (prev_out_idx, ref mut nextin) in prev_vouts {
let mut prevout = TxOutValue::from(prevtx.output[prev_out_idx as usize].clone());
prevout.scriptpubkey_address = script_to_address(&prevout.scriptpubkey, &config.network_type);
prevout.scriptpubkey_address =
script_to_address(&prevout.scriptpubkey, &config.network_type);
nextin.prevout = Some(prevout);
}
}
}
pub fn run_server(config: &Config, query: Arc<Query>) {
let addr = &config.http_addr;
info!("REST server running on {}", addr);
let config = Arc::new(config.clone());
let new_service = move || {
let query = query.clone();
let config = config.clone();
service_fn_ok(move |req: Request<Body>| {
match handle_request(req,&query,&config) {
service_fn_ok(
move |req: Request<Body>| match handle_request(req, &query, &config) {
Ok(response) => response,
Err(e) => {
warn!("{:?}",e);
warn!("{:?}", e);
Response::builder()
.status(e.0)
.header("Content-Type", "text/plain")
.body(Body::from(e.1))
.unwrap()
},
}
})
}
},
)
};
let server = Server::bind(&addr)
@ -443,49 +513,71 @@ pub fn run_server(config: &Config, query: Arc<Query>) {
});
}
fn handle_request(req: Request<Body>, query: &Arc<Query>, config: &Config) -> Result<Response<Body>, HttpError> {
fn handle_request(
req: Request<Body>,
query: &Arc<Query>,
config: &Config,
) -> Result<Response<Body>, HttpError> {
// TODO it looks hyper does not have routing and query parsing :(
let uri = req.uri();
let path: Vec<&str> = uri.path().split('/').skip(1).collect();
info!("path {:?}", path);
match (req.method(), path.get(0), path.get(1), path.get(2), path.get(3)) {
(&Method::GET, Some(&"blocks"), Some(&"tip"), Some(&"hash"), None) =>
http_message(StatusCode::OK, query.get_best_header_hash().be_hex_string(), TTL_SHORT),
(&Method::GET, Some(&"blocks"), Some(&"tip"), Some(&"height"), None) =>
http_message(StatusCode::OK, query.get_best_height().to_string(), TTL_SHORT),
match (
req.method(),
path.get(0),
path.get(1),
path.get(2),
path.get(3),
) {
(&Method::GET, Some(&"blocks"), Some(&"tip"), Some(&"hash"), None) => http_message(
StatusCode::OK,
query.get_best_header_hash().be_hex_string(),
TTL_SHORT,
),
(&Method::GET, Some(&"blocks"), Some(&"tip"), Some(&"height"), None) => http_message(
StatusCode::OK,
query.get_best_height().to_string(),
TTL_SHORT,
),
(&Method::GET, Some(&"blocks"), start_height, None, None) => {
let start_height = start_height.and_then(|height| height.parse::<usize>().ok());
blocks(&query, start_height)
},
}
(&Method::GET, Some(&"block-height"), Some(height), None, None) => {
let height = height.parse::<usize>()?;
let headers = query.get_headers(&[height]);
let header = headers.get(0).ok_or_else(|| HttpError::not_found("Block not found".to_string()))?;
let header = headers
.get(0)
.ok_or_else(|| HttpError::not_found("Block not found".to_string()))?;
let ttl = ttl_by_depth(Some(height), query);
http_message(StatusCode::OK, header.hash().be_hex_string(), ttl)
},
}
(&Method::GET, Some(&"block"), Some(hash), None, None) => {
let hash = Sha256dHash::from_hex(hash)?;
let blockhm = query.get_block_header_with_meta(&hash)?;
let block_value = BlockValue::from(blockhm);
json_response(block_value, TTL_LONG)
},
}
(&Method::GET, Some(&"block"), Some(hash), Some(&"status"), None) => {
let hash = Sha256dHash::from_hex(hash)?;
let status = query.get_block_status(&hash);
let ttl = ttl_by_depth(status.height, query);
json_response(status, ttl)
},
}
(&Method::GET, Some(&"block"), Some(hash), Some(&"txids"), None) => {
let hash = Sha256dHash::from_hex(hash)?;
let txids = query.get_block_txids(&hash).map_err(|_| HttpError::not_found("Block not found".to_string()))?;
let txids = query
.get_block_txids(&hash)
.map_err(|_| HttpError::not_found("Block not found".to_string()))?;
json_response(txids, TTL_LONG)
},
}
(&Method::GET, Some(&"block"), Some(hash), Some(&"txs"), start_index) => {
let hash = Sha256dHash::from_hex(hash)?;
let txids = query.get_block_txids(&hash).map_err(|_| HttpError::not_found("Block not found".to_string()))?;
let txids = query
.get_block_txids(&hash)
.map_err(|_| HttpError::not_found("Block not found".to_string()))?;
let start_index = start_index
.map_or(0u32, |el| el.parse().unwrap_or(0))
@ -493,31 +585,47 @@ fn handle_request(req: Request<Body>, query: &Arc<Query>, config: &Config) -> Re
if start_index >= txids.len() {
bail!(HttpError::not_found("start index out of range".to_string()));
} else if start_index % TX_LIMIT != 0 {
bail!(HttpError::from(format!("start index must be a multipication of {}", TX_LIMIT)));
bail!(HttpError::from(format!(
"start index must be a multipication of {}",
TX_LIMIT
)));
}
let mut txs = txids.iter().skip(start_index).take(TX_LIMIT)
.map(|txid| query.tx_get(&txid).map(TransactionValue::from).ok_or("missing tx".to_string()))
.collect::<Result<Vec<TransactionValue>, _>>()?;
let mut txs = txids
.iter()
.skip(start_index)
.take(TX_LIMIT)
.map(|txid| {
query
.tx_get(&txid)
.map(TransactionValue::from)
.ok_or("missing tx".to_string())
}).collect::<Result<Vec<TransactionValue>, _>>()?;
attach_txs_data(&mut txs, config, query);
json_response(txs, TTL_LONG)
},
}
(&Method::GET, Some(&"address"), Some(address), None, None) => {
// @TODO create new AddressStatsValue struct?
let script_hash = address_to_scripthash(address, &config.network_type)?;
match query.status(&script_hash[..]) {
Ok(status) => json_response(json!({
Ok(status) => json_response(
json!({
"address": address,
"tx_count": status.history().len(),
}), TTL_SHORT),
}),
TTL_SHORT,
),
// if the address has too many txs, just return the address with no additional info (but no error)
Err(errors::Error(errors::ErrorKind::Msg(ref msg), _)) if *msg == "Too many txs".to_string() =>
json_response(json!({ "address": address }), TTL_SHORT),
Err(errors::Error(errors::ErrorKind::Msg(ref msg), _))
if *msg == "Too many txs".to_string() =>
{
json_response(json!({ "address": address }), TTL_SHORT)
}
Err(err) => bail!(err)
Err(err) => bail!(err),
}
},
}
(&Method::GET, Some(&"address"), Some(address), Some(&"txs"), start_index) => {
let start_index = start_index
.map_or(0u32, |el| el.parse().unwrap_or(0))
@ -532,24 +640,38 @@ fn handle_request(req: Request<Body>, query: &Arc<Query>, config: &Config) -> Re
} else if start_index >= txs.len() {
bail!(HttpError::not_found("start index out of range".to_string()));
} else if start_index % TX_LIMIT != 0 {
bail!(HttpError::from(format!("start index must be a multipication of {}", TX_LIMIT)));
bail!(HttpError::from(format!(
"start index must be a multipication of {}",
TX_LIMIT
)));
}
let mut txs = txs.iter().skip(start_index).take(TX_LIMIT).map(|t| TransactionValue::from((*t).clone())).collect();
let mut txs = txs
.iter()
.skip(start_index)
.take(TX_LIMIT)
.map(|t| TransactionValue::from((*t).clone()))
.collect();
attach_txs_data(&mut txs, config, query);
json_response(txs, TTL_SHORT)
},
}
(&Method::GET, Some(&"address"), Some(address), Some(&"utxo"), None) => {
let script_hash = address_to_scripthash(address, &config.network_type)?;
let status = query.status(&script_hash[..])?;
let utxos: Vec<UtxoValue> = status.unspent().into_iter().map(|o| UtxoValue::from(o.clone())).collect();
let utxos: Vec<UtxoValue> = status
.unspent()
.into_iter()
.map(|o| UtxoValue::from(o.clone()))
.collect();
// @XXX no paging, but query.status() is limited to 30 funding txs
json_response(utxos, TTL_SHORT)
},
}
(&Method::GET, Some(&"tx"), Some(hash), None, None) => {
let hash = Sha256dHash::from_hex(hash)?;
let transaction = query.tx_get(&hash).ok_or(HttpError::not_found("Transaction not found".to_string()))?;
let transaction = query
.tx_get(&hash)
.ok_or(HttpError::not_found("Transaction not found".to_string()))?;
let status = query.get_tx_status(&hash)?;
let ttl = ttl_by_depth(status.block_height, query);
@ -557,52 +679,76 @@ fn handle_request(req: Request<Body>, query: &Arc<Query>, config: &Config) -> Re
value.status = Some(status);
let value = attach_tx_data(value, config, query);
json_response(value, ttl)
},
}
(&Method::GET, Some(&"tx"), Some(hash), Some(&"hex"), None) => {
let hash = Sha256dHash::from_hex(hash)?;
let rawtx = query.tx_get_raw(&hash).ok_or(HttpError::not_found("Transaction not found".to_string()))?;
let rawtx = query
.tx_get_raw(&hash)
.ok_or(HttpError::not_found("Transaction not found".to_string()))?;
let ttl = ttl_by_depth(query.get_tx_status(&hash)?.block_height, query);
http_message(StatusCode::OK, hex::encode(rawtx), ttl)
},
}
(&Method::GET, Some(&"tx"), Some(hash), Some(&"status"), None) => {
let hash = Sha256dHash::from_hex(hash)?;
let status = query.get_tx_status(&hash)?;
let ttl = ttl_by_depth(status.block_height, query);
json_response(status, ttl)
},
}
(&Method::GET, Some(&"tx"), Some(hash), Some(&"merkle-proof"), None) => {
let hash = Sha256dHash::from_hex(hash)?;
let status = query.get_tx_status(&hash)?;
if !status.confirmed { bail!("Transaction is unconfirmed".to_string()) };
if !status.confirmed {
bail!("Transaction is unconfirmed".to_string())
};
let proof = query.get_merkle_proof(&hash, &status.block_hash.unwrap())?;
let ttl = ttl_by_depth(status.block_height, query);
json_response(proof, ttl)
},
}
(&Method::GET, Some(&"tx"), Some(hash), Some(&"outspend"), Some(index)) => {
let hash = Sha256dHash::from_hex(hash)?;
let outpoint = (hash, index.parse::<usize>()?);
let spend = query.find_spending_by_outpoint(outpoint)?
.map_or_else(|| SpendingValue::default(), |spend| SpendingValue::from(spend));
let ttl = ttl_by_depth(spend.status.as_ref().and_then(|ref status| status.block_height), query);
let spend = query.find_spending_by_outpoint(outpoint)?.map_or_else(
|| SpendingValue::default(),
|spend| SpendingValue::from(spend),
);
let ttl = ttl_by_depth(
spend
.status
.as_ref()
.and_then(|ref status| status.block_height),
query,
);
json_response(spend, ttl)
},
}
(&Method::GET, Some(&"tx"), Some(hash), Some(&"outspends"), None) => {
let hash = Sha256dHash::from_hex(hash)?;
let tx = query.tx_get(&hash).ok_or(HttpError::not_found("Transaction not found".to_string()))?;
let spends: Vec<SpendingValue> = query.find_spending_for_funding_tx(tx)?
let tx = query
.tx_get(&hash)
.ok_or(HttpError::not_found("Transaction not found".to_string()))?;
let spends: Vec<SpendingValue> = query
.find_spending_for_funding_tx(tx)?
.into_iter()
.map(|spend| spend.map_or_else(|| SpendingValue::default(), |spend| SpendingValue::from(spend)))
.collect();
.map(|spend| {
spend.map_or_else(
|| SpendingValue::default(),
|spend| SpendingValue::from(spend),
)
}).collect();
// @TODO long ttl if all outputs are either spent long ago or unspendable
json_response(spends, TTL_SHORT)
},
_ => {
Err(HttpError::not_found(format!("endpoint does not exist {:?}", uri.path())))
}
_ => Err(HttpError::not_found(format!(
"endpoint does not exist {:?}",
uri.path()
))),
}
}
fn http_message(status: StatusCode, message: String, ttl: u32) -> Result<Response<Body>,HttpError> {
fn http_message(
status: StatusCode,
message: String,
ttl: u32,
) -> Result<Response<Body>, HttpError> {
Ok(Response::builder()
.status(status)
.header("Content-Type", "text/plain")
@ -611,25 +757,28 @@ fn http_message(status: StatusCode, message: String, ttl: u32) -> Result<Respons
.unwrap())
}
fn json_response<T: Serialize>(value : T, ttl: u32) -> Result<Response<Body>,HttpError> {
fn json_response<T: Serialize>(value: T, ttl: u32) -> Result<Response<Body>, HttpError> {
let value = serde_json::to_string(&value)?;
Ok(Response::builder()
.header("Content-Type","application/json")
.header("Content-Type", "application/json")
.header("Cache-Control", format!("public, max-age={:}", ttl))
.body(Body::from(value))
.unwrap())
}
fn blocks(query: &Arc<Query>, start_height: Option<usize>)
-> Result<Response<Body>,HttpError> {
fn blocks(query: &Arc<Query>, start_height: Option<usize>) -> Result<Response<Body>, HttpError> {
let mut values = Vec::new();
let mut current_hash = match start_height {
Some(height) => query.get_headers(&[height]).get(0).ok_or(HttpError::not_found("Block not found".to_string()))?.hash().clone(),
Some(height) => query
.get_headers(&[height])
.get(0)
.ok_or(HttpError::not_found("Block not found".to_string()))?
.hash()
.clone(),
None => query.get_best_header()?.hash().clone(),
};
let zero = [0u8;32];
let zero = [0u8; 32];
for _ in 0..BLOCK_LIMIT {
let blockhm = query.get_block_header_with_meta(&current_hash)?;
current_hash = blockhm.header_entry.header().prev_blockhash.clone();
@ -647,7 +796,9 @@ fn blocks(query: &Arc<Query>, start_height: Option<usize>)
fn address_to_scripthash(addr: &str, network: &Network) -> Result<FullHash, HttpError> {
let addr = Address::from_str(addr)?;
let addr_network = addr.network;
if addr_network != *network && !(addr_network == Network::Testnet && *network == Network::LiquidRegtest) {
if addr_network != *network
&& !(addr_network == Network::Testnet && *network == Network::LiquidRegtest)
{
bail!(HttpError::from("Address on invalid network".to_string()))
}
Ok(compute_script_hash(&addr.script_pubkey().into_bytes()))
@ -692,9 +843,15 @@ impl From<errors::Error> for HttpError {
fn from(e: errors::Error) -> Self {
warn!("errors::Error: {:?}", e);
match e.description().to_string().as_ref() {
"getblock RPC error: {\"code\":-5,\"message\":\"Block not found\"}" => HttpError::not_found("Block not found".to_string()),
"Too many txs" => HttpError(StatusCode::TOO_MANY_REQUESTS, "Sorry! Addresses with a large number of transactions aren\'t currently supported.".to_string()),
_ => HttpError::generic()
"getblock RPC error: {\"code\":-5,\"message\":\"Block not found\"}" => {
HttpError::not_found("Block not found".to_string())
}
"Too many txs" => HttpError(
StatusCode::TOO_MANY_REQUESTS,
"Sorry! Addresses with a large number of transactions aren\'t currently supported."
.to_string(),
),
_ => HttpError::generic(),
}
}
}
@ -710,4 +867,3 @@ impl From<consensus::encode::Error> for HttpError {
HttpError::generic()
}
}

14
src/rpc.rs

@ -1,7 +1,6 @@
use elements::Transaction;
use bitcoin::consensus::encode::{deserialize, serialize};
use utils::address::Address;
use bitcoin::util::hash::Sha256dHash;
use elements::Transaction;
use error_chain::ChainedError;
use hex;
use serde_json::{from_str, Number, Value};
@ -12,6 +11,7 @@ use std::str::FromStr;
use std::sync::mpsc::{Sender, SyncSender, TrySendError};
use std::sync::{Arc, Mutex};
use std::thread;
use utils::address::Address;
use index::compute_script_hash;
use metrics::{Gauge, HistogramOpts, HistogramVec, MetricOpts, Metrics};
@ -157,7 +157,7 @@ impl Connection {
let block = self.query.get_block(&entries.get(0).unwrap().hash());
let block_hex = hex::encode(serialize(&block.unwrap()));
Ok(json!({"hex": &block_hex}))
Ok(json!({ "hex": &block_hex }))
}
fn blockchain_block_get(&self, params: &[Value]) -> Result<Value> {
@ -166,7 +166,7 @@ impl Connection {
let block = self.query.get_block(&block_hash);
let block_hex = hex::encode(serialize(&block.unwrap()));
Ok(json!({"hex": &block_hex}))
Ok(json!({ "hex": &block_hex }))
}
fn blockchain_estimatefee(&self, params: &[Value]) -> Result<Value> {
@ -275,7 +275,11 @@ impl Connection {
fn blockchain_transaction_get_merkle(&self, params: &[Value]) -> Result<Value> {
let tx_hash = hash_from_value(params.get(0)).chain_err(|| "bad tx_hash")?;
let height = usize_from_value(params.get(1), "height")?;
let header = self.query.get_headers(&vec![height]).pop().chain_err(|| "block not found")?;
let header = self
.query
.get_headers(&vec![height])
.pop()
.chain_err(|| "block not found")?;
let (merkle, pos) = self
.query
.get_merkle_proof(&tx_hash, &header.hash())

90
src/util.rs

@ -1,6 +1,6 @@
use elements::{Block, BlockHeader};
use bitcoin::consensus::encode::serialize;
use bitcoin::util::hash::{BitcoinHash, Sha256dHash};
use elements::{Block, BlockHeader};
use std::collections::HashMap;
use std::fmt;
use std::iter::FromIterator;
@ -27,7 +27,6 @@ pub fn full_hash(hash: &[u8]) -> FullHash {
array_ref![hash, 0, HASH_LEN].clone()
}
#[derive(Serialize, Deserialize)]
pub struct TransactionStatus {
pub confirmed: bool,
@ -81,7 +80,6 @@ impl<'a> From<&'a Block> for BlockMeta {
}
}
#[derive(Eq, PartialEq, Clone)]
pub struct HeaderEntry {
height: usize,
@ -310,14 +308,12 @@ where
.unwrap()
}
use bitcoin::{Script};
use utils::address::{Address,Payload};
use bitcoin::util::hash::Hash160;
use bitcoin_bech32::{WitnessProgram,u5};
use daemon::Network;
use bitcoin::Script;
use bitcoin_bech32::constants::Network as B32Network;
use bitcoin_bech32::{u5, WitnessProgram};
use daemon::Network;
use utils::address::{Address, Payload};
// @XXX we can't use any of the Address:p2{...}h utility methods, since they expect the pre-image data, which we don't have.
// we must instead create the Payload manually, which results in code duplication with the p2{...}h methods, especially for witness programs.
@ -328,20 +324,35 @@ pub fn script_to_address(script: &Script, network: &Network) -> Option<String> {
} else if script.is_p2sh() {
Some(Payload::ScriptHash(Hash160::from(&script[2..22])))
} else if script.is_v0_p2wpkh() {
Some(Payload::WitnessProgram(WitnessProgram::new(u5::try_from_u8(0).expect("0<32"),
script[2..22].to_vec(),
B32Network::from(network)).unwrap()))
Some(Payload::WitnessProgram(
WitnessProgram::new(
u5::try_from_u8(0).expect("0<32"),
script[2..22].to_vec(),
B32Network::from(network),
).unwrap(),
))
} else if script.is_v0_p2wsh() {
Some(Payload::WitnessProgram(WitnessProgram::new(u5::try_from_u8(0).expect("0<32"),
script[2..34].to_vec(),
B32Network::from(network)).unwrap()))
} else { None };
Some(Address { payload: payload?, network: *network }.to_string())
Some(Payload::WitnessProgram(
WitnessProgram::new(
u5::try_from_u8(0).expect("0<32"),
script[2..34].to_vec(),
B32Network::from(network),
).unwrap(),
))
} else {
None
};
Some(
Address {
payload: payload?,
network: *network,
}.to_string(),
)
}
use bitcoin::blockdata::script::Instruction::PushBytes;
use hex;
use bitcoin::blockdata::script::Instruction::{PushBytes};
#[derive(Serialize, Deserialize, Clone)]
pub struct PegOutRequest {
@ -352,28 +363,49 @@ pub struct PegOutRequest {
}
impl PegOutRequest {
pub fn parse(script: &Script, parent_network: &Network, parent_genesis_hash: &str) -> Option<PegOutRequest> {
if !script.is_op_return() { return None; }
pub fn parse(
script: &Script,
parent_network: &Network,
parent_genesis_hash: &str,
) -> Option<PegOutRequest> {
if !script.is_op_return() {
return None;
}
let nulldata: Vec<_> = script.iter(true).skip(1).collect();
if nulldata.len() < 2 { return None; }
if nulldata.len() < 2 {
return None;
}
let genesis_hash = if let PushBytes(data) = nulldata[0] { hex::encode(data.to_vec()) }
else { return None };
let genesis_hash = if let PushBytes(data) = nulldata[0] {
hex::encode(data.to_vec())
} else {
return None;
};
let scriptpubkey = if let PushBytes(data) = nulldata[1] { Script::from(data.to_vec()) }
else { return None };
let scriptpubkey = if let PushBytes(data) = nulldata[1] {
Script::from(data.to_vec())
} else {
return None;
};
if genesis_hash != parent_genesis_hash { return None; }
if genesis_hash != parent_genesis_hash {
return None;
}
let scriptpubkey_asm = get_script_asm(&scriptpubkey);
let scriptpubkey_address = script_to_address(&scriptpubkey, parent_network);
Some(PegOutRequest { genesis_hash, scriptpubkey, scriptpubkey_asm, scriptpubkey_address })
Some(PegOutRequest {
genesis_hash,
scriptpubkey,
scriptpubkey_asm,
scriptpubkey_address,
})
}
}
pub fn get_script_asm(script: &Script) -> String {
let asm = format!("{:?}", script);
(&asm[7..asm.len()-1]).to_string()
(&asm[7..asm.len() - 1]).to_string()
}

159
src/utils/address.rs

@ -19,15 +19,15 @@
use std::fmt::{self, Display, Formatter};
use std::str::FromStr;
use bitcoin_bech32::{self, WitnessProgram, u5};
use bitcoin_bech32::{self, u5, WitnessProgram};
use secp256k1::key::PublicKey;
use bitcoin::blockdata::script;
use bitcoin::blockdata::opcodes;
use daemon::Network;
use bitcoin::blockdata::script;
use bitcoin::consensus::encode;
use bitcoin::util::hash::Hash160;
use bitcoin::util::base58;
use bitcoin::util::hash::Hash160;
use daemon::Network;
/// The method used to produce an address
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
@ -49,7 +49,7 @@ pub struct Address {
/// The type of the address
pub payload: Payload,
/// The network on which this address is usable
pub network: Network
pub network: Network,
}
impl Address {
@ -59,7 +59,7 @@ impl Address {
pub fn p2pkh(pk: &PublicKey, network: Network) -> Address {
Address {
network: network,
payload: Payload::PubkeyHash(Hash160::from_data(&pk.serialize()[..]))
payload: Payload::PubkeyHash(Hash160::from_data(&pk.serialize()[..])),
}
}
@ -70,7 +70,7 @@ impl Address {
pub fn p2upkh(pk: &PublicKey, network: Network) -> Address {
Address {
network: network,
payload: Payload::PubkeyHash(Hash160::from_data(&pk.serialize_uncompressed()[..]))
payload: Payload::PubkeyHash(Hash160::from_data(&pk.serialize_uncompressed()[..])),
}
}
@ -81,7 +81,7 @@ impl Address {
pub fn p2pk(pk: &PublicKey, network: Network) -> Address {
Address {
network: network,
payload: Payload::Pubkey(*pk)
payload: Payload::Pubkey(*pk),
}
}
@ -91,41 +91,42 @@ impl Address {
pub fn p2sh(script: &script::Script, network: Network) -> Address {
Address {
network: network,
payload: Payload::ScriptHash(Hash160::from_data(&script[..]))
payload: Payload::ScriptHash(Hash160::from_data(&script[..])),
}
}
/// Create a witness pay to public key address from a public key
/// This is the native segwit address type for an output redemable with a single signature
pub fn p2wpkh (pk: &PublicKey, network: Network) -> Address {
pub fn p2wpkh(pk: &PublicKey, network: Network) -> Address {
Address {
network: network,
payload: Payload::WitnessProgram(
// unwrap is safe as witness program is known to be correct as above
WitnessProgram::new(u5::try_from_u8(0).expect("0<32"),
Hash160::from_data(&pk.serialize()[..])[..].to_vec(),
Address::bech_network(network)).unwrap())
WitnessProgram::new(
u5::try_from_u8(0).expect("0<32"),
Hash160::from_data(&pk.serialize()[..])[..].to_vec(),
Address::bech_network(network),
).unwrap(),
),
}
}
/// Create a pay to script address that embeds a witness pay to public key
/// This is a segwit address type that looks familiar (as p2sh) to legacy clients
pub fn p2shwpkh (pk: &PublicKey, network: Network) -> Address {
pub fn p2shwpkh(pk: &PublicKey, network: Network) -> Address {
let builder = script::Builder::new()
.push_int(0)
.push_slice(&Hash160::from_data(&pk.serialize()[..])[..]);
Address {
network: network,
payload: Payload::ScriptHash(
Hash160::from_data(builder.into_script().as_bytes())
)
payload: Payload::ScriptHash(Hash160::from_data(builder.into_script().as_bytes())),
}
}
/// Create a witness pay to script hash address
pub fn p2wsh (script: &script::Script, network: Network) -> Address {
use crypto::sha2::Sha256;
pub fn p2wsh(script: &script::Script, network: Network) -> Address {
use crypto::digest::Digest;
use crypto::sha2::Sha256;
let mut digest = Sha256::new();
digest.input(script.as_bytes());
@ -139,69 +140,66 @@ impl Address {
WitnessProgram::new(
u5::try_from_u8(0).expect("0<32"),
d.to_vec(),
Address::bech_network(network)
).unwrap()
)
Address::bech_network(network),
).unwrap(),
),
}
}
/// Create a pay to script address that embeds a witness pay to script hash address
/// This is a segwit address type that looks familiar (as p2sh) to legacy clients
pub fn p2shwsh (script: &script::Script, network: Network) -> Address {
use crypto::sha2::Sha256;
pub fn p2shwsh(script: &script::Script, network: Network) -> Address {
use crypto::digest::Digest;
use crypto::sha2::Sha256;
let mut digest = Sha256::new();
digest.input(script.as_bytes());
let mut d = [0u8; 32];
digest.result(&mut d);
let ws = script::Builder::new().push_int(0).push_slice(&d).into_script();
let ws = script::Builder::new()
.push_int(0)
.push_slice(&d)
.into_script();
Address {
network: network,
payload: Payload::ScriptHash(Hash160::from_data(ws.as_bytes()))
payload: Payload::ScriptHash(Hash160::from_data(ws.as_bytes())),
}
}
#[inline]
/// convert Network to bech32 network (this should go away soon)
fn bech_network (network: Network) -> bitcoin_bech32::constants::Network {
fn bech_network(network: Network) -> bitcoin_bech32::constants::Network {
match network {
Network::Bitcoin => bitcoin_bech32::constants::Network::Bitcoin,
Network::Testnet => bitcoin_bech32::constants::Network::Testnet,
Network::Regtest => bitcoin_bech32::constants::Network::Regtest,
// this should never actually happen, Liquid does not have bech32 addresses
Network::Liquid | Network::LiquidV1 | Network::LiquidRegtest => bitcoin_bech32::constants::Network::Bitcoin,
Network::Liquid | Network::LiquidV1 | Network::LiquidRegtest => {
bitcoin_bech32::constants::Network::Bitcoin
}
}
}
/// Generates a script pubkey spending to this address
pub fn script_pubkey(&self) -> script::Script {
match self.payload {
Payload::Pubkey(ref pk) => {
script::Builder::new()
.push_slice(&pk.serialize_uncompressed()[..])
.push_opcode(opcodes::All::OP_CHECKSIG)
},
Payload::PubkeyHash(ref hash) => {
script::Builder::new()
.push_opcode(opcodes::All::OP_DUP)
.push_opcode(opcodes::All::OP_HASH160)
.push_slice(&hash[..])
.push_opcode(opcodes::All::OP_EQUALVERIFY)
.push_opcode(opcodes::All::OP_CHECKSIG)
},
Payload::ScriptHash(ref hash) => {
script::Builder::new()
.push_opcode(opcodes::All::OP_HASH160)
.push_slice(&hash[..])
.push_opcode(opcodes::All::OP_EQUAL)
},
Payload::WitnessProgram(ref witprog) => {
script::Builder::new()
.push_int(witprog.version().to_u8() as i64)
.push_slice(witprog.program())
}
Payload::Pubkey(ref pk) => script::Builder::new()
.push_slice(&pk.serialize_uncompressed()[..])
.push_opcode(opcodes::All::OP_CHECKSIG),
Payload::PubkeyHash(ref hash) => script::Builder::new()
.push_opcode(opcodes::All::OP_DUP)
.push_opcode(opcodes::All::OP_HASH160)
.push_slice(&hash[..])
.push_opcode(opcodes::All::OP_EQUALVERIFY)
.push_opcode(opcodes::All::OP_CHECKSIG),
Payload::ScriptHash(ref hash) => script::Builder::new()
.push_opcode(opcodes::All::OP_HASH160)
.push_slice(&hash[..])
.push_opcode(opcodes::All::OP_EQUAL),
Payload::WitnessProgram(ref witprog) => script::Builder::new()
.push_int(witprog.version().to_u8() as i64)
.push_slice(witprog.program()),
}.into_script()
}
}
@ -221,7 +219,7 @@ impl Display for Address {
};
prefixed[1..].copy_from_slice(&hash[..]);
base58::check_encode_slice_to_fmt(fmt, &prefixed[..])
},
}
Payload::PubkeyHash(ref hash) => {
let mut prefixed = [0; 21];
prefixed[0] = match self.network {
@ -232,7 +230,7 @@ impl Display for Address {
};
prefixed[1..].copy_from_slice(&hash[..]);
base58::check_encode_slice_to_fmt(fmt, &prefixed[..])
},
}
Payload::ScriptHash(ref hash) => {
let mut prefixed = [0; 21];
prefixed[0] = match self.network {
@ -243,10 +241,8 @@ impl Display for Address {
};
prefixed[1..].copy_from_slice(&hash[..]);
base58::check_encode_slice_to_fmt(fmt, &prefixed[..])
},
Payload::WitnessProgram(ref witprog) => {
fmt.write_str(&witprog.to_address())
},
}
Payload::WitnessProgram(ref witprog) => fmt.write_str(&witprog.to_address()),
}
}
}
@ -256,71 +252,84 @@ impl FromStr for Address {
fn from_str(s: &str) -> Result<Address, encode::Error> {
// bech32 (note that upper or lowercase is allowed but NOT mixed case)
if s.starts_with("bc1") || s.starts_with("BC1") ||
s.starts_with("tb1") || s.starts_with("TB1") ||
s.starts_with("bcrt1") || s.starts_with("BCRT1")
if s.starts_with("bc1")
|| s.starts_with("BC1")
|| s.starts_with("tb1")
|| s.starts_with("TB1")
|| s.starts_with("bcrt1")
|| s.starts_with("BCRT1")
{
let witprog = WitnessProgram::from_address(s)?;
let network = match witprog.network() {
bitcoin_bech32::constants::Network::Bitcoin => Network::Bitcoin,
bitcoin_bech32::constants::Network::Testnet => Network::Testnet,
bitcoin_bech32::constants::Network::Regtest => Network::Regtest,
_ => panic!("unknown network")
_ => panic!("unknown network"),
};
if witprog.version().to_u8() != 0 {
return Err(encode::Error::UnsupportedWitnessVersion(witprog.version().to_u8()));
return Err(encode::Error::UnsupportedWitnessVersion(
witprog.version().to_u8(),
));
}
return Ok(Address {
network: network,
payload: Payload::WitnessProgram(witprog)
payload: Payload::WitnessProgram(witprog),
});
}
if s.len() > 50 {
return Err(encode::Error::Base58(base58::Error::InvalidLength(s.len() * 11 / 15)));
return Err(encode::Error::Base58(base58::Error::InvalidLength(
s.len() * 11 / 15,
)));
}
// Base 58
let data = base58::from_check(s)?;
if data.len() != 21 {
return Err(encode::Error::Base58(base58::Error::InvalidLength(data.len())));
return Err(encode::Error::Base58(base58::Error::InvalidLength(
data.len(),
)));
}
let (network, payload) = match data[0] {
0 => (
Network::Bitcoin,
Payload::PubkeyHash(Hash160::from(&data[1..]))
Payload::PubkeyHash(Hash160::from(&data[1..])),
),
5 => (
Network::Bitcoin,
Payload::ScriptHash(Hash160::from(&data[1..]))
Payload::ScriptHash(Hash160::from(&data[1..])),
),
111 => (
Network::Testnet,
Payload::PubkeyHash(Hash160::from(&data[1..]))
Payload::PubkeyHash(Hash160::from(&data[1..])),
),
196 => (
Network::Testnet,
Payload::ScriptHash(Hash160::from(&data[1..]))
Payload::ScriptHash(Hash160::from(&data[1..])),
),
57 => (
Network::LiquidV1,
Payload::PubkeyHash(Hash160::from(&data[1..]))
Payload::PubkeyHash(Hash160::from(&data[1..])),
),
39 => (
Network::LiquidV1,
Payload::ScriptHash(Hash160::from(&data[1..]))
Payload::ScriptHash(Hash160::from(&data[1..])),
),
235 => (
Network::LiquidRegtest,
Payload::PubkeyHash(Hash160::from(&data[1..]))
Payload::PubkeyHash(Hash160::from(&data[1..])),
),
75 => (
Network::LiquidRegtest,
Payload::ScriptHash(Hash160::from(&data[1..]))
Payload::ScriptHash(Hash160::from(&data[1..])),
),
x => return Err(encode::Error::Base58(base58::Error::InvalidVersion(vec![x])))
x => {
return Err(encode::Error::Base58(base58::Error::InvalidVersion(vec![
x,
])))
}
};
Ok(Address {

Loading…
Cancel
Save