diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f01cba9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1688 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "aho-corasick" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b476ce7103678b0c6d3d395dbbae31d48ff910bd28be979ba5d48c6351131d0d" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1fd36ffbb1fb7c834eac128ea8d0e310c5aeb635548f9d58861e1308d46e71c" + +[[package]] +name = "async-channel" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59740d83946db6a5af71ae25ddf9562c2b176b2ca42cf99a455f09f4a220d6b9" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d373d78ded7d0b3fa8039375718cde0aace493f2e34fb60f51cbf567562ca801" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "once_cell", + "vec-arena", +] + +[[package]] +name = "async-global-executor" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "124ac8c265e407641c3362b8f4d39cdb4e243885b71eef087be27199790f5a3a" +dependencies = [ + "async-executor", + "async-io", + "futures-lite", + "num_cpus", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54bc4c1c7292475efb2253227dbcfad8fe1ca4c02bc62c510cc2f3da5c4704e" +dependencies = [ + "concurrent-queue", + "fastrand", + "futures-lite", + "libc", + "log 0.4.11", + "nb-connect", + "once_cell", + "parking", + "polling", + "vec-arena", + "waker-fn", + "winapi", +] + +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-signals" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7bfd327d5b740fe22c1971fdef6c383d3d2e3f6191f8ff6e61b6b8aeaab6591" +dependencies = [ + "futures-util", + "lazy_static", + "nix", +] + +[[package]] +name = "async-std" +version = "1.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9fa76751505e8df1c7a77762f60486f60c71bbd9b8557f4da6ad47d083732ed" +dependencies = [ + "async-global-executor", + "async-io", + "async-mutex", + "blocking", + "crossbeam-utils 0.7.2", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log 0.4.11", + "memchr", + "num_cpus", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" + +[[package]] +name = "atomic-waker" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base64" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" +dependencies = [ + "byteorder", + "safemem", +] + +[[package]] +name = "bech32" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcf67bb7ba7797a081cd19009948ab533af7c355d5caf1d08c777582d351e9c" + +[[package]] +name = "bindgen" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c0bb6167449588ff70803f4127f0684f9063097eca5016f37eb52b92c2cf36" +dependencies = [ + "bitflags", + "cexpr", + "cfg-if 0.1.10", + "clang-sys", + "clap", + "env_logger", + "lazy_static", + "lazycell", + "log 0.4.11", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "which", +] + +[[package]] +name = "bitcoin" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302fba01c5210a0321cc459dbe7f6b4fc21dffe77241c1690bce129814fa11fc" +dependencies = [ + "bech32", + "bitcoin_hashes", + "secp256k1", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d7f47af3cbd7f41c5d9207d10c76aede44087f7c6be090aab6bd431a9daae28" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoincore-rpc" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02ef687583cf0445fdce53765e0479a6f21a422cf6604103246e759f75af87c1" +dependencies = [ + "bitcoincore-rpc-json", + "jsonrpc", + "log 0.4.11", + "serde", + "serde_json", +] + +[[package]] +name = "bitcoincore-rpc-json" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d488ec31e9cb6726c361be5160f7d2aaace89a0681acf1f476b8fada770b6e" +dependencies = [ + "bitcoin", + "hex 0.3.2", + "serde", + "serde_json", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "blocking" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9" +dependencies = [ + "async-channel", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", + "once_cell", +] + +[[package]] +name = "bumpalo" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + +[[package]] +name = "cache-padded" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" + +[[package]] +name = "cc" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cexpr" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + +[[package]] +name = "clang-sys" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6837df1d5cba2397b835c8530f51723267e16abbf83892e9e5af4f0e5dd10a" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "cloudabi" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" +dependencies = [ + "bitflags", +] + +[[package]] +name = "concurrent-queue" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +dependencies = [ + "cache-padded", +] + +[[package]] +name = "const_fn" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce90df4c658c62f12d78f7508cf92f9173e5184a539c10bfe54a3107b3ffd0f2" + +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils 0.8.0", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils 0.8.0", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0f606a85340376eef0d6d8fec399e6d4a544d648386c6645eb6d0653b27d9f" +dependencies = [ + "cfg-if 1.0.0", + "const_fn", + "crossbeam-utils 0.8.0", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "lazy_static", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec91540d98355f690a86367e566ecad2e9e579f230230eb7c21398372be73ea5" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "const_fn", + "lazy_static", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "electrs_index" +version = "0.1.0" +dependencies = [ + "anyhow", + "bitcoin", + "bitcoincore-rpc", + "chrono", + "clap", + "env_logger", + "hyper", + "log 0.4.11", + "prometheus", + "rocksdb", + "rust-crypto", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "electrs_query" +version = "0.1.0" +dependencies = [ + "anyhow", + "bitcoin", + "electrs_index", + "libc", + "log 0.4.11", + "rayon", +] + +[[package]] +name = "electrs_rpc" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-signals", + "async-std", + "bitcoin", + "bitcoincore-rpc", + "clap", + "electrs_index", + "futures", + "hyper", + "libc", + "log 0.4.11", + "rayon", + "rust-crypto", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log 0.4.11", + "regex", + "termcolor", +] + +[[package]] +name = "event-listener" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" + +[[package]] +name = "fastrand" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3" +dependencies = [ + "instant", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] +name = "futures" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95314d38584ffbfda215621d723e0a3906f032e03ae5551e650058dac83d4797" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0448174b01148032eed37ac4aed28963aaaa8cfa93569a08e5b479bbc6c2c151" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18eaa56102984bed2c88ea39026cff3ce3b4c7f508ca970cedf2450ea10d4e46" + +[[package]] +name = "futures-executor" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5f8e0c9258abaea85e78ebdda17ef9666d390e987f006be6080dfe354b708cb" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1798854a4727ff944a7b12aa999f58ce7aa81db80d2dfaaf2ba06f065ddd2b" + +[[package]] +name = "futures-lite" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6c079abfac3ab269e2927ec048dabc89d009ebfdda6b8ee86624f30c689658" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-macro" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36fccf3fc58563b4a14d265027c627c3b665d7fed489427e88e7cc929559efe" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e3ca3f17d6e8804ae5d3df7a7d35b2b3a6fe89dac84b31872720fc3060a0b11" + +[[package]] +name = "futures-task" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d502af37186c4fef99453df03e374683f8a1eec9dcc1e66b3b82dc8278ce3c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "futures-util" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abcb44342f62e6f3e8ac427b8aa815f724fd705dfad060b18ac7866c15bb8e34" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "gloo-timers" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "hermit-abi" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" + +[[package]] +name = "hex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" + +[[package]] +name = "httparse" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "hyper" +version = "0.10.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273" +dependencies = [ + "base64", + "httparse", + "language-tags", + "log 0.3.9", + "mime", + "num_cpus", + "time", + "traitobject", + "typeable", + "unicase", + "url", +] + +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "instant" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb1fc4429a33e1f80d41dc9fea4d108a88bec1de8053878898ae448a0b52f613" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + +[[package]] +name = "jobserver" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonrpc" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436f3455a8a4e9c7b14de9f1206198ee5d0bdc2db1b560339d2141093d7dd389" +dependencies = [ + "hyper", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log 0.4.11", +] + +[[package]] +name = "language-tags" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743" + +[[package]] +name = "libflate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9bac9023e1db29c084f9f8cd9d3852e5e8fddf98fb47c4964a0ea4663d95949" +dependencies = [ + "adler32", + "crc32fast", + "libflate_lz77", + "rle-decode-fast", +] + +[[package]] +name = "libflate_lz77" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3286f09f7d4926fc486334f28d8d2e6ebe4f7f9994494b6dab27ddfad2c9b11b" + +[[package]] +name = "libloading" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" +dependencies = [ + "cc", + "winapi", +] + +[[package]] +name = "librocksdb-sys" +version = "6.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5b56f651c204634b936be2f92dbb42c36867e00ff7fe2405591f3b9fa66f09" +dependencies = [ + "bindgen", + "cc", + "glob", + "libc", +] + +[[package]] +name = "lock_api" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.11", +] + +[[package]] +name = "log" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +dependencies = [ + "cfg-if 0.1.10", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "memoffset" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" +dependencies = [ + "log 0.3.9", +] + +[[package]] +name = "nb-connect" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8123a81538e457d44b933a02faf885d3fe8408806b23fa700e8f01c6c3a98998" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "nix" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" +dependencies = [ + "bitflags", + "cc", + "cfg-if 0.1.10", + "libc", + "void", +] + +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "memchr", + "version_check 0.9.2", +] + +[[package]] +name = "num-integer" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad" + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] +name = "parking_lot" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" +dependencies = [ + "cfg-if 0.1.10", + "cloudabi", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" + +[[package]] +name = "pin-project" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee41d838744f60d959d7074e3afb6b35c7456d0f61cad38a24e35e6553f73841" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81a4ffa594b66bff340084d4081df649a7dc049ac8d7fc458d8e628bfbbb2f86" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "polling" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a7bc6b2a29e632e45451c941832803a18cce6781db04de8a04696cdca8bde4" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "log 0.4.11", + "wepoll-sys", + "winapi", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598" + +[[package]] +name = "proc-macro-nested" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "procfs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a336c8310f4955f343935b9c11a30254d1ad8fad98ec257a4407a061a6fd49" +dependencies = [ + "bitflags", + "byteorder", + "hex 0.4.2", + "lazy_static", + "libc", + "libflate", +] + +[[package]] +name = "prometheus" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d70cf4412832bcac9cffe27906f4a66e450d323525e977168c70d1b36120ae" +dependencies = [ + "cfg-if 0.1.10", + "fnv", + "lazy_static", + "libc", + "parking_lot", + "procfs", + "protobuf", + "regex", + "thiserror", +] + +[[package]] +name = "protobuf" +version = "2.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d147edb77bcccbfc81fabffdc7bd50c13e103b15ca1e27515fe40de69a5776b" + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" +dependencies = [ + "libc", + "rand 0.4.6", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rayon" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils 0.8.0", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "regex" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8963b85b8ce3074fecffde43b4b0dded83ce2f367dc8d363afc56679f3ee820b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c" + +[[package]] +name = "rle-decode-fast" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac" + +[[package]] +name = "rocksdb" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d83c02c429044d58474eaf5ae31e062d0de894e21125b47437ec0edc1397e6" +dependencies = [ + "libc", + "librocksdb-sys", +] + +[[package]] +name = "rust-crypto" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" +dependencies = [ + "gcc", + "libc", + "rand 0.3.23", + "rustc-serialize", + "time", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-serialize" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "secp256k1" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6179428c22c73ac0fbb7b5579a56353ce78ba29759b3b8575183336ea74cdfb" +dependencies = [ + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11553d210db090930f4432bea123b31f70bbf693ace14504ea2a35e796c28dd2" +dependencies = [ + "cc", +] + +[[package]] +name = "serde" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "smallvec" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "syn" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad5de3220ea04da322618ded2c42233d02baca219d6f160a3e9c87cda16c942" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "318234ffa22e0920fe9a40d7b8369b5f649d490980cf7aadcf1eb91594869b42" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cae2447b6282786c3493999f40a9be2a6ad20cb8bd268b0a0dbf5a065535c0ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi", +] + +[[package]] +name = "tinyvec" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117" + +[[package]] +name = "traitobject" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" + +[[package]] +name = "typeable" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" + +[[package]] +name = "unicase" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" +dependencies = [ + "version_check 0.1.5", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +dependencies = [ + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "vec-arena" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasm-bindgen" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42" +dependencies = [ + "cfg-if 0.1.10", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68" +dependencies = [ + "bumpalo", + "lazy_static", + "log 0.4.11", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7866cab0aa01de1edf8b5d7936938a7e397ee50ce24119aef3e1eaa3b6171da" +dependencies = [ + "cfg-if 0.1.10", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307" + +[[package]] +name = "web-sys" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wepoll-sys" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcb14dea929042224824779fbc82d9fab8d2e6d3cbc0ac404de8edf489e77ff" +dependencies = [ + "cc", +] + +[[package]] +name = "which" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" +dependencies = [ + "libc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..bac8d22 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[workspace] + +members = [ + "electrs_index", + "electrs_query", + "electrs_rpc", +] diff --git a/electrs_index/Cargo.toml b/electrs_index/Cargo.toml new file mode 100644 index 0000000..895820c --- /dev/null +++ b/electrs_index/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "electrs_index" +version = "0.1.0" +authors = ["Roman Zeyde "] +edition = "2018" + +[dependencies] +anyhow = "1.0" +bitcoin = { version = "0.25", features = ["use-serde"] } +bitcoincore-rpc = "0.12" +chrono = "0.4" +clap = "2.33" +env_logger = "0.7" +hyper = "0.10" +log = "0.4" +prometheus = { version = "0.10", features = ["process"] } +rocksdb = "^0.15.0" +rust-crypto = "0.2" +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" diff --git a/electrs_index/src/config.rs b/electrs_index/src/config.rs new file mode 100644 index 0000000..552454d --- /dev/null +++ b/electrs_index/src/config.rs @@ -0,0 +1,97 @@ +use bitcoin::Network; + +use clap::{App, Arg}; + +use std::net::SocketAddr; +use std::path::PathBuf; +use std::time::Duration; + +#[derive(Debug)] +pub struct Config { + pub electrum_rpc_addr: SocketAddr, + pub daemon_rpc_addr: SocketAddr, + pub monitoring_addr: SocketAddr, + pub db_path: PathBuf, + pub daemon_dir: PathBuf, + pub wait_duration: Duration, +} + +impl Config { + pub fn from_args() -> Self { + let matches = App::new("Electrum Server in Rust") + .arg( + Arg::with_name("network") + .long("network") + .help("mainnet/testnet/regtest") + .required(true) + .takes_value(true), + ) + .arg( + Arg::with_name("db-dir") + .long("db-dir") + .help("RocksDB directory") + .default_value("./db") + .takes_value(true), + ) + .arg( + Arg::with_name("daemon-dir") + .long("daemon-dir") + .help("bitcoind directory") + .takes_value(true), + ) + .get_matches(); + + let network_str = matches.value_of("network").unwrap(); + let network = match network_str { + "mainnet" => Network::Bitcoin, + "testnet" => Network::Testnet, + "regtest" => Network::Regtest, + _ => panic!("unknown network"), + }; + + let electrum_port = match network { + Network::Bitcoin => 50001, + Network::Testnet => 60001, + Network::Regtest => 60401, + }; + let electrum_rpc_addr: SocketAddr = ([127, 0, 0, 1], electrum_port).into(); + + let daemon_port = match network { + Network::Bitcoin => 8332, + Network::Testnet => 18332, + Network::Regtest => 18443, + }; + let daemon_rpc_addr: SocketAddr = ([127, 0, 0, 1], daemon_port).into(); + + let monitoring_port = match network { + Network::Bitcoin => 4224, + Network::Testnet => 14224, + Network::Regtest => 24224, + }; + let monitoring_addr: SocketAddr = ([127, 0, 0, 1], monitoring_port).into(); + + let daemon_dir: PathBuf = matches.value_of("daemon-dir").unwrap().into(); + let daemon_dir = match network { + Network::Bitcoin => daemon_dir, + Network::Testnet => daemon_dir.join("testnet3"), + Network::Regtest => daemon_dir.join("regtest"), + }; + + let mut db_path: PathBuf = matches.value_of("db-dir").unwrap().into(); + db_path.push(network_str); + + env_logger::Builder::from_default_env() + .default_format() + .format_timestamp_millis() + .init(); + + Self { + electrum_rpc_addr, + daemon_rpc_addr, + monitoring_addr, + db_path, + daemon_dir, + wait_duration: Duration::from_secs(600), + } + } +} diff --git a/electrs_index/src/daemon.rs b/electrs_index/src/daemon.rs new file mode 100644 index 0000000..56c54fa --- /dev/null +++ b/electrs_index/src/daemon.rs @@ -0,0 +1,126 @@ +use anyhow::{Context, Result}; +use bitcoin::{hash_types::BlockHash, Amount, Transaction, Txid}; +use bitcoincore_rpc::{Auth, Client, RpcApi}; +use serde_json::json; + +use std::net::SocketAddr; +use std::path::{Path, PathBuf}; +use std::time::Duration; + +#[derive(Debug, Deserialize, Serialize)] +pub struct BlockLocation { + pub file: usize, + pub data: usize, + pub undo: Option, // genesis block has no undo + pub prev: bitcoin::BlockHash, +} + +#[derive(Debug, Deserialize, Serialize)] +struct GetBlockLocationsResult(Vec); + +pub struct Daemon { + client: Client, + addr: SocketAddr, + daemon_dir: PathBuf, + blocks_dir: PathBuf, +} + +impl Daemon { + pub fn new(addr: SocketAddr, daemon_dir: &Path) -> Result { + let cookie_path = daemon_dir.join(".cookie"); + if !cookie_path.exists() { + bail!("missing cookie file: {:?}", cookie_path); + } + let auth = Auth::CookieFile(cookie_path); + let client = Client::new(format!("http://{}", addr), auth) + .with_context(|| format!("failed to connect {}", addr))?; + let blocks_dir = daemon_dir.join("blocks"); + if !blocks_dir.exists() { + bail!("missing blocks directory: {:?}", blocks_dir); + } + let blockchain_info = client + .get_blockchain_info() + .context("get_network_info failed")?; + debug!("{:?}", blockchain_info); + if blockchain_info.pruned { + bail!("pruned node is not supported (use '-prune=0' bitcoind flag)") + } + let network_info = client + .get_network_info() + .context("get_network_info failed")?; + debug!("{:?}", network_info); + if network_info.version < 20_00_00 { + bail!( + "{} is not supported - please use bitcoind 0.20+", + network_info.subversion, + ) + } + Ok(Daemon { + client, + addr, + daemon_dir: daemon_dir.to_owned(), + blocks_dir, + }) + } + + pub fn reconnect(&self) -> Result { + Self::new(self.addr, &self.daemon_dir) + } + + pub fn client(&self) -> &Client { + &self.client + } + + pub fn broadcast(&self, tx: &Transaction) -> Result { + self.client + .send_raw_transaction(tx) + .context("broadcast failed") + } + + pub fn get_best_block_hash(&self) -> Result { + self.client + .get_best_block_hash() + .context("get_best_block_hash failed") + } + + pub fn wait_for_new_block(&self, timeout: Duration) -> Result { + Ok(self + .client + .wait_for_new_block(timeout.as_millis() as u64)? + .hash) + } + + pub fn get_block_locations( + &self, + hash: &BlockHash, + nblocks: usize, + ) -> Result> { + self.client + .call("getblocklocations", &[json!(hash), nblocks.into()]) + .context("get_block_locations failed") + } + + pub fn get_relay_fee(&self) -> Result { + Ok(self + .client + .get_network_info() + .context("get_network_info failed")? + .relay_fee) + } + + pub fn estimate_fee(&self, nblocks: u16) -> Result> { + Ok(self + .client + .estimate_smart_fee(nblocks, None) + .context("estimate_fee failed")? + .fee_rate) + } + + pub fn blk_file_path(&self, i: usize) -> PathBuf { + self.blocks_dir.join(format!("blk{:05}.dat", i)) + } + + pub fn undo_file_path(&self, i: usize) -> PathBuf { + self.blocks_dir.join(format!("rev{:05}.dat", i)) + } +} diff --git a/electrs_index/src/db.rs b/electrs_index/src/db.rs new file mode 100644 index 0000000..aec1c67 --- /dev/null +++ b/electrs_index/src/db.rs @@ -0,0 +1,202 @@ +use anyhow::{Context, Result}; + +use std::path::{Path, PathBuf}; + +pub(crate) type Row = Box<[u8]>; + +pub(crate) struct WriteBatch { + pub(crate) header_rows: Vec, + pub(crate) index_rows: Vec, +} + +#[derive(Debug)] +struct Options { + path: PathBuf, + bulk_import: bool, +} + +pub struct DBStore { + db: rocksdb::DB, + opts: Options, +} + +const CONFIG_CF: &str = "config"; +const HEADERS_CF: &str = "headers"; +const INDEX_CF: &str = "index"; + +const CONFIG_KEY: &str = "C"; + +#[derive(Debug, Deserialize, Serialize)] +struct Config { + compacted: bool, + format: u64, +} + +const CURRENT_FORMAT: u64 = 1; + +impl DBStore { + fn open_opts(opts: Options) -> Result { + debug!("opening DB with {:?}", opts); + let mut db_opts = rocksdb::Options::default(); + db_opts.create_if_missing(true); + db_opts.create_missing_column_families(true); + db_opts.set_max_background_jobs(4); + + let mut cf_opts = rocksdb::Options::default(); + cf_opts.set_keep_log_file_num(10); + cf_opts.set_max_open_files(16); + cf_opts.set_compaction_style(rocksdb::DBCompactionStyle::Level); + cf_opts.set_compression_type(rocksdb::DBCompressionType::Zstd); + cf_opts.set_target_file_size_base(256 << 20); + cf_opts.set_write_buffer_size(256 << 20); + cf_opts.set_disable_auto_compactions(true); // for initial bulk load + cf_opts.set_advise_random_on_open(false); // bulk load uses sequential I/O + cf_opts.set_prefix_extractor(rocksdb::SliceTransform::create_fixed_prefix(8)); + cf_opts.set_compaction_readahead_size(1 << 20); + let cf_descriptors = vec![ + rocksdb::ColumnFamilyDescriptor::new(CONFIG_CF, cf_opts.clone()), + rocksdb::ColumnFamilyDescriptor::new(HEADERS_CF, cf_opts.clone()), + rocksdb::ColumnFamilyDescriptor::new(INDEX_CF, cf_opts), + ]; + + let db = rocksdb::DB::open_cf_descriptors(&db_opts, &opts.path, cf_descriptors) + .context("failed to open DB")?; + let mut store = DBStore { db, opts }; + + let config = store.get_config(); + debug!("DB {:?}", config); + if config.format < CURRENT_FORMAT { + bail!( + "unsupported storage format {}, re-index required", + config.format + ); + } + if config.compacted { + store.opts.bulk_import = false; + } + store.set_config(config); + Ok(store) + } + + fn config_cf(&self) -> &rocksdb::ColumnFamily { + self.db.cf_handle(CONFIG_CF).expect("missing CONFIG_CF") + } + + fn index_cf(&self) -> &rocksdb::ColumnFamily { + self.db.cf_handle(INDEX_CF).expect("missing INDEX_CF") + } + + fn headers_cf(&self) -> &rocksdb::ColumnFamily { + self.db.cf_handle(HEADERS_CF).expect("missing HEADERS_CF") + } + + /// Opens a new RocksDB at the specified location. + pub fn open(path: &Path) -> Result { + DBStore::open_opts(Options { + path: path.to_path_buf(), + bulk_import: true, + }) + } + + pub(crate) fn iter_index<'a>(&'a self, prefix: &'a [u8]) -> ScanIterator<'a> { + let mode = rocksdb::IteratorMode::From(prefix, rocksdb::Direction::Forward); + let iter = self.db.iterator_cf(self.index_cf(), mode); + ScanIterator { + prefix, + iter, + done: false, + } + } + + pub(crate) fn read_headers(&self) -> Vec { + let mut opts = rocksdb::ReadOptions::default(); + opts.fill_cache(false); + self.db + .iterator_cf_opt(self.headers_cf(), opts, rocksdb::IteratorMode::Start) + .map(|(key, _)| key) + .collect() + } + + pub(crate) fn write(&self, batch: WriteBatch) { + let mut db_batch = rocksdb::WriteBatch::default(); + for key in batch.index_rows { + db_batch.put_cf(self.index_cf(), key, b""); + } + for key in batch.header_rows { + db_batch.put_cf(self.headers_cf(), key, b""); + } + let mut opts = rocksdb::WriteOptions::new(); + opts.set_sync(!self.opts.bulk_import); + opts.disable_wal(self.opts.bulk_import); + self.db.write_opt(db_batch, &opts).unwrap(); + } + + pub(crate) fn start_compactions(&mut self) { + let mut config = self.get_config(); + if !config.compacted { + info!("starting full compaction"); + let cf = self.index_cf(); + self.db.compact_range_cf(cf, None::<&[u8]>, None::<&[u8]>); // would take a while + info!("finished full compaction"); + + config.compacted = true; + self.set_config(config); + } + if self.opts.bulk_import { + self.opts.bulk_import = false; + self.db + .set_options(&[("disable_auto_compactions", "false")]) + .unwrap(); + info!("auto-compactions enabled"); + } + } + + fn set_config(&self, config: Config) { + let mut opts = rocksdb::WriteOptions::default(); + opts.set_sync(true); + opts.disable_wal(false); + let value = serde_json::to_vec(&config).expect("failed to serialize config"); + self.db + .put_cf_opt(self.config_cf(), CONFIG_KEY, value, &opts) + .expect("DB::put failed"); + } + + fn get_config(&self) -> Config { + self.db + .get_cf(self.config_cf(), CONFIG_KEY) + .expect("DB::get failed") + .map(|value| serde_json::from_slice(&value).expect("failed to deserialize Config")) + .unwrap_or_else(|| Config { + compacted: false, + format: CURRENT_FORMAT, + }) + } +} + +pub(crate) struct ScanIterator<'a> { + prefix: &'a [u8], + iter: rocksdb::DBIterator<'a>, + done: bool, +} + +impl<'a> Iterator for ScanIterator<'a> { + type Item = Row; + + fn next(&mut self) -> Option { + if self.done { + return None; + } + let (key, _) = self.iter.next()?; + if !key.starts_with(&self.prefix) { + self.done = true; + return None; + } + Some(key) + } +} + +impl Drop for DBStore { + fn drop(&mut self) { + info!("closing DB at {:?}", self.opts.path); + } +} diff --git a/electrs_index/src/index.rs b/electrs_index/src/index.rs new file mode 100644 index 0000000..7135212 --- /dev/null +++ b/electrs_index/src/index.rs @@ -0,0 +1,543 @@ +use anyhow::{Context, Result}; + +use bitcoin::{ + consensus::{serialize, Decodable}, + Block, BlockHash, BlockHeader, VarInt, +}; + +use std::{ + collections::BTreeMap, + io::{Cursor, Read, Seek, SeekFrom}, + path::{Path, PathBuf}, + sync::{ + mpsc::{sync_channel, Receiver, SyncSender}, + Arc, Mutex, RwLock, RwLockReadGuard, + }, + time::Instant, +}; + +use crate::{ + daemon::{BlockLocation, Daemon}, + db, + map::BlockMap, + metrics::{Histogram, Metrics}, + types::{BlockRow, BlockUndo, Confirmed, FilePos, Reader, ScriptHash, ScriptHashRow}, +}; + +pub struct Index { + store: RwLock, + map: Arc>, + stats: Stats, +} + +#[derive(Clone)] +struct Stats { + update_duration: Histogram, + update_size: Histogram, + lookup_duration: Histogram, +} + +struct ReadRequest { + file_id: usize, + locations: Vec, + blk_file: PathBuf, + undo_file: PathBuf, +} + +struct IndexRequest { + file_id: usize, + locations: Vec, + blk_file: Cursor>, + undo_file: Cursor>, +} + +impl db::WriteBatch { + fn new() -> Self { + Self { + header_rows: vec![], + index_rows: vec![], + } + } + + fn index(&mut self, loc: BlockLocation, block: Block, undo: BlockUndo) { + let (index_rows, header_row) = index_single_block(loc, block, undo); + self.index_rows + .extend(index_rows.iter().map(ScriptHashRow::to_db_row)); + self.header_rows.push(header_row.to_db_row()); + } +} + +pub struct LookupResult { + pub readers: Vec, + pub tip: BlockHash, +} + +impl Index { + pub fn new(store: db::DBStore, metrics: &Metrics) -> Result { + let update_duration = metrics.histogram_vec( + "index_update_duration", + "Index duration (in seconds)", + &["step"], + ); + let update_size = + metrics.histogram_vec("index_update_size", "Index size (in bytes)", &["step"]); + let lookup_duration = metrics.histogram_vec( + "index_lookup_duration", + "Lookup duration (in seconds)", + &["step"], + ); + + let start = Instant::now(); + let rows = store.read_headers(); + let size: u64 = rows.iter().map(|row| row.len() as u64).sum(); + let block_rows = rows + .into_iter() + .map(|row| BlockRow::from_db_row(&row)) + .collect::>>() + .context("failed to read block rows")?; + debug!( + "read {} blocks ({:.3} MB) from DB at {:.3} ms", + block_rows.len(), + size as f64 / 1e6, + start.elapsed().as_millis() + ); + let map = BlockMap::new(block_rows); + + Ok(Index { + store: RwLock::new(store), + map: Arc::new(RwLock::new(map)), + stats: Stats { + update_duration, + update_size, + lookup_duration, + }, + }) + } + + pub fn map(&self) -> RwLockReadGuard { + self.map.read().unwrap() + } + + pub fn block_reader(&self, blockhash: &BlockHash, daemon: &Daemon) -> Result { + Ok(match self.map().get_block_pos(blockhash) { + None => bail!("unknown block {}", blockhash), + Some(pos) => BlockReader { + reader: pos.reader(daemon), + lookup_duration: self.stats.lookup_duration.clone(), + }, + }) + } + + pub fn lookup(&self, script_hash: &ScriptHash, daemon: &Daemon) -> Result { + let scan_prefix = ScriptHashRow::scan_prefix(script_hash); + let rows: Vec = + self.stats + .lookup_duration + .observe_duration("lookup_scan_rows", || { + self.store + .read() + .unwrap() + .iter_index(&scan_prefix) + .map(|row| ScriptHashRow::from_db_row(&row)) + .collect::>>() + .context("failed to parse index rows") + })?; + + debug!("{} has {} index rows", script_hash, rows.len()); + + let map = self.map(); // lock block map for concurrent updates + let readers = rows + .into_iter() + .filter_map(|row| match map.find_block(row.position()) { + Some((hash, height)) => { + let header = map.get_by_hash(hash).expect("missing block header"); + Some(ConfirmedReader { + reader: row.position().reader(daemon), + lookup_duration: self.stats.lookup_duration.clone(), + header: *header, + height, + }) + } + None => { + warn!("{:?} not confirmed", row); + None + } + }) + .collect(); + let tip = map.chain().last().cloned().unwrap_or_default(); + Ok(LookupResult { readers, tip }) + } + + fn start_reader( + &self, + requests: Vec, + reader_tx: SyncSender, + ) -> Result>> { + let update_duration = self.stats.update_duration.clone(); + let update_size = self.stats.update_size.clone(); + let reader_thread = std::thread::spawn(move || -> Result<()> { + for r in requests { + let blk_file = + update_duration.observe_duration("load_block", || read_file(&r.blk_file))?; + update_size.observe_size("load_block", blk_file.len()); + + let undo_file = + update_duration.observe_duration("load_undo", || read_file(&r.undo_file))?; + update_size.observe_size("load_undo", undo_file.len()); + + let file_id = r.file_id; + let index_request = IndexRequest { + file_id, + locations: r.locations, + blk_file: Cursor::new(blk_file), + undo_file: Cursor::new(undo_file), + }; + reader_tx + .send(index_request) + .with_context(|| format!("reader send failed: file_id={}", file_id))? + } + Ok(()) + }); + Ok(reader_thread) + } + + fn start_indexer( + &self, + i: usize, + reader_rx: Arc>>, + indexer_tx: SyncSender, + ) -> std::thread::JoinHandle> { + let update_duration = self.stats.update_duration.clone(); + std::thread::spawn(move || { + loop { + let IndexRequest { + file_id, + locations, + mut blk_file, + mut undo_file, + } = { + // make sure receiver is unlocked after recv() is over + match reader_rx.lock().unwrap().recv() { + Ok(msg) => msg, + Err(_) => break, // channel has disconnected + } + }; + let parsed: Vec<(BlockLocation, Block, BlockUndo)> = update_duration + .observe_duration("parse", || { + locations + .into_iter() + .map(|loc| parse_block_and_undo(loc, &mut blk_file, &mut undo_file)) + .collect() + }); + let blocks_count = parsed.len(); + + let mut result = db::WriteBatch::new(); + update_duration.observe_duration("index", || { + parsed.into_iter().for_each(|(loc, block, undo)| { + result.index(loc, block, undo); + }); + }); + debug!( + "indexer #{}: indexed {} blocks from file #{} into {} rows", + i, + blocks_count, + file_id, + result.index_rows.len() + ); + + update_duration.observe_duration("sort", || { + result.index_rows.sort_unstable(); + result.header_rows.sort_unstable(); + }); + + indexer_tx + .send(result) + .with_context(|| format!("indexer send failed: file_id={}", file_id))? + } + Ok(()) + }) + } + + fn report_stats(&self, batch: &db::WriteBatch) { + self.stats + .update_size + .observe_size("write_index_rows", db_rows_size(&batch.index_rows)); + self.stats + .update_size + .observe_size("write_header_rows", db_rows_size(&batch.header_rows)); + debug!( + "writing index {} rows from {} blocks", + batch.index_rows.len(), + batch.header_rows.len(), + ); + } + + pub fn update(&self, daemon: &Daemon) -> Result { + let start = Instant::now(); + let tip = daemon.get_best_block_hash()?; + let locations = { + let map = self.map(); + if map.chain().last() == Some(&tip) { + debug!("skip update, same tip: {}", tip); + return Ok(tip); + } + load_locations(daemon, tip, &map)? + }; + let read_requests = group_locations_by_file(locations) + .into_iter() + .map(|(file_id, locations)| ReadRequest { + file_id, + locations, + blk_file: daemon.blk_file_path(file_id), + undo_file: daemon.undo_file_path(file_id), + }) + .collect::>(); + let new_blocks = read_requests + .iter() + .map(|r| r.locations.len()) + .sum::(); + info!( + "reading {} new blocks from {} blk*.dat files", + new_blocks, + read_requests.len() + ); + let (reader_tx, reader_rx) = sync_channel(0); + let (indexer_tx, indexer_rx) = sync_channel(0); + + let reader_thread = self.start_reader(read_requests, reader_tx)?; + + let reader_rx = Arc::new(Mutex::new(reader_rx)); + let indexers: Vec> = (0..4) + .map(|i| self.start_indexer(i, Arc::clone(&reader_rx), indexer_tx.clone())) + .collect(); + drop(indexer_tx); // no need for the original sender + + let mut block_rows = vec![]; + let mut index_rows_count = 0usize; + for batch in indexer_rx.into_iter() { + let batch_block_rows = batch + .header_rows + .iter() + .map(|row| BlockRow::from_db_row(row).expect("bad BlockRow")); + block_rows.extend(batch_block_rows); + index_rows_count += batch.index_rows.len(); + self.report_stats(&batch); + self.stats + .update_duration + .observe_duration("write", || self.store.read().unwrap().write(batch)); + } + + indexers + .into_iter() + .map(|indexer| indexer.join().expect("indexer thread panicked")) + .collect::>() + .context("indexer thread failed")?; + + reader_thread + .join() + .expect("reader thread panicked") + .context("reader thread failed")?; + + info!( + "indexed {} new blocks, {} DB rows, took {:.3}s", + block_rows.len(), + index_rows_count, + start.elapsed().as_millis() as f64 / 1e3 + ); + assert_eq!(new_blocks, block_rows.len()); + + // allow only one thread to apply full compaction + self.store.write().unwrap().start_compactions(); + self.map.write().unwrap().update_chain(block_rows, tip); + Ok(tip) + } +} + +fn db_rows_size(rows: &[db::Row]) -> usize { + rows.iter().map(|key| key.len()).sum() +} + +fn index_single_block( + loc: BlockLocation, + block: Block, + undo: BlockUndo, +) -> (Vec, BlockRow) { + assert!(undo.txdata.len() + 1 == block.txdata.len()); + let mut script_hash_rows = vec![]; + + let file_id = loc.file as u16; + let txcount = VarInt(block.txdata.len() as u64); + let mut next_tx_offset: usize = + loc.data + serialize(&block.header).len() + serialize(&txcount).len(); + let mut undo_iter = undo.txdata.into_iter(); + + for tx in &block.txdata { + let tx_offset = next_tx_offset as u32; + next_tx_offset += tx.get_size(); + + let create_index_row = |script| { + let pos = FilePos { + file_id, + offset: tx_offset, + }; + ScriptHashRow::new(ScriptHash::new(script), pos) + }; + tx.output + .iter() + .map(|txo| &txo.script_pubkey) + .for_each(|script| script_hash_rows.push(create_index_row(script))); + + if tx.is_coin_base() { + continue; // coinbase doesn't have an undo + } + let txundo = undo_iter.next().expect("no txundo"); + assert_eq!(tx.input.len(), txundo.scripts.len()); + txundo + .scripts + .iter() + .for_each(|script| script_hash_rows.push(create_index_row(script))); + } + assert!(undo_iter.next().is_none()); + let block_pos = FilePos { + file_id, + offset: loc.data as u32, + }; + let block_size = (next_tx_offset - loc.data) as u32; + ( + script_hash_rows, + BlockRow::new(block.header, block_pos, block_size), + ) +} + +fn load_locations( + daemon: &Daemon, + mut blockhash: BlockHash, + existing: &BlockMap, +) -> Result> { + let start = Instant::now(); + let null = BlockHash::default(); + let mut result = vec![]; + + let mut loc_chunk = Vec::::new(); + let mut loc_iter = loc_chunk.into_iter(); + + let mut total_blocks = 0usize; + let mut chunk_size = 10; + // scan until genesis + while blockhash != null { + if existing.in_valid_chain(&blockhash) { + break; // no need to continue validation + } + total_blocks += 1; + if let Some(header) = existing.get_by_hash(&blockhash) { + // this block was indexed - make sure it points back correctly (advancing loc_iter) + if let Some(loc) = loc_iter.next() { + assert_eq!(loc.prev, header.prev_blockhash); + } + blockhash = header.prev_blockhash; + continue; + } + // get next block location + let location = match loc_iter.next() { + // get a new chunk from daemon if needed + Some(loc) => loc, + None => { + loc_chunk = daemon.get_block_locations(&blockhash, chunk_size)?; + chunk_size = std::cmp::min(chunk_size * 10, 100_000); // takes <1s + trace!("got {} block locations", loc_chunk.len()); + loc_iter = loc_chunk.into_iter(); + loc_iter.next().unwrap() + } + }; + blockhash = location.prev; + result.push(location); + } + debug!( + "loaded {} block locations ({} new) at {} ms", + total_blocks, + result.len(), + start.elapsed().as_millis(), + ); + Ok(result) +} + +fn group_locations_by_file(locations: Vec) -> BTreeMap> { + let mut locations_by_file = BTreeMap::new(); + for loc in locations.into_iter() { + locations_by_file + .entry(loc.file) + .or_insert_with(Vec::new) + .push(loc); + } + for locations in &mut locations_by_file.values_mut() { + locations.sort_by_key(|loc| loc.data); + } + locations_by_file +} + +fn parse_from(src: &mut F, offset: usize) -> Result { + src.seek(SeekFrom::Start(offset as u64))?; + Ok(Decodable::consensus_decode(src).context("parsing failed")?) +} + +fn parse_block_and_undo( + loc: BlockLocation, + blk_file: &mut F, + undo_file: &mut F, +) -> (BlockLocation, Block, BlockUndo) { + blk_file + .seek(SeekFrom::Start(loc.data as u64)) + .expect("failed to seek"); + let block: Block = + parse_from(blk_file, loc.data).unwrap_or_else(|_| panic!("bad Block at {:?}", loc)); + let undo: BlockUndo = loc + .undo + .map(|offset| { + parse_from(undo_file, offset).unwrap_or_else(|_| panic!("bad BlockUndo at {:?}", loc)) + }) + .unwrap_or_else(|| { + // genesis block has no undo + assert_eq!(block.header.prev_blockhash, BlockHash::default()); + BlockUndo::default() // create an empty undo + }); + (loc, block, undo) +} + +fn read_file(path: &Path) -> Result> { + Ok(std::fs::read(&path).with_context(|| format!("failed to read {:?}", path))?) +} + +pub struct ConfirmedReader { + reader: Reader, + lookup_duration: Histogram, + header: BlockHeader, + height: usize, +} + +impl ConfirmedReader { + pub fn read(self) -> Result { + let tx = self + .lookup_duration + .observe_duration("lookup_read_tx", || { + self.reader + .read() + .context("failed to read confirmed transaction") + })?; + + Ok(Confirmed::new(tx, self.header, self.height, &self.reader)) + } +} + +pub struct BlockReader { + reader: Reader, + lookup_duration: Histogram, +} + +impl BlockReader { + pub fn read(self) -> Result { + self.lookup_duration + .observe_duration("lookup_read_block", || { + self.reader.read().context("failed to read block") + }) + } +} diff --git a/electrs_index/src/lib.rs b/electrs_index/src/lib.rs new file mode 100644 index 0000000..f4b26a9 --- /dev/null +++ b/electrs_index/src/lib.rs @@ -0,0 +1,25 @@ +#[macro_use] +extern crate anyhow; + +#[macro_use] +extern crate log; + +#[macro_use] +extern crate serde_derive; + +mod config; +mod daemon; +mod db; +mod index; +mod map; +mod metrics; +mod types; + +pub use { + config::Config, + daemon::Daemon, + db::DBStore, + index::Index, + metrics::{Gauge, GaugeVec, Histogram, Metrics}, + types::{Confirmed, ScriptHash}, +}; diff --git a/electrs_index/src/map.rs b/electrs_index/src/map.rs new file mode 100644 index 0000000..07cf8df --- /dev/null +++ b/electrs_index/src/map.rs @@ -0,0 +1,176 @@ +use bitcoin::{BlockHash, BlockHeader}; +use chrono::{offset::TimeZone, Utc}; + +use std::{ + collections::{BTreeMap, HashMap}, + ops::Bound::{Included, Unbounded}, + time::Instant, +}; + +use crate::types::{BlockRow, FilePos}; + +#[derive(Default)] +struct Chain { + by_height: Vec, + by_hash: HashMap, +} + +impl Chain { + fn build(tip: BlockHash, by_hash: &HashMap) -> Self { + // verify full chain till genesis + let mut by_height = vec![]; + let mut blockhash = tip; + while blockhash != BlockHash::default() { + by_height.push(blockhash); + blockhash = match by_hash.get(&blockhash) { + Some(value) => value.header.prev_blockhash, + None => panic!("missing block header: {}", blockhash), + }; + } + by_height.reverse(); + let by_hash = by_height + .iter() + .enumerate() + .map(|(index, blockhash)| (*blockhash, index)) + .collect(); + Self { by_height, by_hash } + } + + fn len(&self) -> usize { + self.by_height.len() + } + + fn tip(&self) -> Option<&BlockHash> { + self.by_height.last() + } +} + +struct HeaderPos { + header: BlockHeader, + pos: FilePos, +} + +struct HashLimit { + hash: BlockHash, + limit: FilePos, +} + +#[derive(Default)] +pub struct BlockMap { + by_hash: HashMap, + by_pos: BTreeMap, // start -> (limit, hash) + chain: Chain, +} + +impl BlockMap { + pub(crate) fn new(block_rows: Vec) -> Self { + let mut map = Self::default(); + map.update_blocks(block_rows); + map + } + + pub fn chain(&self) -> &[BlockHash] { + &self.chain.by_height + } + + /// May return stale blocks + pub fn get_by_hash(&self, hash: &BlockHash) -> Option<&BlockHeader> { + self.by_hash.get(hash).map(|value| &value.header) + } + + pub(crate) fn get_block_pos(&self, hash: &BlockHash) -> Option<&FilePos> { + self.by_hash.get(hash).map(|value| &value.pos) + } + + pub(crate) fn in_valid_chain(&self, hash: &BlockHash) -> bool { + self.chain.by_hash.contains_key(hash) + } + + fn update_blocks(&mut self, block_rows: Vec) { + let start = Instant::now(); + let total_blocks = block_rows.len(); + let mut new_blocks = 0usize; + for row in block_rows { + let hash = row.header.block_hash(); + self.by_hash.entry(hash).or_insert_with(|| { + new_blocks += 1; + HeaderPos { + header: row.header, + pos: row.pos, + } + }); + let offset = FilePos { + file_id: row.pos.file_id, + offset: row.pos.offset, + }; + let limit = FilePos { + file_id: row.pos.file_id, + offset: row.pos.offset + row.size, + }; + assert!(self + .by_pos + .insert(offset, HashLimit { limit, hash }) + .is_none()); + } + debug!( + "added {}/{} headers at {} ms", + new_blocks, + total_blocks, + start.elapsed().as_millis() + ); + } + + pub(crate) fn find_block(&self, tx_pos: &FilePos) -> Option<(&BlockHash, usize)> { + // look up the block that ends after this position + let (start, HashLimit { limit, hash }) = + match self.by_pos.range((Unbounded, Included(tx_pos))).next_back() { + Some(item) => item, + None => panic!("block not found: {:?}", tx_pos), + }; + // make sure the position is in the block + assert!(tx_pos < limit); + assert!(tx_pos >= start); + // make sure it's part of an active chain + self.chain.by_hash.get(hash).map(|height| (hash, *height)) + } + + pub(crate) fn update_chain(&mut self, block_rows: Vec, tip: BlockHash) { + self.update_blocks(block_rows); + assert_eq!(self.by_hash.len(), self.by_pos.len()); + // make sure there is no overlap between blocks + let mut last_limit = FilePos { + file_id: 0, + offset: 0, + }; + for (start, HashLimit { limit, hash }) in &self.by_pos { + assert!( + start < limit, + "invalid block {}: bad start={:?} limit={:?}", + hash, + start, + limit + ); + assert!( + last_limit < *start, + "invalid block {}: overlap found, start={:?} last_limit={:?}", + hash, + start, + last_limit + ); + last_limit = *limit; + } + let chain = Chain::build(tip, &self.by_hash); + assert_eq!(chain.tip(), Some(&tip)); + let tip_time = match self.get_by_hash(&tip) { + Some(header) => Utc.timestamp(header.time.into(), 0).to_rfc3339(), + None => panic!("missing tip: {}", tip), + }; + info!( + "verified {} blocks, tip={} @ {}", + chain.len(), + tip, + tip_time + ); + self.chain = chain; + } +} diff --git a/electrs_index/src/metrics.rs b/electrs_index/src/metrics.rs new file mode 100644 index 0000000..cb8a970 --- /dev/null +++ b/electrs_index/src/metrics.rs @@ -0,0 +1,105 @@ +use anyhow::{Context, Result}; +use hyper::server::{Handler, Listening, Request, Response, Server}; +use prometheus::{ + self, process_collector::ProcessCollector, Encoder, HistogramOpts, HistogramVec, Opts, Registry, +}; +pub use prometheus::{Gauge, GaugeVec}; + +use std::net::SocketAddr; + +pub struct Metrics { + reg: Registry, + listen: Listening, +} + +impl Drop for Metrics { + fn drop(&mut self) { + debug!("closing Prometheus server"); + if let Err(e) = self.listen.close() { + warn!("failed to stop Prometheus server: {}", e); + } + } +} + +#[derive(Clone)] +pub struct Histogram { + hist: HistogramVec, +} + +impl Histogram { + pub fn observe_size(&self, label: &str, value: usize) { + self.hist.with_label_values(&[label]).observe(value as f64); + } + + pub fn observe_duration(&self, label: &str, func: F) -> T + where + F: FnOnce() -> T, + { + self.hist + .with_label_values(&[label]) + .observe_closure_duration(func) + } +} + +struct RegistryHandler { + reg: Registry, +} + +impl RegistryHandler { + fn gather(&self) -> Result> { + let mut buffer = vec![]; + prometheus::TextEncoder::new() + .encode(&self.reg.gather(), &mut buffer) + .context("failed to encode metrics")?; + Ok(buffer) + } +} + +impl Handler for RegistryHandler { + fn handle(&self, req: Request, res: Response) { + trace!("{} {}", req.method, req.uri); + let buffer = self.gather().expect("failed to gather metrics"); + res.send(&buffer).expect("send failed"); + } +} + +impl Metrics { + pub fn new(addr: SocketAddr) -> Result { + let reg = Registry::new(); + + reg.register(Box::new(ProcessCollector::for_self())) + .expect("failed to register ProcessCollector"); + + let listen = Server::http(addr)? + .handle(RegistryHandler { reg: reg.clone() }) + .with_context(|| format!("failed to serve on {}", addr))?; + info!("serving Prometheus metrics on {}", addr); + Ok(Self { reg, listen }) + } + + pub fn histogram_vec(&self, name: &str, desc: &str, labels: &[&str]) -> Histogram { + let opts = HistogramOpts::new(name, desc); + let hist = HistogramVec::new(opts, labels).unwrap(); + self.reg + .register(Box::new(hist.clone())) + .expect("failed to register Histogram"); + Histogram { hist } + } + + pub fn gauge(&self, name: &str, desc: &str) -> Gauge { + let gauge = Gauge::new(name, desc).unwrap(); + self.reg + .register(Box::new(gauge.clone())) + .expect("failed to register Gauge"); + gauge + } + + pub fn gauge_vec(&self, name: &str, desc: &str, label: &str) -> GaugeVec { + let opts = Opts::new(name, desc); + let gauge_vec = GaugeVec::new(opts, &[label]).unwrap(); + self.reg + .register(Box::new(gauge_vec.clone())) + .expect("failed to register GaugeVec"); + gauge_vec + } +} diff --git a/electrs_index/src/types.rs b/electrs_index/src/types.rs new file mode 100644 index 0000000..8e33f1e --- /dev/null +++ b/electrs_index/src/types.rs @@ -0,0 +1,371 @@ +use anyhow::{Context, Result}; + +use std::fs::File; +use std::io::{Seek, SeekFrom}; +use std::path::PathBuf; + +use bitcoin::{ + blockdata::opcodes::all::*, + consensus::encode::{deserialize, serialize, Decodable, Encodable, ReadExt, VarInt}, + hashes::{borrow_slice_impl, hash_newtype, hex_fmt_impl, index_impl, serde_impl, sha256, Hash}, + util::key::PublicKey, + BlockHeader, Script, Transaction, Txid, +}; + +use crate::{daemon::Daemon, db}; + +hash_newtype!( + ScriptHash, + sha256::Hash, + 32, + doc = "SHA256(scriptPubkey)", + true +); + +macro_rules! impl_consensus_encoding { + ($thing:ident, $($field:ident),+) => ( + impl Encodable for $thing { + #[inline] + fn consensus_encode( + &self, + mut s: S, + ) -> Result { + let mut len = 0; + $(len += self.$field.consensus_encode(&mut s)?;)+ + Ok(len) + } + } + + impl Decodable for $thing { + #[inline] + fn consensus_decode( + mut d: D, + ) -> Result<$thing, bitcoin::consensus::encode::Error> { + Ok($thing { + $($field: Decodable::consensus_decode(&mut d)?),+ + }) + } + } + ); +} + +pub(crate) struct TxUndo { + pub scripts: Vec