Browse Source

Update bitcoin crate to 0.17

fees
Roman Zeyde 6 years ago
parent
commit
77ee699ed5
No known key found for this signature in database GPG Key ID: 87CAE5FA46917CBB
  1. 18
      Cargo.lock
  2. 3
      Cargo.toml
  3. 2
      src/app.rs
  4. 3
      src/bulk.rs
  5. 51
      src/daemon.rs
  6. 4
      src/index.rs
  7. 2
      src/mempool.rs
  8. 8
      src/query.rs
  9. 33
      src/rpc.rs
  10. 3
      src/util.rs

18
Cargo.lock

@ -130,14 +130,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "bitcoin" name = "bitcoin"
version = "0.16.0" version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"bitcoin-bech32 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "bitcoin-bech32 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"bitcoin_hashes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
"secp256k1 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)", "secp256k1 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -149,6 +149,14 @@ dependencies = [
"bech32 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "bech32 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "bitcoin_hashes"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.0.4" version = "1.0.4"
@ -305,7 +313,8 @@ dependencies = [
"arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
"bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"bitcoin 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", "bitcoin 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bitcoin_hashes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"chan 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)", "chan 0.1.23 (registry+https://github.com/rust-lang/crates.io-index)",
"chan-signal 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "chan-signal 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1114,8 +1123,9 @@ dependencies = [
"checksum bindgen 0.43.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d52d263eacd15d26cbcf215d254b410bd58212aaa2d3c453a04b2d3b3adcf41" "checksum bindgen 0.43.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d52d263eacd15d26cbcf215d254b410bd58212aaa2d3c453a04b2d3b3adcf41"
"checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c" "checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c"
"checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f" "checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f"
"checksum bitcoin 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7f9411713f2eec2b76ebb5fc0407e9bcef86130e33e196749be3f9970eb7f7f6" "checksum bitcoin 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "32ce50d0008960205cce3ee21efb813bc276336625c5afb881845e78bbfe1f92"
"checksum bitcoin-bech32 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f0a5cfe5abcb5040b36d4ea8acba95288fefebd7959b59475f2c4ec705974b4c" "checksum bitcoin-bech32 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f0a5cfe5abcb5040b36d4ea8acba95288fefebd7959b59475f2c4ec705974b4c"
"checksum bitcoin_hashes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "323a0efd346fb884b500f23cb0244556aaa46ae2dcf32bbe7fe016b77d88d514"
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
"checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" "checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400"
"checksum byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "94f88df23a25417badc922ab0f5716cc1330e87f71ddd9203b3a3ccd9cedf75d" "checksum byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "94f88df23a25417badc922ab0f5716cc1330e87f71ddd9203b3a3ccd9cedf75d"

3
Cargo.toml

@ -15,7 +15,8 @@ edition = "2018"
arrayref = "0.3" arrayref = "0.3"
base64 = "0.9" base64 = "0.9"
bincode = "1.0" bincode = "1.0"
bitcoin = "0.16" bitcoin = "0.17"
bitcoin_hashes = "0.3"
chan = "0.1" chan = "0.1"
chan-signal = "0.3" chan-signal = "0.3"
clap = "2.31" clap = "2.31"

2
src/app.rs

@ -1,4 +1,4 @@
use bitcoin::util::hash::Sha256dHash; use bitcoin_hashes::sha256d::Hash as Sha256dHash;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use crate::{config::Config, daemon, errors::*, index, signal::Waiter, store}; use crate::{config::Config, daemon, errors::*, index, signal::Waiter, store};

3
src/bulk.rs

@ -1,6 +1,7 @@
use bitcoin::blockdata::block::Block; use bitcoin::blockdata::block::Block;
use bitcoin::consensus::encode::{deserialize, Decodable}; use bitcoin::consensus::encode::{deserialize, Decodable};
use bitcoin::util::hash::{BitcoinHash, Sha256dHash}; use bitcoin::util::hash::BitcoinHash;
use bitcoin_hashes::sha256d::Hash as Sha256dHash;
use libc; use libc;
use std::collections::HashSet; use std::collections::HashSet;
use std::fs; use std::fs;

51
src/daemon.rs

@ -4,7 +4,8 @@ use bitcoin::blockdata::transaction::Transaction;
use bitcoin::consensus::encode::{deserialize, serialize}; use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::network::constants::Network; use bitcoin::network::constants::Network;
use bitcoin::util::hash::BitcoinHash; use bitcoin::util::hash::BitcoinHash;
use bitcoin::util::hash::Sha256dHash; use bitcoin_hashes::hex::{FromHex, ToHex};
use bitcoin_hashes::sha256d::Hash as Sha256dHash;
use glob; use glob;
use hex; use hex;
use serde_json::{from_str, from_value, Value}; use serde_json::{from_str, from_value, Value};
@ -453,7 +454,7 @@ impl Daemon {
pub fn getblockheader(&self, blockhash: &Sha256dHash) -> Result<BlockHeader> { pub fn getblockheader(&self, blockhash: &Sha256dHash) -> Result<BlockHeader> {
header_from_value(self.request( header_from_value(self.request(
"getblockheader", "getblockheader",
json!([blockhash.be_hex_string(), /*verbose=*/ false]), json!([blockhash.to_hex(), /*verbose=*/ false]),
)?) )?)
} }
@ -472,32 +473,28 @@ impl Daemon {
} }
pub fn getblock(&self, blockhash: &Sha256dHash) -> Result<Block> { pub fn getblock(&self, blockhash: &Sha256dHash) -> Result<Block> {
let block = block_from_value(self.request( let block = block_from_value(
"getblock", self.request("getblock", json!([blockhash.to_hex(), /*verbose=*/ false]))?,
json!([blockhash.be_hex_string(), /*verbose=*/ false]), )?;
)?)?;
assert_eq!(block.bitcoin_hash(), *blockhash); assert_eq!(block.bitcoin_hash(), *blockhash);
Ok(block) Ok(block)
} }
pub fn getblocktxids(&self, blockhash: &Sha256dHash) -> Result<Vec<Sha256dHash>> { pub fn getblocktxids(&self, blockhash: &Sha256dHash) -> Result<Vec<Sha256dHash>> {
self.request( self.request("getblock", json!([blockhash.to_hex(), /*verbose=*/ 1]))?
"getblock", .get("tx")
json!([blockhash.be_hex_string(), /*verbose=*/ 1]), .chain_err(|| "block missing txids")?
)? .as_array()
.get("tx") .chain_err(|| "invalid block txids")?
.chain_err(|| "block missing txids")? .iter()
.as_array() .map(parse_hash)
.chain_err(|| "invalid block txids")? .collect::<Result<Vec<Sha256dHash>>>()
.iter()
.map(parse_hash)
.collect::<Result<Vec<Sha256dHash>>>()
} }
pub fn getblocks(&self, blockhashes: &[Sha256dHash]) -> Result<Vec<Block>> { pub fn getblocks(&self, blockhashes: &[Sha256dHash]) -> Result<Vec<Block>> {
let params_list: Vec<Value> = blockhashes let params_list: Vec<Value> = blockhashes
.iter() .iter()
.map(|hash| json!([hash.be_hex_string(), /*verbose=*/ false])) .map(|hash| json!([hash.to_hex(), /*verbose=*/ false]))
.collect(); .collect();
let values = self.requests("getblock", &params_list)?; let values = self.requests("getblock", &params_list)?;
let mut blocks = vec![]; let mut blocks = vec![];
@ -512,11 +509,9 @@ impl Daemon {
txhash: &Sha256dHash, txhash: &Sha256dHash,
blockhash: Option<Sha256dHash>, blockhash: Option<Sha256dHash>,
) -> Result<Transaction> { ) -> Result<Transaction> {
let mut args = json!([txhash.be_hex_string(), /*verbose=*/ false]); let mut args = json!([txhash.to_hex(), /*verbose=*/ false]);
if let Some(blockhash) = blockhash { if let Some(blockhash) = blockhash {
args.as_array_mut() args.as_array_mut().unwrap().push(json!(blockhash.to_hex()));
.unwrap()
.push(json!(blockhash.be_hex_string()));
} }
tx_from_value(self.request("getrawtransaction", args)?) tx_from_value(self.request("getrawtransaction", args)?)
} }
@ -527,11 +522,9 @@ impl Daemon {
blockhash: Option<Sha256dHash>, blockhash: Option<Sha256dHash>,
verbose: bool, verbose: bool,
) -> Result<Value> { ) -> Result<Value> {
let mut args = json!([txhash.be_hex_string(), verbose]); let mut args = json!([txhash.to_hex(), verbose]);
if let Some(blockhash) = blockhash { if let Some(blockhash) = blockhash {
args.as_array_mut() args.as_array_mut().unwrap().push(json!(blockhash.to_hex()));
.unwrap()
.push(json!(blockhash.be_hex_string()));
} }
Ok(self.request("getrawtransaction", args)?) Ok(self.request("getrawtransaction", args)?)
} }
@ -539,7 +532,7 @@ impl Daemon {
pub fn gettransactions(&self, txhashes: &[&Sha256dHash]) -> Result<Vec<Transaction>> { pub fn gettransactions(&self, txhashes: &[&Sha256dHash]) -> Result<Vec<Transaction>> {
let params_list: Vec<Value> = txhashes let params_list: Vec<Value> = txhashes
.iter() .iter()
.map(|txhash| json!([txhash.be_hex_string(), /*verbose=*/ false])) .map(|txhash| json!([txhash.to_hex(), /*verbose=*/ false]))
.collect(); .collect();
let values = self.requests("getrawtransaction", &params_list)?; let values = self.requests("getrawtransaction", &params_list)?;
@ -561,7 +554,7 @@ impl Daemon {
} }
pub fn getmempoolentry(&self, txid: &Sha256dHash) -> Result<MempoolEntry> { pub fn getmempoolentry(&self, txid: &Sha256dHash) -> Result<MempoolEntry> {
let entry = self.request("getmempoolentry", json!([txid.be_hex_string()]))?; let entry = self.request("getmempoolentry", json!([txid.to_hex()]))?;
let fee = (entry let fee = (entry
.get("fee") .get("fee")
.chain_err(|| "missing fee")? .chain_err(|| "missing fee")?
@ -586,7 +579,7 @@ impl Daemon {
} }
fn get_all_headers(&self, tip: &Sha256dHash) -> Result<Vec<BlockHeader>> { fn get_all_headers(&self, tip: &Sha256dHash) -> Result<Vec<BlockHeader>> {
let info: Value = self.request("getblockheader", json!([tip.be_hex_string()]))?; let info: Value = self.request("getblockheader", json!([tip.to_hex()]))?;
let tip_height = info let tip_height = info
.get("height") .get("height")
.expect("missing height") .expect("missing height")

4
src/index.rs

@ -3,7 +3,7 @@ use bitcoin::blockdata::block::{Block, BlockHeader};
use bitcoin::blockdata::transaction::{Transaction, TxIn, TxOut}; use bitcoin::blockdata::transaction::{Transaction, TxIn, TxOut};
use bitcoin::consensus::encode::{deserialize, serialize}; use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::util::hash::BitcoinHash; use bitcoin::util::hash::BitcoinHash;
use bitcoin::util::hash::Sha256dHash; use bitcoin_hashes::sha256d::Hash as Sha256dHash;
use crypto::digest::Digest; use crypto::digest::Digest;
use crypto::sha2::Sha256; use crypto::sha2::Sha256;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@ -40,7 +40,7 @@ impl TxInRow {
TxInRow { TxInRow {
key: TxInKey { key: TxInKey {
code: b'I', code: b'I',
prev_hash_prefix: hash_prefix(&input.previous_output.txid.as_bytes()[..]), prev_hash_prefix: hash_prefix(&input.previous_output.txid[..]),
prev_index: input.previous_output.vout as u16, prev_index: input.previous_output.vout as u16,
}, },
txid_prefix: hash_prefix(&txid[..]), txid_prefix: hash_prefix(&txid[..]),

2
src/mempool.rs

@ -1,5 +1,5 @@
use bitcoin::blockdata::transaction::Transaction; use bitcoin::blockdata::transaction::Transaction;
use bitcoin::util::hash::Sha256dHash; use bitcoin_hashes::sha256d::Hash as Sha256dHash;
use hex; use hex;
use std::collections::{BTreeMap, HashMap, HashSet}; use std::collections::{BTreeMap, HashMap, HashSet};
use std::iter::FromIterator; use std::iter::FromIterator;

8
src/query.rs

@ -1,6 +1,8 @@
use bitcoin::blockdata::transaction::Transaction; use bitcoin::blockdata::transaction::Transaction;
use bitcoin::consensus::encode::deserialize; use bitcoin::consensus::encode::deserialize;
use bitcoin::util::hash::Sha256dHash; use bitcoin_hashes::hex::ToHex;
use bitcoin_hashes::sha256d::Hash as Sha256dHash;
use bitcoin_hashes::Hash;
use crypto::digest::Digest; use crypto::digest::Digest;
use crypto::sha2::Sha256; use crypto::sha2::Sha256;
use lru::LruCache; use lru::LruCache;
@ -100,7 +102,7 @@ impl Status {
let mut hash = FullHash::default(); let mut hash = FullHash::default();
let mut sha2 = Sha256::new(); let mut sha2 = Sha256::new();
for (height, txn_id) in txns { for (height, txn_id) in txns {
let part = format!("{}:{}:", txn_id.be_hex_string(), height); let part = format!("{}:{}:", txn_id.to_hex(), height);
sha2.input(part.as_bytes()); sha2.input(part.as_bytes());
} }
sha2.result(&mut hash); sha2.result(&mut hash);
@ -116,7 +118,7 @@ struct TxnHeight {
fn merklize(left: Sha256dHash, right: Sha256dHash) -> Sha256dHash { fn merklize(left: Sha256dHash, right: Sha256dHash) -> Sha256dHash {
let data = [&left[..], &right[..]].concat(); let data = [&left[..], &right[..]].concat();
Sha256dHash::from_data(&data) Sha256dHash::hash(&data)
} }
fn create_merkle_branch_and_root( fn create_merkle_branch_and_root(

33
src/rpc.rs

@ -1,6 +1,7 @@
use bitcoin::blockdata::transaction::Transaction; use bitcoin::blockdata::transaction::Transaction;
use bitcoin::consensus::encode::{deserialize, serialize}; use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::util::hash::Sha256dHash; use bitcoin_hashes::hex::{FromHex, ToHex};
use bitcoin_hashes::sha256d::Hash as Sha256dHash;
use error_chain::ChainedError; use error_chain::ChainedError;
use hex; use hex;
use serde_json::{from_str, Value}; use serde_json::{from_str, Value};
@ -58,7 +59,7 @@ fn unspent_from_status(status: &Status) -> Value {
.map(|out| json!({ .map(|out| json!({
"height": out.height, "height": out.height,
"tx_pos": out.output_index, "tx_pos": out.output_index,
"tx_hash": out.txn_id.be_hex_string(), "tx_hash": out.txn_id.to_hex(),
"value": out.value, "value": out.value,
})) }))
.collect() .collect()
@ -137,11 +138,11 @@ impl Connection {
} }
let (branch, root) = self.query.get_header_merkle_proof(height, cp_height)?; let (branch, root) = self.query.get_header_merkle_proof(height, cp_height)?;
let branch_vec: Vec<String> = branch.into_iter().map(|b| b.be_hex_string()).collect(); let branch_vec: Vec<String> = branch.into_iter().map(|b| b.to_hex()).collect();
return Ok(json!({ return Ok(json!({
"header": raw_header_hex, "header": raw_header_hex,
"root": root.be_hex_string(), "root": root.to_hex(),
"branch": branch_vec "branch": branch_vec
})); }));
} }
@ -170,13 +171,13 @@ impl Connection {
.query .query
.get_header_merkle_proof(start_height + (count - 1), cp_height)?; .get_header_merkle_proof(start_height + (count - 1), cp_height)?;
let branch_vec: Vec<String> = branch.into_iter().map(|b| b.be_hex_string()).collect(); let branch_vec: Vec<String> = branch.into_iter().map(|b| b.to_hex()).collect();
Ok(json!({ Ok(json!({
"count": headers.len(), "count": headers.len(),
"hex": headers.join(""), "hex": headers.join(""),
"max": 2016, "max": 2016,
"root": root.be_hex_string(), "root": root.to_hex(),
"branch" : branch_vec "branch" : branch_vec
})) }))
} }
@ -214,7 +215,7 @@ impl Connection {
status status
.history() .history()
.into_iter() .into_iter()
.map(|item| json!({"height": item.0, "tx_hash": item.1.be_hex_string()})) .map(|item| json!({"height": item.0, "tx_hash": item.1.to_hex()}))
.collect() .collect()
))) )))
} }
@ -234,7 +235,7 @@ impl Connection {
if let Err(e) = self.chan.sender().try_send(Message::PeriodicUpdate) { if let Err(e) = self.chan.sender().try_send(Message::PeriodicUpdate) {
warn!("failed to issue PeriodicUpdate after broadcast: {}", e); warn!("failed to issue PeriodicUpdate after broadcast: {}", e);
} }
Ok(json!(txid.be_hex_string())) Ok(json!(txid.to_hex()))
} }
fn blockchain_transaction_get(&self, params: &[Value]) -> Result<Value> { fn blockchain_transaction_get(&self, params: &[Value]) -> Result<Value> {
@ -253,10 +254,7 @@ impl Connection {
.query .query
.get_merkle_proof(&tx_hash, height) .get_merkle_proof(&tx_hash, height)
.chain_err(|| "cannot create merkle proof")?; .chain_err(|| "cannot create merkle proof")?;
let merkle: Vec<String> = merkle let merkle: Vec<String> = merkle.into_iter().map(|txid| txid.to_hex()).collect();
.into_iter()
.map(|txid| txid.be_hex_string())
.collect();
Ok(json!({ Ok(json!({
"block_height": height, "block_height": height,
"merkle": merkle, "merkle": merkle,
@ -271,16 +269,13 @@ impl Connection {
let (txid, merkle) = self.query.get_id_from_pos(height, tx_pos, want_merkle)?; let (txid, merkle) = self.query.get_id_from_pos(height, tx_pos, want_merkle)?;
if !want_merkle { if !want_merkle {
return Ok(json!(txid.be_hex_string())); return Ok(json!(txid.to_hex()));
} }
let merkle_vec: Vec<String> = merkle let merkle_vec: Vec<String> = merkle.into_iter().map(|entry| entry.to_hex()).collect();
.into_iter()
.map(|entry| entry.be_hex_string())
.collect();
Ok(json!({ Ok(json!({
"tx_hash" : txid.be_hex_string(), "tx_hash" : txid.to_hex(),
"merkle" : merkle_vec})) "merkle" : merkle_vec}))
} }
@ -359,7 +354,7 @@ impl Connection {
result.push(json!({ result.push(json!({
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "blockchain.scripthash.subscribe", "method": "blockchain.scripthash.subscribe",
"params": [script_hash.be_hex_string(), new_status_hash]})); "params": [script_hash.to_hex(), new_status_hash]}));
*status_hash = new_status_hash; *status_hash = new_status_hash;
} }
timer.observe_duration(); timer.observe_duration();

3
src/util.rs

@ -1,5 +1,6 @@
use bitcoin::blockdata::block::BlockHeader; use bitcoin::blockdata::block::BlockHeader;
use bitcoin::util::hash::{BitcoinHash, Sha256dHash}; use bitcoin::util::hash::BitcoinHash;
use bitcoin_hashes::sha256d::Hash as Sha256dHash;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt; use std::fmt;
use std::iter::FromIterator; use std::iter::FromIterator;

Loading…
Cancel
Save