chainseeker_server/
lib.rs

1use std::io::{Read, Write};
2use num_format::{Locale, ToFormattedStr, ToFormattedString};
3pub use bitcoin_rest::bitcoin;
4use bitcoin::hashes::hex::FromHex;
5use bitcoin::consensus::{Encodable, Decodable};
6use bitcoin::{BlockHash, Block, BlockHeader, Address, Script, Network};
7use bitcoin::util::uint::Uint256;
8use bitcoin::util::address::Payload;
9use bitcoin::util::base58;
10use bitcoin::bech32;
11use bitcoin::bech32::ToBase32;
12
13pub mod rocks_db;
14pub use rocks_db::*;
15pub mod rocks_db_multi;
16pub use rocks_db_multi::*;
17pub mod db;
18pub use db::*;
19pub mod syncer;
20pub use syncer::*;
21pub mod rest;
22pub use rest::*;
23pub mod http_server;
24pub use http_server::*;
25pub mod web_socket_relay;
26pub use web_socket_relay::*;
27#[cfg(test)]
28pub mod fixtures;
29
30const DEFAULT_DATA_DIR: &str = ".chainseeker";
31
32pub fn parse_arguments() -> Result<(String, Config), String> {
33    // Read arguments.
34    let args: Vec<String> = std::env::args().collect();
35    if args.len() < 2 {
36        println!("usage: {} COIN", args[0]);
37        return Err("Insufficient arguments.".to_string());
38    }
39    let coin = &args[1];
40    // Load config.
41    let config = load_config(coin);
42    Ok((coin.to_string(), config))
43}
44
45pub async fn main(coin: &str, config: &Config) {
46    // Create Syncer instance.
47    let mut syncer = Syncer::new(&coin, &config).await;
48    let mut handles = Vec::new();
49    // Run HTTP server.
50    {
51        let server = syncer.http_server.clone();
52        let http_ip = config.http_ip.clone();
53        let http_port = config.http_port;
54        handles.push(tokio::spawn(async move {
55            server.run(&http_ip, http_port).await;
56        }));
57    }
58    // Run WebSocketRelay.
59    {
60        let ws = WebSocketRelay::new(&config.zmq_endpoint, &config.ws_endpoint);
61        handles.push(tokio::spawn(async move {
62            ws.run().await;
63        }));
64    }
65    // Do initial sync.
66    syncer.initial_sync().await;
67    // Run syncer.
68    syncer.run().await;
69    // Join for the threads.
70    for handle in handles.iter_mut() {
71        handle.await.expect("Failed to await a tokio JoinHandle.");
72    }
73}
74
75pub fn flush_stdout() {
76    std::io::stdout().flush().expect("Failed to flush.");
77}
78
79pub fn data_dir() -> String {
80    let home = std::env::var("HOME").unwrap();
81    format!("{}/{}", home, DEFAULT_DATA_DIR)
82}
83
84pub fn get_rest(config: &Config) -> bitcoin_rest::Context {
85    bitcoin_rest::new(&config.rest_endpoint)
86}
87
88#[derive(Debug, Clone, serde::Deserialize)]
89pub struct Config {
90    pub genesis_block_hash: BlockHash,
91    pub p2pkh_version: u8,
92    pub p2sh_version : u8,
93    pub segwit_hrp   : String,
94    pub rpc_endpoint : String,
95    pub rpc_user     : String,
96    pub rpc_pass     : String,
97    pub rest_endpoint: String,
98    pub zmq_endpoint : String,
99    pub http_ip      : String,
100    pub http_port    : u16,
101    pub ws_endpoint  : String,
102}
103
104#[derive(Debug, Clone, serde::Deserialize)]
105struct TomlConfigEntry {
106    genesis_block_hash: Option<String>,
107    p2pkh_version     : Option<u8>,
108    p2sh_version      : Option<u8>,
109    segwit_hrp        : Option<String>,
110    rpc_endpoint      : Option<String>,
111    rpc_user          : Option<String>,
112    rpc_pass          : Option<String>,
113    rest_endpoint     : Option<String>,
114    zmq_endpoint      : Option<String>,
115    http_ip           : Option<String>,
116    http_port         : Option<u16>,
117    ws_endpoint       : Option<String>,
118}
119
120pub fn default_genesis_block_hash() -> String {
121    "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f".to_string()
122}
123pub fn default_p2pkh_version() -> u8 {
124    0
125}
126pub fn default_p2sh_version() -> u8 {
127    5
128}
129pub fn default_segwit_hrp() -> String {
130    "bc".to_string()
131}
132pub fn default_rpc_endpoint() -> String {
133    "http://localhost:8332".to_string()
134}
135pub fn default_rpc_user() -> String {
136    "bitcoin".to_string()
137}
138pub fn default_rpc_pass() -> String {
139    "bitcoinrpc".to_string()
140}
141pub fn default_rest_endpoint() -> String {
142    bitcoin_rest::DEFAULT_ENDPOINT.to_string()
143}
144pub fn default_zmq_endpoint() -> String {
145    "tcp://localhost:28332".to_string()
146}
147pub fn default_http_ip() -> String {
148    "127.0.0.1".to_string()
149}
150pub fn default_http_port() -> u16 {
151    8000
152}
153pub fn default_ws_endpoint() -> String {
154    "127.0.0.1:8001".to_string()
155}
156
157#[derive(Debug, Clone, serde::Deserialize)]
158struct TomlConfig {
159    #[serde(default = "default_genesis_block_hash")]
160    genesis_block_hash: String,
161    #[serde(default = "default_p2pkh_version")]
162    p2pkh_version     : u8,
163    #[serde(default = "default_p2sh_version")]
164    p2sh_version      : u8,
165    #[serde(default = "default_segwit_hrp")]
166    segwit_hrp        : String,
167    #[serde(default = "default_rpc_endpoint")]
168    rpc_endpoint      : String,
169    #[serde(default = "default_rpc_user")]
170    rpc_user          : String,
171    #[serde(default = "default_rpc_pass")]
172    rpc_pass          : String,
173    #[serde(default = "default_rest_endpoint")]
174    rest_endpoint     : String,
175    #[serde(default = "default_zmq_endpoint")]
176    zmq_endpoint      : String,
177    #[serde(default = "default_http_ip")]
178    http_ip           : String,
179    #[serde(default = "default_http_port")]
180    http_port         : u16,
181    #[serde(default = "default_ws_endpoint")]
182    ws_endpoint       : String,
183    coins             : std::collections::HashMap<String, TomlConfigEntry>,
184}
185
186pub fn load_config_from_str(config_str: &str, coin: &str) -> Config {
187    let mut config: TomlConfig = toml::from_str(&config_str).expect("Failed to parse config file.");
188    let coin_config = config.coins.remove(coin);
189    if coin_config.is_none() {
190        panic!("Cannot find the specified coin in your config.");
191    }
192    let coin_config = coin_config.unwrap();
193    let genesis_block_hash = BlockHash::from_hex(&coin_config.genesis_block_hash.unwrap_or(config.genesis_block_hash)).unwrap();
194    Config {
195        genesis_block_hash,
196        p2pkh_version: coin_config.p2pkh_version.unwrap_or(config.p2pkh_version),
197        p2sh_version : coin_config.p2sh_version .unwrap_or(config.p2sh_version ),
198        segwit_hrp   : coin_config.segwit_hrp   .unwrap_or(config.segwit_hrp   ),
199        rpc_endpoint : coin_config.rpc_endpoint .unwrap_or(config.rpc_endpoint ),
200        rpc_user     : coin_config.rpc_user     .unwrap_or(config.rpc_user     ),
201        rpc_pass     : coin_config.rpc_pass     .unwrap_or(config.rpc_pass     ),
202        rest_endpoint: coin_config.rest_endpoint.unwrap_or(config.rest_endpoint),
203        zmq_endpoint : coin_config.zmq_endpoint .unwrap_or(config.zmq_endpoint ),
204        http_ip      : coin_config.http_ip      .unwrap_or(config.http_ip      ),
205        http_port    : coin_config.http_port    .unwrap_or(config.http_port    ),
206        ws_endpoint  : coin_config.ws_endpoint  .unwrap_or(config.ws_endpoint  ),
207    }
208}
209
210pub fn load_config(coin: &str) -> Config {
211    let mut config_file = std::fs::File::open(&format!("{}/config.toml", data_dir()))
212        .expect("Failed to open config file.\nPlease copy \"config.example.toml\" to \"~/.chainseeker/config.toml\".");
213    let mut config_str = String::new();
214    config_file.read_to_string(&mut config_str).expect("Failed to read config file.");
215    load_config_from_str(&config_str, coin)
216}
217
218pub fn config_example(coin: &str) -> Config {
219    let config_str = include_bytes!("../config.example.toml");
220    load_config_from_str(std::str::from_utf8(config_str).unwrap(), coin)
221}
222
223pub fn consensus_encode<E>(enc: &E) -> Vec<u8>
224    where E: Encodable,
225{
226    let mut vec = Vec::new();
227    enc.consensus_encode(&mut vec).unwrap();
228    vec
229}
230
231pub fn consensus_decode<D>(dec: &[u8]) -> D
232    where D: Decodable,
233{
234    D::consensus_decode(dec).unwrap()
235}
236
237fn address_to_string_internal(addr: &Address, p2pkh_version: u8, p2sh_version: u8, segwit_hrp: &str) -> String {
238    match addr.payload {
239        Payload::PubkeyHash(ref hash) => {
240            let mut prefixed = [0; 21];
241            prefixed[0] = p2pkh_version;
242            prefixed[1..].copy_from_slice(&hash[..]);
243            base58::check_encode_slice(&prefixed[..])
244        }
245        Payload::ScriptHash(ref hash) => {
246            let mut prefixed = [0; 21];
247            prefixed[0] = p2sh_version;
248            prefixed[1..].copy_from_slice(&hash[..]);
249            base58::check_encode_slice(&prefixed[..])
250        }
251        Payload::WitnessProgram {
252            version: ver,
253            program: ref prog,
254        } => {
255            let vec = vec![vec![ver], prog.to_base32()].concat();
256            bech32::encode(&segwit_hrp, &vec).unwrap()
257        }
258    }
259}
260
261pub fn address_to_string(addr: &Address, config: &Config) -> String {
262    address_to_string_internal(addr, config.p2pkh_version, config.p2sh_version, &config.segwit_hrp)
263}
264
265fn script_to_address_string_internal(script: &Script, p2pkh_version: u8, p2sh_version: u8, segwit_hrp: &str) -> Option<String> {
266    let addr = Address::from_script(script, Network::Bitcoin /* any */);
267    addr.map(|addr| address_to_string_internal(&addr, p2pkh_version, p2sh_version, segwit_hrp))
268}
269
270pub fn script_to_address_string(script: &Script, config: &Config) -> Option<String> {
271    script_to_address_string_internal(script, config.p2pkh_version, config.p2sh_version, &config.segwit_hrp)
272}
273
274pub fn uint256_as_f64(num: &Uint256) -> f64 {
275    let be = num.to_be_bytes();
276    let mut ret = 0f64;
277    for i in 0..32 {
278        ret += (be[31 - i] as f64) * 2f64.powi(8 * i as i32);
279    }
280    ret
281}
282
283pub fn get_difficulty(block_header: &BlockHeader, _config: &Config) -> f64 {
284    let max_target = Uint256::from_u64(0xFFFF).unwrap() << 208;
285    uint256_as_f64(&max_target) / uint256_as_f64(&block_header.target())
286}
287
288pub fn bytes_to_u16(buf: &[u8]) -> u16 {
289    assert_eq!(buf.len(), 2);
290    let mut tmp: [u8; 2] = [0; 2];
291    tmp.copy_from_slice(&buf);
292    u16::from_le_bytes(tmp)
293}
294
295pub fn bytes_to_u32(buf: &[u8]) -> u32 {
296    assert_eq!(buf.len(), 4);
297    let mut tmp: [u8; 4] = [0; 4];
298    tmp.copy_from_slice(&buf);
299    u32::from_le_bytes(tmp)
300}
301
302pub fn bytes_to_i32(buf: &[u8]) -> i32 {
303    assert_eq!(buf.len(), 4);
304    let mut tmp: [u8; 4] = [0; 4];
305    tmp.copy_from_slice(&buf);
306    i32::from_le_bytes(tmp)
307}
308
309pub fn bytes_to_u64(buf: &[u8]) -> u64 {
310    assert_eq!(buf.len(), 8);
311    let mut tmp: [u8; 8] = [0; 8];
312    tmp.copy_from_slice(&buf);
313    u64::from_le_bytes(tmp)
314}
315
316pub fn write_u32<W>(w: &mut W, n: u32)
317    where W: Write
318{
319    w.write_all(&n.to_le_bytes()).expect("Failed to write u32.");
320}
321
322pub fn read_u32<R>(r: &mut R) -> u32
323    where R: Read
324{
325    let mut buf: [u8; 4] = [0; 4];
326    r.read_exact(&mut buf).expect("Failed to read u32.");
327    u32::from_le_bytes(buf)
328}
329
330pub fn write_u64<W>(w: &mut W, n: u64)
331    where W: Write
332{
333    w.write_all(&n.to_le_bytes()).expect("Failed to write u64.");
334}
335
336pub fn read_u64<R>(r: &mut R) -> u64
337    where R: Read
338{
339    let mut buf: [u8; 8] = [0; 8];
340    r.read_exact(&mut buf).expect("Failed to read u64.");
341    u64::from_le_bytes(buf)
342}
343
344pub fn write_usize<W>(w: &mut W, n: usize)
345    where W: Write
346{
347    w.write_all(&n.to_le_bytes()).expect("Failed to write usize.");
348}
349
350pub fn read_usize<R>(r: &mut R) -> usize
351    where R: Read
352{
353    const BYTES: usize = std::mem::size_of::<usize>();
354    let mut buf: [u8; BYTES] = [0; BYTES];
355    r.read_exact(&mut buf).expect("Failed to read usize.");
356    usize::from_le_bytes(buf)
357}
358
359pub fn write_arr<W>(w: &mut W, arr: &[u8])
360    where W: Write
361{
362    w.write_all(&arr).expect("Failed to write arr.");
363}
364
365pub fn read_vec<R>(r: &mut R, len: usize) -> Vec<u8>
366    where R: Read
367{
368    let mut vec = vec![0; len];
369    r.read_exact(&mut vec).expect("Failed to read vec.");
370    vec
371}
372
373pub fn to_locale_string<T>(num: T) -> String
374    where T: ToFormattedStr,
375{
376    num.to_formatted_string(&Locale::en)
377}
378
379#[cfg(test)]
380mod tests {
381    use std::str::FromStr;
382    use super::*;
383    #[test]
384    fn script_or_address_to_string() {
385        // Test vectors come from https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki.
386        let addr_str = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
387        let addr = Address::from_str(addr_str).unwrap();
388        assert_eq!(address_to_string_internal(&addr, 0, 5, "bc"), addr_str);
389        let script_pubkey_str = "0014751e76e8199196d454941c45d1b3a323f1433bd6";
390        let script_pubkey = Script::from_str(script_pubkey_str).unwrap();
391        assert_eq!(script_to_address_string_internal(&script_pubkey, 0, 5, "bc").unwrap(), addr_str);
392    }
393    #[test]
394    fn uint256_as_f64_12345() {
395        assert!((uint256_as_f64(&Uint256::from_u64(12345).unwrap()) - 12345f64).abs() < f64::EPSILON);
396    }
397}