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 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 let config = load_config(coin);
42 Ok((coin.to_string(), config))
43}
44
45pub async fn main(coin: &str, config: &Config) {
46 let mut syncer = Syncer::new(&coin, &config).await;
48 let mut handles = Vec::new();
49 {
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 {
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 syncer.initial_sync().await;
67 syncer.run().await;
69 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 );
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 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}