diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..9d5f1bb32d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,29 @@ +# Contributing to AtomicDEX Marketmaker + +We welcomes contribution from everyone in the form of suggestions, bug reports, pull requests, and feedback. +Please note we have a code of conduct, please follow it in all your interactions with the project. + +## Submitting feature requests + +Before uploading any changes, please make sure that the test suite passes locally before submitting a pull request with your changes. + +``` +cargo test --all --features native +``` + +We also use [Clippy](https://github.com/rust-lang/rust-clippy) to avoid common mistakes +and we use [rustfmt](https://github.com/rust-lang/rustfmt) to make our code clear to everyone. + +1. Install these tools (only once): + ``` + rustup component add rustfmt --toolchain nightly-2020-02-01 + rustup component add clippy + ``` +1. Format the code using rustfmt: + ``` + cargo +nightly fmt + ``` +1. Make sure there are no warnings and errors. Run the Clippy: + ``` + cargo clippy --features native -- -D warnings + ``` diff --git a/Cargo.lock b/Cargo.lock index 9be9365a64..e8929655ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1355,6 +1355,14 @@ dependencies = [ "miniz_oxide 0.4.0", ] +[[package]] +name = "float-cmp" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "fnv" version = "1.0.7" @@ -5635,6 +5643,383 @@ dependencies = [ "libc", ] +[metadata] +"checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" +"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum arc-swap 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4662175ead9cd84451d5c35070517777949a2ed84551764129cedb88384841" +"checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392" +"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" +"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" +"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" +"checksum async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "538ecb01eb64eecd772087e5b6f7540cbc917f047727339a472dafed2185b267" +"checksum async-task 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0ac2c016b079e771204030951c366db398864f5026f84a44dafb0ff20f02085d" +"checksum atomic 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c210c1f4db048cda477b652d170572d84c9640695835f17663595d3bd543fc28" +"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +"checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" +"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +"checksum backtrace 0.3.32 (git+https://github.com/artemii235/backtrace-rs.git)" = "" +"checksum backtrace-sys 0.1.30 (git+https://github.com/artemii235/backtrace-rs.git)" = "" +"checksum base58 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" +"checksum base64 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "621fc7ecb8008f86d7fb9b95356cd692ce9514b80a86d85b397f32a22da7b9e2" +"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +"checksum bigdecimal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "460825c9e21708024d67c07057cd5560e5acdccac85de0de624a81d3de51bacb" +"checksum bigint 4.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0e8c8a600052b52482eff2cf4d810e462fdff1f656ac1ecb6232132a1ed7def" +"checksum bindgen 0.43.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d52d263eacd15d26cbcf215d254b410bd58212aaa2d3c453a04b2d3b3adcf41" +"checksum bitcrypto 0.1.0 (git+https://github.com/artemii235/parity-bitcoin.git)" = "" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +"checksum blake2b_simd 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce2571a6cd634670daa2977cc894c1cc2ba57c563c498e5a82c35446f34d056e" +"checksum blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" +"checksum block-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1339a1042f5d9f295737ad4d9a6ab6bf81c84a933dba110b9200cd6d1448b814" +"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +"checksum block-padding 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4fc4358306e344bf9775d0197fd00d2603e5afb0771bb353538630f022068ea3" +"checksum broadcaster 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9c972e21e0d055a36cf73e4daae870941fe7a8abcd5ac3396aab9e4c126bd87" +"checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" +"checksum bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad807f2fc2bf185eeb98ff3a901bd46dc5ad58163d0fa4577ba0d25674d71708" +"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" +"checksum byte-tools 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "980479e6fde23246dfb54d47580d66b4e99202e7579c5eaa9fe10ecb5ebd2182" +"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" +"checksum bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "40ade3d27603c2cb345eb0912aec461a6dec7e06a4ae48589904e808335c7afa" +"checksum bzip2 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "42b7c3cbf0fa9c1b82308d57191728ca0256cb821220f4e2fd410a72ade26e3b" +"checksum bzip2-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6584aa36f5ad4c9247f5323b0a42f37802b37a836f0ad87084d7a33961abe25f" +"checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" +"checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d" +"checksum cexpr 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8fc0086be9ca82f7fc89fc873435531cb898b86e850005850de1f820e2db6e9b" +"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" +"checksum chain 0.1.0 (git+https://github.com/artemii235/parity-bitcoin.git)" = "" +"checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878" +"checksum clang-sys 0.26.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6ef0c1bcf2e99c649104bd7a7012d8f8802684400e03db0ec0af48583c6fa0e4" +"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum console_error_panic_hook 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211" +"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" +"checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" +"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +"checksum crdts 1.3.0 (git+https://github.com/rust-crdt/rust-crdt)" = "" +"checksum crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2d818a4990769aac0c7ff1360e233ef3a41adcb009ebb2036bf6915eb0f6b23c" +"checksum crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c8ec7fcd21571dc78f96cc96243cab8d8f035247c3efd16c687be154c3fa9efa" +"checksum crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" +"checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3" +"checksum crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" +"checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150" +"checksum crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9" +"checksum crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +"checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" +"checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9" +"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" +"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +"checksum crunchy 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda" +"checksum crypto-mac 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "779015233ac67d65098614aec748ac1c756ab6677fa2e14cf8b37c08dfed1198" +"checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +"checksum ct-logs 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1b4660f8b07a560a88c02d76286edb9f0d5d64e495d2b0f233186155aa51be1f" +"checksum debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "496b7f8a2f853313c3ca370641d7ff3e42c32974fdccda8f0684599ed0a3ff6b" +"checksum digest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e5b29bf156f3f4b3c4f610a25ff69370616ae6e0657d416de22645483e72af0a" +"checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c" +"checksum dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "88972de891f6118092b643d85a0b28e0678e0f948d7f879aa32f2d5aafe97d2a" +"checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +"checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" +"checksum doc-comment 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "923dea538cea0aa3025e8685b20d6ee21ef99c4f77e954a30febbaac5ec73a97" +"checksum edit-distance 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3bd26878c3d921f89797a4e1a1711919f999a9f6946bb6f5a4ffda126d297b7e" +"checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" +"checksum enum-primitive-derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b90e520ec62c1864c8c78d637acbfe8baf5f63240f2fb8165b8325c07812dd" +"checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" +"checksum error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07e791d3be96241c77c43846b665ef1384606da2cd2a48730abe606a12906e02" +"checksum ethabi 6.1.0 (git+https://github.com/artemii235/ethabi)" = "" +"checksum ethbloom 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a93a43ce2e9f09071449da36bfa7a1b20b950ee344b6904ff23de493b03b386" +"checksum ethcore-transaction 0.1.0 (git+https://github.com/artemii235/parity-ethereum.git)" = "" +"checksum ethereum-types 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e742184dc63a01c8ea0637369f8faa27c40f537949908a237f95c05e68d2c96" +"checksum ethereum-types-serialize 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1873d77b32bc1891a79dad925f2acbc318ee942b38b9110f9dbc5fbeffcea350" +"checksum ethkey 0.3.0 (git+https://github.com/artemii235/parity-ethereum.git)" = "" +"checksum failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6dd377bcc1b1b7ce911967e3ec24fa19c3224394ec05b54aa7b083d498341ac7" +"checksum failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "64c2d913fe8ed3b6c6518eedf4538255b989945c14c2a7d5cbff62a5e2120596" +"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +"checksum filetime 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a2df5c1a8c4be27e7707789dc42ae65976e60b394afd293d1419ab915833e646" +"checksum findshlibs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1260d61e4fe2a6ab845ffdc426a0bd68ffb240b91cf0ec5a8d1170cec535bd8" +"checksum fixed-hash 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7afe6ce860afb14422711595a7b26ada9ed7de2f43c0b2ab79d09ee196287273" +"checksum flate2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42" +"checksum float-cmp 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" +"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" +"checksum fomat-macros 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b541b35d643a4dfbba66fc2a4d401314d6eb16e36155c92b439baeb232c5d0c7" +"checksum fomat-macros 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fe56556a8c9f9f556150eb6b390bc1a8b3715fd2ddbb4585f36b6a5672c6a833" +"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef" +"checksum futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8" +"checksum futures-channel-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)" = "f477fd0292c4a4ae77044454e7f2b413207942ad405f759bb0b4698b7ace5b12" +"checksum futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a" +"checksum futures-core-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)" = "4a2f26f774b81b3847dcda0c81bd4b6313acfb4f69e5a0390c7cb12c058953e9" +"checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" +"checksum futures-executor-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)" = "80705612926df8a1bc05f0057e77460e29318801f988bf7d803a734cf54e7528" +"checksum futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6" +"checksum futures-io-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)" = "ee7de0c1c9ed23f9457b0437fec7663ce64d9cc3c906597e714e529377b5ddd1" +"checksum futures-join-macro-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)" = "1b151e04c412159cfe4ac5cd0d0bc037addda57f48c4d46d00152cfdae7e52d9" +"checksum futures-macro 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7" +"checksum futures-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)" = "efa8f90c4fb2328e381f8adfd4255b4a2b696f77d1c63a3dee6700b564c4e4b5" +"checksum futures-select-macro-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)" = "767dbbb9accba815dc1f327b20cbe932e42ef11668fe35764ed52f74c66a54c3" +"checksum futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6" +"checksum futures-sink-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)" = "e9b65a2481863d1b78e094a07e9c0eed458cc7dc6e72b22b7138b8a67d924859" +"checksum futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27" +"checksum futures-timer 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a5cedfe9b6dc756220782cc1ba5bcb1fa091cdcba155e40d3556159c3db58043" +"checksum futures-timer 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6" +"checksum futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5" +"checksum futures-util-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)" = "7df53daff1e98cc024bf2720f3ceb0414d96fbb0a94f3cad3a5c3bf3be1d261c" +"checksum generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0f28c2f5bfb5960175af447a2da7c18900693738343dc896ffbcabd9839592" +"checksum generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fceb69994e330afed50c93524be68c42fa898c2d9fd4ee8da03bd7363acd26f2" +"checksum getrandom 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e65cce4e5084b14874c4e7097f38cab54f47ee554f9194673456ea379dcc4c55" +"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" +"checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +"checksum groestl 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0261755b496855e90fb72689afbd2e8fa8a4d1e529073163149e9628eea20afc" +"checksum gstuff 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eee441975a61a5b8313c2a7846dfcaacf34e2656d5a837bf2c360fcf838e636e" +"checksum h2 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "69b2a5a3092cbebbc951fe55408402e696ee2ed09019137d1800fc2c411265d2" +"checksum hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3bae29b6653b3412c2e71e9d486db9f9df5d701941d86683005efb9f2d28e3da" +"checksum hdrhistogram 6.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08d331ebcdbca4acbefe5da8c3299b2e246f198a8294cc5163354e743398b89d" +"checksum hdrhistogram 7.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "30473369d16f9df5aefc9708bd165e0dbaca7405179257678b549774d2af9f41" +"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +"checksum hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8" +"checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" +"checksum hmac 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7a13f4163aa0c5ca1be584aace0e2212b2e41be5478218d4f657f5f778b2ae2a" +"checksum hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" +"checksum hmac-drbg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4fe727d41d2eec0a6574d887914347e5ff96a3b87177817e2a9820c5c87fecc2" +"checksum http 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "eed324f0f0daf6ec10c474f150505af2c143f251722bf9dbd1261bd1f2ee2c1a" +"checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" +"checksum httparse 1.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e8734b0cfd3bc3e101ec59100e101c2eecd19282202e87808b3037b442777a83" +"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" +"checksum hyper 0.12.31 (registry+https://github.com/rust-lang/crates.io-index)" = "6481fff8269772d4463253ca83c788104a7305cb3fb9136bc651a6211e46e03f" +"checksum hyper-rustls 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)" = "15b66d1bd4864ef036adf2363409caa3acd63ebb4725957b66e621c8a36631a3" +"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +"checksum im 12.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "de38d1511a0ce7677538acb1e31b5df605147c458e061b2cdb89858afb1cd182" +"checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" +"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" +"checksum itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d" +"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" +"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" +"checksum js-sys 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)" = "2cc9a97d7cec30128fd8b28a7c1f9df1c001ceb9b441e2b755e24130a6b43c79" +"checksum jsonrpc-core 8.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ddf83704f4e79979a424d1082dd2c1e52683058056c9280efa19ac5f6bc9033c" +"checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" +"checksum keccak-hash 0.1.2 (git+https://github.com/artemii235/parity-common)" = "" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum keys 0.1.0 (git+https://github.com/artemii235/parity-bitcoin.git)" = "" +"checksum kv-log-macro 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c54d9f465d530a752e6ebdc217e081a7a614b48cb200f6f0aee21ba6bc9aabb" +"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +"checksum lexical-core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d7043aa5c05dd34fb73b47acb8c3708eac428de4545ea3682ed2f11293ebd890" +"checksum libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" +"checksum libflate 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)" = "d9135df43b1f5d0e333385cb6e7897ecd1a43d7d11b91ac003f4d2c2d2401fdd" +"checksum libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3ad660d7cb8c5822cd83d10897b0f1f1526792737a179e73896152f85b88c2" +"checksum libsecp256k1 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "688e8d65e495567c2c35ea0001b26b9debf0b4ea11f8cccc954233b75fc3428a" +"checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" +"checksum lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +"checksum mem 0.1.0 (git+https://github.com/artemii235/parity-ethereum.git)" = "" +"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +"checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" +"checksum memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f" +"checksum metrics 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "51b70227ece8711a1aa2f99655efd795d0cff297a5b9fe39645a93aacf6ad39d" +"checksum metrics-core 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c064b3a1ff41f4bf6c91185c8a0caeccf8a8a27e9d0f92cc54cf3dbec812f48" +"checksum metrics-observer-prometheus 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4f9bb94f40e189c87cf70ef1c78815b949ab9d28fe76ebb81f15f79bd19a33d6" +"checksum metrics-runtime 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "beb3035626782c533953bcc1a349467543b7f0e9d7b0c92edc61ee2bda7033d6" +"checksum metrics-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d11f8090a8886339f9468a04eeea0711e4cf27538b134014664308041307a1c5" +"checksum miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5" +"checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" +"checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" +"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +"checksum mocktopus 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c3ddc2275f8c1c95c016bd7fa23b2debcc6e2f24f05cbbfa250e67ea32428ad5" +"checksum mocktopus_macros 0.7.3 (git+https://github.com/artemii235/Mocktopus.git)" = "" +"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" +"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" +"checksum nom 4.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9c349f68f25f596b9f44cf0e7c69752a5c633b0550c3ff849518bfba0233774a" +"checksum nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6" +"checksum num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "57450397855d951f1a41305e54851b1a7b8f5d2e349543a02a2effe25459f718" +"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" +"checksum num-rational 0.2.2 (git+https://github.com/artemii235/num-rational.git)" = "" +"checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" +"checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" +"checksum once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" +"checksum opaque-debug 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "51ecbcb821e1bd256d456fe858aaa7f380b63863eab2eb86eee1bd9f33dd6682" +"checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" +"checksum parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" +"checksum parking_lot 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9723236a9525c757d9725b993511e3fc941e33f27751942232f0058298297edf" +"checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" +"checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" +"checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" +"checksum parking_lot_core 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" +"checksum peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" +"checksum pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" +"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" +"checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" +"checksum primitives 0.1.0 (git+https://github.com/artemii235/parity-bitcoin.git)" = "" +"checksum proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" +"checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" +"checksum proc-macro2 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "77997c53ae6edd6d187fec07ec41b207063b5ee6f33680e9fa86d405cdd313d4" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +"checksum proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afdc77cc74ec70ed262262942ebb7dac3d479e9e5cfa2da1841c0806f6cdabcc" +"checksum quanta 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f4f7a1905379198075914bc93d32a5465c40474f90a078bb13439cb00c547bcc" +"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" +"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" +"checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8" +"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" +"checksum rand 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8356f47b32624fef5b3301c1be97e5944ecdd595409cc5da11d05f211db6cfbd" +"checksum rand 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ae9d223d52ae411a33cf7e54ec6034ec165df296ccd23533d671a28252b6f66a" +"checksum rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c" +"checksum rand_chacha 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "771b009e3a508cb67e8823dda454aaa5368c7bc1c16829fb77d3e980440dd34a" +"checksum rand_chacha 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e193067942ef6f485a349a113329140d0ab9e2168ce92274499bb0e9a4190d9d" +"checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db" +"checksum rand_core 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "615e683324e75af5d43d8f7a39ffe3ee4a9dc42c5c701167a71dc59c3a493aca" +"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +"checksum rand_pcg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "086bd09a33c7044e56bb44d5bdde5a60e7f119a9e95b0775f545de759a32fe05" +"checksum rand_pcg 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e196346cbbc5c70c77e7b4926147ee8e383a38ee4d15d58a08098b169e492b6" +"checksum rand_xorshift 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effa3fcaa47e18db002bdde6060944b6d2f9cfd8db471c30e873448ad9187be3" +"checksum rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "373814f27745b2686b350dd261bfd24576a6fb0e2c5919b3a2b6005f820b0473" +"checksum rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b055d1e92aba6877574d8fe604a63c8b5df60f60e5982bf7ccbb1338ea527356" +"checksum redox_syscall 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "a84bcd297b87a545980a2d25a0beb72a1f490c31f0a9fde52fca35bfbb1ceb70" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum redox_users 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "214a97e49be64fd2c86f568dd0cb2c757d2cc53de95b273b6ad0a1c908482f26" +"checksum redox_users 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1dc1887cbcd764cc066e2c08681a5615433ac3de9752838a9ec114613b118575" +"checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" +"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" +"checksum ring 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)" = "426bc186e3e95cac1e4a4be125a4aca7e84c2d616ffc02244eef36e2a60a093c" +"checksum ripemd160 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad5112e0dbbb87577bfbc56c42450235e3012ce336e29c5befd7807bd626da4a" +"checksum rle-decode-fast 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac" +"checksum rlp 0.3.0 (git+https://github.com/artemii235/parity-common)" = "" +"checksum rpc 0.1.0 (git+https://github.com/artemii235/parity-bitcoin.git)" = "" +"checksum rust-argon2 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "200e04888fdcb513d63734ba40c7b2dff485539ba3e480ffa62bce89e99602ce" +"checksum rust-ini 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" +"checksum rustc-demangle 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "01b90379b8664dd83460d59bdc5dd1fd3172b8913788db483ed1325171eab2f7" +"checksum rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0ceb8ce7a5e520de349e1fa172baeba4a9e8d5ef06c47471863530bc4972ee1e" +"checksum rustc-hex 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "403bb3a286107a04825a5f82e1270acc1e14028d3d554d7a1e08914549575ab8" +"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +"checksum rustls 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f271e3552cd835fa28c541c34a7e8fdd8cdff09d77fe4eb8f6c42e87a11b096e" +"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7" +"checksum ryu 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" +"checksum scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28" +"checksum scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +"checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" +"checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" +"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" +"checksum script 0.1.0 (git+https://github.com/artemii235/parity-bitcoin.git)" = "" +"checksum sct 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f5adf8fbd58e1b1b52699dc8bed2630faecb6d8c7bee77d009d6bbe4af569b9" +"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum serde 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)" = "6fa52f19aee12441d5ad11c9a00459122bd8f98707cadf9778c540674f1935b6" +"checksum serde_bencode 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b79ce11369638af2fea08e00390316304c5b5e1b7a53d58ace925152b657443" +"checksum serde_bytes 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)" = "adb6e51a6b3696b301bc221d785f898b4457c619b51d7ce195a6d20baecb37b3" +"checksum serde_bytes 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aaff47db6ef8771cca5d88febef2f22f47f645420e51226374049f68c6b08569" +"checksum serde_derive 1.0.82 (registry+https://github.com/rust-lang/crates.io-index)" = "96a7f9496ac65a2db5929afa087b54f8fc5008dcfbe48a8874ed20049b0d6154" +"checksum serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)" = "c37ccd6be3ed1fdf419ee848f7c758eb31b054d7cd3ae3600e3bae0adf569811" +"checksum serialization 0.1.0 (git+https://github.com/artemii235/parity-bitcoin.git)" = "" +"checksum serialization_derive 0.1.0 (git+https://github.com/artemii235/parity-bitcoin.git)" = "" +"checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68" +"checksum sha2 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d963c78ce367df26d7ea8b8cc655c651b42e8a1e584e869c1e17dae3ccb116a" +"checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" +"checksum sha3 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "34a5e54083ce2b934bf059fdf38e7330a154177e029ab6c4e18638f2f624053a" +"checksum siphasher 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "833011ca526bd88f16778d32c699d325a9ad302fa06381cd66f7be63351d3f6d" +"checksum sized-chunks 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9d3e7f23bad2d6694e0f46f5e470ec27eb07b8f3e8b309a4b0dc17501928b9f2" +"checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" +"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" +"checksum smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" +"checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" +"checksum spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44363f6f51401c34e7be73db0db371c04705d35efbe9f7d6082e03a921a32c55" +"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +"checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" +"checksum string 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0bbfb8937e38e34c3444ff00afb28b0811d9554f15c5ad64d12b0308d1d1995" +"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" +"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +"checksum syn 0.15.42 (registry+https://github.com/rust-lang/crates.io-index)" = "eadc09306ca51a40555dd6fc2b415538e9e18bc9f870e47b1a524a79fe2dcf5e" +"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" +"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" +"checksum sysinfo 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d5bd3b813d94552a8033c650691645f8dd5a63d614dddd62428a95d3931ef7b6" +"checksum take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" +"checksum tar 0.4.22 (registry+https://github.com/rust-lang/crates.io-index)" = "c2167ff53da2a661702b3299f71a91b61b1dffef36b4b2884b1f9c67254c0133" +"checksum tc_cli_client 0.2.0 (git+https://github.com/artemii235/testcontainers-rs.git)" = "" +"checksum tc_coblox_bitcoincore 0.5.0 (git+https://github.com/artemii235/testcontainers-rs.git)" = "" +"checksum tc_core 0.3.0 (git+https://github.com/artemii235/testcontainers-rs.git)" = "" +"checksum tc_dynamodb_local 0.2.0 (git+https://github.com/artemii235/testcontainers-rs.git)" = "" +"checksum tc_elasticmq 0.2.0 (git+https://github.com/artemii235/testcontainers-rs.git)" = "" +"checksum tc_generic 0.2.0 (git+https://github.com/artemii235/testcontainers-rs.git)" = "" +"checksum tc_parity_parity 0.5.0 (git+https://github.com/artemii235/testcontainers-rs.git)" = "" +"checksum tc_postgres 0.2.0 (git+https://github.com/artemii235/testcontainers-rs.git)" = "" +"checksum tc_redis 0.2.0 (git+https://github.com/artemii235/testcontainers-rs.git)" = "" +"checksum tc_trufflesuite_ganachecli 0.4.0 (git+https://github.com/artemii235/testcontainers-rs.git)" = "" +"checksum term 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5e6b677dd1e8214ea1ef4297f85dbcbed8e8cdddb561040cc998ca2551c37561" +"checksum term 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0863a3345e70f61d613eab32ee046ccd1bcc5f9105fe402c61fcd0c13eeb8b5" +"checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f" +"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" +"checksum testcontainers 0.7.0 (git+https://github.com/artemii235/testcontainers-rs.git)" = "" +"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" +"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +"checksum time 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "847da467bf0db05882a9e2375934a8a55cffdc9db0d128af1518200260ba1f6c" +"checksum tiny-keccak 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e9175261fbdb60781fcd388a4d6cc7e14764a2b629a7ad94abb439aed223a44f" +"checksum tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)" = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6" +"checksum tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46" +"checksum tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c501eceaf96f0e1793cf26beb63da3d11c738c4a943fdf3746d81d64684c39f" +"checksum tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "aeeffbbb94209023feaef3c196a41cbcdafa06b4a6f893f68779bb5e53796f71" +"checksum tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443" +"checksum tokio-executor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671" +"checksum tokio-fs 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe6dc22b08d6993916647d108a1a7d15b9cd29c4f4496c62b92c45b5041b7af" +"checksum tokio-io 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" +"checksum tokio-reactor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" +"checksum tokio-rustls 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "369282441514a4e8bc4d935714e4ee3f9735796f0882e1240f60db5bd94cb826" +"checksum tokio-sync 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee" +"checksum tokio-tcp 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72" +"checksum tokio-threadpool 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72558af20be886ea124595ea0f806dd5703b8958e4705429dd58b3d8231f72f2" +"checksum tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6131e780037787ff1b3f8aad9da83bca02438b72277850dd6ad0d455e0e20efc" +"checksum tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f2106812d500ed25a4f38235b9cae8f78a09edf43203e16e59c3b769a342a60e" +"checksum tokio-udp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "66268575b80f4a4a710ef83d087fdfeeabdce9b74c797535fbac18a2cb906e92" +"checksum tokio-uds 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "037ffc3ba0e12a0ab4aca92e5234e0dedeb48fddf6ccd260f1f150a36a9f2445" +"checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" +"checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" +"checksum uint 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "754ba11732b9161b94c41798e5197e5e75388d012f760c42adb5000353e98646" +"checksum unexpected 0.1.0 (git+https://github.com/artemii235/parity-ethereum.git)" = "" +"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +"checksum unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6a0180bc61fc5a987082bfa111f4cc95c4caff7f9799f3e46df09163a937aa25" +"checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" +"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +"checksum untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f" +"checksum unwrap 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e33648dd74328e622c7be51f3b40a303c63f93e6fa5f08778b6203a4c25c20f" +"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +"checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" +"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" +"checksum want 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230" +"checksum wasm-bindgen 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "cd34c5ba0d228317ce388e87724633c57edca3e7531feb4e25e35aaa07a656af" +"checksum wasm-bindgen-backend 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "927196b315c23eed2748442ba675a4c54a1a079d90d9bdc5ad16ce31cf90b15b" +"checksum wasm-bindgen-futures 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fb4410bcecc9a1c38c3021d95e2d99536cd6c426e2c424f307a3ff326edcb48" +"checksum wasm-bindgen-macro 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "92c2442bf04d89792816650820c3fb407af8da987a9f10028d5317f5b04c2b4a" +"checksum wasm-bindgen-macro-support 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "9c075d27b7991c68ca0f77fe628c3513e64f8c477d422b859e03f28751b46fc5" +"checksum wasm-bindgen-shared 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "83d61fe986a7af038dd8b5ec660e5849cbd9f38e7492b9404cc48b2b4df731d1" +"checksum wasm-bindgen-test 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2c3d30c1e43ebb4c4835f8163456d16f83dd6c1831424cb22680c680ef5f8ea8" +"checksum wasm-bindgen-test-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5f093012630c0c14be061ac7a8d99f82a94e2b1cfd74619fa71090705d2c91be" +"checksum wasm-bindgen-webidl 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "9b979afb0535fe4749906a674082db1211de8aef466331d43232f63accb7c07c" +"checksum web-sys 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)" = "c84440699cd02ca23bed6f045ffb1497bc18a3c2628bd13e2093186faaaacf6b" +"checksum web3 0.6.0 (git+https://github.com/artemii235/rust-web3)" = "" +"checksum webpki 0.19.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4f7e1cd7900a3a6b65a3e8780c51a3e6b59c0e2c55c6dc69578c288d69f7d082" +"checksum webpki-roots 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c10fa4212003ba19a564f25cd8ab572c6791f99a03cc219c13ed35ccab00de0e" +"checksum weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bb43f70885151e629e2a19ce9e50bd730fd436cfd4b666894c9ce4de9141164" +"checksum which 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e84a603e7e0b1ce1aa1ee2b109c7be00155ce52df5081590d1ffb93f4f515cb2" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "afc5508759c5bf4285e61feb862b6083c8480aec864fa17a81fdec6f69b461ab" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" +"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +"checksum xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" +"checksum zstd-sys 1.4.10+zstd.1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46f433134fbd0c37c9eb5929733df5f34bcdff464722eb93155fcee93eb57652" [[patch.unused]] name = "backtrace" version = "0.3.32" diff --git a/azure-pipelines-build-stage-job.yml b/azure-pipelines-build-stage-job.yml index 3e362f9c18..e8a0b0a031 100644 --- a/azure-pipelines-build-stage-job.yml +++ b/azure-pipelines-build-stage-job.yml @@ -52,6 +52,14 @@ jobs: BOB_USERPASS: $(${{ parameters.bob_userpass }}) ALICE_PASSPHRASE: $(${{ parameters.alice_passphrase }}) ALICE_USERPASS: $(${{ parameters.alice_userpass }}) + - bash: | + cargo clippy --features native -- -D warnings + displayName: 'Check Clippy warnings' + condition: eq( variables['Agent.OS'], 'Linux' ) + - bash: | + cargo +nightly fmt -- --check + displayName: 'Check rustfmt warnings' + condition: eq( variables['Agent.OS'], 'Linux' ) - bash: | zip upload/mm2-$(COMMIT_HASH)-$(Agent.OS)-Debug target/debug/mm2 -j displayName: 'Prepare debug build upload Linux' diff --git a/mm2src/coins/coins_tests.rs b/mm2src/coins/coins_tests.rs index 72804c595b..d16721ab2d 100644 --- a/mm2src/coins/coins_tests.rs +++ b/mm2src/coins/coins_tests.rs @@ -7,37 +7,47 @@ pub fn test_list_unspent() { let client = NativeClientImpl { coin_ticker: "RICK".into(), uri: "http://127.0.0.1:10271".to_owned(), - auth: fomat!("Basic " (base64_encode("user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", URL_SAFE))), + auth: fomat!("Basic "(base64_encode( + "user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", + URL_SAFE + ))), event_handlers: Default::default(), }; let unspents = client.list_unspent(0, std::i32::MAX, vec!["RBs52D7pVq7txo6SCz1Tuyw2WrPmdqU3qw".to_owned()]); - let unspents = unwrap! (unspents.wait()); - log!("Unspents " [unspents]); + let unspents = unwrap!(unspents.wait()); + log!("Unspents "[unspents]); } pub fn test_get_block_count() { let client = NativeClientImpl { coin_ticker: "RICK".into(), uri: "http://127.0.0.1:10271".to_owned(), - auth: fomat!("Basic " (base64_encode("user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", URL_SAFE))), + auth: fomat!("Basic "(base64_encode( + "user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", + URL_SAFE + ))), event_handlers: Default::default(), }; - let block_count = unwrap! (client.validate_address("RBs52D7pVq7txo6SCz1Tuyw2WrPmdqU3qw".to_owned()).wait()); - log!("Block count " [block_count]); + let block_count = unwrap!(client + .validate_address("RBs52D7pVq7txo6SCz1Tuyw2WrPmdqU3qw".to_owned()) + .wait()); + log!("Block count "[block_count]); } pub fn test_import_address() { let client = NativeClientImpl { coin_ticker: "RICK".into(), uri: "http://127.0.0.1:10271".to_owned(), - auth: fomat!("Basic " (base64_encode("user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", URL_SAFE))), + auth: fomat!("Basic "(base64_encode( + "user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", + URL_SAFE + ))), event_handlers: Default::default(), }; let import_addr = client.import_address( "bMjWGCinft5qEvsuf9Wg1fgz1CjpXBXbTB", "bMjWGCinft5qEvsuf9Wg1fgz1CjpXBXbTB", - true + true, ); - let import_addr = import_addr.wait().unwrap(); - log!("Block count " [import_addr]); + import_addr.wait().unwrap(); } diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 56e4fd3cec..08f18e138d 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -20,41 +20,42 @@ // use bigdecimal::BigDecimal; use bitcrypto::sha256; -use common::{now_ms, slurp_url, small_rng}; use common::custom_futures::TimedAsyncMutex; use common::executor::Timer; use common::mm_ctx::{MmArc, MmWeak}; -use secp256k1::PublicKey; +use common::{now_ms, slurp_url, small_rng}; use ethabi::{Contract, Token}; -use ethcore_transaction::{ Action, Transaction as UnSignedEthTx, UnverifiedTransaction}; -use ethereum_types::{Address, U256, H160}; -use ethkey::{ KeyPair, Public, public_to_address }; -use futures01::Future; -use futures01::future::{Either as Either01}; +use ethcore_transaction::{Action, Transaction as UnSignedEthTx, UnverifiedTransaction}; +use ethereum_types::{Address, H160, U256}; +use ethkey::{public_to_address, KeyPair, Public}; use futures::compat::Future01CompatExt; -use futures::future::{Either, FutureExt, join_all, select, TryFutureExt}; +use futures::future::{join_all, select, Either, FutureExt, TryFutureExt}; +use futures01::future::Either as Either01; +use futures01::Future; use gstuff::slurp; use http::StatusCode; -// #[cfg(test)] -use mocktopus::macros::*; +#[cfg(test)] use mocktopus::macros::*; use rand::seq::SliceRandom; -use rpc::v1::types::{Bytes as BytesJson}; +use rpc::v1::types::Bytes as BytesJson; +use secp256k1::PublicKey; use serde_json::{self as json, Value as Json}; -use sha3::{Keccak256, Digest}; -use std::collections::HashMap; +use sha3::{Digest, Keccak256}; use std::cmp::Ordering; +use std::collections::HashMap; use std::ops::Deref; use std::path::PathBuf; use std::str::FromStr; -use std::sync::{Arc, Mutex}; use std::sync::atomic::{AtomicU64, Ordering as AtomicOrderding}; +use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; -use web3::{ self, Web3 }; -use web3::types::{Action as TraceAction, BlockId, BlockNumber, Bytes, CallRequest, FilterBuilder, Log, Transaction as Web3Transaction, TransactionId, H256, Trace, TraceFilterBuilder}; +use web3::types::{Action as TraceAction, BlockId, BlockNumber, Bytes, CallRequest, FilterBuilder, Log, Trace, + TraceFilterBuilder, Transaction as Web3Transaction, TransactionId, H256}; +use web3::{self, Web3}; -use super::{CoinsContext, CoinTransportMetrics, FoundSwapTxSpend, HistorySyncState, MarketCoinOps, MmCoin, RpcClientType, RpcTransportEventHandler, RpcTransportEventHandlerShared, - SwapOps, TradeFee, TransactionFut, TransactionEnum, Transaction, TransactionDetails, WithdrawFee, WithdrawRequest}; +use super::{CoinTransportMetrics, CoinsContext, FoundSwapTxSpend, HistorySyncState, MarketCoinOps, MmCoin, + RpcClientType, RpcTransportEventHandler, RpcTransportEventHandlerShared, SwapOps, TradeFee, Transaction, + TransactionDetails, TransactionEnum, TransactionFut, WithdrawFee, WithdrawRequest}; pub use ethcore_transaction::SignedTransaction as SignedEthTx; pub use rlp; @@ -62,16 +63,15 @@ pub use rlp; mod web3_transport; use self::web3_transport::Web3Transport; -#[cfg(test)] -mod eth_tests; +#[cfg(test)] mod eth_tests; /// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol /// Dev chain (195.201.0.6:8565) contract address: 0xa09ad3cd7e96586ebd05a2607ee56b56fb2db8fd /// Ropsten: https://ropsten.etherscan.io/address/0x7bc1bbdd6a0a722fc9bffc49c921b685ecb84b94 /// ETH mainnet: https://etherscan.io/address/0x8500AFc0bc5214728082163326C2FF0C73f4a871 -const SWAP_CONTRACT_ABI: &'static str = r#"[{"constant":false,"inputs":[{"name":"_id","type":"bytes32"},{"name":"_amount","type":"uint256"},{"name":"_secret","type":"bytes32"},{"name":"_tokenAddress","type":"address"},{"name":"_sender","type":"address"}],"name":"receiverSpend","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"payments","outputs":[{"name":"paymentHash","type":"bytes20"},{"name":"lockTime","type":"uint64"},{"name":"state","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"bytes32"},{"name":"_receiver","type":"address"},{"name":"_secretHash","type":"bytes20"},{"name":"_lockTime","type":"uint64"}],"name":"ethPayment","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"bytes32"},{"name":"_amount","type":"uint256"},{"name":"_paymentHash","type":"bytes20"},{"name":"_tokenAddress","type":"address"},{"name":"_receiver","type":"address"}],"name":"senderRefund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"bytes32"},{"name":"_amount","type":"uint256"},{"name":"_tokenAddress","type":"address"},{"name":"_receiver","type":"address"},{"name":"_secretHash","type":"bytes20"},{"name":"_lockTime","type":"uint64"}],"name":"erc20Payment","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"bytes32"}],"name":"PaymentSent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"bytes32"},{"indexed":false,"name":"secret","type":"bytes32"}],"name":"ReceiverSpent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"bytes32"}],"name":"SenderRefunded","type":"event"}]"#; +const SWAP_CONTRACT_ABI: &str = r#"[{"constant":false,"inputs":[{"name":"_id","type":"bytes32"},{"name":"_amount","type":"uint256"},{"name":"_secret","type":"bytes32"},{"name":"_tokenAddress","type":"address"},{"name":"_sender","type":"address"}],"name":"receiverSpend","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"payments","outputs":[{"name":"paymentHash","type":"bytes20"},{"name":"lockTime","type":"uint64"},{"name":"state","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"bytes32"},{"name":"_receiver","type":"address"},{"name":"_secretHash","type":"bytes20"},{"name":"_lockTime","type":"uint64"}],"name":"ethPayment","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"bytes32"},{"name":"_amount","type":"uint256"},{"name":"_paymentHash","type":"bytes20"},{"name":"_tokenAddress","type":"address"},{"name":"_receiver","type":"address"}],"name":"senderRefund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"bytes32"},{"name":"_amount","type":"uint256"},{"name":"_tokenAddress","type":"address"},{"name":"_receiver","type":"address"},{"name":"_secretHash","type":"bytes20"},{"name":"_lockTime","type":"uint64"}],"name":"erc20Payment","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"bytes32"}],"name":"PaymentSent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"bytes32"},{"indexed":false,"name":"secret","type":"bytes32"}],"name":"ReceiverSpent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"bytes32"}],"name":"SenderRefunded","type":"event"}]"#; /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md -const ERC20_ABI: &'static str = r#"[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseApproval","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseApproval","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]"#; +const ERC20_ABI: &str = r#"[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseApproval","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseApproval","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]"#; /// Payment states from etomic swap smart contract: https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol#L5 const PAYMENT_STATE_UNINITIALIZED: u8 = 0; @@ -81,7 +81,6 @@ const _PAYMENT_STATE_REFUNDED: u8 = 3; lazy_static! { static ref SWAP_CONTRACT: Contract = unwrap!(Contract::load(SWAP_CONTRACT_ABI.as_bytes())); - static ref ERC20_CONTRACT: Contract = unwrap!(Contract::load(ERC20_ABI.as_bytes())); } @@ -114,8 +113,9 @@ enum EthCoinType { Erc20(Address), } +/// pImpl idiom. #[derive(Debug)] -pub struct EthCoinImpl { // pImpl idiom. +pub struct EthCoinImpl { ticker: String, coin_type: EthCoinType, key_pair: KeyPair, @@ -149,8 +149,8 @@ impl EthCoinImpl { to_addr: Option
, from_block: BlockNumber, to_block: BlockNumber, - limit: Option - ) -> Box, Error=String>> { + limit: Option, + ) -> Box, Error = String>> { let contract_event = try_fus!(ERC20_CONTRACT.event("Transfer")); let topic0 = Some(vec![contract_event.signature()]); let topic1 = from_addr.map(|addr| vec![addr.into()]); @@ -175,8 +175,8 @@ impl EthCoinImpl { to_addr: Vec
, from_block: BlockNumber, to_block: BlockNumber, - limit: Option - ) -> Box, Error=String>> { + limit: Option, + ) -> Box, Error = String>> { let mut filter = TraceFilterBuilder::default() .from_address(from_addr) .to_address(to_addr) @@ -191,14 +191,16 @@ impl EthCoinImpl { } fn eth_traces_path(&self, ctx: &MmArc) -> PathBuf { - ctx.dbdir().join("TRANSACTIONS").join(format!("{}_{:#02x}_trace.json", self.ticker, self.my_address)) + ctx.dbdir() + .join("TRANSACTIONS") + .join(format!("{}_{:#02x}_trace.json", self.ticker, self.my_address)) } /// Load saved ETH traces from local DB fn load_saved_traces(&self, ctx: &MmArc) -> Option { let content = slurp(&self.eth_traces_path(ctx)); if content.is_empty() { - return None + None } else { match json::from_slice(&content) { Ok(t) => Some(t), @@ -216,7 +218,9 @@ impl EthCoinImpl { } fn erc20_events_path(&self, ctx: &MmArc) -> PathBuf { - ctx.dbdir().join("TRANSACTIONS").join(format!("{}_{:#02x}_events.json", self.ticker, self.my_address)) + ctx.dbdir() + .join("TRANSACTIONS") + .join(format!("{}_{:#02x}_events.json", self.ticker, self.my_address)) } /// Store ERC20 events to local DB @@ -231,7 +235,7 @@ impl EthCoinImpl { fn load_saved_erc20_events(&self, ctx: &MmArc) -> Option { let content = slurp(&self.erc20_events_path(ctx)); if content.is_empty() { - return None + None } else { match json::from_slice(&content) { Ok(t) => Some(t), @@ -241,11 +245,7 @@ impl EthCoinImpl { } /// The id used to differentiate payments on Etomic swap smart contract - fn etomic_swap_id( - &self, - time_lock: u32, - secret_hash: &[u8], - ) -> Vec { + fn etomic_swap_id(&self, time_lock: u32, secret_hash: &[u8]) -> Vec { let mut input = vec![]; input.extend_from_slice(&time_lock.to_le_bytes()); input.extend_from_slice(secret_hash); @@ -253,16 +253,16 @@ impl EthCoinImpl { } /// Get gas price - fn get_gas_price(&self) -> impl Future { + fn get_gas_price(&self) -> impl Future { if let Some(url) = &self.gas_station_url { - Either01::A(GasStationData::get_gas_price(&url).map(|price| add_ten_pct_one_gwei(price))) + Either01::A(GasStationData::get_gas_price(&url).map(add_ten_pct_one_gwei)) } else { Either01::B(self.web3.eth().gas_price().map_err(|e| ERRL!("{}", e))) } } /// Gets `ReceiverSpent` events from etomic swap smart contract (`self.swap_contract_address` ) since `from_block` - fn spend_events(&self, from_block: u64) -> Box, Error=String> + Send> { + fn spend_events(&self, from_block: u64) -> Box, Error = String> + Send> { let contract_event = try_fus!(SWAP_CONTRACT.event("ReceiverSpent")); let filter = FilterBuilder::default() .topics(Some(vec![contract_event.signature()]), None, None, None) @@ -274,7 +274,7 @@ impl EthCoinImpl { } /// Gets `SenderRefunded` events from etomic swap smart contract (`self.swap_contract_address` ) since `from_block` - fn refund_events(&self, from_block: u64) -> Box, Error=String>> { + fn refund_events(&self, from_block: u64) -> Box, Error = String>> { let contract_event = try_fus!(SWAP_CONTRACT.event("SenderRefunded")); log!([contract_event.signature()]); let filter = FilterBuilder::default() @@ -286,11 +286,7 @@ impl EthCoinImpl { Box::new(self.web3.eth().logs(filter).map_err(|e| ERRL!("{}", e))) } - fn search_for_swap_tx_spend( - &self, - tx: &[u8], - search_from_block: u64, - ) -> Result, String> { + fn search_for_swap_tx_spend(&self, tx: &[u8], search_from_block: u64) -> Result, String> { let unverified: UnverifiedTransaction = try_s!(rlp::decode(tx)); let tx = try_s!(SignedEthTx::new(unverified)); @@ -317,7 +313,9 @@ impl EthCoinImpl { None => return ERR!("Found ReceiverSpent event, but transaction {:02x} is missing", tx_hash), }; - return Ok(Some(FoundSwapTxSpend::Spent(TransactionEnum::from(try_s!(signed_tx_from_web3_tx(transaction)))))); + return Ok(Some(FoundSwapTxSpend::Spent(TransactionEnum::from(try_s!( + signed_tx_from_web3_tx(transaction) + ))))); }, None => return ERR!("Found ReceiverSpent event, but it doesn't have tx_hash"), } @@ -334,7 +332,9 @@ impl EthCoinImpl { None => return ERR!("Found SenderRefunded event, but transaction {:02x} is missing", tx_hash), }; - return Ok(Some(FoundSwapTxSpend::Refunded(TransactionEnum::from(try_s!(signed_tx_from_web3_tx(transaction)))))); + return Ok(Some(FoundSwapTxSpend::Refunded(TransactionEnum::from(try_s!( + signed_tx_from_web3_tx(transaction) + ))))); }, None => return ERR!("Found SenderRefunded event, but it doesn't have tx_hash"), } @@ -361,13 +361,11 @@ async fn withdraw_impl(ctx: MmArc, coin: EthCoin, req: WithdrawRequest) -> Resul let function = try_s!(ERC20_CONTRACT.function("transfer")); let data = try_s!(function.encode_input(&[Token::Address(to_addr), Token::Uint(wei_amount)])); (0.into(), data, token_addr) - } + }, }; let (gas, gas_price) = match req.fee { - Some(WithdrawFee::EthGas { gas_price, gas }) => { - (gas.into(), try_s!(wei_from_big_decimal(&gas_price, 9))) - }, + Some(WithdrawFee::EthGas { gas_price, gas }) => (gas.into(), try_s!(wei_from_big_decimal(&gas_price, 9))), Some(_) => return ERR!("Unsupported input fee type"), None => { let gas_price = try_s!(coin.get_gas_price().compat().await); @@ -381,9 +379,14 @@ async fn withdraw_impl(ctx: MmArc, coin: EthCoin, req: WithdrawRequest) -> Resul // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 gas_price: Some(gas_price), }; - let gas_fut = coin.web3.eth().estimate_gas(estimate_gas_req, None).map_err(|e| ERRL!("{}", e)).compat(); + let gas_fut = coin + .web3 + .eth() + .estimate_gas(estimate_gas_req, None) + .map_err(|e| ERRL!("{}", e)) + .compat(); (try_s!(gas_fut.await), gas_price) - } + }, }; let total_fee = gas * gas_price; @@ -394,25 +397,39 @@ async fn withdraw_impl(ctx: MmArc, coin: EthCoin, req: WithdrawRequest) -> Resul eth_value -= total_fee; wei_amount -= total_fee; }; - let _nonce_lock = try_s!(NONCE_LOCK.lock(|_start, _now| { - if ctx.is_stopping() {return ERR!("MM is stopping, aborting withdraw_impl in NONCE_LOCK")} - Ok(0.5) - }).await); - let nonce_fut = get_addr_nonce(coin.my_address, &coin.web3_instances).compat(); + let _nonce_lock = try_s!( + NONCE_LOCK + .lock(|_start, _now| { + if ctx.is_stopping() { + return ERR!("MM is stopping, aborting withdraw_impl in NONCE_LOCK"); + } + Ok(0.5) + }) + .await + ); + let nonce_fut = get_addr_nonce(coin.my_address, coin.web3_instances.clone()).compat(); let nonce = match select(nonce_fut, Timer::sleep(30.)).await { Either::Left((nonce_res, _)) => try_s!(nonce_res), Either::Right(_) => return ERR!("Get address nonce timed out"), }; - let tx = UnSignedEthTx { nonce, value: eth_value, action: Action::Call(call_addr), data, gas, gas_price }; + let tx = UnSignedEthTx { + nonce, + value: eth_value, + action: Action::Call(call_addr), + data, + gas, + gas_price, + }; let signed = tx.sign(coin.key_pair.secret(), None); let bytes = rlp::encode(&signed); let amount_decimal = try_s!(u256_to_big_decimal(wei_amount, coin.decimals)); let mut spent_by_me = amount_decimal.clone(); - let mut received_by_me = 0.into(); - if to_addr == coin.my_address { - received_by_me = amount_decimal.clone(); - } + let received_by_me = if to_addr == coin.my_address { + amount_decimal.clone() + } else { + 0.into() + }; let fee_details = try_s!(EthTxFeeDetails::new(gas, gas_price, "ETH")); if coin.coin_type == EthCoinType::Eth { spent_by_me += &fee_details.total_fee; @@ -436,16 +453,19 @@ async fn withdraw_impl(ctx: MmArc, coin: EthCoin, req: WithdrawRequest) -> Resul #[derive(Clone, Debug)] pub struct EthCoin(Arc); -impl Deref for EthCoin {type Target = EthCoinImpl; fn deref (&self) -> &EthCoinImpl {&*self.0}} +impl Deref for EthCoin { + type Target = EthCoinImpl; + fn deref(&self) -> &EthCoinImpl { &*self.0 } +} impl SwapOps for EthCoin { fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal) -> TransactionFut { let address = try_fus!(addr_from_raw_pubkey(fee_addr)); - Box::new(self.send_to_address( - address, - try_fus!(wei_from_big_decimal(&amount, self.decimals)), - ).map(TransactionEnum::from)) + Box::new( + self.send_to_address(address, try_fus!(wei_from_big_decimal(&amount, self.decimals))) + .map(TransactionEnum::from), + ) } fn send_maker_payment( @@ -457,13 +477,16 @@ impl SwapOps for EthCoin { ) -> TransactionFut { let taker_addr = try_fus!(addr_from_raw_pubkey(taker_pub)); - Box::new(self.send_hash_time_locked_payment( - self.etomic_swap_id(time_lock, secret_hash), - try_fus!(wei_from_big_decimal(&amount, self.decimals)), - time_lock, - secret_hash, - taker_addr, - ).map(TransactionEnum::from)) + Box::new( + self.send_hash_time_locked_payment( + self.etomic_swap_id(time_lock, secret_hash), + try_fus!(wei_from_big_decimal(&amount, self.decimals)), + time_lock, + secret_hash, + taker_addr, + ) + .map(TransactionEnum::from), + ) } fn send_taker_payment( @@ -475,13 +498,16 @@ impl SwapOps for EthCoin { ) -> TransactionFut { let maker_addr = try_fus!(addr_from_raw_pubkey(maker_pub)); - Box::new(self.send_hash_time_locked_payment( - self.etomic_swap_id(time_lock, secret_hash), - try_fus!(wei_from_big_decimal(&amount, self.decimals)), - time_lock, - secret_hash, - maker_addr, - ).map(TransactionEnum::from)) + Box::new( + self.send_hash_time_locked_payment( + self.etomic_swap_id(time_lock, secret_hash), + try_fus!(wei_from_big_decimal(&amount, self.decimals)), + time_lock, + secret_hash, + maker_addr, + ) + .map(TransactionEnum::from), + ) } fn send_maker_spends_taker_payment( @@ -494,7 +520,10 @@ impl SwapOps for EthCoin { let tx: UnverifiedTransaction = try_fus!(rlp::decode(taker_payment_tx)); let signed = try_fus!(SignedEthTx::new(tx)); - Box::new(self.spend_hash_time_locked_payment(signed, secret).map(TransactionEnum::from)) + Box::new( + self.spend_hash_time_locked_payment(signed, secret) + .map(TransactionEnum::from), + ) } fn send_taker_spends_maker_payment( @@ -506,7 +535,10 @@ impl SwapOps for EthCoin { ) -> TransactionFut { let tx: UnverifiedTransaction = try_fus!(rlp::decode(maker_payment_tx)); let signed = try_fus!(SignedEthTx::new(tx)); - Box::new(self.spend_hash_time_locked_payment(signed, secret).map(TransactionEnum::from)) + Box::new( + self.spend_hash_time_locked_payment(signed, secret) + .map(TransactionEnum::from), + ) } fn send_taker_refunds_payment( @@ -540,7 +572,7 @@ impl SwapOps for EthCoin { fee_tx: &TransactionEnum, fee_addr: &[u8], amount: &BigDecimal, - ) -> Box + Send> { + ) -> Box + Send> { let selfi = self.clone(); let tx = match fee_tx { TransactionEnum::SignedEthTx(t) => t.clone(), @@ -551,7 +583,14 @@ impl SwapOps for EthCoin { let fut = async move { let expected_value = try_s!(wei_from_big_decimal(&amount, selfi.decimals)); - let tx_from_rpc = try_s!(selfi.web3.eth().transaction(TransactionId::Hash(tx.hash)).compat().await); + let tx_from_rpc = try_s!( + selfi + .web3 + .eth() + .transaction(TransactionId::Hash(tx.hash)) + .compat() + .await + ); let tx_from_rpc = match tx_from_rpc { Some(t) => t, None => return ERR!("Didn't find provided tx {:?} on ETH node", tx), @@ -560,23 +599,39 @@ impl SwapOps for EthCoin { match selfi.coin_type { EthCoinType::Eth => { if tx_from_rpc.to != Some(fee_addr) { - return ERR!("Fee tx {:?} was sent to wrong address, expected {:?}", tx_from_rpc, fee_addr); + return ERR!( + "Fee tx {:?} was sent to wrong address, expected {:?}", + tx_from_rpc, + fee_addr + ); } if tx_from_rpc.value < expected_value { - return ERR!("Fee tx {:?} value is less than expected {:?}", tx_from_rpc, expected_value); + return ERR!( + "Fee tx {:?} value is less than expected {:?}", + tx_from_rpc, + expected_value + ); } }, EthCoinType::Erc20(token_addr) => { if tx_from_rpc.to != Some(token_addr) { - return ERR!("ERC20 Fee tx {:?} called wrong smart contract, expected {:?}", tx_from_rpc, token_addr); + return ERR!( + "ERC20 Fee tx {:?} called wrong smart contract, expected {:?}", + tx_from_rpc, + token_addr + ); } let function = try_s!(ERC20_CONTRACT.function("transfer")); let decoded_input = try_s!(function.decode_input(&tx_from_rpc.input.0)); if decoded_input[0] != Token::Address(fee_addr) { - return ERR!("ERC20 Fee tx was sent to wrong address {:?}, expected {:?}", decoded_input[0], fee_addr); + return ERR!( + "ERC20 Fee tx was sent to wrong address {:?}, expected {:?}", + decoded_input[0], + fee_addr + ); } match decoded_input[1] { @@ -585,7 +640,7 @@ impl SwapOps for EthCoin { return ERR!("ERC20 Fee tx value {} is less than expected {}", value, expected_value); } }, - _ => return ERR!("Should have got uint token but got {:?}", decoded_input[1]) + _ => return ERR!("Should have got uint token but got {:?}", decoded_input[1]), } }, } @@ -602,14 +657,8 @@ impl SwapOps for EthCoin { maker_pub: &[u8], secret_hash: &[u8], amount: BigDecimal, - ) -> Box + Send> { - self.validate_payment( - payment_tx, - time_lock, - maker_pub, - secret_hash, - amount, - ) + ) -> Box + Send> { + self.validate_payment(payment_tx, time_lock, maker_pub, secret_hash, amount) } fn validate_taker_payment( @@ -619,14 +668,8 @@ impl SwapOps for EthCoin { taker_pub: &[u8], secret_hash: &[u8], amount: BigDecimal, - ) -> Box + Send> { - self.validate_payment( - payment_tx, - time_lock, - taker_pub, - secret_hash, - amount, - ) + ) -> Box + Send> { + self.validate_payment(payment_tx, time_lock, taker_pub, secret_hash, amount) } fn check_if_my_payment_sent( @@ -635,7 +678,7 @@ impl SwapOps for EthCoin { _other_pub: &[u8], secret_hash: &[u8], from_block: u64, - ) -> Box, Error=String> + Send> { + ) -> Box, Error = String> + Send> { let id = self.etomic_swap_id(time_lock, secret_hash); let selfi = self.clone(); let fut = async move { @@ -649,13 +692,20 @@ impl SwapOps for EthCoin { match found { Some(event) => { - let transaction = try_s!(selfi.web3.eth().transaction(TransactionId::Hash(event.transaction_hash.unwrap())).compat().await); + let transaction = try_s!( + selfi + .web3 + .eth() + .transaction(TransactionId::Hash(event.transaction_hash.unwrap())) + .compat() + .await + ); match transaction { Some(t) => Ok(Some(try_s!(signed_tx_from_web3_tx(t)).into())), None => Ok(None), } }, - None => Ok(None) + None => Ok(None), } }; Box::new(fut.boxed().compat()) @@ -685,34 +735,36 @@ impl SwapOps for EthCoin { } impl MarketCoinOps for EthCoin { - fn ticker (&self) -> &str {&self.ticker[..]} + fn ticker(&self) -> &str { &self.ticker[..] } - fn my_address(&self) -> Result { - Ok(checksum_address(&format!("{:#02x}", self.my_address))) - } + fn my_address(&self) -> Result { Ok(checksum_address(&format!("{:#02x}", self.my_address))) } - fn my_balance(&self) -> Box + Send> { + fn my_balance(&self) -> Box + Send> { let decimals = self.decimals; - Box::new(self.my_balance().and_then(move |result| { - Ok(try_s!(u256_to_big_decimal(result, decimals))) - })) + Box::new( + self.my_balance() + .and_then(move |result| Ok(try_s!(u256_to_big_decimal(result, decimals)))), + ) } - fn base_coin_balance(&self) -> Box + Send> { - Box::new(self.eth_balance().and_then(move |result| { - Ok(try_s!(u256_to_big_decimal(result, 18))) - })) + fn base_coin_balance(&self) -> Box + Send> { + Box::new( + self.eth_balance() + .and_then(move |result| Ok(try_s!(u256_to_big_decimal(result, 18)))), + ) } - fn send_raw_tx(&self, mut tx: &str) -> Box + Send> { + fn send_raw_tx(&self, mut tx: &str) -> Box + Send> { if tx.starts_with("0x") { tx = &tx[2..]; } let bytes = try_fus!(hex::decode(tx)); Box::new( - self.web3.eth().send_raw_transaction(bytes.into()) + self.web3 + .eth() + .send_raw_transaction(bytes.into()) .map(|res| format!("{:02x}", res)) - .map_err(|e| ERRL!("{}", e)) + .map_err(|e| ERRL!("{}", e)), ) } @@ -723,11 +775,11 @@ impl MarketCoinOps for EthCoin { _requires_nota: bool, wait_until: u64, check_every: u64, - ) -> Box + Send> { + ) -> Box + Send> { let ctx = try_fus!(MmArc::from_weak(&self.ctx).ok_or("No context")); let mut status = ctx.log.status_handle(); - status.status (&[&self.ticker], "Waiting for confirmations…"); - status.deadline (wait_until * 1000); + status.status(&[&self.ticker], "Waiting for confirmations…"); + status.deadline(wait_until * 1000); let unsigned: UnverifiedTransaction = try_fus!(rlp::decode(tx)); let tx = try_fus!(SignedEthTx::new(unsigned)); @@ -736,9 +788,13 @@ impl MarketCoinOps for EthCoin { let selfi = self.clone(); let fut = async move { loop { - if unwrap! (status.ms2deadline()) < 0 { - status.append (" Timed out."); - return ERR!("Waited too long until {} for transaction {:?} confirmation ", wait_until, tx); + if unwrap!(status.ms2deadline()) < 0 { + status.append(" Timed out."); + return ERR!( + "Waited too long until {} for transaction {:?} confirmation ", + wait_until, + tx + ); } let web3_receipt = match selfi.web3.eth().transaction_receipt(tx.hash()).compat().await { @@ -747,12 +803,17 @@ impl MarketCoinOps for EthCoin { log!("Error " [e] " getting the " (selfi.ticker()) " transaction " [tx.tx_hash()] ", retrying in 15 seconds"); Timer::sleep(check_every as f64).await; continue; - } + }, }; if let Some(receipt) = web3_receipt { if receipt.status != Some(1.into()) { - status.append (" Failed."); - return ERR!("Tx receipt {:?} status of {} tx {:?} is failed", receipt, selfi.ticker(), tx.tx_hash()); + status.append(" Failed."); + return ERR!( + "Tx receipt {:?} status of {} tx {:?} is failed", + receipt, + selfi.ticker(), + tx.tx_hash() + ); } if let Some(confirmed_at) = receipt.block_number { @@ -762,10 +823,10 @@ impl MarketCoinOps for EthCoin { log!("Error " [e] " getting the " (selfi.ticker()) " block number retrying in 15 seconds"); Timer::sleep(check_every as f64).await; continue; - } + }, }; if current_block - confirmed_at + 1 >= required_confirms { - status.append (" Confirmed."); + status.append(" Confirmed."); return Ok(()); } } @@ -808,26 +869,36 @@ impl MarketCoinOps for EthCoin { if let Some(event) = found { if let Some(tx_hash) = event.transaction_hash { - let transaction = match selfi.web3.eth().transaction(TransactionId::Hash(tx_hash)).compat().await { + let transaction = match selfi + .web3 + .eth() + .transaction(TransactionId::Hash(tx_hash)) + .compat() + .await + { Ok(Some(t)) => t, Ok(None) => { log!("Tx " (tx_hash) " not found yet"); Timer::sleep(5.).await; continue; - } + }, Err(e) => { log!("Get tx " (tx_hash) " error " (e)); Timer::sleep(5.).await; continue; - } + }, }; - return Ok(TransactionEnum::from(try_s!(signed_tx_from_web3_tx(transaction)))) + return Ok(TransactionEnum::from(try_s!(signed_tx_from_web3_tx(transaction)))); } } if now_ms() / 1000 > wait_until { - return ERR!("Waited too long until {} for transaction {:?} to be spent ", wait_until, tx); + return ERR!( + "Waited too long until {} for transaction {:?} to be spent ", + wait_until, + tx + ); } Timer::sleep(5.).await; continue; @@ -840,8 +911,14 @@ impl MarketCoinOps for EthCoin { Ok(try_s!(signed_eth_tx_from_bytes(bytes)).into()) } - fn current_block(&self) -> Box + Send> { - Box::new(self.web3.eth().block_number().map(|res| res.into()).map_err(|e| ERRL!("{}", e))) + fn current_block(&self) -> Box + Send> { + Box::new( + self.web3 + .eth() + .block_number() + .map(|res| res.into()) + .map_err(|e| ERRL!("{}", e)), + ) } fn address_from_pubkey_str(&self, pubkey: &str) -> Result { @@ -850,9 +927,7 @@ impl MarketCoinOps for EthCoin { Ok(format!("{:#02x}", addr)) } - fn display_priv_key(&self) -> String { - format!("{:#02x}", self.key_pair.secret()) - } + fn display_priv_key(&self) -> String { format!("{:#02x}", self.key_pair.secret()) } } pub fn signed_eth_tx_from_bytes(bytes: &[u8]) -> Result { @@ -865,9 +940,11 @@ pub fn signed_eth_tx_from_bytes(bytes: &[u8]) -> Result { // It's highly likely that we won't experience any issues with it as we won't need to send "a lot" of transactions concurrently. // For ETH it makes even more sense because different ERC20 tokens can be running on same ETH blockchain. // So we would need to handle shared locks anyway. -lazy_static! {static ref NONCE_LOCK: TimedAsyncMutex<()> = TimedAsyncMutex::new(());} +lazy_static! { + static ref NONCE_LOCK: TimedAsyncMutex<()> = TimedAsyncMutex::new(()); +} -type EthTxFut = Box + Send + 'static>; +type EthTxFut = Box + Send + 'static>; async fn sign_and_send_transaction_impl( ctx: MmArc, @@ -878,14 +955,28 @@ async fn sign_and_send_transaction_impl( gas: U256, ) -> Result { let mut status = ctx.log.status_handle(); - macro_rules! tags {() => {&[&"sign-and-send"]}}; - let _nonce_lock = NONCE_LOCK.lock(|start, now| { - if ctx.is_stopping() {return ERR!("MM is stopping, aborting sign_and_send_transaction_impl in NONCE_LOCK")} - if start < now {status.status(tags!(), "Waiting for NONCE_LOCK…")} - Ok(0.5) - }).await; + macro_rules! tags { + () => { + &[&"sign-and-send"] + }; + }; + let _nonce_lock = NONCE_LOCK + .lock(|start, now| { + if ctx.is_stopping() { + return ERR!("MM is stopping, aborting sign_and_send_transaction_impl in NONCE_LOCK"); + } + if start < now { + status.status(tags!(), "Waiting for NONCE_LOCK…") + } + Ok(0.5) + }) + .await; status.status(tags!(), "get_addr_nonce…"); - let nonce = try_s!(get_addr_nonce(coin.my_address, &coin.web3_instances).compat().await); + let nonce = try_s!( + get_addr_nonce(coin.my_address, coin.web3_instances.clone()) + .compat() + .await + ); status.status(tags!(), "get_gas_price…"); let gas_price = try_s!(coin.get_gas_price().compat().await); let tx = UnSignedEthTx { @@ -899,22 +990,34 @@ async fn sign_and_send_transaction_impl( let signed = tx.sign(coin.key_pair.secret(), None); let bytes = web3::types::Bytes(rlp::encode(&signed).to_vec()); status.status(tags!(), "send_raw_transaction…"); - try_s!(coin.web3.eth().send_raw_transaction(bytes).map_err(|e| ERRL!("{}", e)).compat().await); + try_s!( + coin.web3 + .eth() + .send_raw_transaction(bytes) + .map_err(|e| ERRL!("{}", e)) + .compat() + .await + ); status.status(tags!(), "get_addr_nonce…"); loop { // Check every second till ETH nodes recognize that nonce is increased // Parity has reliable "nextNonce" method that always returns correct nonce for address // But we can't expect that all nodes will always be Parity. // Some of ETH forks use Geth only so they don't have Parity nodes at all. - let new_nonce = match get_addr_nonce(coin.my_address, &coin.web3_instances).compat().await { + let new_nonce = match get_addr_nonce(coin.my_address, coin.web3_instances.clone()) + .compat() + .await + { Ok(n) => n, Err(e) => { log!("Error " [e] " getting " [coin.ticker()] " " [coin.my_address] " nonce"); // we can just keep looping in case of error hoping it will go away continue; - } + }, + }; + if new_nonce > nonce { + break; }; - if new_nonce > nonce { break }; Timer::sleep(1.).await; } Ok(signed) @@ -922,15 +1025,16 @@ async fn sign_and_send_transaction_impl( #[cfg_attr(test, mockable)] impl EthCoin { - fn sign_and_send_transaction( - &self, - value: U256, - action: Action, - data: Vec, - gas: U256, - ) -> EthTxFut { + fn sign_and_send_transaction(&self, value: U256, action: Action, data: Vec, gas: U256) -> EthTxFut { let ctx = try_fus!(MmArc::from_weak(&self.ctx).ok_or("!ctx")); - let fut = Box::pin(sign_and_send_transaction_impl(ctx, self.clone(), value, action, data, gas)); + let fut = Box::pin(sign_and_send_transaction_impl( + ctx, + self.clone(), + value, + action, + data, + gas, + )); Box::new(fut.compat()) } @@ -940,12 +1044,9 @@ impl EthCoin { EthCoinType::Erc20(token_addr) => { let abi = try_fus!(Contract::load(ERC20_ABI.as_bytes())); let function = try_fus!(abi.function("transfer")); - let data = try_fus!(function.encode_input(&[ - Token::Address(address), - Token::Uint(value) - ])); - self.sign_and_send_transaction(0.into(), Action::Call(token_addr), data, U256::from(210000)) - } + let data = try_fus!(function.encode_input(&[Token::Address(address), Token::Uint(value)])); + self.sign_and_send_transaction(0.into(), Action::Call(token_addr), data, U256::from(210_000)) + }, } } @@ -966,7 +1067,12 @@ impl EthCoin { Token::FixedBytes(secret_hash.to_vec()), Token::Uint(U256::from(time_lock)) ])); - self.sign_and_send_transaction(value, Action::Call(self.swap_contract_address), data, U256::from(150000)) + self.sign_and_send_transaction( + value, + Action::Call(self.swap_contract_address), + data, + U256::from(150_000), + ) }, EthCoinType::Erc20(token_addr) => { let allowance_fut = self.allowance(self.swap_contract_address); @@ -974,7 +1080,7 @@ impl EthCoin { let function = try_fus!(SWAP_CONTRACT.function("erc20Payment")); let data = try_fus!(function.encode_input(&[ Token::FixedBytes(id), - Token::Uint(U256::from(value)), + Token::Uint(value), Token::Address(token_addr), Token::Address(receiver_addr), Token::FixedBytes(secret_hash.to_vec()), @@ -986,23 +1092,30 @@ impl EthCoin { if allowed < value { let balance_f = arc.my_balance(); Box::new(balance_f.and_then(move |balance| { - arc.approve(arc.swap_contract_address, balance).and_then(move |_approved| { - arc.sign_and_send_transaction(0.into(), Action::Call(arc.swap_contract_address), data, U256::from(150000)) - }) + arc.approve(arc.swap_contract_address, balance) + .and_then(move |_approved| { + arc.sign_and_send_transaction( + 0.into(), + Action::Call(arc.swap_contract_address), + data, + U256::from(150_000), + ) + }) })) } else { - Box::new(arc.sign_and_send_transaction(0.into(), Action::Call(arc.swap_contract_address), data, U256::from(150000))) + Box::new(arc.sign_and_send_transaction( + 0.into(), + Action::Call(arc.swap_contract_address), + data, + U256::from(150_000), + )) } })) - } + }, } } - fn spend_hash_time_locked_payment( - &self, - payment: SignedEthTx, - secret: &[u8], - ) -> EthTxFut { + fn spend_hash_time_locked_payment(&self, payment: SignedEthTx, secret: &[u8]) -> EthTxFut { let spend_func = try_fus!(SWAP_CONTRACT.function("receiverSpend")); let clone = self.clone(); let secret_vec = secret.to_vec(); @@ -1015,7 +1128,11 @@ impl EthCoin { let state_f = self.payment_status(decoded[0].clone()); Box::new(state_f.and_then(move |state| -> EthTxFut { if state != PAYMENT_STATE_SENT.into() { - return Box::new(futures01::future::err(ERRL!("Payment {:?} state is not PAYMENT_STATE_SENT, got {}", payment, state))); + return Box::new(futures01::future::err(ERRL!( + "Payment {:?} state is not PAYMENT_STATE_SENT, got {}", + payment, + state + ))); } let value = payment.value; @@ -1027,7 +1144,12 @@ impl EthCoin { Token::Address(payment.sender()), ])); - clone.sign_and_send_transaction(0.into(), Action::Call(clone.swap_contract_address), data, U256::from(150000)) + clone.sign_and_send_transaction( + 0.into(), + Action::Call(clone.swap_contract_address), + data, + U256::from(150_000), + ) })) }, EthCoinType::Erc20(token_addr) => { @@ -1037,7 +1159,11 @@ impl EthCoin { Box::new(state_f.and_then(move |state| -> EthTxFut { if state != PAYMENT_STATE_SENT.into() { - return Box::new(futures01::future::err(ERRL!("Payment {:?} state is not PAYMENT_STATE_SENT, got {}", payment, state))); + return Box::new(futures01::future::err(ERRL!( + "Payment {:?} state is not PAYMENT_STATE_SENT, got {}", + payment, + state + ))); } let data = try_fus!(spend_func.encode_input(&[ decoded[0].clone(), @@ -1047,16 +1173,18 @@ impl EthCoin { Token::Address(payment.sender()), ])); - clone.sign_and_send_transaction(0.into(), Action::Call(clone.swap_contract_address), data, U256::from(150000)) + clone.sign_and_send_transaction( + 0.into(), + Action::Call(clone.swap_contract_address), + data, + U256::from(150_000), + ) })) - } + }, } } - fn refund_hash_time_locked_payment( - &self, - payment: SignedEthTx, - ) -> EthTxFut { + fn refund_hash_time_locked_payment(&self, payment: SignedEthTx) -> EthTxFut { let refund_func = try_fus!(SWAP_CONTRACT.function("senderRefund")); let clone = self.clone(); @@ -1068,7 +1196,11 @@ impl EthCoin { let state_f = self.payment_status(decoded[0].clone()); Box::new(state_f.and_then(move |state| -> EthTxFut { if state != PAYMENT_STATE_SENT.into() { - return Box::new(futures01::future::err(ERRL!("Payment {:?} state is not PAYMENT_STATE_SENT, got {}", payment, state))); + return Box::new(futures01::future::err(ERRL!( + "Payment {:?} state is not PAYMENT_STATE_SENT, got {}", + payment, + state + ))); } let value = payment.value; @@ -1080,7 +1212,12 @@ impl EthCoin { decoded[1].clone(), ])); - clone.sign_and_send_transaction(0.into(), Action::Call(clone.swap_contract_address), data, U256::from(150000)) + clone.sign_and_send_transaction( + 0.into(), + Action::Call(clone.swap_contract_address), + data, + U256::from(150_000), + ) })) }, EthCoinType::Erc20(token_addr) => { @@ -1089,7 +1226,11 @@ impl EthCoin { let state_f = self.payment_status(decoded[0].clone()); Box::new(state_f.and_then(move |state| -> EthTxFut { if state != PAYMENT_STATE_SENT.into() { - return Box::new(futures01::future::err(ERRL!("Payment {:?} state is not PAYMENT_STATE_SENT, got {}", payment, state))); + return Box::new(futures01::future::err(ERRL!( + "Payment {:?} state is not PAYMENT_STATE_SENT, got {}", + payment, + state + ))); } let data = try_fus!(refund_func.encode_input(&[ @@ -1100,20 +1241,28 @@ impl EthCoin { decoded[3].clone(), ])); - clone.sign_and_send_transaction(0.into(), Action::Call(clone.swap_contract_address), data, U256::from(150000)) + clone.sign_and_send_transaction( + 0.into(), + Action::Call(clone.swap_contract_address), + data, + U256::from(150_000), + ) })) - } + }, } } - fn my_balance(&self) -> Box + Send> { + fn my_balance(&self) -> Box + Send> { match self.coin_type { - EthCoinType::Eth => Box::new(self.web3.eth().balance(self.my_address, Some(BlockNumber::Latest)).map_err(|e| ERRL!("{}", e))), + EthCoinType::Eth => Box::new( + self.web3 + .eth() + .balance(self.my_address, Some(BlockNumber::Latest)) + .map_err(|e| ERRL!("{}", e)), + ), EthCoinType::Erc20(token_addr) => { let function = try_fus!(ERC20_CONTRACT.function("balanceOf")); - let data = try_fus!(function.encode_input(&[ - Token::Address(self.my_address), - ])); + let data = try_fus!(function.encode_input(&[Token::Address(self.my_address),])); let call_fut = self.call_request(token_addr, None, Some(data.into())); @@ -1125,36 +1274,47 @@ impl EthCoin { _ => ERR!("Expected U256 as balanceOf result but got {:?}", decoded), } })) - } + }, } } - fn eth_balance(&self) -> Box + Send> { - Box::new(self.web3.eth().balance(self.my_address, Some(BlockNumber::Latest)).map_err(|e| ERRL!("{}", e))) + fn eth_balance(&self) -> Box + Send> { + Box::new( + self.web3 + .eth() + .balance(self.my_address, Some(BlockNumber::Latest)) + .map_err(|e| ERRL!("{}", e)), + ) } - fn call_request(&self, to: Address, value: Option, data: Option) -> impl Future { + fn call_request( + &self, + to: Address, + value: Option, + data: Option, + ) -> impl Future { let request = CallRequest { from: Some(self.my_address), to, gas: None, gas_price: None, value, - data + data, }; - self.web3.eth().call(request, Some(BlockNumber::Latest)).map_err(|e| ERRL!("{}", e)) + self.web3 + .eth() + .call(request, Some(BlockNumber::Latest)) + .map_err(|e| ERRL!("{}", e)) } - fn allowance(&self, spender: Address) -> Box + Send + 'static> { + fn allowance(&self, spender: Address) -> Box + Send + 'static> { match self.coin_type { EthCoinType::Eth => panic!(), EthCoinType::Erc20(token_addr) => { let function = try_fus!(ERC20_CONTRACT.function("allowance")); - let data = try_fus!(function.encode_input(&[ - Token::Address(self.my_address), - Token::Address(spender), - ])); + let data = + try_fus!(function.encode_input(&[Token::Address(self.my_address), Token::Address(spender),])); let call_fut = self.call_request(token_addr, None, Some(data.into())); @@ -1166,7 +1326,7 @@ impl EthCoin { _ => ERR!("Expected U256 as allowance result but got {:?}", decoded), } })) - } + }, } } @@ -1175,18 +1335,15 @@ impl EthCoin { EthCoinType::Eth => panic!(), EthCoinType::Erc20(token_addr) => { let function = try_fus!(ERC20_CONTRACT.function("approve")); - let data = try_fus!(function.encode_input(&[ - Token::Address(spender), - Token::Uint(amount), - ])); + let data = try_fus!(function.encode_input(&[Token::Address(spender), Token::Uint(amount),])); - self.sign_and_send_transaction(0.into(), Action::Call(token_addr), data, U256::from(150000)) - } + self.sign_and_send_transaction(0.into(), Action::Call(token_addr), data, U256::from(150_000)) + }, } } /// Gets `PaymentSent` events from etomic swap smart contract (`self.swap_contract_address` ) since `from_block` - fn payment_sent_events(&self, from_block: u64) -> Box, Error=String> + Send> { + fn payment_sent_events(&self, from_block: u64) -> Box, Error = String> + Send> { let contract_event = try_fus!(SWAP_CONTRACT.event("PaymentSent")); let filter = FilterBuilder::default() .topics(Some(vec![contract_event.signature()]), None, None, None) @@ -1205,7 +1362,7 @@ impl EthCoin { sender_pub: &[u8], secret_hash: &[u8], amount: BigDecimal, - ) -> Box + Send> { + ) -> Box + Send> { let unsigned: UnverifiedTransaction = try_fus!(rlp::decode(payment_tx)); let tx = try_fus!(SignedEthTx::new(unsigned)); let sender = try_fus!(addr_from_raw_pubkey(sender_pub)); @@ -1213,65 +1370,120 @@ impl EthCoin { let selfi = self.clone(); let secret_hash = secret_hash.to_vec(); let fut = async move { - let tx_from_rpc = try_s!(selfi.web3.eth().transaction(TransactionId::Hash(tx.hash)).compat().await); + let tx_from_rpc = try_s!( + selfi + .web3 + .eth() + .transaction(TransactionId::Hash(tx.hash)) + .compat() + .await + ); let tx_from_rpc = match tx_from_rpc { Some(t) => t, None => return ERR!("Didn't find provided tx {:?} on ETH node", tx), }; if tx_from_rpc.from != sender { - return ERR!("Payment tx {:?} was sent from wrong address, expected {:?}", tx_from_rpc, sender); + return ERR!( + "Payment tx {:?} was sent from wrong address, expected {:?}", + tx_from_rpc, + sender + ); } match selfi.coin_type { EthCoinType::Eth => { if tx_from_rpc.to != Some(selfi.swap_contract_address) { - return ERR!("Payment tx {:?} was sent to wrong address, expected {:?}", tx_from_rpc, selfi.swap_contract_address); + return ERR!( + "Payment tx {:?} was sent to wrong address, expected {:?}", + tx_from_rpc, + selfi.swap_contract_address + ); } if tx_from_rpc.value != expected_value { - return ERR!("Payment tx {:?} value is invalid, expected {:?}", tx_from_rpc, expected_value); + return ERR!( + "Payment tx {:?} value is invalid, expected {:?}", + tx_from_rpc, + expected_value + ); } let function = try_s!(SWAP_CONTRACT.function("ethPayment")); let decoded = try_s!(function.decode_input(&tx_from_rpc.input.0)); if decoded[1] != Token::Address(selfi.my_address) { - return ERR!("Payment tx receiver arg {:?} is invalid, expected {:?}", decoded[1], Token::Address(selfi.my_address)); + return ERR!( + "Payment tx receiver arg {:?} is invalid, expected {:?}", + decoded[1], + Token::Address(selfi.my_address) + ); } if decoded[2] != Token::FixedBytes(secret_hash.to_vec()) { - return ERR!("Payment tx secret_hash arg {:?} is invalid, expected {:?}", decoded[2], Token::FixedBytes(secret_hash.to_vec())); + return ERR!( + "Payment tx secret_hash arg {:?} is invalid, expected {:?}", + decoded[2], + Token::FixedBytes(secret_hash.to_vec()) + ); } if decoded[3] != Token::Uint(U256::from(time_lock)) { - return ERR!("Payment tx time_lock arg {:?} is invalid, expected {:?}", decoded[3], Token::Uint(U256::from(time_lock))); + return ERR!( + "Payment tx time_lock arg {:?} is invalid, expected {:?}", + decoded[3], + Token::Uint(U256::from(time_lock)) + ); } }, EthCoinType::Erc20(token_addr) => { if tx_from_rpc.to != Some(selfi.swap_contract_address) { - return ERR!("Payment tx {:?} was sent to wrong address, expected {:?}", tx_from_rpc, selfi.swap_contract_address); + return ERR!( + "Payment tx {:?} was sent to wrong address, expected {:?}", + tx_from_rpc, + selfi.swap_contract_address + ); } let function = try_s!(SWAP_CONTRACT.function("erc20Payment")); let decoded = try_s!(function.decode_input(&tx_from_rpc.input.0)); if decoded[1] != Token::Uint(expected_value) { - return ERR!("Payment tx value arg {:?} is invalid, expected {:?}", decoded[1], Token::Uint(expected_value)); + return ERR!( + "Payment tx value arg {:?} is invalid, expected {:?}", + decoded[1], + Token::Uint(expected_value) + ); } if decoded[2] != Token::Address(token_addr) { - return ERR!("Payment tx token_addr arg {:?} is invalid, expected {:?}", decoded[2], Token::Address(token_addr)); + return ERR!( + "Payment tx token_addr arg {:?} is invalid, expected {:?}", + decoded[2], + Token::Address(token_addr) + ); } if decoded[3] != Token::Address(selfi.my_address) { - return ERR!("Payment tx receiver arg {:?} is invalid, expected {:?}", decoded[3], Token::Address(selfi.my_address)); + return ERR!( + "Payment tx receiver arg {:?} is invalid, expected {:?}", + decoded[3], + Token::Address(selfi.my_address) + ); } if decoded[4] != Token::FixedBytes(secret_hash.to_vec()) { - return ERR!("Payment tx secret_hash arg {:?} is invalid, expected {:?}", decoded[4], Token::FixedBytes(secret_hash.to_vec())); + return ERR!( + "Payment tx secret_hash arg {:?} is invalid, expected {:?}", + decoded[4], + Token::FixedBytes(secret_hash.to_vec()) + ); } if decoded[5] != Token::Uint(U256::from(time_lock)) { - return ERR!("Payment tx time_lock arg {:?} is invalid, expected {:?}", decoded[5], Token::Uint(U256::from(time_lock))); + return ERR!( + "Payment tx time_lock arg {:?} is invalid, expected {:?}", + decoded[5], + Token::Uint(U256::from(time_lock)) + ); } }, } @@ -1281,43 +1493,53 @@ impl EthCoin { Box::new(fut.boxed().compat()) } - fn payment_status(&self, token: Token) -> Box + Send + 'static> { + fn payment_status(&self, token: Token) -> Box + Send + 'static> { let function = try_fus!(SWAP_CONTRACT.function("payments")); let data = try_fus!(function.encode_input(&[token])); - Box::new(self.call_request(self.swap_contract_address, None, Some(data.into())).and_then(move |bytes| { - let decoded_tokens = try_s!(function.decode_output(&bytes.0)); - match decoded_tokens[2] { - Token::Uint(state) => Ok(state), - _ => ERR!("Payment status must be uint, got {:?}", decoded_tokens[2]), - } - })) + Box::new( + self.call_request(self.swap_contract_address, None, Some(data.into())) + .and_then(move |bytes| { + let decoded_tokens = try_s!(function.decode_output(&bytes.0)); + match decoded_tokens[2] { + Token::Uint(state) => Ok(state), + _ => ERR!("Payment status must be uint, got {:?}", decoded_tokens[2]), + } + }), + ) } /// Downloads and saves ERC20 transaction history of my_address + #[allow(clippy::cognitive_complexity)] fn process_erc20_history(&self, token_addr: H160, ctx: &MmArc) { let delta = U256::from(10000); let mut success_iteration = 0i32; loop { - if ctx.is_stopping() { break }; + if ctx.is_stopping() { + break; + }; { let coins_ctx = unwrap!(CoinsContext::from_ctx(&ctx)); let coins = unwrap!(coins_ctx.coins.spinlock(77)); if !coins.contains_key(&self.ticker) { ctx.log.log("", &[&"tx_history", &self.ticker], "Loop stopped"); - break + break; }; } let current_block = match self.web3.eth().block_number().wait() { Ok(block) => block, Err(e) => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {} on eth_block_number, retrying", e)); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("Error {} on eth_block_number, retrying", e), + ); thread::sleep(Duration::from_secs(10)); continue; - } + }, }; let mut saved_events = match self.load_saved_erc20_events(&ctx) { @@ -1326,7 +1548,7 @@ impl EthCoin { events: vec![], earliest_block: current_block, latest_block: current_block, - } + }, }; *unwrap!(self.history_sync_state.lock()) = HistorySyncState::InProgress(json!({ "blocks_left": u64::from(saved_events.earliest_block), @@ -1342,36 +1564,50 @@ impl EthCoin { 0.into() }; - let from_events_before_earliest = match self.erc20_transfer_events( - token_addr, - Some(self.my_address), - None, - BlockNumber::Number(before_earliest.into()), - BlockNumber::Number((saved_events.earliest_block - 1).into()), - None, - ).wait() { + let from_events_before_earliest = match self + .erc20_transfer_events( + token_addr, + Some(self.my_address), + None, + BlockNumber::Number(before_earliest.into()), + BlockNumber::Number((saved_events.earliest_block - 1).into()), + None, + ) + .wait() + { Ok(events) => events, Err(e) => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {} on erc20_transfer_events, retrying", e)); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("Error {} on erc20_transfer_events, retrying", e), + ); thread::sleep(Duration::from_secs(10)); continue; - } + }, }; - let to_events_before_earliest = match self.erc20_transfer_events( - token_addr, - None, - Some(self.my_address), - BlockNumber::Number(before_earliest.into()), - BlockNumber::Number((saved_events.earliest_block - 1).into()), - None, - ).wait() { + let to_events_before_earliest = match self + .erc20_transfer_events( + token_addr, + None, + Some(self.my_address), + BlockNumber::Number(before_earliest.into()), + BlockNumber::Number((saved_events.earliest_block - 1).into()), + None, + ) + .wait() + { Ok(events) => events, Err(e) => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {} on erc20_transfer_events, retrying", e)); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("Error {} on erc20_transfer_events, retrying", e), + ); thread::sleep(Duration::from_secs(10)); continue; - } + }, }; let total_length = from_events_before_earliest.len() + to_events_before_earliest.len(); @@ -1389,36 +1625,50 @@ impl EthCoin { } if current_block > saved_events.latest_block { - let from_events_after_latest = match self.erc20_transfer_events( - token_addr, - Some(self.my_address), - None, - BlockNumber::Number((saved_events.latest_block + 1).into()), - BlockNumber::Number(current_block.into()), - None, - ).wait() { + let from_events_after_latest = match self + .erc20_transfer_events( + token_addr, + Some(self.my_address), + None, + BlockNumber::Number((saved_events.latest_block + 1).into()), + BlockNumber::Number(current_block.into()), + None, + ) + .wait() + { Ok(events) => events, Err(e) => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {} on erc20_transfer_events, retrying", e)); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("Error {} on erc20_transfer_events, retrying", e), + ); thread::sleep(Duration::from_secs(10)); continue; - } + }, }; - let to_events_after_latest = match self.erc20_transfer_events( - token_addr, - None, - Some(self.my_address), - BlockNumber::Number((saved_events.latest_block + 1).into()), - BlockNumber::Number(current_block.into()), - None, - ).wait() { + let to_events_after_latest = match self + .erc20_transfer_events( + token_addr, + None, + Some(self.my_address), + BlockNumber::Number((saved_events.latest_block + 1).into()), + BlockNumber::Number(current_block.into()), + None, + ) + .wait() + { Ok(events) => events, Err(e) => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {} on erc20_transfer_events, retrying", e)); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("Error {} on erc20_transfer_events, retrying", e), + ); thread::sleep(Duration::from_secs(10)); continue; - } + }, }; let total_length = from_events_after_latest.len() + to_events_after_latest.len(); @@ -1431,16 +1681,19 @@ impl EthCoin { self.store_erc20_events(&ctx, &saved_events); } - let all_events: HashMap<_, _> = saved_events.events.iter() + let all_events: HashMap<_, _> = saved_events + .events + .iter() .filter(|e| e.block_number.is_some() && e.transaction_hash.is_some() && !e.is_removed()) - .map(|e| (e.transaction_hash.clone().unwrap(), e)).collect(); + .map(|e| (e.transaction_hash.clone().unwrap(), e)) + .collect(); let mut all_events: Vec<_> = all_events.into_iter().map(|(_, log)| log).collect(); all_events.sort_by(|a, b| b.block_number.unwrap().cmp(&a.block_number.unwrap())); for event in all_events { let mut existing_history = self.load_history_from_file(ctx); let internal_id = BytesJson::from(sha256(&json::to_vec(&event).unwrap()).to_vec()); - if existing_history.iter().find(|item| item.internal_id == internal_id).is_some() { + if existing_history.iter().any(|item| item.internal_id == internal_id) { // the transaction already imported continue; }; @@ -1464,12 +1717,25 @@ impl EthCoin { mm_counter!(ctx.metrics, "tx.history.request.count", 1, "coin" => self.ticker.clone(), "client" => "ethereum", "method" => "tx_detail_by_hash"); - let web3_tx = match self.web3.eth().transaction(TransactionId::Hash(event.transaction_hash.unwrap())).wait() { + let web3_tx = match self + .web3 + .eth() + .transaction(TransactionId::Hash(event.transaction_hash.unwrap())) + .wait() + { Ok(tx) => tx, Err(e) => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {} on getting transaction {:?}", e, event.transaction_hash.unwrap())); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!( + "Error {} on getting transaction {:?}", + e, + event.transaction_hash.unwrap() + ), + ); continue; - } + }, }; mm_counter!(ctx.metrics, "tx.history.response.count", 1, @@ -1478,33 +1744,67 @@ impl EthCoin { let web3_tx = match web3_tx { Some(t) => t, None => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("No such transaction {:?}", event.transaction_hash.unwrap())); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("No such transaction {:?}", event.transaction_hash.unwrap()), + ); continue; - } + }, }; - let receipt = match self.web3.eth().transaction_receipt(event.transaction_hash.unwrap()).wait() { + let receipt = match self + .web3 + .eth() + .transaction_receipt(event.transaction_hash.unwrap()) + .wait() + { Ok(r) => r, Err(e) => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {} on getting transaction {:?} receipt", e, event.transaction_hash.unwrap())); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!( + "Error {} on getting transaction {:?} receipt", + e, + event.transaction_hash.unwrap() + ), + ); continue; - } + }, }; let fee_details = match receipt { - Some(r) => Some(unwrap!(EthTxFeeDetails::new(r.gas_used.unwrap_or(0.into()), web3_tx.gas_price, "ETH"))), + Some(r) => Some(unwrap!(EthTxFeeDetails::new( + r.gas_used.unwrap_or_else(|| 0.into()), + web3_tx.gas_price, + "ETH" + ))), None => None, }; let block_number = event.block_number.unwrap(); - let block = match self.web3.eth().block(BlockId::Number(BlockNumber::Number(block_number.into()))).wait() { + let block = match self + .web3 + .eth() + .block(BlockId::Number(BlockNumber::Number(block_number.into()))) + .wait() + { Ok(Some(b)) => b, Ok(None) => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Block {} is None", block_number)); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("Block {} is None", block_number), + ); continue; }, Err(e) => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {} on getting block {} data", e, block_number)); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("Error {} on getting block {} data", e, block_number), + ); continue; - } + }, }; let raw = signed_tx_from_web3_tx(web3_tx).unwrap(); @@ -1525,18 +1825,24 @@ impl EthCoin { }; existing_history.push(details); - existing_history.sort_unstable_by(|a, b| if a.block_height == 0 { - Ordering::Less - } else if b.block_height == 0 { - Ordering::Greater - } else { - b.block_height.cmp(&a.block_height) + existing_history.sort_unstable_by(|a, b| { + if a.block_height == 0 { + Ordering::Less + } else if b.block_height == 0 { + Ordering::Greater + } else { + b.block_height.cmp(&a.block_height) + } }); self.save_history_to_file(&unwrap!(json::to_vec(&existing_history)), &ctx); } if saved_events.earliest_block == 0.into() { if success_iteration == 0 { - ctx.log.log("😅", &[&"tx_history", &("coin", self.ticker.clone().as_str())], "history has been loaded successfully"); + ctx.log.log( + "😅", + &[&"tx_history", &("coin", self.ticker.clone().as_str())], + "history has been loaded successfully", + ); } success_iteration += 1; @@ -1551,6 +1857,7 @@ impl EthCoin { /// Downloads and saves ETH transaction history of my_address, relies on Parity trace_filter API /// https://wiki.parity.io/JSONRPC-trace-module#trace_filter, this requires tracing to be enabled /// in node config. Other ETH clients (Geth, etc.) are `not` supported (yet). + #[allow(clippy::cognitive_complexity)] fn process_eth_history(&self, ctx: &MmArc) { // Artem Pikulin: by playing a bit with Parity mainnet node I've discovered that trace_filter API responds after reasonable time for 1000 blocks. // I've tried to increase the amount to 10000, but request times out somewhere near 2500000 block. @@ -1559,7 +1866,9 @@ impl EthCoin { let mut success_iteration = 0i32; loop { - if ctx.is_stopping() { break }; + if ctx.is_stopping() { + break; + }; { let coins_ctx = unwrap!(CoinsContext::from_ctx(&ctx)); let coins = unwrap!(coins_ctx.coins.spinlock(77)); @@ -1572,10 +1881,14 @@ impl EthCoin { let current_block = match self.web3.eth().block_number().wait() { Ok(block) => block, Err(e) => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {} on eth_block_number, retrying", e)); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("Error {} on eth_block_number, retrying", e), + ); thread::sleep(Duration::from_secs(10)); continue; - } + }, }; let mut saved_traces = match self.load_saved_traces(&ctx) { @@ -1584,7 +1897,7 @@ impl EthCoin { traces: vec![], earliest_block: current_block, latest_block: current_block, - } + }, }; *unwrap!(self.history_sync_state.lock()) = HistorySyncState::InProgress(json!({ "blocks_left": u64::from(saved_traces.earliest_block), @@ -1601,34 +1914,48 @@ impl EthCoin { 0.into() }; - let from_traces_before_earliest = match self.eth_traces( - vec![self.my_address], - vec![], - BlockNumber::Number(before_earliest.into()), - BlockNumber::Number((saved_traces.earliest_block).into()), - None, - ).wait() { + let from_traces_before_earliest = match self + .eth_traces( + vec![self.my_address], + vec![], + BlockNumber::Number(before_earliest.into()), + BlockNumber::Number((saved_traces.earliest_block).into()), + None, + ) + .wait() + { Ok(traces) => traces, Err(e) => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {} on eth_traces, retrying", e)); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("Error {} on eth_traces, retrying", e), + ); thread::sleep(Duration::from_secs(10)); continue; - } + }, }; - let to_traces_before_earliest = match self.eth_traces( - vec![], - vec![self.my_address], - BlockNumber::Number(before_earliest.into()), - BlockNumber::Number((saved_traces.earliest_block).into()), - None, - ).wait() { + let to_traces_before_earliest = match self + .eth_traces( + vec![], + vec![self.my_address], + BlockNumber::Number(before_earliest.into()), + BlockNumber::Number((saved_traces.earliest_block).into()), + None, + ) + .wait() + { Ok(traces) => traces, Err(e) => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {} on eth_traces, retrying", e)); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("Error {} on eth_traces, retrying", e), + ); thread::sleep(Duration::from_secs(10)); continue; - } + }, }; let total_length = from_traces_before_earliest.len() + to_traces_before_earliest.len(); @@ -1647,34 +1974,48 @@ impl EthCoin { } if current_block > saved_traces.latest_block { - let from_traces_after_latest = match self.eth_traces( - vec![self.my_address], - vec![], - BlockNumber::Number((saved_traces.latest_block + 1).into()), - BlockNumber::Number(current_block.into()), - None, - ).wait() { + let from_traces_after_latest = match self + .eth_traces( + vec![self.my_address], + vec![], + BlockNumber::Number((saved_traces.latest_block + 1).into()), + BlockNumber::Number(current_block.into()), + None, + ) + .wait() + { Ok(traces) => traces, Err(e) => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {} on eth_traces, retrying", e)); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("Error {} on eth_traces, retrying", e), + ); thread::sleep(Duration::from_secs(10)); continue; - } + }, }; - let to_traces_after_latest = match self.eth_traces( - vec![], - vec![self.my_address], - BlockNumber::Number((saved_traces.latest_block + 1).into()), - BlockNumber::Number(current_block.into()), - None, - ).wait() { + let to_traces_after_latest = match self + .eth_traces( + vec![], + vec![self.my_address], + BlockNumber::Number((saved_traces.latest_block + 1).into()), + BlockNumber::Number(current_block.into()), + None, + ) + .wait() + { Ok(traces) => traces, Err(e) => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {} on eth_traces, retrying", e)); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("Error {} on eth_traces, retrying", e), + ); thread::sleep(Duration::from_secs(10)); continue; - } + }, }; let total_length = from_traces_after_latest.len() + to_traces_after_latest.len(); @@ -1704,32 +2045,66 @@ impl EthCoin { mm_counter!(ctx.metrics, "tx.history.request.count", 1, "coin" => self.ticker.clone(), "method" => "tx_detail_by_hash"); - let web3_tx = match self.web3.eth().transaction(TransactionId::Hash(trace.transaction_hash.unwrap())).wait() { + let web3_tx = match self + .web3 + .eth() + .transaction(TransactionId::Hash(trace.transaction_hash.unwrap())) + .wait() + { Ok(tx) => tx, Err(e) => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {} on getting transaction {:?}", e, trace.transaction_hash.unwrap())); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!( + "Error {} on getting transaction {:?}", + e, + trace.transaction_hash.unwrap() + ), + ); continue; - } + }, }; let web3_tx = match web3_tx { Some(t) => t, None => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("No such transaction {:?}", trace.transaction_hash.unwrap())); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("No such transaction {:?}", trace.transaction_hash.unwrap()), + ); continue; - } + }, }; mm_counter!(ctx.metrics, "tx.history.response.count", 1, "coin" => self.ticker.clone(), "method" => "tx_detail_by_hash"); - let receipt = match self.web3.eth().transaction_receipt(trace.transaction_hash.unwrap()).wait() { + let receipt = match self + .web3 + .eth() + .transaction_receipt(trace.transaction_hash.unwrap()) + .wait() + { Ok(r) => r, Err(e) => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {} on getting transaction {:?} receipt", e, trace.transaction_hash.unwrap())); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!( + "Error {} on getting transaction {:?} receipt", + e, + trace.transaction_hash.unwrap() + ), + ); continue; - } + }, }; let fee_details: Option = match receipt { - Some(r) => Some(unwrap!(EthTxFeeDetails::new(r.gas_used.unwrap_or(0.into()), web3_tx.gas_price, "ETH"))), + Some(r) => Some(unwrap!(EthTxFeeDetails::new( + r.gas_used.unwrap_or_else(|| 0.into()), + web3_tx.gas_price, + "ETH" + ))), None => None, }; @@ -1755,12 +2130,21 @@ impl EthCoin { } let raw = signed_tx_from_web3_tx(web3_tx).unwrap(); - let block = match self.web3.eth().block(BlockId::Number(BlockNumber::Number(trace.block_number))).wait() { + let block = match self + .web3 + .eth() + .block(BlockId::Number(BlockNumber::Number(trace.block_number))) + .wait() + { Ok(b) => unwrap!(b), Err(e) => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {} on getting block {} data", e, trace.block_number)); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("Error {} on getting block {} data", e, trace.block_number), + ); continue; - } + }, }; let details = TransactionDetails { @@ -1780,18 +2164,24 @@ impl EthCoin { }; existing_history.push(details); - existing_history.sort_unstable_by(|a, b| if a.block_height == 0 { - Ordering::Less - } else if b.block_height == 0 { - Ordering::Greater - } else { - b.block_height.cmp(&a.block_height) + existing_history.sort_unstable_by(|a, b| { + if a.block_height == 0 { + Ordering::Less + } else if b.block_height == 0 { + Ordering::Greater + } else { + b.block_height.cmp(&a.block_height) + } }); self.save_history_to_file(&unwrap!(json::to_vec(&existing_history)), &ctx); } if saved_traces.earliest_block == 0.into() { if success_iteration == 0 { - ctx.log.log("😅", &[&"tx_history", &("coin", self.ticker.clone().as_str())], "history has been loaded successfully"); + ctx.log.log( + "😅", + &[&"tx_history", &("coin", self.ticker.clone().as_str())], + "history has been loaded successfully", + ); } success_iteration += 1; @@ -1832,25 +2222,27 @@ impl EthTxFeeDetails { impl MmCoin for EthCoin { fn is_asset_chain(&self) -> bool { false } - fn can_i_spend_other_payment(&self) -> Box + Send> { + fn can_i_spend_other_payment(&self) -> Box + Send> { Box::new(self.eth_balance().and_then(move |eth_balance| { let eth_balance_f64: f64 = try_s!(display_u256_with_decimal_point(eth_balance, 18).parse()); if eth_balance_f64 < 0.0002 { - ERR!("Base coin balance {} is too low to cover gas fee, required {}", eth_balance_f64, 0.0002) + ERR!( + "Base coin balance {} is too low to cover gas fee, required {}", + eth_balance_f64, + 0.0002 + ) } else { Ok(()) } })) } - fn withdraw(&self, req: WithdrawRequest) -> Box + Send> { + fn withdraw(&self, req: WithdrawRequest) -> Box + Send> { let ctx = try_fus!(MmArc::from_weak(&self.ctx).ok_or("!ctx")); Box::new(Box::pin(withdraw_impl(ctx, self.clone(), req)).compat()) } - fn decimals(&self) -> u8 { - self.decimals - } + fn decimals(&self) -> u8 { self.decimals } fn process_history_loop(&self, ctx: MmArc) { match self.coin_type { @@ -1859,7 +2251,7 @@ impl MmCoin for EthCoin { } } - fn tx_details_by_hash(&self, hash: &[u8]) -> Box + Send> { + fn tx_details_by_hash(&self, hash: &[u8]) -> Box + Send> { let selfi = self.clone(); let hash = H256::from(hash); @@ -1891,24 +2283,7 @@ impl MmCoin for EthCoin { from: vec![checksum_address(&format!("{:#02x}", tx.from))], to, coin: selfi.ticker.clone(), - block_height: tx.block_number.unwrap_or(U256::from(0)).into(), - tx_hex: rlp::encode(&raw).into(), - tx_hash: tx.hash.0.to_vec().into(), - received_by_me, - spent_by_me, - total_amount, - fee_details: None, - internal_id: vec![0].into(), - timestamp: now_ms() / 1000, - }) - }, - EthCoinType::Erc20(_addr) => { - Ok(TransactionDetails { - my_balance_change: &received_by_me - &spent_by_me, - from: vec![checksum_address(&format!("{:#02x}", tx.from))], - to, - coin: selfi.ticker.clone(), - block_height: tx.block_number.unwrap_or(U256::from(0)).into(), + block_height: tx.block_number.unwrap_or_else(|| U256::from(0)).into(), tx_hex: rlp::encode(&raw).into(), tx_hash: tx.hash.0.to_vec().into(), received_by_me, @@ -1919,37 +2294,49 @@ impl MmCoin for EthCoin { timestamp: now_ms() / 1000, }) }, + EthCoinType::Erc20(_addr) => Ok(TransactionDetails { + my_balance_change: &received_by_me - &spent_by_me, + from: vec![checksum_address(&format!("{:#02x}", tx.from))], + to, + coin: selfi.ticker.clone(), + block_height: tx.block_number.unwrap_or_else(|| U256::from(0)).into(), + tx_hex: rlp::encode(&raw).into(), + tx_hash: tx.hash.0.to_vec().into(), + received_by_me, + spent_by_me, + total_amount, + fee_details: None, + internal_id: vec![0].into(), + timestamp: now_ms() / 1000, + }), } }; Box::new(fut.boxed().compat()) } - fn history_sync_status(&self) -> HistorySyncState { - unwrap!(self.history_sync_state.lock()).clone() - } + fn history_sync_status(&self) -> HistorySyncState { unwrap!(self.history_sync_state.lock()).clone() } - fn get_trade_fee(&self) -> Box + Send> { + fn get_trade_fee(&self) -> Box + Send> { Box::new(self.get_gas_price().and_then(|gas_price| { - let fee = gas_price * U256::from(150000); + let fee = gas_price * U256::from(150_000); Ok(TradeFee { coin: "ETH".into(), - amount: try_s!(u256_to_big_decimal(fee, 18)).into() + amount: try_s!(u256_to_big_decimal(fee, 18)).into(), }) })) } - fn required_confirmations(&self) -> u64 { - self.required_confirmations.load(AtomicOrderding::Relaxed) - } + fn required_confirmations(&self) -> u64 { self.required_confirmations.load(AtomicOrderding::Relaxed) } fn requires_notarization(&self) -> bool { false } fn set_required_confirmations(&self, confirmations: u64) { - self.required_confirmations.store(confirmations, AtomicOrderding::Relaxed); + self.required_confirmations + .store(confirmations, AtomicOrderding::Relaxed); } fn set_requires_notarization(&self, _requires_nota: bool) { - log!("Warning: set_requires_notarization doesn't take any effect on ETH/ERC20 coins"); + log!("Warning: set_requires_notarization doesn't take any effect on ETH/ERC20 coins"); } } @@ -2002,7 +2389,10 @@ impl Transaction for SignedEthTx { let tokens = try_s!(function.decode_input(&self.data)); match &tokens[2] { Token::FixedBytes(secret) => Ok(secret.to_vec()), - _ => ERR!("Expected secret to be fixed bytes, decoded function data is {:?}", tokens), + _ => ERR!( + "Expected secret to be fixed bytes, decoded function data is {:?}", + tokens + ), } } @@ -2024,8 +2414,8 @@ fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result Action::Call(addr), None => Action::Create, - } - } + }, + }, }; Ok(try_s!(SignedEthTx::new(unverified))) @@ -2049,7 +2439,7 @@ struct GasStationData { fastest_wait: f64, #[serde(rename = "safeLow")] safe_low: f64, - average: f64 + average: f64, } impl GasStationData { @@ -2058,7 +2448,7 @@ impl GasStationData { U256::from(self.average as u64) * U256::exp10(8) } - fn get_gas_price(uri: &str) -> Box + Send> { + fn get_gas_price(uri: &str) -> Box + Send> { Box::new(slurp_url(uri).and_then(|res| -> Result { if res.0 != StatusCode::OK { return ERR!("Gas price request failed with status code {}", res.0); @@ -2079,10 +2469,13 @@ async fn get_token_decimals(web3: &Web3, token_addr: Address) -> gas: None, gas_price: None, value: Some(0.into()), - data: Some(data.into()) + data: Some(data.into()), }; - let f = web3.eth().call(request, Some(BlockNumber::Latest)).map_err(|e| ERRL!("{}", e)); + let f = web3 + .eth() + .call(request, Some(BlockNumber::Latest)) + .map_err(|e| ERRL!("{}", e)); let res = try_s!(f.compat().await); let tokens = try_s!(function.decode_output(&res.0)); let decimals: u64 = match tokens[0] { @@ -2105,14 +2498,9 @@ fn addr_from_str(addr_str: &str) -> Result { Ok(addr) } -fn rpc_event_handlers_for_eth_transport( - ctx: &MmArc, - ticker: String) - -> Vec { +fn rpc_event_handlers_for_eth_transport(ctx: &MmArc, ticker: String) -> Vec { let metrics = ctx.metrics.weak(); - vec![ - CoinTransportMetrics::new(metrics, ticker, RpcClientType::Ethereum).into_shared(), - ] + vec![CoinTransportMetrics::new(metrics, ticker, RpcClientType::Ethereum).into_shared()] } pub async fn eth_coin_from_conf_and_request( @@ -2140,21 +2528,22 @@ pub async fn eth_coin_from_conf_and_request( let mut web3_instances = vec![]; let event_handlers = rpc_event_handlers_for_eth_transport(ctx, ticker.to_string()); for url in urls.iter() { - let transport = try_s!(Web3Transport::with_event_handlers(vec![url.clone()], event_handlers.clone())); + let transport = try_s!(Web3Transport::with_event_handlers( + vec![url.clone()], + event_handlers.clone() + )); let web3 = Web3::new(transport); let version = match web3.web3().client_version().compat().await { Ok(v) => v, Err(e) => { log!("Couldn't get client version for url " (url) ", " (e)); continue; - } + }, }; - web3_instances.push( - Web3Instance { - web3, - is_parity: version.contains("Parity") || version.contains("parity") - } - ) + web3_instances.push(Web3Instance { + web3, + is_parity: version.contains("Parity") || version.contains("parity"), + }) } if web3_instances.is_empty() { @@ -2177,9 +2566,10 @@ pub async fn eth_coin_from_conf_and_request( }; // param from request should override the config - let required_confirmations = req["required_confirmations"].as_u64().unwrap_or( - conf["required_confirmations"].as_u64().unwrap_or(1) - ).into(); + let required_confirmations = req["required_confirmations"] + .as_u64() + .unwrap_or_else(|| conf["required_confirmations"].as_u64().unwrap_or(1)) + .into(); if req["requires_notarization"].as_bool().is_some() { log!("Warning: requires_notarization doesn't take any effect on ETH/ERC20 coins"); @@ -2240,33 +2630,41 @@ fn checksum_address(addr: &str) -> String { /// Checks that input is valid mixed-case checksum form address /// The input must be 0x prefixed hex string -fn is_valid_checksum_addr(addr: &str) -> bool { - addr == &checksum_address(addr) -} +fn is_valid_checksum_addr(addr: &str) -> bool { addr == checksum_address(addr) } /// Requests the nonce from all available nodes and checks that returned results equal. /// Nodes might need some time to sync and there can be other coins that use same nodes in different order. /// We need to be sure that nonce is updated on all of them before and after transaction is sent. -#[mockable] -fn get_addr_nonce(addr: Address, web3s: &Vec) -> Box + Send> { - let web3s = web3s.clone(); +#[cfg_attr(test, mockable)] +fn get_addr_nonce(addr: Address, web3s: Vec) -> Box + Send> { let fut = async move { let mut errors: u32 = 0; loop { - let futures: Vec<_> = web3s.iter().map(|web3| if web3.is_parity { - web3.web3.eth().parity_next_nonce(addr).compat() - } else { - web3.web3.eth().transaction_count(addr, Some(BlockNumber::Pending)).compat() - } - ).collect(); - - let nonces: Vec<_> = join_all(futures).await.into_iter().filter_map(|nonce_res| match nonce_res { - Ok(n) => Some(n), - Err(e) => { - log!("Error " (e) " when getting nonce for addr " [addr]); - None - } - }).collect(); + let futures: Vec<_> = web3s + .iter() + .map(|web3| { + if web3.is_parity { + web3.web3.eth().parity_next_nonce(addr).compat() + } else { + web3.web3 + .eth() + .transaction_count(addr, Some(BlockNumber::Pending)) + .compat() + } + }) + .collect(); + + let nonces: Vec<_> = join_all(futures) + .await + .into_iter() + .filter_map(|nonce_res| match nonce_res { + Ok(n) => Some(n), + Err(e) => { + log!("Error " (e) " when getting nonce for addr " [addr]); + None + }, + }) + .collect(); if nonces.is_empty() { // all requests errored errors += 1; diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index ce26b4db71..cccc8b4ccb 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -1,8 +1,8 @@ +use super::*; use common::block_on; -use common::mm_ctx::{MmArc, MmCtxBuilder}; use common::for_tests::wait_for_log; +use common::mm_ctx::{MmArc, MmCtxBuilder}; use futures::future::join_all; -use super::*; use mocktopus::mocking::*; fn check_sum(addr: &str, expected: &str) { @@ -11,7 +11,10 @@ fn check_sum(addr: &str, expected: &str) { } fn eth_coin_for_test(coin_type: EthCoinType, urls: Vec) -> (MmArc, EthCoin) { - let key_pair = KeyPair::from_secret_slice(&hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap()).unwrap(); + let key_pair = KeyPair::from_secret_slice( + &hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap(), + ) + .unwrap(); let transport = Web3Transport::new(urls).unwrap(); let web3 = Web3::new(transport); let ctx = MmCtxBuilder::new().into_mm_arc(); @@ -25,7 +28,10 @@ fn eth_coin_for_test(coin_type: EthCoinType, urls: Vec) -> (MmArc, EthCo key_pair, swap_contract_address: Address::from("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94"), ticker: "ETH".into(), - web3_instances: vec![Web3Instance {web3: web3.clone(), is_parity: true}], + web3_instances: vec![Web3Instance { + web3: web3.clone(), + is_parity: true, + }], web3, ctx: ctx.weak(), required_confirmations: 1.into(), @@ -36,15 +42,42 @@ fn eth_coin_for_test(coin_type: EthCoinType, urls: Vec) -> (MmArc, EthCo #[test] /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md#test-cases fn test_check_sum_address() { - check_sum("0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359", "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359"); - check_sum("0x52908400098527886e0f7030069857d2e4169ee7", "0x52908400098527886E0F7030069857D2E4169EE7"); - check_sum("0x8617e340b3d01fa5f11f306f4090fd50e238070d", "0x8617E340B3D01FA5F11F306F4090FD50E238070D"); - check_sum("0xde709f2102306220921060314715629080e2fb77", "0xde709f2102306220921060314715629080e2fb77"); - check_sum("0x27b1fdb04752bbc536007a920d24acb045561c26", "0x27b1fdb04752bbc536007a920d24acb045561c26"); - check_sum("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"); - check_sum("0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359"); - check_sum("0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB"); - check_sum("0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb"); + check_sum( + "0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359", + "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", + ); + check_sum( + "0x52908400098527886e0f7030069857d2e4169ee7", + "0x52908400098527886E0F7030069857D2E4169EE7", + ); + check_sum( + "0x8617e340b3d01fa5f11f306f4090fd50e238070d", + "0x8617E340B3D01FA5F11F306F4090FD50E238070D", + ); + check_sum( + "0xde709f2102306220921060314715629080e2fb77", + "0xde709f2102306220921060314715629080e2fb77", + ); + check_sum( + "0x27b1fdb04752bbc536007a920d24acb045561c26", + "0x27b1fdb04752bbc536007a920d24acb045561c26", + ); + check_sum( + "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", + "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", + ); + check_sum( + "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", + "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", + ); + check_sum( + "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", + "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", + ); + check_sum( + "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", + "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", + ); } #[test] @@ -137,7 +170,10 @@ fn test_wei_from_big_decimal() { #[ignore] /// temporary ignore, will refactor later to use dev chain and properly check transaction statuses fn send_and_refund_erc20_payment() { - let key_pair = KeyPair::from_secret_slice(&hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap()).unwrap(); + let key_pair = KeyPair::from_secret_slice( + &hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap(), + ) + .unwrap(); let transport = Web3Transport::new(vec!["http://195.201.0.6:8545".into()]).unwrap(); let web3 = Web3::new(transport); let ctx = MmCtxBuilder::new().into_mm_arc(); @@ -147,7 +183,10 @@ fn send_and_refund_erc20_payment() { my_address: key_pair.address(), key_pair, swap_contract_address: Address::from("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94"), - web3_instances: vec![Web3Instance {web3: web3.clone(), is_parity: true}], + web3_instances: vec![Web3Instance { + web3: web3.clone(), + is_parity: true, + }], web3, decimals: 18, gas_station_url: None, @@ -156,23 +195,33 @@ fn send_and_refund_erc20_payment() { required_confirmations: 1.into(), })); - let payment = coin.send_maker_payment( - (now_ms() / 1000) as u32 - 200, - &unwrap!(hex::decode("03bc2c7ba671bae4a6fc835244c9762b41647b9827d4780a89a949b984a8ddcc06")), - &[1; 20], - "0.001".parse().unwrap(), - ).wait().unwrap(); + let payment = coin + .send_maker_payment( + (now_ms() / 1000) as u32 - 200, + &unwrap!(hex::decode( + "03bc2c7ba671bae4a6fc835244c9762b41647b9827d4780a89a949b984a8ddcc06" + )), + &[1; 20], + "0.001".parse().unwrap(), + ) + .wait() + .unwrap(); log!([payment]); thread::sleep(Duration::from_secs(60)); - let refund = coin.send_maker_refunds_payment( - &payment.tx_hex(), - (now_ms() / 1000) as u32 - 200, - &unwrap!(hex::decode("03bc2c7ba671bae4a6fc835244c9762b41647b9827d4780a89a949b984a8ddcc06")), - &[1; 20], - ).wait().unwrap(); + let refund = coin + .send_maker_refunds_payment( + &payment.tx_hex(), + (now_ms() / 1000) as u32 - 200, + &unwrap!(hex::decode( + "03bc2c7ba671bae4a6fc835244c9762b41647b9827d4780a89a949b984a8ddcc06" + )), + &[1; 20], + ) + .wait() + .unwrap(); log!([refund]); } @@ -181,7 +230,10 @@ fn send_and_refund_erc20_payment() { #[ignore] /// temporary ignore, will refactor later to use dev chain and properly check transaction statuses fn send_and_refund_eth_payment() { - let key_pair = KeyPair::from_secret_slice(&hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap()).unwrap(); + let key_pair = KeyPair::from_secret_slice( + &hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap(), + ) + .unwrap(); let transport = Web3Transport::new(vec!["http://195.201.0.6:8545".into()]).unwrap(); let web3 = Web3::new(transport); let ctx = MmCtxBuilder::new().into_mm_arc(); @@ -191,7 +243,10 @@ fn send_and_refund_eth_payment() { my_address: key_pair.address(), key_pair, swap_contract_address: Address::from("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94"), - web3_instances: vec![Web3Instance {web3: web3.clone(), is_parity: true}], + web3_instances: vec![Web3Instance { + web3: web3.clone(), + is_parity: true, + }], web3, decimals: 18, gas_station_url: None, @@ -200,23 +255,33 @@ fn send_and_refund_eth_payment() { required_confirmations: 1.into(), })); - let payment = coin.send_maker_payment( - (now_ms() / 1000) as u32 - 200, - &unwrap!(hex::decode("03bc2c7ba671bae4a6fc835244c9762b41647b9827d4780a89a949b984a8ddcc06")), - &[1; 20], - "0.001".parse().unwrap(), - ).wait().unwrap(); + let payment = coin + .send_maker_payment( + (now_ms() / 1000) as u32 - 200, + &unwrap!(hex::decode( + "03bc2c7ba671bae4a6fc835244c9762b41647b9827d4780a89a949b984a8ddcc06" + )), + &[1; 20], + "0.001".parse().unwrap(), + ) + .wait() + .unwrap(); log!([payment]); thread::sleep(Duration::from_secs(60)); - let refund = coin.send_maker_refunds_payment( - &payment.tx_hex(), - (now_ms() / 1000) as u32 - 200, - &unwrap!(hex::decode("03bc2c7ba671bae4a6fc835244c9762b41647b9827d4780a89a949b984a8ddcc06")), - &[1; 20], - ).wait().unwrap(); + let refund = coin + .send_maker_refunds_payment( + &payment.tx_hex(), + (now_ms() / 1000) as u32 - 200, + &unwrap!(hex::decode( + "03bc2c7ba671bae4a6fc835244c9762b41647b9827d4780a89a949b984a8ddcc06" + )), + &[1; 20], + ) + .wait() + .unwrap(); log!([refund]); } @@ -224,8 +289,14 @@ fn send_and_refund_eth_payment() { #[test] #[ignore] fn test_nonce_several_urls() { - let key_pair = KeyPair::from_secret_slice(&hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap()).unwrap(); - let infura_transport = Web3Transport::new(vec!["https://ropsten.infura.io/v3/c01c1b4cf66642528547624e1d6d9d6b".into()]).unwrap(); + let key_pair = KeyPair::from_secret_slice( + &hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap(), + ) + .unwrap(); + let infura_transport = Web3Transport::new(vec![ + "https://ropsten.infura.io/v3/c01c1b4cf66642528547624e1d6d9d6b".into() + ]) + .unwrap(); let linkpool_transport = Web3Transport::new(vec!["https://ropsten-rpc.linkpool.io".into()]).unwrap(); // get nonce must succeed if some nodes are down at the moment for some reason let failing_transport = Web3Transport::new(vec!["http://195.201.0.6:8989".into()]).unwrap(); @@ -242,9 +313,18 @@ fn test_nonce_several_urls() { key_pair, swap_contract_address: Address::from("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94"), web3_instances: vec![ - Web3Instance { web3: web3_infura.clone(), is_parity: false }, - Web3Instance { web3: web3_linkpool, is_parity: false }, - Web3Instance { web3: web3_failing, is_parity: false }, + Web3Instance { + web3: web3_infura.clone(), + is_parity: false, + }, + Web3Instance { + web3: web3_linkpool, + is_parity: false, + }, + Web3Instance { + web3: web3_failing, + is_parity: false, + }, ], web3: web3_infura, decimals: 18, @@ -254,12 +334,14 @@ fn test_nonce_several_urls() { required_confirmations: 1.into(), })); - log!("My address " [coin.my_address]); + log!("My address "[coin.my_address]); log!("before payment"); let payment = coin.send_to_address(coin.my_address, 200000000.into()).wait().unwrap(); log!([payment]); - let new_nonce = get_addr_nonce(coin.my_address, &coin.web3_instances).wait().unwrap(); + let new_nonce = get_addr_nonce(coin.my_address, coin.web3_instances.clone()) + .wait() + .unwrap(); log!([new_nonce]); } @@ -267,7 +349,10 @@ fn test_nonce_several_urls() { fn test_wait_for_payment_spend_timeout() { EthCoinImpl::spend_events.mock_safe(|_, _| MockResult::Return(Box::new(futures01::future::ok(vec![])))); - let key_pair = KeyPair::from_secret_slice(&hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap()).unwrap(); + let key_pair = KeyPair::from_secret_slice( + &hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap(), + ) + .unwrap(); let transport = Web3Transport::new(vec!["http://195.201.0.6:8555".into()]).unwrap(); let web3 = Web3::new(transport); let ctx = MmCtxBuilder::new().into_mm_arc(); @@ -281,7 +366,10 @@ fn test_wait_for_payment_spend_timeout() { key_pair, swap_contract_address: Address::from("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94"), ticker: "ETH".into(), - web3_instances: vec![Web3Instance {web3: web3.clone(), is_parity: true}], + web3_instances: vec![Web3Instance { + web3: web3.clone(), + is_parity: true, + }], web3, ctx: ctx.weak(), required_confirmations: 1.into(), @@ -291,15 +379,35 @@ fn test_wait_for_payment_spend_timeout() { let wait_until = (now_ms() / 1000) - 1; let from_block = 1; // raw transaction bytes of https://etherscan.io/tx/0x0869be3e5d4456a29d488a533ad6c118620fef450f36778aecf31d356ff8b41f - let tx_bytes = [248, 240, 3, 133, 1, 42, 5, 242, 0, 131, 2, 73, 240, 148, 133, 0, 175, 192, 188, 82, 20, 114, 128, 130, 22, 51, 38, 194, 255, 12, 115, 244, 168, 113, 135, 110, 205, 245, 24, 127, 34, 254, 184, 132, 21, 44, 243, 175, 73, 33, 143, 82, 117, 16, 110, 27, 133, 82, 200, 114, 233, 42, 140, 198, 35, 21, 201, 249, 187, 180, 20, 46, 148, 40, 9, 228, 193, 130, 71, 199, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 152, 41, 132, 9, 201, 73, 19, 94, 237, 137, 35, 61, 4, 194, 207, 239, 152, 75, 175, 245, 157, 174, 10, 214, 161, 207, 67, 70, 87, 246, 231, 212, 47, 216, 119, 68, 237, 197, 125, 141, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 93, 72, 125, 102, 28, 159, 180, 237, 198, 97, 87, 80, 82, 200, 104, 40, 245, 221, 7, 28, 122, 104, 91, 99, 1, 159, 140, 25, 131, 101, 74, 87, 50, 168, 146, 187, 90, 160, 51, 1, 123, 247, 6, 108, 165, 181, 188, 40, 56, 47, 211, 229, 221, 73, 5, 15, 89, 81, 117, 225, 216, 108, 98, 226, 119, 232, 94, 184, 42, 106]; - - assert!(coin.wait_for_tx_spend(&tx_bytes, wait_until, from_block).wait().is_err()); + let tx_bytes = [ + 248, 240, 3, 133, 1, 42, 5, 242, 0, 131, 2, 73, 240, 148, 133, 0, 175, 192, 188, 82, 20, 114, 128, 130, 22, 51, + 38, 194, 255, 12, 115, 244, 168, 113, 135, 110, 205, 245, 24, 127, 34, 254, 184, 132, 21, 44, 243, 175, 73, 33, + 143, 82, 117, 16, 110, 27, 133, 82, 200, 114, 233, 42, 140, 198, 35, 21, 201, 249, 187, 180, 20, 46, 148, 40, + 9, 228, 193, 130, 71, 199, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 152, 41, 132, 9, 201, 73, 19, 94, 237, 137, 35, + 61, 4, 194, 207, 239, 152, 75, 175, 245, 157, 174, 10, 214, 161, 207, 67, 70, 87, 246, 231, 212, 47, 216, 119, + 68, 237, 197, 125, 141, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 93, 72, 125, 102, 28, 159, 180, 237, 198, 97, 87, 80, 82, 200, 104, 40, 245, + 221, 7, 28, 122, 104, 91, 99, 1, 159, 140, 25, 131, 101, 74, 87, 50, 168, 146, 187, 90, 160, 51, 1, 123, 247, + 6, 108, 165, 181, 188, 40, 56, 47, 211, 229, 221, 73, 5, 15, 89, 81, 117, 225, 216, 108, 98, 226, 119, 232, 94, + 184, 42, 106, + ]; + + assert!(coin + .wait_for_tx_spend(&tx_bytes, wait_until, from_block) + .wait() + .is_err()); } #[test] fn test_search_for_swap_tx_spend_was_spent() { - let key_pair = KeyPair::from_secret_slice(&hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap()).unwrap(); - let transport = Web3Transport::new(vec!["https://ropsten.infura.io/v3/c01c1b4cf66642528547624e1d6d9d6b".into()]).unwrap(); + let key_pair = KeyPair::from_secret_slice( + &hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap(), + ) + .unwrap(); + let transport = Web3Transport::new(vec![ + "https://ropsten.infura.io/v3/c01c1b4cf66642528547624e1d6d9d6b".into() + ]) + .unwrap(); let web3 = Web3::new(transport); let ctx = MmCtxBuilder::new().into_mm_arc(); @@ -312,29 +420,57 @@ fn test_search_for_swap_tx_spend_was_spent() { key_pair, swap_contract_address: Address::from("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94"), ticker: "ETH".into(), - web3_instances: vec![Web3Instance {web3: web3.clone(), is_parity: true}], + web3_instances: vec![Web3Instance { + web3: web3.clone(), + is_parity: true, + }], web3, ctx: ctx.weak(), required_confirmations: 1.into(), })); // raw transaction bytes of https://ropsten.etherscan.io/tx/0xb1c987e2ac79581bb8718267b5cb49a18274890494299239d1d0dfdb58d6d76a - let payment_tx = [248, 240, 52, 132, 119, 53, 148, 0, 131, 2, 73, 240, 148, 123, 193, 187, 221, 106, 10, 114, 47, 201, 191, 252, 73, 201, 33, 182, 133, 236, 184, 75, 148, 135, 71, 13, 228, 223, 130, 0, 0, 184, 132, 21, 44, 243, 175, 188, 96, 248, 252, 165, 132, 81, 30, 243, 34, 85, 165, 46, 224, 176, 90, 137, 30, 19, 123, 224, 67, 83, 53, 74, 57, 148, 140, 95, 45, 70, 147, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 117, 244, 28, 175, 51, 95, 91, 184, 141, 201, 45, 116, 26, 102, 210, 119, 151, 124, 143, 52, 215, 128, 89, 116, 30, 25, 35, 128, 122, 186, 177, 228, 149, 250, 55, 53, 62, 196, 51, 72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 93, 56, 62, 80, 28, 160, 65, 22, 195, 212, 184, 202, 226, 151, 224, 111, 174, 31, 160, 219, 39, 69, 137, 37, 8, 127, 177, 4, 104, 248, 27, 41, 245, 176, 131, 188, 215, 136, 160, 91, 134, 199, 67, 1, 58, 57, 103, 23, 215, 176, 64, 124, 1, 44, 88, 161, 200, 160, 64, 110, 13, 145, 127, 180, 27, 171, 131, 253, 90, 48, 147]; + let payment_tx = [ + 248, 240, 52, 132, 119, 53, 148, 0, 131, 2, 73, 240, 148, 123, 193, 187, 221, 106, 10, 114, 47, 201, 191, 252, + 73, 201, 33, 182, 133, 236, 184, 75, 148, 135, 71, 13, 228, 223, 130, 0, 0, 184, 132, 21, 44, 243, 175, 188, + 96, 248, 252, 165, 132, 81, 30, 243, 34, 85, 165, 46, 224, 176, 90, 137, 30, 19, 123, 224, 67, 83, 53, 74, 57, + 148, 140, 95, 45, 70, 147, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 117, 244, 28, 175, 51, 95, 91, 184, 141, 201, + 45, 116, 26, 102, 210, 119, 151, 124, 143, 52, 215, 128, 89, 116, 30, 25, 35, 128, 122, 186, 177, 228, 149, + 250, 55, 53, 62, 196, 51, 72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 93, 56, 62, 80, 28, 160, 65, 22, 195, 212, 184, 202, 226, 151, 224, 111, + 174, 31, 160, 219, 39, 69, 137, 37, 8, 127, 177, 4, 104, 248, 27, 41, 245, 176, 131, 188, 215, 136, 160, 91, + 134, 199, 67, 1, 58, 57, 103, 23, 215, 176, 64, 124, 1, 44, 88, 161, 200, 160, 64, 110, 13, 145, 127, 180, 27, + 171, 131, 253, 90, 48, 147, + ]; // raw transaction bytes of https://ropsten.etherscan.io/tx/0xcb7c14d3ff309996d582400369393b6fa42314c52245115d4a3f77f072c36da9 - let spend_tx = [249, 1, 9, 37, 132, 119, 53, 148, 0, 131, 2, 73, 240, 148, 123, 193, 187, 221, 106, 10, 114, 47, 201, 191, 252, 73, 201, 33, 182, 133, 236, 184, 75, 148, 128, 184, 164, 2, 237, 41, 43, 188, 96, 248, 252, 165, 132, 81, 30, 243, 34, 85, 165, 46, 224, 176, 90, 137, 30, 19, 123, 224, 67, 83, 53, 74, 57, 148, 140, 95, 45, 70, 147, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 13, 228, 223, 130, 0, 0, 168, 151, 11, 232, 224, 253, 63, 180, 26, 114, 23, 184, 27, 10, 161, 80, 178, 251, 73, 204, 80, 174, 97, 118, 149, 204, 186, 187, 243, 185, 19, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 157, 73, 251, 238, 138, 245, 142, 240, 85, 44, 209, 63, 194, 242, 109, 242, 246, 6, 76, 176, 27, 160, 29, 157, 226, 23, 81, 174, 34, 82, 93, 182, 41, 248, 119, 42, 221, 214, 38, 243, 128, 2, 235, 208, 193, 192, 74, 208, 242, 26, 221, 83, 54, 74, 160, 111, 29, 92, 8, 75, 61, 97, 103, 199, 100, 189, 72, 74, 221, 144, 66, 170, 68, 121, 29, 105, 19, 194, 35, 245, 196, 131, 236, 29, 105, 101, 30]; + let spend_tx = [ + 249, 1, 9, 37, 132, 119, 53, 148, 0, 131, 2, 73, 240, 148, 123, 193, 187, 221, 106, 10, 114, 47, 201, 191, 252, + 73, 201, 33, 182, 133, 236, 184, 75, 148, 128, 184, 164, 2, 237, 41, 43, 188, 96, 248, 252, 165, 132, 81, 30, + 243, 34, 85, 165, 46, 224, 176, 90, 137, 30, 19, 123, 224, 67, 83, 53, 74, 57, 148, 140, 95, 45, 70, 147, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 71, 13, 228, 223, 130, 0, 0, 168, 151, 11, + 232, 224, 253, 63, 180, 26, 114, 23, 184, 27, 10, 161, 80, 178, 251, 73, 204, 80, 174, 97, 118, 149, 204, 186, + 187, 243, 185, 19, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 157, 73, 251, 238, 138, 245, 142, 240, 85, 44, 209, 63, 194, 242, + 109, 242, 246, 6, 76, 176, 27, 160, 29, 157, 226, 23, 81, 174, 34, 82, 93, 182, 41, 248, 119, 42, 221, 214, 38, + 243, 128, 2, 235, 208, 193, 192, 74, 208, 242, 26, 221, 83, 54, 74, 160, 111, 29, 92, 8, 75, 61, 97, 103, 199, + 100, 189, 72, 74, 221, 144, 66, 170, 68, 121, 29, 105, 19, 194, 35, 245, 196, 131, 236, 29, 105, 101, 30, + ]; let spend_tx = FoundSwapTxSpend::Spent(unwrap!(signed_eth_tx_from_bytes(&spend_tx)).into()); - let found_tx = unwrap!(unwrap!(coin.search_for_swap_tx_spend( - &payment_tx, - 6051857, - ))); + let found_tx = unwrap!(unwrap!(coin.search_for_swap_tx_spend(&payment_tx, 6051857,))); assert_eq!(spend_tx, found_tx); } #[test] fn test_search_for_swap_tx_spend_was_refunded() { - let key_pair = KeyPair::from_secret_slice(&hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap()).unwrap(); - let transport = Web3Transport::new(vec!["https://ropsten.infura.io/v3/c01c1b4cf66642528547624e1d6d9d6b".into()]).unwrap(); + let key_pair = KeyPair::from_secret_slice( + &hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap(), + ) + .unwrap(); + let transport = Web3Transport::new(vec![ + "https://ropsten.infura.io/v3/c01c1b4cf66642528547624e1d6d9d6b".into() + ]) + .unwrap(); let web3 = Web3::new(transport); let ctx = MmCtxBuilder::new().into_mm_arc(); @@ -347,22 +483,45 @@ fn test_search_for_swap_tx_spend_was_refunded() { key_pair, swap_contract_address: Address::from("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94"), ticker: "ETH".into(), - web3_instances: vec![Web3Instance {web3: web3.clone(), is_parity: true}], + web3_instances: vec![Web3Instance { + web3: web3.clone(), + is_parity: true, + }], web3, ctx: ctx.weak(), required_confirmations: 1.into(), })); // raw transaction bytes of https://ropsten.etherscan.io/tx/0xe18bbca69dea9a4624e1f5b0b2021d5fe4c8daa03f36084a8ba011b08e5cd938 - let payment_tx = [249, 1, 43, 130, 10, 96, 132, 149, 2, 249, 0, 131, 2, 73, 240, 148, 123, 193, 187, 221, 106, 10, 114, 47, 201, 191, 252, 73, 201, 33, 182, 133, 236, 184, 75, 148, 128, 184, 196, 155, 65, 91, 42, 192, 158, 192, 175, 210, 198, 159, 244, 116, 46, 255, 28, 236, 147, 240, 68, 91, 16, 19, 6, 59, 187, 149, 138, 179, 151, 121, 47, 14, 80, 251, 147, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 141, 126, 164, 198, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 235, 122, 237, 116, 14, 23, 150, 153, 42, 8, 150, 44, 21, 102, 27, 222, 181, 128, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 153, 121, 65, 221, 19, 70, 233, 35, 17, 24, 213, 104, 93, 134, 98, 148, 245, 158, 91, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 93, 23, 98, 207, 27, 160, 4, 198, 61, 242, 141, 248, 157, 72, 229, 2, 162, 163, 250, 159, 26, 66, 37, 42, 159, 35, 58, 94, 57, 121, 252, 166, 34, 25, 206, 193, 113, 198, 160, 68, 125, 142, 153, 210, 177, 60, 173, 67, 127, 138, 52, 112, 9, 49, 108, 109, 44, 177, 142, 9, 124, 10, 200, 37, 100, 52, 137, 196, 74, 67, 192]; + let payment_tx = [ + 249, 1, 43, 130, 10, 96, 132, 149, 2, 249, 0, 131, 2, 73, 240, 148, 123, 193, 187, 221, 106, 10, 114, 47, 201, + 191, 252, 73, 201, 33, 182, 133, 236, 184, 75, 148, 128, 184, 196, 155, 65, 91, 42, 192, 158, 192, 175, 210, + 198, 159, 244, 116, 46, 255, 28, 236, 147, 240, 68, 91, 16, 19, 6, 59, 187, 149, 138, 179, 151, 121, 47, 14, + 80, 251, 147, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 141, 126, 164, 198, + 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 235, 122, 237, 116, 14, 23, 150, 153, 42, 8, 150, 44, 21, 102, + 27, 222, 181, 128, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 153, 121, 65, 221, 19, 70, 233, 35, 17, 24, 213, + 104, 93, 134, 98, 148, 245, 158, 91, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 93, + 23, 98, 207, 27, 160, 4, 198, 61, 242, 141, 248, 157, 72, 229, 2, 162, 163, 250, 159, 26, 66, 37, 42, 159, 35, + 58, 94, 57, 121, 252, 166, 34, 25, 206, 193, 113, 198, 160, 68, 125, 142, 153, 210, 177, 60, 173, 67, 127, 138, + 52, 112, 9, 49, 108, 109, 44, 177, 142, 9, 124, 10, 200, 37, 100, 52, 137, 196, 74, 67, 192, + ]; // raw transaction bytes of https://ropsten.etherscan.io/tx/0x9a50ac4d1737f4f04b94177996da7fa942b09469de52cfdadce891cd85afc37c - let refund_tx = [249, 1, 11, 130, 10, 97, 132, 149, 2, 249, 0, 131, 2, 73, 240, 148, 123, 193, 187, 221, 106, 10, 114, 47, 201, 191, 252, 73, 201, 33, 182, 133, 236, 184, 75, 148, 128, 184, 164, 70, 252, 2, 148, 192, 158, 192, 175, 210, 198, 159, 244, 116, 46, 255, 28, 236, 147, 240, 68, 91, 16, 19, 6, 59, 187, 149, 138, 179, 151, 121, 47, 14, 80, 251, 147, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 141, 126, 164, 198, 128, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 235, 122, 237, 116, 14, 23, 150, 153, 42, 8, 150, 44, 21, 102, 27, 222, 181, 128, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 153, 121, 65, 221, 19, 70, 233, 35, 17, 24, 213, 104, 93, 134, 98, 148, 245, 158, 91, 28, 160, 127, 220, 190, 77, 221, 188, 140, 162, 198, 6, 127, 102, 222, 66, 38, 96, 10, 19, 27, 208, 119, 219, 60, 231, 2, 118, 91, 169, 99, 78, 209, 135, 160, 51, 115, 90, 189, 124, 172, 205, 134, 203, 159, 238, 40, 39, 99, 88, 48, 160, 189, 37, 60, 20, 117, 65, 238, 36, 98, 226, 48, 22, 235, 86, 183]; + let refund_tx = [ + 249, 1, 11, 130, 10, 97, 132, 149, 2, 249, 0, 131, 2, 73, 240, 148, 123, 193, 187, 221, 106, 10, 114, 47, 201, + 191, 252, 73, 201, 33, 182, 133, 236, 184, 75, 148, 128, 184, 164, 70, 252, 2, 148, 192, 158, 192, 175, 210, + 198, 159, 244, 116, 46, 255, 28, 236, 147, 240, 68, 91, 16, 19, 6, 59, 187, 149, 138, 179, 151, 121, 47, 14, + 80, 251, 147, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 141, 126, 164, 198, + 128, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 235, 122, 237, 116, 14, 23, 150, 153, 42, 8, 150, 44, 21, 102, 27, 222, 181, + 128, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 216, 153, 121, 65, 221, 19, 70, 233, 35, 17, 24, 213, 104, 93, 134, + 98, 148, 245, 158, 91, 28, 160, 127, 220, 190, 77, 221, 188, 140, 162, 198, 6, 127, 102, 222, 66, 38, 96, 10, + 19, 27, 208, 119, 219, 60, 231, 2, 118, 91, 169, 99, 78, 209, 135, 160, 51, 115, 90, 189, 124, 172, 205, 134, + 203, 159, 238, 40, 39, 99, 88, 48, 160, 189, 37, 60, 20, 117, 65, 238, 36, 98, 226, 48, 22, 235, 86, 183, + ]; let refund_tx = FoundSwapTxSpend::Refunded(unwrap!(signed_eth_tx_from_bytes(&refund_tx)).into()); - let found_tx = unwrap!(unwrap!(coin.search_for_swap_tx_spend( - &payment_tx, - 5886908, - ))); + let found_tx = unwrap!(unwrap!(coin.search_for_swap_tx_spend(&payment_tx, 5886908,))); assert_eq!(refund_tx, found_tx); } @@ -381,17 +540,23 @@ fn test_withdraw_impl_manual_fee() { to: "0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94".to_string(), coin: "ETH".to_string(), max: false, - fee: Some(WithdrawFee::EthGas { gas: 150000, gas_price: 1.into() }), + fee: Some(WithdrawFee::EthGas { + gas: 150000, + gas_price: 1.into(), + }), }; coin.my_balance().wait().unwrap(); let tx_details = unwrap!(block_on(withdraw_impl(ctx, coin.clone(), withdraw_req))); - let expected = Some(EthTxFeeDetails { - coin: "ETH".into(), - gas_price: "0.000000001".parse().unwrap(), - gas: 150000, - total_fee: "0.00015".parse().unwrap(), - }.into()); + let expected = Some( + EthTxFeeDetails { + coin: "ETH".into(), + gas_price: "0.000000001".parse().unwrap(), + gas: 150000, + total_fee: "0.00015".parse().unwrap(), + } + .into(), + ); assert_eq!(expected, tx_details.fee_details); } @@ -423,16 +588,16 @@ fn test_nonce_lock() { #[cfg(feature = "w-bindgen")] mod wasm_bindgen_tests { - use crate::lp_coininit; use super::*; - use wasm_bindgen_test::*; + use crate::lp_coininit; use wasm_bindgen::prelude::*; + use wasm_bindgen_test::*; use web_sys::console; #[wasm_bindgen_test] fn pass() { - use common::mm_ctx::MmCtxBuilder; use super::CoinsContext; + use common::mm_ctx::MmCtxBuilder; let ctx = MmCtxBuilder::default().into_mm_arc(); let coins_context = unwrap!(CoinsContext::from_ctx(&ctx)); assert_eq!(1, 1); @@ -453,9 +618,7 @@ mod wasm_bindgen_tests { impl Interval { fn new() -> Interval { let closure = Closure::new(common::executor::run); - Interval { - closure, - } + Interval { closure } } } @@ -468,10 +631,13 @@ mod wasm_bindgen_tests { } #[wasm_bindgen_test(async)] - fn test_send() -> impl Future { + fn test_send() -> impl Future { setInterval(&EXECUTOR_INTERVAL.closure, 200); Box::pin(async move { - let key_pair = KeyPair::from_secret_slice(&hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap()).unwrap(); + let key_pair = KeyPair::from_secret_slice( + &hex::decode("809465b17d0a4ddb3e4c69e8f23c2cabad868f51f8bed5c765ad1d6516c3306f").unwrap(), + ) + .unwrap(); let transport = Web3Transport::new(vec!["http://195.201.0.6:8565".into()]).unwrap(); let web3 = Web3::new(transport); let ctx = MmCtxBuilder::new().into_mm_arc(); @@ -481,7 +647,10 @@ mod wasm_bindgen_tests { my_address: key_pair.address(), key_pair, swap_contract_address: Address::from("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94"), - web3_instances: vec![Web3Instance { web3: web3.clone(), is_parity: true }], + web3_instances: vec![Web3Instance { + web3: web3.clone(), + is_parity: true, + }], web3, decimals: 18, gas_station_url: None, @@ -489,38 +658,49 @@ mod wasm_bindgen_tests { ctx: ctx.weak(), required_confirmations: 1.into(), })); - let tx = coin.send_maker_payment( - 1000, - &unwrap!(hex::decode("03bc2c7ba671bae4a6fc835244c9762b41647b9827d4780a89a949b984a8ddcc06")), - &[1; 20], - "0.001".parse().unwrap(), - ).compat().await; + let tx = coin + .send_maker_payment( + 1000, + &unwrap!(hex::decode( + "03bc2c7ba671bae4a6fc835244c9762b41647b9827d4780a89a949b984a8ddcc06" + )), + &[1; 20], + "0.001".parse().unwrap(), + ) + .compat() + .await; console::log_1(&format!("{:?}", tx).into()); let block = coin.current_block().compat().await; console::log_1(&format!("{:?}", block).into()); Ok(()) - }).compat() + }) + .compat() } #[wasm_bindgen_test(async)] - fn test_init_eth_coin() -> impl Future { + fn test_init_eth_coin() -> impl Future { use common::privkey::key_pair_from_seed; setInterval(&EXECUTOR_INTERVAL.closure, 200); Box::pin(async move { - let key_pair = key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid").unwrap(); + let key_pair = + key_pair_from_seed("spice describe gravity federal blast come thank unfair canal monkey style afraid") + .unwrap(); let conf = json!({ - "coins": [{ - "coin": "ETH", - "name": "ethereum", - "fname": "Ethereum", - "etomic": "0x0000000000000000000000000000000000000000", - "rpcport": 80, - "mm2": 1 - }] - }); - let ctx = MmCtxBuilder::new().with_conf(conf).with_secp256k1_key_pair(key_pair).into_mm_arc(); + "coins": [{ + "coin": "ETH", + "name": "ethereum", + "fname": "Ethereum", + "etomic": "0x0000000000000000000000000000000000000000", + "rpcport": 80, + "mm2": 1 + }] + }); + let ctx = MmCtxBuilder::new() + .with_conf(conf) + .with_secp256k1_key_pair(key_pair) + .into_mm_arc(); let req = json!({ "urls":["http://195.201.0.6:8565"], @@ -528,7 +708,8 @@ mod wasm_bindgen_tests { }); let coin = lp_coininit(&ctx, "ETH", &req).await.unwrap(); Ok(()) - }).compat() + }) + .compat() } } diff --git a/mm2src/coins/eth/web3_transport.rs b/mm2src/coins/eth/web3_transport.rs index c177ef9c83..17e28dc5d8 100644 --- a/mm2src/coins/eth/web3_transport.rs +++ b/mm2src/coins/eth/web3_transport.rs @@ -1,24 +1,25 @@ -use futures01::{Future, Poll}; +use super::{RpcTransportEventHandler, RpcTransportEventHandlerShared}; use common::executor::Timer; use common::wio::slurp_reqʹ; -use futures::compat::{Compat}; +use futures::compat::Compat; use futures::future::{select, Either}; +use futures01::{Future, Poll}; use gstuff::binprint; use http::header::HeaderValue; use jsonrpc_core::{Call, Response}; -use serde_json::{Value as Json}; +use serde_json::Value as Json; use std::ops::Deref; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; -use super::{RpcTransportEventHandler, RpcTransportEventHandlerShared}; -use web3::{RequestId, Transport}; use web3::error::{Error, ErrorKind}; use web3::helpers::{build_request, to_result_from_output, to_string}; +use web3::{RequestId, Transport}; /// Parse bytes RPC response into `Result`. /// Implementation copied from Web3 HTTP transport fn single_response>(response: T) -> Result { - let response = serde_json::from_slice(&*response).map_err(|e| Error::from(ErrorKind::InvalidResponse(format!("{}", e))))?; + let response = + serde_json::from_slice(&*response).map_err(|e| Error::from(ErrorKind::InvalidResponse(format!("{}", e))))?; match response { Response::Single(output) => to_result_from_output(output), @@ -47,7 +48,10 @@ impl Web3Transport { }) } - pub fn with_event_handlers(urls: Vec, event_handlers: Vec) -> Result { + pub fn with_event_handlers( + urls: Vec, + event_handlers: Vec, + ) -> Result { let mut uris = vec![]; for url in urls.iter() { uris.push(try_s!(url.parse())); @@ -67,16 +71,14 @@ impl Future for SendFuture { type Error = T::Error; - fn poll(&mut self) -> Poll { - self.0.poll() - } + fn poll(&mut self) -> Poll { self.0.poll() } } unsafe impl Send for SendFuture {} unsafe impl Sync for SendFuture {} impl Transport for Web3Transport { - type Out = Box + Send>; + type Out = Box + Send>; fn prepare(&self, method: &str, params: Vec) -> (RequestId, Call) { let id = self.id.fetch_add(1, Ordering::AcqRel); @@ -85,12 +87,16 @@ impl Transport for Web3Transport { (id, request) } - #[cfg(not(feature="w-bindgen"))] + #[cfg(not(feature = "w-bindgen"))] fn send(&self, _id: RequestId, request: Call) -> Self::Out { - Box::new(Compat::new(Box::pin(sendʹ(request, self.uris.clone(), self.event_handlers.clone())))) + Box::new(Compat::new(Box::pin(sendʹ( + request, + self.uris.clone(), + self.event_handlers.clone(), + )))) } - #[cfg(feature="w-bindgen")] + #[cfg(feature = "w-bindgen")] fn send(&self, _id: RequestId, request: Call) -> Self::Out { use js_sys; use js_sys::Promise; @@ -107,20 +113,11 @@ impl Transport for Web3Transport { opts.mode(RequestMode::Cors); opts.body(Some(&JsValue::from_str(&body))); - let request = Request::new_with_str_and_init( - "http://195.201.0.6:8565", - &opts, - ).unwrap(); + let request = Request::new_with_str_and_init("http://195.201.0.6:8565", &opts).unwrap(); - request - .headers() - .set("Accept", "application/json") - .unwrap(); + request.headers().set("Accept", "application/json").unwrap(); - request - .headers() - .set("Content-Type", "application/json") - .unwrap(); + request.headers().set("Content-Type", "application/json").unwrap(); let window = web_sys::window().unwrap(); let request_promise = window.fetch_with_request(&request); @@ -144,7 +141,11 @@ impl Transport for Web3Transport { } } -async fn sendʹ(request: Call, uris: Vec, event_handlers: Vec) -> Result { +async fn sendʹ( + request: Call, + uris: Vec, + event_handlers: Vec, +) -> Result { let mut errors = Vec::new(); for uri in uris.iter() { let request = to_string(&request); @@ -153,34 +154,39 @@ async fn sendʹ(request: Call, uris: Vec, event_handlers: Vec r, - Either::Right((_t, _r)) => {errors.push(ERRL!("timeout")); continue} + Either::Right((_t, _r)) => { + errors.push(ERRL!("timeout")); + continue; + }, }; let (status, _headers, body) = match res { Ok(r) => r, Err(err) => { errors.push(err); - continue - } + continue; + }, }; event_handlers.on_incoming_response(&body); if !status.is_success() { errors.push(ERRL!("!200: {}, {}", status, binprint(&body, b'.'))); - continue + continue; } - return single_response(body) + return single_response(body); } Err(ErrorKind::Transport(fomat!( "request " [request] " failed: " for err in errors {(err)} sep {"; "} - )).into()) + )) + .into()) } diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index a646a419a7..4d28ca3280 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -21,7 +21,6 @@ #![feature(non_ascii_idents)] #![feature(async_closure)] #![feature(hash_raw_entry)] - #![allow(uncommon_codepoints)] #[macro_use] extern crate common; @@ -33,16 +32,16 @@ #[macro_use] extern crate unwrap; use bigdecimal::BigDecimal; -use common::{rpc_response, rpc_err_response, HyRes}; use common::duplex_mutex::DuplexMutex; use common::executor::{spawn, Timer}; use common::mm_ctx::{from_ctx, MmArc}; -use common::mm_metrics::{MetricsWeak}; -use futures01::Future; +use common::mm_metrics::MetricsWeak; +use common::{rpc_err_response, rpc_response, HyRes}; use futures::compat::Future01CompatExt; -use gstuff::{slurp}; +use futures01::Future; +use gstuff::slurp; use http::Response; -use rpc::v1::types::{Bytes as BytesJson}; +use rpc::v1::types::Bytes as BytesJson; use serde_json::{self as json, Value as Json}; use std::collections::hash_map::{HashMap, RawEntryMut}; use std::fmt; @@ -53,12 +52,15 @@ use std::thread; // using custom copy of try_fus as futures crate was renamed to futures01 macro_rules! try_fus { - ($e: expr) => {match $e { - Ok (ok) => ok, - Err (err) => {return Box::new (futures01::future::err (ERRL! ("{}", err)))}}}} + ($e: expr) => { + match $e { + Ok(ok) => ok, + Err(err) => return Box::new(futures01::future::err(ERRL!("{}", err))), + } + }; +} -#[doc(hidden)] -pub mod coins_tests; +#[doc(hidden)] pub mod coins_tests; pub mod eth; use self::eth::{eth_coin_from_conf_and_request, EthCoin, EthTxFeeDetails, SignedEthTx}; pub mod utxo; @@ -79,22 +81,24 @@ pub trait Transaction: fmt::Debug + 'static { #[derive(Clone, Debug, PartialEq)] pub enum TransactionEnum { - UtxoTx (UtxoTx), - SignedEthTx (SignedEthTx) + UtxoTx(UtxoTx), + SignedEthTx(SignedEthTx), } -ifrom! (TransactionEnum, UtxoTx); -ifrom! (TransactionEnum, SignedEthTx); +ifrom!(TransactionEnum, UtxoTx); +ifrom!(TransactionEnum, SignedEthTx); // NB: When stable and groked by IDEs, `enum_dispatch` can be used instead of `Deref` to speed things up. impl Deref for TransactionEnum { type Target = dyn Transaction; - fn deref (&self) -> &dyn Transaction { + fn deref(&self) -> &dyn Transaction { match self { - &TransactionEnum::UtxoTx (ref t) => t, - &TransactionEnum::SignedEthTx (ref t) => t, -} } } + TransactionEnum::UtxoTx(ref t) => t, + TransactionEnum::SignedEthTx(ref t) => t, + } + } +} -pub type TransactionFut = Box + Send>; +pub type TransactionFut = Box + Send>; #[derive(Debug, PartialEq)] pub enum FoundSwapTxSpend { @@ -159,7 +163,7 @@ pub trait SwapOps { fee_tx: &TransactionEnum, fee_addr: &[u8], amount: &BigDecimal, - ) -> Box + Send>; + ) -> Box + Send>; fn validate_maker_payment( &self, @@ -168,7 +172,7 @@ pub trait SwapOps { maker_pub: &[u8], priv_bn_hash: &[u8], amount: BigDecimal, - ) -> Box + Send>; + ) -> Box + Send>; fn validate_taker_payment( &self, @@ -177,7 +181,7 @@ pub trait SwapOps { taker_pub: &[u8], priv_bn_hash: &[u8], amount: BigDecimal, - ) -> Box + Send>; + ) -> Box + Send>; fn check_if_my_payment_sent( &self, @@ -185,7 +189,7 @@ pub trait SwapOps { other_pub: &[u8], secret_hash: &[u8], search_from_block: u64, - ) -> Box, Error=String> + Send>; + ) -> Box, Error = String> + Send>; fn search_for_swap_tx_spend_my( &self, @@ -209,17 +213,17 @@ pub trait SwapOps { /// Operations that coins have independently from the MarketMaker. /// That is, things implemented by the coin wallets or public coin services. pub trait MarketCoinOps { - fn ticker (&self) -> &str; + fn ticker(&self) -> &str; fn my_address(&self) -> Result; - fn my_balance(&self) -> Box + Send>; + fn my_balance(&self) -> Box + Send>; /// Base coin balance for tokens, e.g. ETH balance in ERC20 case - fn base_coin_balance(&self) -> Box + Send>; + fn base_coin_balance(&self) -> Box + Send>; /// Receives raw transaction bytes in hexadecimal format as input and returns tx hash in hexadecimal format - fn send_raw_tx(&self, tx: &str) -> Box + Send>; + fn send_raw_tx(&self, tx: &str) -> Box + Send>; fn wait_for_confirmations( &self, @@ -228,13 +232,13 @@ pub trait MarketCoinOps { requires_nota: bool, wait_until: u64, check_every: u64, - ) -> Box + Send>; + ) -> Box + Send>; fn wait_for_tx_spend(&self, transaction: &[u8], wait_until: u64, from_block: u64) -> TransactionFut; fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result; - fn current_block(&self) -> Box + Send>; + fn current_block(&self) -> Box + Send>; fn address_from_pubkey_str(&self, pubkey: &str) -> Result; @@ -244,8 +248,12 @@ pub trait MarketCoinOps { #[derive(Deserialize)] #[serde(tag = "type")] pub enum WithdrawFee { - UtxoFixed { amount: BigDecimal }, - UtxoPerKbyte { amount: BigDecimal }, + UtxoFixed { + amount: BigDecimal, + }, + UtxoPerKbyte { + amount: BigDecimal, + }, EthGas { // in gwei gas_price: BigDecimal, @@ -273,15 +281,11 @@ pub enum TxFeeDetails { } impl Into for EthTxFeeDetails { - fn into(self: EthTxFeeDetails) -> TxFeeDetails { - TxFeeDetails::Eth(self) - } + fn into(self: EthTxFeeDetails) -> TxFeeDetails { TxFeeDetails::Eth(self) } } impl Into for UtxoFeeDetails { - fn into(self: UtxoFeeDetails) -> TxFeeDetails { - TxFeeDetails::Utxo(self) - } + fn into(self: UtxoFeeDetails) -> TxFeeDetails { TxFeeDetails::Utxo(self) } } /// Transaction details @@ -356,9 +360,9 @@ pub trait MmCoin: SwapOps + MarketCoinOps + fmt::Debug + Send + Sync + 'static { fn is_asset_chain(&self) -> bool; - fn can_i_spend_other_payment(&self) -> Box + Send>; + fn can_i_spend_other_payment(&self) -> Box + Send>; - fn withdraw(&self, req: WithdrawRequest) -> Box + Send>; + fn withdraw(&self, req: WithdrawRequest) -> Box + Send>; /// Maximum number of digits after decimal point used to denominate integer coin units (satoshis, wei, etc.) fn decimals(&self) -> u8; @@ -368,8 +372,10 @@ pub trait MmCoin: SwapOps + MarketCoinOps + fmt::Debug + Send + Sync + 'static { /// Path to tx history file fn tx_history_path(&self, ctx: &MmArc) -> PathBuf { - let my_address = self.my_address().unwrap_or(Default::default()); - ctx.dbdir().join("TRANSACTIONS").join(format!("{}_{}.json", self.ticker(), my_address)) + let my_address = self.my_address().unwrap_or_default(); + ctx.dbdir() + .join("TRANSACTIONS") + .join(format!("{}_{}.json", self.ticker(), my_address)) } /// Loads existing tx history from file, returns empty vector if file is not found @@ -382,11 +388,14 @@ pub trait MmCoin: SwapOps + MarketCoinOps + fmt::Debug + Send + Sync + 'static { match json::from_slice(&content) { Ok(c) => c, Err(e) => { - ctx.log.log("🌋", &[&"tx_history", &self.ticker().to_string()], - &ERRL!("Error {} on history deserialization, resetting the cache.", e)); + ctx.log.log( + "🌋", + &[&"tx_history", &self.ticker().to_string()], + &ERRL!("Error {} on history deserialization, resetting the cache.", e), + ); unwrap!(std::fs::remove_file(&self.tx_history_path(&ctx))); vec![] - } + }, } }; history @@ -399,13 +408,13 @@ pub trait MmCoin: SwapOps + MarketCoinOps + fmt::Debug + Send + Sync + 'static { } /// Gets tx details by hash requesting the coin RPC if required - fn tx_details_by_hash(&self, hash: &[u8]) -> Box + Send>; + fn tx_details_by_hash(&self, hash: &[u8]) -> Box + Send>; /// Transaction history background sync status fn history_sync_status(&self) -> HistorySyncState; /// Get fee to be paid per 1 swap transaction - fn get_trade_fee(&self) -> Box + Send>; + fn get_trade_fee(&self) -> Box + Send>; /// required transaction confirmations number to ensure double-spend safety fn required_confirmations(&self) -> u64; @@ -422,35 +431,34 @@ pub trait MmCoin: SwapOps + MarketCoinOps + fmt::Debug + Send + Sync + 'static { #[derive(Clone, Debug)] pub enum MmCoinEnum { - UtxoCoin (UtxoCoin), - EthCoin (EthCoin), - Test (TestCoin) + UtxoCoin(UtxoCoin), + EthCoin(EthCoin), + Test(TestCoin), } impl From for MmCoinEnum { - fn from (c: UtxoCoin) -> MmCoinEnum { - MmCoinEnum::UtxoCoin (c) -} } + fn from(c: UtxoCoin) -> MmCoinEnum { MmCoinEnum::UtxoCoin(c) } +} impl From for MmCoinEnum { - fn from (c: EthCoin) -> MmCoinEnum { - MmCoinEnum::EthCoin (c) -} } + fn from(c: EthCoin) -> MmCoinEnum { MmCoinEnum::EthCoin(c) } +} impl From for MmCoinEnum { - fn from (c: TestCoin) -> MmCoinEnum { - MmCoinEnum::Test (c) -} } + fn from(c: TestCoin) -> MmCoinEnum { MmCoinEnum::Test(c) } +} // NB: When stable and groked by IDEs, `enum_dispatch` can be used instead of `Deref` to speed things up. impl Deref for MmCoinEnum { type Target = dyn MmCoin; - fn deref (&self) -> &dyn MmCoin { + fn deref(&self) -> &dyn MmCoin { match self { - &MmCoinEnum::UtxoCoin (ref c) => c, - &MmCoinEnum::EthCoin (ref c) => c, - &MmCoinEnum::Test (ref c) => c, -} } } + MmCoinEnum::UtxoCoin(ref c) => c, + MmCoinEnum::EthCoin(ref c) => c, + MmCoinEnum::Test(ref c) => c, + } + } +} pub trait BalanceUpdateEventHandler { fn balance_updated(&self, ticker: &str, new_balance: &BigDecimal); @@ -464,11 +472,11 @@ struct CoinsContext { } impl CoinsContext { /// Obtains a reference to this crate context, creating it if necessary. - fn from_ctx (ctx: &MmArc) -> Result, String> { - Ok (try_s! (from_ctx (&ctx.coins_ctx, move || { - Ok (CoinsContext { - coins: DuplexMutex::new (HashMap::new()), - balance_update_handlers: DuplexMutex::new (vec![]), + fn from_ctx(ctx: &MmArc) -> Result, String> { + Ok(try_s!(from_ctx(&ctx.coins_ctx, move || { + Ok(CoinsContext { + coins: DuplexMutex::new(HashMap::new()), + balance_update_handlers: DuplexMutex::new(vec![]), }) }))) } @@ -488,27 +496,17 @@ pub trait RpcTransportEventHandler { } impl fmt::Debug for dyn RpcTransportEventHandler + Send + Sync { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.debug_info()) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.debug_info()) } } impl RpcTransportEventHandler for RpcTransportEventHandlerShared { - fn debug_info(&self) -> String { - self.deref().debug_info() - } + fn debug_info(&self) -> String { self.deref().debug_info() } - fn on_outgoing_request(&self, data: &[u8]) { - self.as_ref().on_outgoing_request(data) - } + fn on_outgoing_request(&self, data: &[u8]) { self.as_ref().on_outgoing_request(data) } - fn on_incoming_response(&self, data: &[u8]) { - self.as_ref().on_incoming_response(data) - } + fn on_incoming_response(&self, data: &[u8]) { self.as_ref().on_incoming_response(data) } - fn on_connected(&self, address: String) -> Result<(), String> { - self.as_ref().on_connected(address) - } + fn on_connected(&self, address: String) -> Result<(), String> { self.as_ref().on_connected(address) } } impl RpcTransportEventHandler for Vec { @@ -565,18 +563,18 @@ pub struct CoinTransportMetrics { impl CoinTransportMetrics { fn new(metrics: MetricsWeak, ticker: String, client: RpcClientType) -> CoinTransportMetrics { - CoinTransportMetrics { metrics, ticker, client: client.to_string() } + CoinTransportMetrics { + metrics, + ticker, + client: client.to_string(), + } } - fn into_shared(self) -> RpcTransportEventHandlerShared { - Arc::new(self) - } + fn into_shared(self) -> RpcTransportEventHandlerShared { Arc::new(self) } } impl RpcTransportEventHandler for CoinTransportMetrics { - fn debug_info(&self) -> String { - "CoinTransportMetrics".into() - } + fn debug_info(&self) -> String { "CoinTransportMetrics".into() } fn on_outgoing_request(&self, data: &[u8]) { mm_counter!(self.metrics, "rpc_client.traffic.out", data.len() as u64, @@ -614,48 +612,68 @@ impl BalanceUpdateEventHandler for CoinsContext { /// and should be fixed on the call site. /// /// * `req` - Payload of the corresponding "enable" or "electrum" RPC request. -pub async fn lp_coininit (ctx: &MmArc, ticker: &str, req: &Json) -> Result { - let cctx = try_s! (CoinsContext::from_ctx (ctx)); - { let coins = try_s! (cctx.coins.sleeplock (77) .await); - if coins.get (ticker) .is_some() {return ERR! ("Coin {} already initialized", ticker)} } +pub async fn lp_coininit(ctx: &MmArc, ticker: &str, req: &Json) -> Result { + let cctx = try_s!(CoinsContext::from_ctx(ctx)); + { + let coins = try_s!(cctx.coins.sleeplock(77).await); + if coins.get(ticker).is_some() { + return ERR!("Coin {} already initialized", ticker); + } + } - let coins_en = if let Some (coins) = ctx.conf["coins"].as_array() { - coins.iter().find (|coin| coin["coin"].as_str() == Some (ticker)) .unwrap_or (&Json::Null) - } else {&Json::Null}; + let coins_en = if let Some(coins) = ctx.conf["coins"].as_array() { + coins + .iter() + .find(|coin| coin["coin"].as_str() == Some(ticker)) + .unwrap_or(&Json::Null) + } else { + &Json::Null + }; if coins_en.is_null() { - ctx.log.log ("😅", &[&("coin" as &str), &ticker, &("no-conf" as &str)], - &fomat! ("Warning, coin " (ticker) " is used without a corresponding configuration.")); + ctx.log.log( + "😅", + &[&("coin" as &str), &ticker, &("no-conf" as &str)], + &fomat! ("Warning, coin " (ticker) " is used without a corresponding configuration."), + ); } - if coins_en["mm2"].is_null() && req["mm2"].is_null() {return ERR! (concat! ( - "mm2 param is not set neither in coins config nor enable request, ", - "assuming that coin is not supported" - ))} + if coins_en["mm2"].is_null() && req["mm2"].is_null() { + return ERR!(concat!( + "mm2 param is not set neither in coins config nor enable request, ", + "assuming that coin is not supported" + )); + } let secret = &*ctx.secp256k1_key_pair().private().secret; let coin: MmCoinEnum = if coins_en["etomic"].is_null() { - try_s! (utxo_coin_from_conf_and_request (ctx, ticker, coins_en, req, secret) .await) .into() + try_s!(utxo_coin_from_conf_and_request(ctx, ticker, coins_en, req, secret).await).into() } else { - try_s! (eth_coin_from_conf_and_request (ctx, ticker, coins_en, req, secret) .await) .into() + try_s!(eth_coin_from_conf_and_request(ctx, ticker, coins_en, req, secret).await).into() }; - let block_count = try_s! (coin.current_block().compat().await); + let block_count = try_s!(coin.current_block().compat().await); // TODO, #156: Warn the user when we know that the wallet is under-initialized. log! ([=ticker] if !coins_en["etomic"].is_null() {", etomic"} ", " [=block_count]); // TODO AP: locking the coins list during the entire initialization prevents different coins from being // activated concurrently which results in long activation time: https://github.com/KomodoPlatform/atomicDEX/issues/24 // So I'm leaving the possibility of race condition intentionally in favor of faster concurrent activation. // Should consider refactoring: maybe extract the RPC client initialization part from coin init functions. - let mut coins = try_s! (cctx.coins.sleeplock (77) .await); - match coins.raw_entry_mut().from_key (ticker) { - RawEntryMut::Occupied (_oe) => return ERR! ("Coin {} already initialized", ticker), - RawEntryMut::Vacant (ve) => ve.insert (ticker.to_string(), coin.clone()) + let mut coins = try_s!(cctx.coins.sleeplock(77).await); + match coins.raw_entry_mut().from_key(ticker) { + RawEntryMut::Occupied(_oe) => return ERR!("Coin {} already initialized", ticker), + RawEntryMut::Vacant(ve) => ve.insert(ticker.to_string(), coin.clone()), }; let history = req["tx_history"].as_bool().unwrap_or(false); - #[cfg(not(feature = "native"))] let history = { - if history {ctx.log.log ("🍼", &[&("tx_history" as &str), &ticker], - "Note that the WASM port does not include the history loading thread at the moment.")} + #[cfg(not(feature = "native"))] + let history = { + if history { + ctx.log.log( + "🍼", + &[&("tx_history" as &str), &ticker], + "Note that the WASM port does not include the history loading thread at the moment.", + ) + } false }; if history { @@ -668,47 +686,47 @@ pub async fn lp_coininit (ctx: &MmArc, ticker: &str, req: &Json) -> Result Result, String> { - let cctx = try_s! (CoinsContext::from_ctx (ctx)); - let coins = try_s! (cctx.coins.spinlock (77)); - Ok (coins.get (ticker) .map (|coin| coin.clone())) +pub fn lp_coinfind(ctx: &MmArc, ticker: &str) -> Result, String> { + let cctx = try_s!(CoinsContext::from_ctx(ctx)); + let coins = try_s!(cctx.coins.spinlock(77)); + Ok(coins.get(ticker).cloned()) } /// NB: Returns only the enabled (aka active) coins. -pub async fn lp_coinfindᵃ (ctx: &MmArc, ticker: &str) -> Result, String> { - let cctx = try_s! (CoinsContext::from_ctx (ctx)); - let coins = try_s! (cctx.coins.sleeplock (77) .await); - Ok (coins.get (ticker) .map (|coin| coin.clone())) -} - -pub async fn withdraw (ctx: MmArc, req: Json) -> Result>, String> { - let ticker = try_s! (req["coin"].as_str().ok_or ("No 'coin' field")) .to_owned(); - let coin = match lp_coinfindᵃ (&ctx, &ticker) .await { - Ok (Some (t)) => t, - Ok (None) => return ERR! ("No such coin: {}", ticker), - Err (err) => return ERR! ("!lp_coinfind({}): {}", ticker, err) +pub async fn lp_coinfindᵃ(ctx: &MmArc, ticker: &str) -> Result, String> { + let cctx = try_s!(CoinsContext::from_ctx(ctx)); + let coins = try_s!(cctx.coins.sleeplock(77).await); + Ok(coins.get(ticker).cloned()) +} + +pub async fn withdraw(ctx: MmArc, req: Json) -> Result>, String> { + let ticker = try_s!(req["coin"].as_str().ok_or("No 'coin' field")).to_owned(); + let coin = match lp_coinfindᵃ(&ctx, &ticker).await { + Ok(Some(t)) => t, + Ok(None) => return ERR!("No such coin: {}", ticker), + Err(err) => return ERR!("!lp_coinfind({}): {}", ticker, err), }; - let withdraw_req: WithdrawRequest = try_s! (json::from_value (req)); - let res = try_s! (coin.withdraw (withdraw_req) .compat().await); - let body = try_s! (json::to_vec (&res)); - Ok (try_s! (Response::builder().body (body))) -} - -pub async fn send_raw_transaction (ctx: MmArc, req: Json) -> Result>, String> { - let ticker = try_s! (req["coin"].as_str().ok_or ("No 'coin' field")) .to_owned(); - let coin = match lp_coinfindᵃ (&ctx, &ticker) .await { - Ok (Some (t)) => t, - Ok (None) => return ERR! ("No such coin: {}", ticker), - Err (err) => return ERR! ("!lp_coinfind({}): {}", ticker, err) + let withdraw_req: WithdrawRequest = try_s!(json::from_value(req)); + let res = try_s!(coin.withdraw(withdraw_req).compat().await); + let body = try_s!(json::to_vec(&res)); + Ok(try_s!(Response::builder().body(body))) +} + +pub async fn send_raw_transaction(ctx: MmArc, req: Json) -> Result>, String> { + let ticker = try_s!(req["coin"].as_str().ok_or("No 'coin' field")).to_owned(); + let coin = match lp_coinfindᵃ(&ctx, &ticker).await { + Ok(Some(t)) => t, + Ok(None) => return ERR!("No such coin: {}", ticker), + Err(err) => return ERR!("!lp_coinfind({}): {}", ticker, err), }; - let bytes_string = try_s! (req["tx_hex"].as_str().ok_or ("No 'tx_hex' field")); - let res = try_s! (coin.send_raw_tx (&bytes_string) .compat().await); - let body = try_s! (json::to_vec (&json! ({"tx_hash": res}))); - Ok (try_s! (Response::builder().body (body))) + let bytes_string = try_s!(req["tx_hex"].as_str().ok_or("No 'tx_hex' field")); + let res = try_s!(coin.send_raw_tx(&bytes_string).compat().await); + let body = try_s!(json::to_vec(&json!({ "tx_hash": res }))); + Ok(try_s!(Response::builder().body(body))) } #[derive(Clone, Debug, Serialize)] @@ -725,11 +743,12 @@ pub enum HistorySyncState { /// Skips the first `skip` records (default: 0). /// Transactions are sorted by number of confirmations in ascending order. pub fn my_tx_history(ctx: MmArc, req: Json) -> HyRes { - let ticker = try_h!(req["coin"].as_str().ok_or ("No 'coin' field")).to_owned(); - let coin = match lp_coinfind(&ctx, &ticker) { // Should switch to lp_coinfindᵃ when my_tx_history is async. + let ticker = try_h!(req["coin"].as_str().ok_or("No 'coin' field")).to_owned(); + let coin = match lp_coinfind(&ctx, &ticker) { + // Should switch to lp_coinfindᵃ when my_tx_history is async. Ok(Some(t)) => t, - Ok(None) => return rpc_err_response(500, &fomat!("No such coin: " (ticker))), - Err(err) => return rpc_err_response(500, &fomat!("!lp_coinfind(" (ticker) "): " (err))) + Ok(None) => return rpc_err_response(500, &fomat!("No such coin: "(ticker))), + Err(err) => return rpc_err_response(500, &fomat!("!lp_coinfind(" (ticker) "): " (err))), }; let limit = req["limit"].as_u64().unwrap_or(10); let from_id: Option = try_h!(json::from_value(req["from_id"].clone())); @@ -742,47 +761,55 @@ pub fn my_tx_history(ctx: MmArc, req: Json) -> HyRes { log!("Error " (e) " on attempt to deserialize file " (file_path.display()) " content as Vec"); } vec![] - } + }, }; let total_records = history.len(); Box::new(coin.current_block().and_then(move |block_number| { let skip = match &from_id { Some(id) => { - try_h!(history.iter().position(|item| item.internal_id == *id).ok_or(format!("from_id {:02x} is not found", id))) + 1 + try_h!(history + .iter() + .position(|item| item.internal_id == *id) + .ok_or(format!("from_id {:02x} is not found", id))) + + 1 }, None => 0, }; let history = history.into_iter().skip(skip).take(limit as usize); - let history: Vec = history.map(|item| { - let tx_block = item.block_height; - let mut json = unwrap!(json::to_value(item)); - json["confirmations"] = if tx_block == 0 { - Json::from(0) - } else { - if block_number >= tx_block { + let history: Vec = history + .map(|item| { + let tx_block = item.block_height; + let mut json = unwrap!(json::to_value(item)); + json["confirmations"] = if tx_block == 0 { + Json::from(0) + } else if block_number >= tx_block { Json::from((block_number - tx_block) + 1) } else { Json::from(0) + }; + json + }) + .collect(); + rpc_response( + 200, + json!({ + "result": { + "transactions": history, + "limit": limit, + "skipped": skip, + "from_id": from_id, + "total": total_records, + "current_block": block_number, + "sync_status": coin.history_sync_status(), } - }; - json - }).collect(); - rpc_response(200, json!({ - "result": { - "transactions": history, - "limit": limit, - "skipped": skip, - "from_id": from_id, - "total": total_records, - "current_block": block_number, - "sync_status": coin.history_sync_status(), - } - }).to_string()) + }) + .to_string(), + ) })) } pub async fn get_trade_fee(ctx: MmArc, req: Json) -> Result>, String> { - let ticker = try_s!(req["coin"].as_str().ok_or ("No 'coin' field")).to_owned(); + let ticker = try_s!(req["coin"].as_str().ok_or("No 'coin' field")).to_owned(); let coin = match lp_coinfindᵃ(&ctx, &ticker).await { Ok(Some(t)) => t, Ok(None) => return ERR!("No such coin: {}", ticker), @@ -809,17 +836,18 @@ struct EnabledCoin { pub async fn get_enabled_coins(ctx: MmArc) -> Result>, String> { let coins_ctx: Arc = try_s!(CoinsContext::from_ctx(&ctx)); let coins = try_s!(coins_ctx.coins.sleeplock(77).await); - let enabled_coins: Vec<_> = try_s!(coins.iter().map(|(ticker, coin)| { - let address = try_s!(coin.my_address()); - Ok(EnabledCoin { - ticker: ticker.clone(), - address, + let enabled_coins: Vec<_> = try_s!(coins + .iter() + .map(|(ticker, coin)| { + let address = try_s!(coin.my_address()); + Ok(EnabledCoin { + ticker: ticker.clone(), + address, + }) }) - }).collect()); + .collect()); - let res = try_s!(json::to_vec(&json!({ - "result": enabled_coins - }))); + let res = try_s!(json::to_vec(&json!({ "result": enabled_coins }))); Ok(try_s!(Response::builder().body(res))) } @@ -828,7 +856,7 @@ pub fn disable_coin(ctx: &MmArc, ticker: &str) -> Result<(), String> { let mut coins = try_s!(coins_ctx.coins.spinlock(77)); match coins.remove(ticker) { Some(_) => Ok(()), - None => ERR!("{} is disabled already", ticker) + None => ERR!("{} is disabled already", ticker), } } @@ -879,7 +907,7 @@ pub async fn set_requires_notarization(ctx: MmArc, req: Json) -> Result Result>, String> { - let ticker = try_s!(req["coin"].as_str().ok_or ("No 'coin' field")).to_owned(); + let ticker = try_s!(req["coin"].as_str().ok_or("No 'coin' field")).to_owned(); let coin = match lp_coinfindᵃ(&ctx, &ticker).await { Ok(Some(t)) => t, Ok(None) => return ERR!("No such coin: {}", ticker), diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 24e48bbca9..67bfc54d13 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -1,9 +1,10 @@ +use super::{HistorySyncState, MarketCoinOps, MmCoin, SwapOps, TradeFee, TransactionDetails, TransactionEnum, + TransactionFut}; +use crate::{FoundSwapTxSpend, WithdrawRequest}; use bigdecimal::BigDecimal; use common::mm_ctx::MmArc; -use crate::{FoundSwapTxSpend, WithdrawRequest}; use futures01::Future; use mocktopus::macros::*; -use super::{HistorySyncState, MarketCoinOps, MmCoin, SwapOps, TradeFee, TransactionDetails, TransactionEnum, TransactionFut}; /// Dummy coin struct used in tests which functions are unimplemented but then mocked /// in specific test to emulate the required behavior @@ -11,27 +12,18 @@ use super::{HistorySyncState, MarketCoinOps, MmCoin, SwapOps, TradeFee, Transact pub struct TestCoin {} #[mockable] +#[allow(clippy::forget_ref, clippy::forget_copy)] impl MarketCoinOps for TestCoin { - fn ticker (&self) -> &str { - unimplemented!() - } + fn ticker(&self) -> &str { unimplemented!() } - fn my_address(&self) -> Result { - unimplemented!() - } + fn my_address(&self) -> Result { unimplemented!() } - fn my_balance(&self) -> Box + Send> { - unimplemented!() - } + fn my_balance(&self) -> Box + Send> { unimplemented!() } - fn base_coin_balance(&self) -> Box + Send> { - unimplemented!() - } + fn base_coin_balance(&self) -> Box + Send> { unimplemented!() } /// Receives raw transaction bytes in hexadecimal format as input and returns tx hash in hexadecimal format - fn send_raw_tx(&self, tx: &str) -> Box + Send> { - unimplemented!() - } + fn send_raw_tx(&self, tx: &str) -> Box + Send> { unimplemented!() } fn wait_for_confirmations( &self, @@ -40,7 +32,7 @@ impl MarketCoinOps for TestCoin { requires_nota: bool, wait_until: u64, check_every: u64, - ) -> Box + Send> { + ) -> Box + Send> { unimplemented!() } @@ -48,28 +40,19 @@ impl MarketCoinOps for TestCoin { unimplemented!() } - fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result { - unimplemented!() - } + fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result { unimplemented!() } - fn current_block(&self) -> Box + Send> { - unimplemented!() - } + fn current_block(&self) -> Box + Send> { unimplemented!() } - fn address_from_pubkey_str(&self, pubkey: &str) -> Result { - unimplemented!() - } + fn address_from_pubkey_str(&self, pubkey: &str) -> Result { unimplemented!() } - fn display_priv_key(&self) -> String { - unimplemented!() - } + fn display_priv_key(&self) -> String { unimplemented!() } } #[mockable] +#[allow(clippy::forget_ref, clippy::forget_copy)] impl SwapOps for TestCoin { - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal) -> TransactionFut { - unimplemented!() - } + fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal) -> TransactionFut { unimplemented!() } fn send_maker_payment( &self, @@ -136,7 +119,7 @@ impl SwapOps for TestCoin { fee_tx: &TransactionEnum, fee_addr: &[u8], amount: &BigDecimal, - ) -> Box + Send> { + ) -> Box + Send> { unimplemented!() } @@ -147,7 +130,7 @@ impl SwapOps for TestCoin { maker_pub: &[u8], priv_bn_hash: &[u8], amount: BigDecimal, - ) -> Box + Send> { + ) -> Box + Send> { unimplemented!() } @@ -158,7 +141,7 @@ impl SwapOps for TestCoin { taker_pub: &[u8], priv_bn_hash: &[u8], amount: BigDecimal, - ) -> Box + Send> { + ) -> Box + Send> { unimplemented!() } @@ -168,7 +151,7 @@ impl SwapOps for TestCoin { other_pub: &[u8], secret_hash: &[u8], search_from_block: u64, - ) -> Box, Error=String> + Send> { + ) -> Box, Error = String> + Send> { unimplemented!() } @@ -196,49 +179,34 @@ impl SwapOps for TestCoin { } #[mockable] +#[allow(clippy::forget_ref, clippy::forget_copy)] impl MmCoin for TestCoin { - fn is_asset_chain(&self) -> bool { - unimplemented!() - } + fn is_asset_chain(&self) -> bool { unimplemented!() } - fn can_i_spend_other_payment(&self) -> Box + Send> { - unimplemented!() - } + fn can_i_spend_other_payment(&self) -> Box + Send> { unimplemented!() } - fn withdraw(&self, req: WithdrawRequest) -> Box + Send> { + fn withdraw(&self, req: WithdrawRequest) -> Box + Send> { unimplemented!() } - fn decimals(&self) -> u8 { - unimplemented!() - } + fn decimals(&self) -> u8 { unimplemented!() } - fn process_history_loop(&self, ctx: MmArc) { - unimplemented!() - } + fn process_history_loop(&self, ctx: MmArc) { unimplemented!() } - fn tx_details_by_hash(&self, hash: &[u8]) -> Box + Send> { + fn tx_details_by_hash(&self, hash: &[u8]) -> Box + Send> { unimplemented!() } - fn history_sync_status(&self) -> HistorySyncState { - unimplemented!() - } + fn history_sync_status(&self) -> HistorySyncState { unimplemented!() } /// Get fee to be paid per 1 swap transaction - fn get_trade_fee(&self) -> Box + Send> { - unimplemented!() - } + fn get_trade_fee(&self) -> Box + Send> { unimplemented!() } - fn required_confirmations(&self) -> u64 { - 1 - } + fn required_confirmations(&self) -> u64 { 1 } fn requires_notarization(&self) -> bool { false } - fn set_required_confirmations(&self, _confirmations: u64) { - unimplemented!() - } + fn set_required_confirmations(&self, _confirmations: u64) { unimplemented!() } fn set_requires_notarization(&self, _requires_nota: bool) { unimplemented!() } } diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index 8f251c6a01..d6f2a4679b 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -25,54 +25,54 @@ pub mod rpc_clients; use base64::{encode_config as base64_encode, URL_SAFE}; use bigdecimal::BigDecimal; -pub use bitcrypto::{dhash160, ChecksumType, sha256}; -use chain::{TransactionOutput, TransactionInput, OutPoint}; -use chain::constants::{SEQUENCE_FINAL}; -use common::{first_char_to_upper, small_rng, MM_VERSION}; +pub use bitcrypto::{dhash160, sha256, ChecksumType}; +use chain::constants::SEQUENCE_FINAL; +use chain::{OutPoint, TransactionInput, TransactionOutput}; use common::executor::{spawn, Timer}; use common::jsonrpc_client::{JsonRpcError, JsonRpcErrorType}; use common::mm_ctx::MmArc; -#[cfg(feature = "native")] -use dirs::home_dir; -use futures01::{Future}; -use futures01::future::Either; +use common::{first_char_to_upper, small_rng, MM_VERSION}; +#[cfg(feature = "native")] use dirs::home_dir; use futures::channel::mpsc; use futures::compat::Future01CompatExt; use futures::future::{FutureExt, TryFutureExt}; -use futures::lock::{Mutex as AsyncMutex}; +use futures::lock::Mutex as AsyncMutex; use futures::stream::StreamExt; -use gstuff::{now_ms}; -use keys::{KeyPair, Private, Public, Address, Secret, Type}; +use futures01::future::Either; +use futures01::Future; +use gstuff::now_ms; use keys::bytes::Bytes; +use keys::{Address, KeyPair, Private, Public, Secret, Type}; use num_traits::cast::ToPrimitive; use primitives::hash::{H256, H264, H512}; use rand::seq::SliceRandom; use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json}; -use script::{Opcode, Builder, Script, ScriptAddress, TransactionInputSigner, UnsignedTransactionInput, SignatureVersion}; +use script::{Builder, Opcode, Script, ScriptAddress, SignatureVersion, TransactionInputSigner, + UnsignedTransactionInput}; use serde_json::{self as json, Value as Json}; -use serialization::{serialize, deserialize}; -use std::collections::hash_map::{HashMap, Entry}; -use std::convert::TryInto; +use serialization::{deserialize, serialize}; use std::cmp::Ordering; +use std::collections::hash_map::{Entry, HashMap}; +use std::convert::TryInto; use std::num::NonZeroU64; use std::ops::Deref; use std::path::{Path, PathBuf}; use std::str::FromStr; -use std::sync::{Arc, Mutex, Weak}; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering as AtomicOrderding}; +use std::sync::{Arc, Mutex, Weak}; use std::thread; use std::time::Duration; pub use chain::Transaction as UtxoTx; -use self::rpc_clients::{electrum_script_hash, ElectrumClient, ElectrumClientImpl, - EstimateFeeMethod, EstimateFeeMode, NativeClient, UtxoRpcClientEnum, UnspentInfo}; -use super::{CoinsContext, CoinTransportMetrics, FoundSwapTxSpend, HistorySyncState, MarketCoinOps, MmCoin, RpcClientType, RpcTransportEventHandler, - RpcTransportEventHandlerShared, SwapOps, TradeFee, Transaction, TransactionEnum, TransactionFut, TransactionDetails, WithdrawFee, WithdrawRequest}; -use crate::utxo::rpc_clients::{NativeClientImpl, UtxoRpcClientOps, ElectrumRpcRequest}; +use self::rpc_clients::{electrum_script_hash, ElectrumClient, ElectrumClientImpl, EstimateFeeMethod, EstimateFeeMode, + NativeClient, UnspentInfo, UtxoRpcClientEnum}; +use super::{CoinTransportMetrics, CoinsContext, FoundSwapTxSpend, HistorySyncState, MarketCoinOps, MmCoin, + RpcClientType, RpcTransportEventHandler, RpcTransportEventHandlerShared, SwapOps, TradeFee, Transaction, + TransactionDetails, TransactionEnum, TransactionFut, WithdrawFee, WithdrawRequest}; +use crate::utxo::rpc_clients::{ElectrumRpcRequest, NativeClientImpl, UtxoRpcClientOps}; -#[cfg(test)] -pub mod utxo_tests; +#[cfg(test)] pub mod utxo_tests; const SWAP_TX_SPEND_SIZE: u64 = 305; const KILO_BYTE: u64 = 1000; @@ -93,34 +93,32 @@ fn get_special_folder_path() -> PathBuf { use std::ffi::CStr; use std::mem::zeroed; use std::ptr::null_mut; - use winapi::um::shlobj::SHGetSpecialFolderPathA; use winapi::shared::minwindef::MAX_PATH; + use winapi::um::shlobj::SHGetSpecialFolderPathA; use winapi::um::shlobj::CSIDL_APPDATA; - let mut buf: [c_char; MAX_PATH + 1] = unsafe {zeroed()}; + let mut buf: [c_char; MAX_PATH + 1] = unsafe { zeroed() }; // https://docs.microsoft.com/en-us/windows/desktop/api/shlobj_core/nf-shlobj_core-shgetspecialfolderpatha - let rc = unsafe {SHGetSpecialFolderPathA (null_mut(), buf.as_mut_ptr(), CSIDL_APPDATA, 1)}; - if rc != 1 {panic! ("!SHGetSpecialFolderPathA")} - Path::new (unwrap! (unsafe {CStr::from_ptr (buf.as_ptr())} .to_str())) .to_path_buf() + let rc = unsafe { SHGetSpecialFolderPathA(null_mut(), buf.as_mut_ptr(), CSIDL_APPDATA, 1) }; + if rc != 1 { + panic!("!SHGetSpecialFolderPathA") + } + Path::new(unwrap!(unsafe { CStr::from_ptr(buf.as_ptr()) }.to_str())).to_path_buf() } #[cfg(not(windows))] #[cfg(feature = "native")] -fn get_special_folder_path() -> PathBuf {panic!("!windows")} +fn get_special_folder_path() -> PathBuf { panic!("!windows") } impl Transaction for UtxoTx { - fn tx_hex(&self) -> Vec { - serialize(self).into() - } + fn tx_hex(&self) -> Vec { serialize(self).into() } fn extract_secret(&self) -> Result, String> { let script: Script = self.inputs[0].script_sig.clone().into(); for (i, instr) in script.iter().enumerate() { let instruction = instr.unwrap(); - if i == 1 { - if instruction.opcode == Opcode::OP_PUSHBYTES_32 { - return Ok(instruction.data.unwrap().to_vec()); - } + if i == 1 && instruction.opcode == Opcode::OP_PUSHBYTES_32 { + return Ok(instruction.data.unwrap().to_vec()); } } ERR!("Couldn't extract secret") @@ -179,13 +177,12 @@ enum UtxoAddressFormat { } impl Default for UtxoAddressFormat { - fn default() -> Self { - UtxoAddressFormat::Standard - } + fn default() -> Self { UtxoAddressFormat::Standard } } +/// pImpl idiom. #[derive(Debug)] -pub struct UtxoCoinImpl { // pImpl idiom. +pub struct UtxoCoinImpl { ticker: String, /// https://en.bitcoin.it/wiki/List_of_address_prefixes /// https://github.com/jl777/coins/blob/master/coins @@ -264,7 +261,11 @@ impl UtxoCoinImpl { match &self.tx_fee { TxFee::Fixed(fee) => Ok(ActualTxFee::Fixed(*fee)), TxFee::Dynamic(method) => { - let fee = self.rpc_client.estimate_fee_sat(self.decimals, method, &self.estimate_fee_mode).compat().await?; + let fee = self + .rpc_client + .estimate_fee_sat(self.decimals, method, &self.estimate_fee_mode) + .compat() + .await?; Ok(ActualTxFee::Dynamic(fee)) }, } @@ -291,26 +292,27 @@ impl UtxoCoinImpl { fn addresses_from_script(&self, script: &Script) -> Result, String> { let destinations: Vec = try_s!(script.extract_destinations()); - let addresses = destinations.into_iter().map(|dst| { - let (prefix, t_addr_prefix) = match dst.kind { - Type::P2PKH => (self.pub_addr_prefix, self.pub_t_addr_prefix), - Type::P2SH => (self.p2sh_addr_prefix, self.p2sh_t_addr_prefix), - }; + let addresses = destinations + .into_iter() + .map(|dst| { + let (prefix, t_addr_prefix) = match dst.kind { + Type::P2PKH => (self.pub_addr_prefix, self.pub_t_addr_prefix), + Type::P2SH => (self.p2sh_addr_prefix, self.p2sh_t_addr_prefix), + }; - Address { - hash: dst.hash, - checksum_type: self.checksum_type, - prefix, - t_addr_prefix, - } - }).collect(); + Address { + hash: dst.hash, + checksum_type: self.checksum_type, + prefix, + t_addr_prefix, + } + }) + .collect(); Ok(addresses) } - pub fn denominate_satoshis(&self, satoshi: i64) -> f64 { - satoshi as f64 / 10f64.powf(self.decimals as f64) - } + pub fn denominate_satoshis(&self, satoshi: i64) -> f64 { satoshi as f64 / 10f64.powf(self.decimals as f64) } fn search_for_swap_tx_spend( &self, @@ -325,64 +327,61 @@ impl UtxoCoinImpl { let script = payment_script(time_lock, secret_hash, first_pub, second_pub); let expected_script_pubkey = Builder::build_p2sh(&dhash160(&script)).to_bytes(); if tx.outputs[0].script_pubkey != expected_script_pubkey { - return ERR!("Transaction {:?} output 0 script_pubkey doesn't match expected {:?}", tx, expected_script_pubkey); + return ERR!( + "Transaction {:?} output 0 script_pubkey doesn't match expected {:?}", + tx, + expected_script_pubkey + ); } let spend = try_s!(self.rpc_client.find_output_spend(&tx, 0, search_from_block).wait()); match spend { Some(tx) => { let script: Script = tx.inputs[0].script_sig.clone().into(); - match script.iter().nth(2) { - Some(instruction) => match instruction { - Ok(ref i) if i.opcode == Opcode::OP_0 => return Ok(Some(FoundSwapTxSpend::Spent(tx.into()))), - _ => (), - }, - None => (), - }; + if let Some(Ok(ref i)) = script.iter().nth(2) { + if i.opcode == Opcode::OP_0 { + return Ok(Some(FoundSwapTxSpend::Spent(tx.into()))); + } + } - match script.iter().nth(1) { - Some(instruction) => match instruction { - Ok(ref i) if i.opcode == Opcode::OP_1 => return Ok(Some(FoundSwapTxSpend::Refunded(tx.into()))), - _ => (), - }, - None => (), - }; + if let Some(Ok(ref i)) = script.iter().nth(1) { + if i.opcode == Opcode::OP_1 { + return Ok(Some(FoundSwapTxSpend::Refunded(tx.into()))); + } + } - ERR!("Couldn't find required instruction in script_sig of input 0 of tx {:?}", tx) + ERR!( + "Couldn't find required instruction in script_sig of input 0 of tx {:?}", + tx + ) }, None => Ok(None), } } - pub fn my_public_key(&self) -> &Public { - self.key_pair.public() - } + pub fn my_public_key(&self) -> &Public { self.key_pair.public() } - pub fn rpc_client(&self) -> &UtxoRpcClientEnum { - &self.rpc_client - } + pub fn rpc_client(&self) -> &UtxoRpcClientEnum { &self.rpc_client } pub fn display_address(&self, address: &Address) -> Result { match &self.address_format { UtxoAddressFormat::Standard => Ok(address.to_string()), - UtxoAddressFormat::CashAddress { network } => - address.to_cashaddress(&network, self.pub_addr_prefix, self.p2sh_addr_prefix) - .and_then(|cashaddress| cashaddress.encode()), + UtxoAddressFormat::CashAddress { network } => address + .to_cashaddress(&network, self.pub_addr_prefix, self.p2sh_addr_prefix) + .and_then(|cashaddress| cashaddress.encode()), } } async fn get_current_mtp(&self) -> Result { let current_block = try_s!(self.rpc_client.get_block_count().compat().await); - self.rpc_client.get_median_time_past(current_block, self.mtp_block_count).compat().await + self.rpc_client + .get_median_time_past(current_block, self.mtp_block_count) + .compat() + .await } } -fn payment_script( - time_lock: u32, - secret_hash: &[u8], - pub_0: &Public, - pub_1: &Public -) -> Script { +fn payment_script(time_lock: u32, secret_hash: &[u8], pub_0: &Public, pub_1: &Public) -> Script { let builder = Builder::default(); builder .push_opcode(Opcode::OP_IF) @@ -437,10 +436,20 @@ fn p2pkh_spend( ) -> Result { let script = Builder::build_p2pkh(&key_pair.public().address_hash()); if script != *prev_script { - return ERR!("p2pkh script {} built from input key pair doesn't match expected prev script {}", script, prev_script); + return ERR!( + "p2pkh script {} built from input key pair doesn't match expected prev script {}", + script, + prev_script + ); } let sighash_type = 1 | fork_id; - let sighash = signer.signature_hash(input_index, signer.inputs[input_index].amount, &script, signature_version, sighash_type); + let sighash = signer.signature_hash( + input_index, + signer.inputs[input_index].amount, + &script, + signature_version, + sighash_type, + ); let script_sig = try_s!(script_sig_with_pub(&sighash, key_pair, fork_id)); @@ -448,7 +457,7 @@ fn p2pkh_spend( script_sig, sequence: signer.inputs[input_index].sequence, script_witness: vec![], - previous_output: signer.inputs[input_index].previous_output.clone() + previous_output: signer.inputs[input_index].previous_output.clone(), }) } @@ -462,7 +471,13 @@ fn p2sh_spend( signature_version: SignatureVersion, fork_id: u32, ) -> Result { - let sighash = signer.signature_hash(input_index, signer.inputs[input_index].amount, &redeem_script, signature_version, 1 | fork_id); + let sighash = signer.signature_hash( + input_index, + signer.inputs[input_index].amount, + &redeem_script, + signature_version, + 1 | fork_id, + ); let sig = try_s!(script_sig(&sighash, &key_pair, fork_id)); @@ -475,14 +490,19 @@ fn p2sh_spend( resulting_script.extend_from_slice(&redeem_part); Ok(TransactionInput { - script_sig: resulting_script.into(), + script_sig: resulting_script, sequence: signer.inputs[input_index].sequence, script_witness: vec![], - previous_output: signer.inputs[input_index].previous_output.clone() + previous_output: signer.inputs[input_index].previous_output.clone(), }) } -fn address_from_raw_pubkey(pub_key: &[u8], prefix: u8, t_addr_prefix: u8, checksum_type: ChecksumType) -> Result { +fn address_from_raw_pubkey( + pub_key: &[u8], + prefix: u8, + t_addr_prefix: u8, + checksum_type: ChecksumType, +) -> Result { Ok(Address { t_addr_prefix, prefix, @@ -500,9 +520,14 @@ fn sign_tx( ) -> Result { let mut signed_inputs = vec![]; for (i, _) in unsigned.inputs.iter().enumerate() { - signed_inputs.push( - try_s!(p2pkh_spend(&unsigned, i, key_pair, &prev_script, signature_version, fork_id)) - ); + signed_inputs.push(try_s!(p2pkh_spend( + &unsigned, + i, + key_pair, + &prev_script, + signature_version, + fork_id + ))); } Ok(UtxoTx { inputs: signed_inputs, @@ -527,7 +552,13 @@ fn sign_tx( /// Denominate BigDecimal amount of coin units to satoshis fn sat_from_big_decimal(amount: &BigDecimal, decimals: u8) -> Result { - (amount * BigDecimal::from(10u64.pow(decimals as u32))).to_u64().ok_or(ERRL!("Could not get sat from amount {} with decimals {}", amount, decimals)) + (amount * BigDecimal::from(10u64.pow(decimals as u32))) + .to_u64() + .ok_or(ERRL!( + "Could not get sat from amount {} with decimals {}", + amount, + decimals + )) } /// Convert satoshis to BigDecimal amount of coin units @@ -537,17 +568,20 @@ fn big_decimal_from_sat(satoshis: i64, decimals: u8) -> BigDecimal { #[derive(Clone, Debug)] pub struct UtxoCoin(Arc); -impl Deref for UtxoCoin {type Target = UtxoCoinImpl; fn deref (&self) -> &UtxoCoinImpl {&*self.0}} +impl Deref for UtxoCoin { + type Target = UtxoCoinImpl; + fn deref(&self) -> &UtxoCoinImpl { &*self.0 } +} impl From for UtxoCoin { - fn from(coin: UtxoCoinImpl) -> UtxoCoin { - UtxoCoin(Arc::new(coin)) - } + fn from(coin: UtxoCoinImpl) -> UtxoCoin { UtxoCoin(Arc::new(coin)) } } // We can use a shared UTXO lock for all UTXO coins at 1 time. // It's highly likely that we won't experience any issues with it as we won't need to send "a lot" of transactions concurrently. -lazy_static! {static ref UTXO_LOCK: AsyncMutex<()> = AsyncMutex::new(());} +lazy_static! { + static ref UTXO_LOCK: AsyncMutex<()> = AsyncMutex::new(()); +} macro_rules! true_or_err { ($cond: expr, $msg: expr $(, $args:expr)*) => { @@ -557,14 +591,34 @@ macro_rules! true_or_err { }; } -async fn send_outputs_from_my_address_impl(coin: UtxoCoin, outputs: Vec) - -> Result { +async fn send_outputs_from_my_address_impl(coin: UtxoCoin, outputs: Vec) -> Result { let _utxo_lock = UTXO_LOCK.lock().await; - let unspents = try_s!(coin.rpc_client.list_unspent_ordered(&coin.my_address).map_err(|e| ERRL!("{}", e)).compat().await); - let (unsigned, _) = try_s!(coin.generate_transaction(unspents, outputs, FeePolicy::SendExact, None).await); + let unspents = try_s!( + coin.rpc_client + .list_unspent_ordered(&coin.my_address) + .map_err(|e| ERRL!("{}", e)) + .compat() + .await + ); + let (unsigned, _) = try_s!( + coin.generate_transaction(unspents, outputs, FeePolicy::SendExact, None) + .await + ); let prev_script = Builder::build_p2pkh(&coin.my_address.hash); - let signed = try_s!(sign_tx(unsigned, &coin.key_pair, prev_script, coin.signature_version, coin.fork_id)); - try_s!(coin.rpc_client.send_transaction(&signed, coin.my_address.clone()).map_err(|e| ERRL!("{}", e)).compat().await); + let signed = try_s!(sign_tx( + unsigned, + &coin.key_pair, + prev_script, + coin.signature_version, + coin.fork_id + )); + try_s!( + coin.rpc_client + .send_transaction(&signed, coin.my_address.clone()) + .map_err(|e| ERRL!("{}", e)) + .compat() + .await + ); Ok(signed) } @@ -582,7 +636,7 @@ impl UtxoCoin { second_pub0: &Public, priv_bn_hash: &[u8], amount: BigDecimal, - ) -> Box + Send> { + ) -> Box + Send> { let tx: UtxoTx = try_fus!(deserialize(payment_tx).map_err(|e| ERRL!("{:?}", e))); let amount = try_fus!(sat_from_big_decimal(&amount, self.decimals)); let selfi = self.clone(); @@ -596,20 +650,33 @@ impl UtxoCoin { let fut = async move { let mut attempts = 0; loop { - let tx_from_rpc = match selfi.rpc_client.get_transaction_bytes(tx.hash().reversed().into()).compat().await { + let tx_from_rpc = match selfi + .rpc_client + .get_transaction_bytes(tx.hash().reversed().into()) + .compat() + .await + { Ok(t) => t, Err(e) => { if attempts > 2 { - return ERR!("Got error {:?} after 3 attempts of getting tx {:?} from RPC", e, tx.tx_hash()); + return ERR!( + "Got error {:?} after 3 attempts of getting tx {:?} from RPC", + e, + tx.tx_hash() + ); }; attempts += 1; log!("Error " [e] " getting the tx " [tx.tx_hash()] " from rpc"); Timer::sleep(10.).await; continue; - } + }, }; if serialize(&tx).take() != tx_from_rpc.0 { - return ERR!("Provided payment tx {:?} doesn't match tx data from rpc {:?}", tx, tx_from_rpc); + return ERR!( + "Provided payment tx {:?} doesn't match tx data from rpc {:?}", + tx, + tx_from_rpc + ); } let expected_output = TransactionOutput { @@ -618,7 +685,11 @@ impl UtxoCoin { }; if tx.outputs[0] != expected_output { - return ERR!("Provided payment tx output doesn't match expected {:?} {:?}", tx.outputs[0], expected_output); + return ERR!( + "Provided payment tx output doesn't match expected {:?} {:?}", + tx.outputs[0], + expected_output + ); } return Ok(()); } @@ -652,8 +723,13 @@ impl UtxoCoin { let mut received_by_me = 0; for output in outputs.iter() { let script: Script = output.script_pubkey.clone().into(); - if script.opcodes().nth(0) != Some(Ok(Opcode::OP_RETURN)) { - true_or_err!(output.value >= DUST, "Output value {} is less than dust amount {}", output.value, DUST); + if script.opcodes().next() != Some(Ok(Opcode::OP_RETURN)) { + true_or_err!( + output.value >= DUST, + "Output value {} is less than dust amount {}", + output.value, + DUST + ); } sum_outputs_value += output.value; if output.script_pubkey == change_script_pubkey { @@ -661,17 +737,17 @@ impl UtxoCoin { } } - let str_d_zeel = if self.ticker == "NAV" { - Some("".into()) - } else { - None - }; + let str_d_zeel = if self.ticker == "NAV" { Some("".into()) } else { None }; let mut tx = TransactionInputSigner { inputs: vec![], outputs, lock_time, version: self.tx_version, - n_time: if self.is_pos { Some((now_ms() / 1000) as u32) } else { None }, + n_time: if self.is_pos { + Some((now_ms() / 1000) as u32) + } else { + None + }, overwintered: self.overwintered, expiry_height: 0, join_splits: vec![], @@ -732,7 +808,6 @@ impl UtxoCoin { break; } } - () }, FeePolicy::DeductFromOutput(_) => { if sum_inputs >= sum_outputs_value { @@ -756,21 +831,32 @@ impl UtxoCoin { FeePolicy::DeductFromOutput(i) => { let min_output = tx_fee + DUST; let val = tx.outputs[i].value; - true_or_err!(val >= min_output, "Output {} value {} is too small, required no less than {}", i, val, min_output); + true_or_err!( + val >= min_output, + "Output {} value {} is too small, required no less than {}", + i, + val, + min_output + ); tx.outputs[i].value -= tx_fee; if tx.outputs[i].script_pubkey == change_script_pubkey { received_by_me -= tx_fee; } }, }; - true_or_err!(sum_inputs >= sum_outputs_value, "Not sufficient balance. Couldn't collect enough value from utxos {:?} to create tx with outputs {:?}", utxos, tx.outputs); + true_or_err!( + sum_inputs >= sum_outputs_value, + "Not sufficient balance. Couldn't collect enough value from utxos {:?} to create tx with outputs {:?}", + utxos, + tx.outputs + ); let change = sum_inputs - sum_outputs_value; if change >= DUST { tx.outputs.push({ TransactionOutput { value: change, - script_pubkey: change_script_pubkey.clone() + script_pubkey: change_script_pubkey.clone(), } }); received_by_me += change; @@ -783,7 +869,7 @@ impl UtxoCoin { received_by_me, spent_by_me: sum_inputs, }; - self.calc_interest_if_required(tx.into(), data, change_script_pubkey).await + self.calc_interest_if_required(tx, data, change_script_pubkey).await } /// Calculates interest if the coin is KMD @@ -807,7 +893,10 @@ impl UtxoCoin { } if interest > 0 { data.received_by_me += interest; - let mut output_to_me = unsigned.outputs.iter_mut().find(|out| out.script_pubkey == my_script_pub); + let mut output_to_me = unsigned + .outputs + .iter_mut() + .find(|out| out.script_pubkey == my_script_pub); // add calculated interest to existing output to my address // or create the new one if it's not found match output_to_me { @@ -818,7 +907,7 @@ impl UtxoCoin { value: interest, }; unsigned.outputs.push(interest_output); - } + }, }; } else { // if interest is zero attempt to set the lowest possible lock_time to claim it later @@ -846,7 +935,11 @@ impl UtxoCoin { } else { (now_ms() / 1000) as u32 - 3600 }; - let n_time = if self.is_pos { Some((now_ms() / 1000) as u32) } else { None }; + let n_time = if self.is_pos { + Some((now_ms() / 1000) as u32) + } else { + None + }; let str_d_zeel = if self.ticker == "NAV" { Some("".into()) } else { None }; let unsigned = TransactionInputSigner { lock_time, @@ -872,9 +965,15 @@ impl UtxoCoin { zcash: self.zcash, str_d_zeel, }; - let signed_input = try_s!( - p2sh_spend(&unsigned, 0, &self.key_pair, script_data, redeem_script.into(), self.signature_version, self.fork_id) - ); + let signed_input = try_s!(p2sh_spend( + &unsigned, + 0, + &self.key_pair, + script_data, + redeem_script.into(), + self.signature_version, + self.fork_id + )); Ok(UtxoTx { version: unsigned.version, n_time: unsigned.n_time, @@ -918,11 +1017,16 @@ pub fn compressed_pub_key_from_priv_raw(raw_priv: &[u8], sum_type: ChecksumType) impl SwapOps for UtxoCoin { fn send_taker_fee(&self, fee_pub_key: &[u8], amount: BigDecimal) -> TransactionFut { - let address = try_fus!(address_from_raw_pubkey(fee_pub_key, self.pub_addr_prefix, self.pub_t_addr_prefix, self.checksum_type)); + let address = try_fus!(address_from_raw_pubkey( + fee_pub_key, + self.pub_addr_prefix, + self.pub_t_addr_prefix, + self.checksum_type + )); let amount = try_fus!(sat_from_big_decimal(&amount, self.decimals)); let output = TransactionOutput { value: amount, - script_pubkey: Builder::build_p2pkh(&address.hash).to_bytes() + script_pubkey: Builder::build_p2pkh(&address.hash).to_bytes(), }; self.send_outputs_from_my_address(vec![output]) } @@ -956,9 +1060,9 @@ impl SwapOps for UtxoCoin { script_pubkey: secret_hash_op_return_script, }; let send_fut = match &self.rpc_client { - UtxoRpcClientEnum::Electrum(_) => Either::A(self.send_outputs_from_my_address( - vec![htlc_out, secret_hash_op_return_out] - )), + UtxoRpcClientEnum::Electrum(_) => { + Either::A(self.send_outputs_from_my_address(vec![htlc_out, secret_hash_op_return_out])) + }, UtxoRpcClientEnum::Native(client) => { let payment_addr = Address { checksum_type: self.checksum_type, @@ -968,10 +1072,13 @@ impl SwapOps for UtxoCoin { }; let arc = self.clone(); let addr_string = try_fus!(self.display_address(&payment_addr)); - Either::B(client.import_address(&addr_string, &addr_string, false).map_err(|e| ERRL!("{}", e)).and_then(move |_| - arc.send_outputs_from_my_address(vec![htlc_out, secret_hash_op_return_out]) - )) - } + Either::B( + client + .import_address(&addr_string, &addr_string, false) + .map_err(|e| ERRL!("{}", e)) + .and_then(move |_| arc.send_outputs_from_my_address(vec![htlc_out, secret_hash_op_return_out])), + ) + }, }; Box::new(send_fut) } @@ -1007,9 +1114,9 @@ impl SwapOps for UtxoCoin { script_pubkey: secret_hash_op_return_script, }; let send_fut = match &self.rpc_client { - UtxoRpcClientEnum::Electrum(_) => Either::A(self.send_outputs_from_my_address( - vec![htlc_out, secret_hash_op_return_out] - )), + UtxoRpcClientEnum::Electrum(_) => { + Either::A(self.send_outputs_from_my_address(vec![htlc_out, secret_hash_op_return_out])) + }, UtxoRpcClientEnum::Native(client) => { let payment_addr = Address { checksum_type: self.checksum_type, @@ -1019,10 +1126,13 @@ impl SwapOps for UtxoCoin { }; let arc = self.clone(); let addr_string = try_fus!(self.display_address(&payment_addr)); - Either::B(client.import_address(&addr_string, &addr_string, false).map_err(|e| ERRL!("{}", e)).and_then(move |_| - arc.send_outputs_from_my_address(vec![htlc_out, secret_hash_op_return_out]) - )) - } + Either::B( + client + .import_address(&addr_string, &addr_string, false) + .map_err(|e| ERRL!("{}", e)) + .and_then(move |_| arc.send_outputs_from_my_address(vec![htlc_out, secret_hash_op_return_out])), + ) + }, }; Box::new(send_fut) } @@ -1039,22 +1149,25 @@ impl SwapOps for UtxoCoin { .push_data(secret) .push_opcode(Opcode::OP_0) .into_script(); - let redeem_script = payment_script(time_lock, &*dhash160(secret), &try_fus!(Public::from_slice(taker_pub)), self.key_pair.public()); + let redeem_script = payment_script( + time_lock, + &*dhash160(secret), + &try_fus!(Public::from_slice(taker_pub)), + self.key_pair.public(), + ); let arc = self.clone(); let fut = async move { let fee = try_s!(arc.get_htlc_spend_fee().await); let output = TransactionOutput { value: prev_tx.outputs[0].value - fee, - script_pubkey: Builder::build_p2pkh(&arc.key_pair.public().address_hash()).to_bytes() + script_pubkey: Builder::build_p2pkh(&arc.key_pair.public().address_hash()).to_bytes(), }; - let transaction = try_s!(arc.p2sh_spending_tx( - prev_tx, - redeem_script.into(), - vec![output], - script_data, - SEQUENCE_FINAL, - )); - let tx_fut = arc.rpc_client.send_transaction(&transaction, arc.my_address.clone()).compat(); + let transaction = + try_s!(arc.p2sh_spending_tx(prev_tx, redeem_script.into(), vec![output], script_data, SEQUENCE_FINAL,)); + let tx_fut = arc + .rpc_client + .send_transaction(&transaction, arc.my_address.clone()) + .compat(); try_s!(tx_fut.await); Ok(transaction.into()) }; @@ -1073,22 +1186,25 @@ impl SwapOps for UtxoCoin { .push_data(secret) .push_opcode(Opcode::OP_0) .into_script(); - let redeem_script = payment_script(time_lock, &*dhash160(secret), &try_fus!(Public::from_slice(maker_pub)), self.key_pair.public()); + let redeem_script = payment_script( + time_lock, + &*dhash160(secret), + &try_fus!(Public::from_slice(maker_pub)), + self.key_pair.public(), + ); let arc = self.clone(); let fut = async move { let fee = try_s!(arc.get_htlc_spend_fee().await); let output = TransactionOutput { value: prev_tx.outputs[0].value - fee, - script_pubkey: Builder::build_p2pkh(&arc.key_pair.public().address_hash()).to_bytes() + script_pubkey: Builder::build_p2pkh(&arc.key_pair.public().address_hash()).to_bytes(), }; - let transaction = try_s!(arc.p2sh_spending_tx( - prev_tx, - redeem_script.into(), - vec![output], - script_data, - SEQUENCE_FINAL, - )); - let tx_fut = arc.rpc_client.send_transaction(&transaction, arc.my_address.clone()).compat(); + let transaction = + try_s!(arc.p2sh_spending_tx(prev_tx, redeem_script.into(), vec![output], script_data, SEQUENCE_FINAL,)); + let tx_fut = arc + .rpc_client + .send_transaction(&transaction, arc.my_address.clone()) + .compat(); try_s!(tx_fut.await); Ok(transaction.into()) }; @@ -1103,16 +1219,19 @@ impl SwapOps for UtxoCoin { secret_hash: &[u8], ) -> TransactionFut { let prev_tx: UtxoTx = try_fus!(deserialize(taker_payment_tx).map_err(|e| ERRL!("{:?}", e))); - let script_data = Builder::default() - .push_opcode(Opcode::OP_1) - .into_script(); - let redeem_script = payment_script(time_lock, secret_hash, self.key_pair.public(), &try_fus!(Public::from_slice(maker_pub))); + let script_data = Builder::default().push_opcode(Opcode::OP_1).into_script(); + let redeem_script = payment_script( + time_lock, + secret_hash, + self.key_pair.public(), + &try_fus!(Public::from_slice(maker_pub)), + ); let arc = self.clone(); let fut = async move { let fee = try_s!(arc.get_htlc_spend_fee().await); let output = TransactionOutput { value: prev_tx.outputs[0].value - fee, - script_pubkey: Builder::build_p2pkh(&arc.key_pair.public().address_hash()).to_bytes() + script_pubkey: Builder::build_p2pkh(&arc.key_pair.public().address_hash()).to_bytes(), }; let transaction = try_s!(arc.p2sh_spending_tx( prev_tx, @@ -1121,7 +1240,10 @@ impl SwapOps for UtxoCoin { script_data, SEQUENCE_FINAL - 1, )); - let tx_fut = arc.rpc_client.send_transaction(&transaction, arc.my_address.clone()).compat(); + let tx_fut = arc + .rpc_client + .send_transaction(&transaction, arc.my_address.clone()) + .compat(); try_s!(tx_fut.await); Ok(transaction.into()) }; @@ -1136,9 +1258,7 @@ impl SwapOps for UtxoCoin { secret_hash: &[u8], ) -> TransactionFut { let prev_tx: UtxoTx = try_fus!(deserialize(maker_payment_tx).map_err(|e| ERRL!("{:?}", e))); - let script_data = Builder::default() - .push_opcode(Opcode::OP_1) - .into_script(); + let script_data = Builder::default().push_opcode(Opcode::OP_1).into_script(); let redeem_script = payment_script( time_lock, secret_hash, @@ -1150,7 +1270,7 @@ impl SwapOps for UtxoCoin { let fee = try_s!(arc.get_htlc_spend_fee().await); let output = TransactionOutput { value: prev_tx.outputs[0].value - fee, - script_pubkey: Builder::build_p2pkh(&arc.key_pair.public().address_hash()).to_bytes() + script_pubkey: Builder::build_p2pkh(&arc.key_pair.public().address_hash()).to_bytes(), }; let transaction = try_s!(arc.p2sh_spending_tx( prev_tx, @@ -1159,7 +1279,10 @@ impl SwapOps for UtxoCoin { script_data, SEQUENCE_FINAL - 1, )); - let tx_fut = arc.rpc_client.send_transaction(&transaction, arc.my_address.clone()).compat(); + let tx_fut = arc + .rpc_client + .send_transaction(&transaction, arc.my_address.clone()) + .compat(); try_s!(tx_fut.await); Ok(transaction.into()) }; @@ -1171,36 +1294,59 @@ impl SwapOps for UtxoCoin { fee_tx: &TransactionEnum, fee_addr: &[u8], amount: &BigDecimal, - ) -> Box + Send> { + ) -> Box + Send> { let selfi = self.clone(); let tx = match fee_tx { TransactionEnum::UtxoTx(tx) => tx.clone(), _ => panic!(), }; let amount = amount.clone(); - let address = try_fus!(address_from_raw_pubkey(fee_addr, selfi.pub_addr_prefix, selfi.pub_t_addr_prefix, selfi.checksum_type)); + let address = try_fus!(address_from_raw_pubkey( + fee_addr, + selfi.pub_addr_prefix, + selfi.pub_t_addr_prefix, + selfi.checksum_type + )); let fut = async move { let amount = try_s!(sat_from_big_decimal(&amount, selfi.decimals)); - let tx_from_rpc = try_s!(selfi.rpc_client.get_transaction_bytes(tx.hash().reversed().into()).compat().await); + let tx_from_rpc = try_s!( + selfi + .rpc_client + .get_transaction_bytes(tx.hash().reversed().into()) + .compat() + .await + ); if tx_from_rpc.0 != serialize(&tx).take() { - return ERR!("Provided dex fee tx {:?} doesn't match tx data from rpc {:?}", tx, tx_from_rpc); + return ERR!( + "Provided dex fee tx {:?} doesn't match tx data from rpc {:?}", + tx, + tx_from_rpc + ); } match tx.outputs.first() { Some(out) => { let expected_script_pubkey = Builder::build_p2pkh(&address.hash).to_bytes(); if out.script_pubkey != expected_script_pubkey { - return ERR!("Provided dex fee tx output script_pubkey doesn't match expected {:?} {:?}", out.script_pubkey, expected_script_pubkey); + return ERR!( + "Provided dex fee tx output script_pubkey doesn't match expected {:?} {:?}", + out.script_pubkey, + expected_script_pubkey + ); } if out.value < amount { - return ERR!("Provided dex fee tx output value is less than expected {:?} {:?}", out.value, amount); + return ERR!( + "Provided dex fee tx output value is less than expected {:?} {:?}", + out.value, + amount + ); } }, None => { return ERR!("Provided dex fee tx {:?} has no outputs", tx); - } + }, } Ok(()) }; @@ -1214,14 +1360,14 @@ impl SwapOps for UtxoCoin { maker_pub: &[u8], priv_bn_hash: &[u8], amount: BigDecimal, - ) -> Box + Send> { + ) -> Box + Send> { self.validate_payment( payment_tx, time_lock, &try_fus!(Public::from_slice(maker_pub)), self.key_pair.public(), priv_bn_hash, - amount + amount, ) } @@ -1232,14 +1378,14 @@ impl SwapOps for UtxoCoin { taker_pub: &[u8], priv_bn_hash: &[u8], amount: BigDecimal, - ) -> Box + Send> { + ) -> Box + Send> { self.validate_payment( payment_tx, time_lock, &try_fus!(Public::from_slice(taker_pub)), self.key_pair.public(), priv_bn_hash, - amount + amount, ) } @@ -1249,7 +1395,7 @@ impl SwapOps for UtxoCoin { other_pub: &[u8], secret_hash: &[u8], _from_block: u64, - ) -> Box, Error=String> + Send> { + ) -> Box, Error = String> + Send> { let script = payment_script( time_lock, secret_hash, @@ -1286,7 +1432,7 @@ impl SwapOps for UtxoCoin { if item.address == target_addr && !item.txids.is_empty() { let tx_bytes = try_s!(client.get_transaction_bytes(item.txids[0].clone()).compat().await); let tx: UtxoTx = try_s!(deserialize(tx_bytes.0.as_slice()).map_err(|e| ERRL!("{:?}", e))); - return Ok(Some(tx.into())) + return Ok(Some(tx.into())); } } Ok(None) @@ -1310,7 +1456,7 @@ impl SwapOps for UtxoCoin { &try_s!(Public::from_slice(other_pub)), secret_hash, tx, - search_from_block + search_from_block, ) } @@ -1328,29 +1474,34 @@ impl SwapOps for UtxoCoin { self.key_pair.public(), secret_hash, tx, - search_from_block + search_from_block, ) } } impl MarketCoinOps for UtxoCoin { - fn ticker (&self) -> &str {&self.ticker[..]} + fn ticker(&self) -> &str { &self.ticker[..] } - fn my_address(&self) -> Result { - self.display_address(&self.my_address) - } + fn my_address(&self) -> Result { self.display_address(&self.my_address) } - fn my_balance(&self) -> Box + Send> { - Box::new(self.rpc_client.display_balance(self.my_address.clone(), self.decimals).map_err(|e| ERRL!("{}", e))) + fn my_balance(&self) -> Box + Send> { + Box::new( + self.rpc_client + .display_balance(self.my_address.clone(), self.decimals) + .map_err(|e| ERRL!("{}", e)), + ) } - fn base_coin_balance(&self) -> Box + Send> { - self.my_balance() - } + fn base_coin_balance(&self) -> Box + Send> { self.my_balance() } - fn send_raw_tx(&self, tx: &str) -> Box + Send> { + fn send_raw_tx(&self, tx: &str) -> Box + Send> { let bytes = try_fus!(hex::decode(tx)); - Box::new(self.rpc_client.send_raw_transaction(bytes.into()).map_err(|e| ERRL!("{}", e)).map(|hash| format!("{:?}", hash))) + Box::new( + self.rpc_client + .send_raw_transaction(bytes.into()) + .map_err(|e| ERRL!("{}", e)) + .map(|hash| format!("{:?}", hash)), + ) } fn wait_for_confirmations( @@ -1360,15 +1511,10 @@ impl MarketCoinOps for UtxoCoin { requires_nota: bool, wait_until: u64, check_every: u64, - ) -> Box + Send> { + ) -> Box + Send> { let tx: UtxoTx = try_fus!(deserialize(tx).map_err(|e| ERRL!("{:?}", e))); - self.rpc_client.wait_for_confirmations( - &tx, - confirmations as u32, - requires_nota, - wait_until, - check_every, - ) + self.rpc_client + .wait_for_confirmations(&tx, confirmations as u32, requires_nota, wait_until, check_every) } fn wait_for_tx_spend(&self, tx_bytes: &[u8], wait_until: u64, from_block: u64) -> TransactionFut { @@ -1382,12 +1528,16 @@ impl MarketCoinOps for UtxoCoin { Ok(None) => (), Err(e) => { log!("Error " (e) " on find_output_spend of tx " [e]); - () }, }; if now_ms() / 1000 > wait_until { - return ERR!("Waited too long until {} for transaction {:?} {} to be spent ", wait_until, tx, vout); + return ERR!( + "Waited too long until {} for transaction {:?} {} to be spent ", + wait_until, + tx, + vout + ); } Timer::sleep(10.).await; } @@ -1400,29 +1550,40 @@ impl MarketCoinOps for UtxoCoin { Ok(transaction.into()) } - fn current_block(&self) -> Box + Send> { + fn current_block(&self) -> Box + Send> { Box::new(self.rpc_client.get_block_count().map_err(|e| ERRL!("{}", e))) } fn address_from_pubkey_str(&self, pubkey: &str) -> Result { let pubkey_bytes = try_s!(hex::decode(pubkey)); - let addr = try_s!(address_from_raw_pubkey(&pubkey_bytes, self.pub_addr_prefix, self.pub_t_addr_prefix, self.checksum_type)); + let addr = try_s!(address_from_raw_pubkey( + &pubkey_bytes, + self.pub_addr_prefix, + self.pub_t_addr_prefix, + self.checksum_type + )); self.display_address(&addr) } - fn display_priv_key(&self) -> String { - format!("{}", self.key_pair.private()) - } + fn display_priv_key(&self) -> String { format!("{}", self.key_pair.private()) } } async fn withdraw_impl(coin: UtxoCoin, req: WithdrawRequest) -> Result { let to = match &coin.address_format { UtxoAddressFormat::Standard => try_s!(Address::from_str(&req.to)), - UtxoAddressFormat::CashAddress {..} => try_s!(Address::from_cashaddress( - &req.to, coin.checksum_type.clone(),coin.pub_addr_prefix, coin.p2sh_addr_prefix)) + UtxoAddressFormat::CashAddress { .. } => try_s!(Address::from_cashaddress( + &req.to, + coin.checksum_type, + coin.pub_addr_prefix, + coin.p2sh_addr_prefix + )), }; if to.checksum_type != coin.checksum_type { - return ERR!("Address {} has invalid checksum type, it must be {:?}", to, coin.checksum_type); + return ERR!( + "Address {} has invalid checksum type, it must be {:?}", + to, + coin.checksum_type + ); } let script_pubkey = if to.prefix == coin.pub_addr_prefix && to.t_addr_prefix == coin.pub_t_addr_prefix { Builder::build_p2pkh(&to.hash).to_bytes() @@ -1432,25 +1593,45 @@ async fn withdraw_impl(coin: UtxoCoin, req: WithdrawRequest) -> Result Some(ActualTxFee::Fixed(try_s!(sat_from_big_decimal(&amount, coin.decimals)))), - Some(WithdrawFee::UtxoPerKbyte { amount }) => Some(ActualTxFee::Dynamic(try_s!(sat_from_big_decimal(&amount, coin.decimals)))), + Some(WithdrawFee::UtxoFixed { amount }) => { + Some(ActualTxFee::Fixed(try_s!(sat_from_big_decimal(&amount, coin.decimals)))) + }, + Some(WithdrawFee::UtxoPerKbyte { amount }) => Some(ActualTxFee::Dynamic(try_s!(sat_from_big_decimal( + &amount, + coin.decimals + )))), Some(_) => return ERR!("Unsupported input fee type"), None => None, }; let (unsigned, data) = try_s!(coin.generate_transaction(unspents, outputs, fee_policy, fee).await); let prev_script = Builder::build_p2pkh(&coin.my_address.hash); - let signed = try_s!(sign_tx(unsigned, &coin.key_pair, prev_script, coin.signature_version, coin.fork_id)); + let signed = try_s!(sign_tx( + unsigned, + &coin.key_pair, + prev_script, + coin.signature_version, + coin.fork_id + )); let fee_details = UtxoFeeDetails { amount: big_decimal_from_sat(data.fee_amount as i64, coin.decimals), }; @@ -1481,19 +1662,18 @@ pub struct UtxoFeeDetails { impl MmCoin for UtxoCoin { fn is_asset_chain(&self) -> bool { self.asset_chain } - fn can_i_spend_other_payment(&self) -> Box + Send> { + fn can_i_spend_other_payment(&self) -> Box + Send> { Box::new(futures01::future::ok(())) } - fn withdraw(&self, req: WithdrawRequest) -> Box + Send> { + fn withdraw(&self, req: WithdrawRequest) -> Box + Send> { let fut = withdraw_impl(self.clone(), req); Box::new(fut.boxed().compat()) } - fn decimals(&self) -> u8 { - self.decimals - } + fn decimals(&self) -> u8 { self.decimals } + #[allow(clippy::cognitive_complexity)] fn process_history_loop(&self, ctx: MmArc) { const HISTORY_TOO_LARGE_ERR_CODE: i64 = -1; let history_too_large = json!({ @@ -1503,50 +1683,60 @@ impl MmCoin for UtxoCoin { let mut my_balance: Option = None; let history = self.load_history_from_file(&ctx); - let mut history_map: HashMap = history.into_iter().map(|tx| (H256Json::from(tx.tx_hash.as_slice()), tx)).collect(); + let mut history_map: HashMap = history + .into_iter() + .map(|tx| (H256Json::from(tx.tx_hash.as_slice()), tx)) + .collect(); let my_address = match self.my_address() { Ok(addr) => addr, Err(e) => { log!("Error on getting self address: " [e] ". Stop tx history"); return; - } + }, }; let mut success_iteration = 0i32; loop { - if ctx.is_stopping() { break }; + if ctx.is_stopping() { + break; + }; { let coins_ctx = unwrap!(CoinsContext::from_ctx(&ctx)); - let coins = match coins_ctx.coins.spinlock (22) { - Ok (guard) => guard, - Err (_err) => {thread::sleep (Duration::from_millis (99)); continue} + let coins = match coins_ctx.coins.spinlock(22) { + Ok(guard) => guard, + Err(_err) => { + thread::sleep(Duration::from_millis(99)); + continue; + }, }; if !coins.contains_key(&self.ticker) { ctx.log.log("", &[&"tx_history", &self.ticker], "Loop stopped"); - break + break; }; } let actual_balance = match self.my_balance().wait() { Ok(actual_balance) => Some(actual_balance), Err(err) => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {:?} on getting balance", err)); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("Error {:?} on getting balance", err), + ); None }, }; let need_update = history_map .iter() - .find(|(_, tx)| tx.should_update_timestamp() || tx.should_update_block_height()) - .is_some(); + .any(|(_, tx)| tx.should_update_timestamp() || tx.should_update_block_height()); match (&my_balance, &actual_balance) { - (Some(prev_balance), Some(actual_balance)) - if prev_balance == actual_balance && !need_update => { + (Some(prev_balance), Some(actual_balance)) if prev_balance == actual_balance && !need_update => { // my balance hasn't been changed, there is no need to reload tx_history thread::sleep(Duration::from_secs(30)); continue; }, - _ => () + _ => (), } let tx_ids: Vec<(H256Json, u64)> = match &self.rpc_client { @@ -1560,10 +1750,14 @@ impl MmCoin for UtxoCoin { let transactions = match client.list_transactions(100, from).wait() { Ok(value) => value, Err(e) => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {} on list transactions, retrying", e)); + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("Error {} on list transactions, retrying", e), + ); thread::sleep(Duration::from_secs(10)); continue; - } + }, }; mm_counter!(ctx.metrics, "tx.history.response.count", 1, @@ -1579,13 +1773,16 @@ impl MmCoin for UtxoCoin { mm_counter!(ctx.metrics, "tx.history.response.total_length", all_transactions.len() as u64, "coin" => self.ticker.clone(), "client" => "native", "method" => "listtransactions"); - all_transactions.into_iter().filter_map(|item| { - if item.address == my_address { - Some((item.txid, item.blockindex)) - } else { - None - } - }).collect() + all_transactions + .into_iter() + .filter_map(|item| { + if item.address == my_address { + Some((item.txid, item.blockindex)) + } else { + None + } + }) + .collect() }, UtxoRpcClientEnum::Electrum(client) => { let script = Builder::build_p2pkh(&self.my_address.hash); @@ -1596,29 +1793,39 @@ impl MmCoin for UtxoCoin { let electrum_history = match client.scripthash_get_history(&hex::encode(script_hash)).wait() { Ok(value) => value, - Err(e) => { - match &e.error { - JsonRpcErrorType::Transport(e) | JsonRpcErrorType::Parse(_, e) => { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {} on scripthash_get_history, retrying", e)); + Err(e) => match &e.error { + JsonRpcErrorType::Transport(e) | JsonRpcErrorType::Parse(_, e) => { + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("Error {} on scripthash_get_history, retrying", e), + ); + thread::sleep(Duration::from_secs(10)); + continue; + }, + JsonRpcErrorType::Response(_addr, err) => { + if *err == history_too_large { + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("Got `history too large`, stopping further attempts to retrieve it"), + ); + *unwrap!(self.history_sync_state.lock()) = HistorySyncState::Error(json!({ + "code": HISTORY_TOO_LARGE_ERR_CODE, + "message": "Got `history too large` error from Electrum server. History is not available", + })); + break; + } else { + ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("Error {:?} on scripthash_get_history, retrying", e), + ); thread::sleep(Duration::from_secs(10)); continue; - }, - JsonRpcErrorType::Response(_addr, err) => { - if *err == history_too_large { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Got `history too large`, stopping further attempts to retrieve it")); - *unwrap!(self.history_sync_state.lock()) = HistorySyncState::Error(json!({ - "code": HISTORY_TOO_LARGE_ERR_CODE, - "message": "Got `history too large` error from Electrum server. History is not available", - })); - break; - } else { - ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {:?} on scripthash_get_history, retrying", e)); - thread::sleep(Duration::from_secs(10)); - continue; - } } - } - } + }, + }, }; mm_counter!(ctx.metrics, "tx.history.response.count", 1, "coin" => self.ticker.clone(), "client" => "electrum", "method" => "blockchain.scripthash.get_history"); @@ -1628,15 +1835,15 @@ impl MmCoin for UtxoCoin { // electrum returns the most recent transactions in the end but we need to // process them first so rev is required - electrum_history.into_iter().rev().map(|item| { - let height = if item.height < 0 { - 0 - } else { - item.height as u64 - }; - (item.tx_hash, height) - }).collect() - } + electrum_history + .into_iter() + .rev() + .map(|item| { + let height = if item.height < 0 { 0 } else { item.height as u64 }; + (item.tx_hash, height) + }) + .collect() + }, }; let mut transactions_left = if tx_ids.len() > history_map.len() { *unwrap!(self.history_sync_state.lock()) = HistorySyncState::InProgress(json!({ @@ -1667,13 +1874,16 @@ impl MmCoin for UtxoCoin { e.insert(tx_details); if transactions_left > 0 { transactions_left -= 1; - *unwrap!(self.history_sync_state.lock()) = HistorySyncState::InProgress(json!({ - "transactions_left": transactions_left - })); + *unwrap!(self.history_sync_state.lock()) = + HistorySyncState::InProgress(json!({ "transactions_left": transactions_left })); } updated = true; }, - Err(e) => ctx.log.log("", &[&"tx_history", &self.ticker], &ERRL!("Error {:?} on getting the details of {:?}, skipping the tx", e, txid)), + Err(e) => ctx.log.log( + "", + &[&"tx_history", &self.ticker], + &ERRL!("Error {:?} on getting the details of {:?}, skipping the tx", e, txid), + ), } }, Entry::Occupied(mut e) => { @@ -1692,17 +1902,19 @@ impl MmCoin for UtxoCoin { updated = true; } } - } + }, } if updated { let mut to_write: Vec<&TransactionDetails> = history_map.iter().map(|(_, value)| value).collect(); // the transactions with block_height == 0 are the most recent so we need to separately handle them while sorting - to_write.sort_unstable_by(|a, b| if a.block_height == 0 { - Ordering::Less - } else if b.block_height == 0 { - Ordering::Greater - } else { - b.block_height.cmp(&a.block_height) + to_write.sort_unstable_by(|a, b| { + if a.block_height == 0 { + Ordering::Less + } else if b.block_height == 0 { + Ordering::Greater + } else { + b.block_height.cmp(&a.block_height) + } }); self.save_history_to_file(&unwrap!(json::to_vec(&to_write)), &ctx); } @@ -1710,7 +1922,11 @@ impl MmCoin for UtxoCoin { *unwrap!(self.history_sync_state.lock()) = HistorySyncState::Finished; if success_iteration == 0 { - ctx.log.log("😅", &[&"tx_history", &("coin", self.ticker.clone().as_str())], "history has been loaded successfully"); + ctx.log.log( + "😅", + &[&"tx_history", &("coin", self.ticker.clone().as_str())], + "history has been loaded successfully", + ); } my_balance = actual_balance; @@ -1719,7 +1935,7 @@ impl MmCoin for UtxoCoin { } } - fn tx_details_by_hash(&self, hash: &[u8]) -> Box + Send> { + fn tx_details_by_hash(&self, hash: &[u8]) -> Box + Send> { let hash = H256Json::from(hash); let selfi = self.clone(); let fut = async move { @@ -1741,19 +1957,31 @@ impl MmCoin for UtxoCoin { let input_tx = match input_transactions.entry(&input.previous_output.hash) { Entry::Vacant(e) => { let prev_hash = input.previous_output.hash.reversed(); - let prev: BytesJson = try_s!(selfi.rpc_client.get_transaction_bytes(prev_hash.clone().into()).compat().await); - let prev_tx: UtxoTx = try_s!(deserialize(prev.as_slice()).map_err(|e| ERRL!("{:?}, tx: {:?}", e, prev_hash))); + let prev: BytesJson = try_s!( + selfi + .rpc_client + .get_transaction_bytes(prev_hash.clone().into()) + .compat() + .await + ); + let prev_tx: UtxoTx = + try_s!(deserialize(prev.as_slice()).map_err(|e| ERRL!("{:?}, tx: {:?}", e, prev_hash))); e.insert(prev_tx) }, Entry::Occupied(e) => e.into_mut(), }; input_amount += input_tx.outputs[input.previous_output.index as usize].value; - let from: Vec
= try_s!(selfi.addresses_from_script(&input_tx.outputs[input.previous_output.index as usize].script_pubkey.clone().into())); + let from: Vec
= try_s!(selfi.addresses_from_script( + &input_tx.outputs[input.previous_output.index as usize] + .script_pubkey + .clone() + .into() + )); if from.contains(&selfi.my_address) { spent_by_me += input_tx.outputs[input.previous_output.index as usize].value; } from_addresses.push(from); - }; + } for output in tx.outputs.iter() { output_amount += output.value; @@ -1765,12 +1993,18 @@ impl MmCoin for UtxoCoin { } // remove address duplicates in case several inputs were spent from same address // or several outputs are sent to same address - let mut from_addresses: Vec = - try_s!(from_addresses.into_iter().flatten().map(|addr| selfi.display_address(&addr)).collect()); + let mut from_addresses: Vec = try_s!(from_addresses + .into_iter() + .flatten() + .map(|addr| selfi.display_address(&addr)) + .collect()); from_addresses.sort(); from_addresses.dedup(); - let mut to_addresses: Vec = - try_s!(to_addresses.into_iter().flatten().map(|addr| selfi.display_address(&addr)).collect()); + let mut to_addresses: Vec = try_s!(to_addresses + .into_iter() + .flatten() + .map(|addr| selfi.display_address(&addr)) + .collect()); to_addresses.sort(); to_addresses.dedup(); @@ -1784,9 +2018,7 @@ impl MmCoin for UtxoCoin { total_amount: big_decimal_from_sat(input_amount as i64, selfi.decimals), tx_hash: tx.hash().reversed().to_vec().into(), tx_hex: verbose_tx.hex, - fee_details: Some(UtxoFeeDetails { - amount: fee, - }.into()), + fee_details: Some(UtxoFeeDetails { amount: fee }.into()), block_height: verbose_tx.height.unwrap_or(0), coin: selfi.ticker.clone(), internal_id: tx.hash().reversed().to_vec().into(), @@ -1796,11 +2028,9 @@ impl MmCoin for UtxoCoin { Box::new(fut.boxed().compat()) } - fn history_sync_status(&self) -> HistorySyncState { - unwrap!(self.history_sync_state.lock()).clone() - } + fn history_sync_status(&self) -> HistorySyncState { unwrap!(self.history_sync_state.lock()).clone() } - fn get_trade_fee(&self) -> Box + Send> { + fn get_trade_fee(&self) -> Box + Send> { let ticker = self.ticker.clone(); let decimals = self.decimals; let arc = self.clone(); @@ -1818,18 +2048,18 @@ impl MmCoin for UtxoCoin { Box::new(fut.boxed().compat()) } - fn required_confirmations(&self) -> u64 { - self.required_confirmations.load(AtomicOrderding::Relaxed) - } + fn required_confirmations(&self) -> u64 { self.required_confirmations.load(AtomicOrderding::Relaxed) } fn requires_notarization(&self) -> bool { self.requires_notarization.load(AtomicOrderding::Relaxed) } fn set_required_confirmations(&self, confirmations: u64) { - self.required_confirmations.store(confirmations, AtomicOrderding::Relaxed); + self.required_confirmations + .store(confirmations, AtomicOrderding::Relaxed); } fn set_requires_notarization(&self, requires_nota: bool) { - self.requires_notarization.store(requires_nota, AtomicOrderding::Relaxed); + self.requires_notarization + .store(requires_nota, AtomicOrderding::Relaxed); } } @@ -1837,11 +2067,14 @@ impl MmCoin for UtxoCoin { // https://github.com/KomodoPlatform/komodo/blob/master/zcutil/fetch-params.sh#L5 // https://github.com/KomodoPlatform/komodo/blob/master/zcutil/fetch-params.bat#L4 pub fn zcash_params_path() -> PathBuf { - if cfg! (windows) { + if cfg!(windows) { // >= Vista: c:\Users\$username\AppData\Roaming get_special_folder_path().join("ZcashParams") - } else if cfg! (target_os = "macos") { - unwrap!(home_dir()).join("Library").join("Application Support").join("ZcashParams") + } else if cfg!(target_os = "macos") { + unwrap!(home_dir()) + .join("Library") + .join("Application Support") + .join("ZcashParams") } else { unwrap!(home_dir()).join(".zcash-params") } @@ -1851,80 +2084,84 @@ pub fn zcash_params_path() -> PathBuf { pub fn coin_daemon_data_dir(name: &str, is_asset_chain: bool) -> PathBuf { // komodo/util.cpp/GetDefaultDataDir let mut data_dir = match dirs::home_dir() { - Some (hd) => hd, - None => Path::new ("/") .to_path_buf() + Some(hd) => hd, + None => Path::new("/").to_path_buf(), }; - if cfg! (windows) { + if cfg!(windows) { // >= Vista: c:\Users\$username\AppData\Roaming data_dir = get_special_folder_path(); if is_asset_chain { - data_dir.push ("Komodo"); + data_dir.push("Komodo"); } else { - data_dir.push (first_char_to_upper(name)); + data_dir.push(first_char_to_upper(name)); } - } else if cfg! (target_os = "macos") { - data_dir.push ("Library"); - data_dir.push ("Application Support"); + } else if cfg!(target_os = "macos") { + data_dir.push("Library"); + data_dir.push("Application Support"); if is_asset_chain { - data_dir.push ("Komodo"); + data_dir.push("Komodo"); } else { - data_dir.push (first_char_to_upper(name)); + data_dir.push(first_char_to_upper(name)); } + } else if is_asset_chain { + data_dir.push(".komodo"); } else { - if is_asset_chain { - data_dir.push (".komodo"); - } else { - data_dir.push (format!(".{}", name)); - } + data_dir.push(format!(".{}", name)); } - if is_asset_chain {data_dir.push (name)}; + if is_asset_chain { + data_dir.push(name) + }; data_dir } #[cfg(not(feature = "native"))] -pub fn coin_daemon_data_dir(_name: &str, _is_asset_chain: bool) -> PathBuf { - unimplemented!() -} +pub fn coin_daemon_data_dir(_name: &str, _is_asset_chain: bool) -> PathBuf { unimplemented!() } #[cfg(feature = "native")] /// Returns a path to the native coin wallet configuration. /// (This path is used in to read the wallet credentials). /// cf. https://github.com/artemii235/SuperNET/issues/346 -fn confpath (coins_en: &Json) -> Result { +fn confpath(coins_en: &Json) -> Result { // Documented at https://github.com/jl777/coins#bitcoin-protocol-specific-json // "USERHOME/" prefix should be replaced with the user's home folder. - let confpathˢ = coins_en["confpath"].as_str().unwrap_or ("") .trim(); + let confpathˢ = coins_en["confpath"].as_str().unwrap_or("").trim(); if confpathˢ.is_empty() { let (name, is_asset_chain) = { match coins_en["asset"].as_str() { Some(a) => (a, true), - None => (try_s!(coins_en["name"].as_str().ok_or("'name' field is not found in config")), false), + None => ( + try_s!(coins_en["name"].as_str().ok_or("'name' field is not found in config")), + false, + ), } }; let data_dir = coin_daemon_data_dir(name, is_asset_chain); - let confname = format! ("{}.conf", name); + let confname = format!("{}.conf", name); - return Ok (data_dir.join (&confname[..])) + return Ok(data_dir.join(&confname[..])); } - let (confpathˢ, rel_to_home) = - if confpathˢ.starts_with ("~/") {(&confpathˢ[2..], true)} - else if confpathˢ.starts_with ("USERHOME/") {(&confpathˢ[9..], true)} - else {(confpathˢ, false)}; + let (confpathˢ, rel_to_home) = if confpathˢ.starts_with("~/") { + (&confpathˢ[2..], true) + } else if confpathˢ.starts_with("USERHOME/") { + (&confpathˢ[9..], true) + } else { + (confpathˢ, false) + }; if rel_to_home { - let home = try_s! (home_dir().ok_or ("Can not detect the user home directory")); - Ok (home.join (confpathˢ)) + let home = try_s!(home_dir().ok_or("Can not detect the user home directory")); + Ok(home.join(confpathˢ)) } else { - Ok (confpathˢ.into()) + Ok(confpathˢ.into()) } } #[cfg(not(feature = "native"))] -fn confpath (_coins_en: &Json) -> Result {unimplemented!()} +fn confpath(_coins_en: &Json) -> Result { unimplemented!() } /// Attempts to parse native daemon conf file and return rpcport, rpcuser and rpcpassword #[cfg(feature = "native")] @@ -1933,15 +2170,27 @@ fn read_native_mode_conf(filename: &dyn AsRef) -> Result<(Option, Str let conf: Ini = match Ini::load_from_file(&filename) { Ok(ini) => ini, - Err(err) => return ERR!("Error parsing the native wallet configuration '{}': {}", filename.as_ref().display(), err) + Err(err) => { + return ERR!( + "Error parsing the native wallet configuration '{}': {}", + filename.as_ref().display(), + err + ) + }, }; let section = conf.general_section(); let rpc_port = match section.get("rpcport") { Some(port) => port.parse::().ok(), None => None, }; - let rpc_user = try_s!(section.get("rpcuser").ok_or(ERRL!("Conf file {} doesn't have the rpcuser key", filename.as_ref().display()))); - let rpc_password = try_s!(section.get("rpcpassword").ok_or(ERRL!("Conf file {} doesn't have the rpcpassword key", filename.as_ref().display()))); + let rpc_user = try_s!(section.get("rpcuser").ok_or(ERRL!( + "Conf file {} doesn't have the rpcuser key", + filename.as_ref().display() + ))); + let rpc_password = try_s!(section.get("rpcpassword").ok_or(ERRL!( + "Conf file {} doesn't have the rpcpassword key", + filename.as_ref().display() + ))); Ok((rpc_port, rpc_user.clone(), rpc_password.clone())) } @@ -1957,22 +2206,19 @@ struct ElectrumProtoVerifier { } impl ElectrumProtoVerifier { - fn into_shared(self) -> RpcTransportEventHandlerShared { - Arc::new(self) - } + fn into_shared(self) -> RpcTransportEventHandlerShared { Arc::new(self) } } impl RpcTransportEventHandler for ElectrumProtoVerifier { - fn debug_info(&self) -> String { - "ElectrumProtoVerifier".into() - } + fn debug_info(&self) -> String { "ElectrumProtoVerifier".into() } fn on_outgoing_request(&self, _data: &[u8]) {} fn on_incoming_response(&self, _data: &[u8]) {} fn on_connected(&self, address: String) -> Result<(), String> { - Ok(try_s!(self.on_connect_tx.unbounded_send(address))) + try_s!(self.on_connect_tx.unbounded_send(address)); + Ok(()) } } @@ -1991,8 +2237,10 @@ pub async fn utxo_coin_from_conf_and_request( ChecksumType::DSHA256 }; - let pub_addr_prefix = conf["pubtype"].as_u64().unwrap_or (if ticker == "BTC" {0} else {60}) as u8; - let wif_prefix = conf["wiftype"].as_u64().unwrap_or (if ticker == "BTC" {128} else {188}) as u8; + let pub_addr_prefix = conf["pubtype"].as_u64().unwrap_or(if ticker == "BTC" { 0 } else { 60 }) as u8; + let wif_prefix = conf["wiftype"] + .as_u64() + .unwrap_or(if ticker == "BTC" { 128 } else { 188 }) as u8; let private = Private { prefix: wif_prefix, @@ -2004,7 +2252,7 @@ pub async fn utxo_coin_from_conf_and_request( let key_pair = try_s!(KeyPair::from_private(private)); let my_address = Address { prefix: pub_addr_prefix, - t_addr_prefix: conf["taddr"].as_u64().unwrap_or (0) as u8, + t_addr_prefix: conf["taddr"].as_u64().unwrap_or(0) as u8, hash: key_pair.public().address_hash(), checksum_type, }; @@ -2023,9 +2271,15 @@ pub async fn utxo_coin_from_conf_and_request( let auth_str = fomat!((rpc_user)":"(rpc_password)); let rpc_port = match rpc_port { Some(p) => p, - None => try_s!(conf["rpcport"].as_u64().ok_or(ERRL!("Rpc port is not set neither in `coins` file nor in native daemon config"))) as u16, + None => try_s!(conf["rpcport"].as_u64().ok_or(ERRL!( + "Rpc port is not set neither in `coins` file nor in native daemon config" + ))) as u16, }; - let event_handlers = vec![CoinTransportMetrics::new(ctx.metrics.weak(), ticker.to_owned(), RpcClientType::Native).into_shared()]; + let event_handlers = + vec![ + CoinTransportMetrics::new(ctx.metrics.weak(), ticker.to_owned(), RpcClientType::Native) + .into_shared(), + ]; let client = Arc::new(NativeClientImpl { coin_ticker: ticker.to_string(), uri: fomat!("http://127.0.0.1:"(rpc_port)), @@ -2052,7 +2306,7 @@ pub async fn utxo_coin_from_conf_and_request( for server in servers.iter() { match client.add_server(server).await { Ok(_) => (), - Err(e) => log!("Error " (e) " connecting to " [server] ". Address won't be used") + Err(e) => log!("Error " (e) " connecting to " [server] ". Address won't be used"), }; } @@ -2081,19 +2335,19 @@ pub async fn utxo_coin_from_conf_and_request( _ => return ERR!("utxo_coin_from_conf_and_request should be called only by enable or electrum requests"), }; let asset_chain = conf["asset"].as_str().is_some(); - let tx_version = conf["txversion"].as_i64().unwrap_or (1) as i32; - let overwintered = conf["overwintered"].as_u64().unwrap_or (0) == 1; + let tx_version = conf["txversion"].as_i64().unwrap_or(1) as i32; + let overwintered = conf["overwintered"].as_u64().unwrap_or(0) == 1; let tx_fee = match conf["txfee"].as_u64() { None => TxFee::Fixed(1000), - Some (0) => { + Some(0) => { let fee_method = match &rpc_client { UtxoRpcClientEnum::Electrum(_) => EstimateFeeMethod::Standard, - UtxoRpcClientEnum::Native(client) => try_s!(client.detect_fee_method().compat().await) + UtxoRpcClientEnum::Native(client) => try_s!(client.detect_fee_method().compat().await), }; TxFee::Dynamic(fee_method) }, - Some (fee) => TxFee::Fixed(fee), + Some(fee) => TxFee::Fixed(fee), }; let version_group_id = match conf["version_group_id"].as_str() { Some(mut s) => { @@ -2103,13 +2357,15 @@ pub async fn utxo_coin_from_conf_and_request( let bytes = try_s!(hex::decode(s)); u32::from_be_bytes(try_s!(bytes.as_slice().try_into())) }, - None => if tx_version == 3 && overwintered { - 0x03c48270 - } else if tx_version == 4 && overwintered { - 0x892f2085 - } else { - 0 - } + None => { + if tx_version == 3 && overwintered { + 0x03c4_8270 + } else if tx_version == 4 && overwintered { + 0x892f_2085 + } else { + 0 + } + }, }; let consensus_branch_id = match conf["consensus_branch_id"].as_str() { @@ -2120,16 +2376,14 @@ pub async fn utxo_coin_from_conf_and_request( let bytes = try_s!(hex::decode(s)); u32::from_be_bytes(try_s!(bytes.as_slice().try_into())) }, - None => { - match tx_version { - 3 => 0x5ba81b19, - 4 => 0x76b809bb, - _ => 0, - } + None => match tx_version { + 3 => 0x5ba8_1b19, + 4 => 0x76b8_09bb, + _ => 0, }, }; - let decimals = conf["decimals"].as_u64().unwrap_or (8) as u8; + let decimals = conf["decimals"].as_u64().unwrap_or(8) as u8; let (signature_version, fork_id) = if ticker == "BCH" { (SignatureVersion::ForkId, 0x40) @@ -2146,12 +2400,13 @@ pub async fn utxo_coin_from_conf_and_request( }; // param from request should override the config - let required_confirmations = req["required_confirmations"].as_u64().unwrap_or( - conf["required_confirmations"].as_u64().unwrap_or(1) - ); - let requires_notarization = req["requires_notarization"].as_bool().unwrap_or( - conf["requires_notarization"].as_bool().unwrap_or(false) - ).into(); + let required_confirmations = req["required_confirmations"] + .as_u64() + .unwrap_or_else(|| conf["required_confirmations"].as_u64().unwrap_or(1)); + let requires_notarization = req["requires_notarization"] + .as_bool() + .unwrap_or_else(|| conf["requires_notarization"].as_bool().unwrap_or(false)) + .into(); let coin = UtxoCoinImpl { ticker: ticker.into(), @@ -2162,10 +2417,12 @@ pub async fn utxo_coin_from_conf_and_request( requires_notarization, overwintered, pub_addr_prefix, - p2sh_addr_prefix: conf["p2shtype"].as_u64().unwrap_or (if ticker == "BTC" {5} else {85}) as u8, - pub_t_addr_prefix: conf["taddr"].as_u64().unwrap_or (0) as u8, - p2sh_t_addr_prefix: conf["taddr"].as_u64().unwrap_or (0) as u8, - segwit: conf["segwit"].as_bool().unwrap_or (false), + p2sh_addr_prefix: conf["p2shtype"] + .as_u64() + .unwrap_or(if ticker == "BTC" { 5 } else { 85 }) as u8, + pub_t_addr_prefix: conf["taddr"].as_u64().unwrap_or(0) as u8, + p2sh_t_addr_prefix: conf["taddr"].as_u64().unwrap_or(0) as u8, + segwit: conf["segwit"].as_bool().unwrap_or(false), wif_prefix, tx_version, my_address: my_address.clone(), @@ -2180,8 +2437,8 @@ pub async fn utxo_coin_from_conf_and_request( fork_id, history_sync_state: Mutex::new(initial_history_state), required_confirmations: required_confirmations.into(), - force_min_relay_fee: conf["force_min_relay_fee"].as_bool().unwrap_or (false), - mtp_block_count: json::from_value(conf["mtp_block_count"].clone()).unwrap_or (KMD_MTP_BLOCK_COUNT), + force_min_relay_fee: conf["force_min_relay_fee"].as_bool().unwrap_or(false), + mtp_block_count: json::from_value(conf["mtp_block_count"].clone()).unwrap_or(KMD_MTP_BLOCK_COUNT), estimate_fee_mode: json::from_value(conf["estimate_fee_mode"].clone()).unwrap_or(None), }; Ok(UtxoCoin(Arc::new(coin))) @@ -2230,13 +2487,17 @@ fn spawn_electrum_version_loop( }; let available_protocols = client.protocol_version(); - let version = match client.server_version(&electrum_addr, &client_name, available_protocols).compat().await { + let version = match client + .server_version(&electrum_addr, &client_name, available_protocols) + .compat() + .await + { Ok(version) => version, Err(e) => { log!("Electrum " (electrum_addr) " server.version error \"" [e] "\". Remove the connection"); remove_server(client, &electrum_addr).await; - continue - } + continue; + }, }; // check if the version is allowed @@ -2245,14 +2506,14 @@ fn spawn_electrum_version_loop( Err(e) => { log!("Error on parse protocol_version "[e]); remove_server(client, &electrum_addr).await; - continue + continue; }, }; if !available_protocols.contains(&actual_version) { log!("Received unsupported protocol version " [actual_version] " from " [electrum_addr] ". Remove the connection"); remove_server(client, &electrum_addr).await; - continue + continue; } if let Err(e) = client.set_protocol_version(&electrum_addr, actual_version).await { @@ -2276,7 +2537,10 @@ async fn wait_for_protocol_version_checked(client: &ElectrumClientImpl) -> Resul if client.count_connections().await == 0 { // All of the connections were removed because of server.version checking - return ERR!("There are no Electrums with the required protocol version {:?}", client.protocol_version()); + return ERR!( + "There are no Electrums with the required protocol version {:?}", + client.protocol_version() + ); } if client.is_protocol_version_checked().await { @@ -2298,25 +2562,39 @@ fn kmd_interest(height: Option, value: u64, lock_time: u64, current_time: u Some(h) => h, None => return 0, // return 0 if height is unknown }; - const KOMODO_ENDOFERA: u64 = 7777777; - const LOCKTIME_THRESHOLD: u64 = 500000000; + const KOMODO_ENDOFERA: u64 = 7_777_777; + const LOCKTIME_THRESHOLD: u64 = 500_000_000; // value must be at least 10 KMD - if value < 1000000000 { return 0; } - // interest will stop accrue after block 7777777 - if height >= KOMODO_ENDOFERA { return 0 }; - // interest doesn't accrue for lock_time < 500000000 - if lock_time < LOCKTIME_THRESHOLD { return 0; } + if value < 1_000_000_000 { + return 0; + } + // interest will stop accrue after block 7_777_777 + if height >= KOMODO_ENDOFERA { + return 0; + }; + // interest doesn't accrue for lock_time < 500_000_000 + if lock_time < LOCKTIME_THRESHOLD { + return 0; + } // current time must be greater than tx lock_time - if current_time < lock_time { return 0; } + if current_time < lock_time { + return 0; + } let mut minutes = (current_time - lock_time) / 60; // at least 1 hour should pass - if minutes < 60 { return 0; } + if minutes < 60 { + return 0; + } // interest stop accruing after 1 year before block 1000000 - if minutes > 365 * 24 * 60 { minutes = 365 * 24 * 60 }; + if minutes > 365 * 24 * 60 { + minutes = 365 * 24 * 60 + }; // interest stop accruing after 1 month past 1000000 block - if height >= 1000000 && minutes > 31 * 24 * 60 { minutes = 31 * 24 * 60; } + if height >= 1_000_000 && minutes > 31 * 24 * 60 { + minutes = 31 * 24 * 60; + } // next 2 lines ported as is from Komodo codebase minutes -= 59; - (value / 10512000) * minutes + (value / 10_512_000) * minutes } diff --git a/mm2src/coins/utxo/rpc_clients.rs b/mm2src/coins/utxo/rpc_clients.rs index 5377a04aae..609d700649 100644 --- a/mm2src/coins/utxo/rpc_clients.rs +++ b/mm2src/coins/utxo/rpc_clients.rs @@ -2,50 +2,48 @@ #![cfg_attr(not(feature = "native"), allow(unused_macros))] #![cfg_attr(not(feature = "native"), allow(dead_code))] +use crate::{RpcTransportEventHandler, RpcTransportEventHandlerShared}; use bigdecimal::BigDecimal; -use bytes::{BytesMut}; +use bytes::BytesMut; use chain::{BlockHeader, OutPoint, Transaction as UtxoTx}; -use common::{median, OrdRange, StringError}; use common::custom_futures::{join_all_sequential, select_ok_sequential}; use common::executor::{spawn, Timer}; -use common::jsonrpc_client::{JsonRpcClient, JsonRpcMultiClient, JsonRpcRemoteAddr, JsonRpcResponseFut, JsonRpcRequest, JsonRpcResponse, RpcRes}; -use common::wio::{slurp_req}; -use crate::{RpcTransportEventHandler, RpcTransportEventHandlerShared}; -use futures01::{Future, Poll, Sink, Stream}; -use futures01::future::{Either, loop_fn, Loop, select_ok}; -use futures01::sync::{mpsc, oneshot}; +use common::jsonrpc_client::{JsonRpcClient, JsonRpcMultiClient, JsonRpcRemoteAddr, JsonRpcRequest, JsonRpcResponse, + JsonRpcResponseFut, RpcRes}; +use common::wio::slurp_req; +use common::{median, OrdRange, StringError}; use futures::channel::oneshot as async_oneshot; -use futures::compat::{Future01CompatExt}; #[cfg(not(feature = "native"))] use futures::channel::oneshot::Sender as ShotSender; -use futures::future::{FutureExt, select as select_func, TryFutureExt}; -use futures::lock::{Mutex as AsyncMutex}; +use futures::compat::Future01CompatExt; +use futures::future::{select as select_func, FutureExt, TryFutureExt}; +use futures::lock::Mutex as AsyncMutex; use futures::select; +use futures01::future::{loop_fn, select_ok, Either, Loop}; +use futures01::sync::{mpsc, oneshot}; +use futures01::{Future, Poll, Sink, Stream}; use futures_timer::{Delay, FutureExt as FutureTimerExt}; use gstuff::{now_float, now_ms}; -use http::{Request, StatusCode}; use http::header::AUTHORIZATION; use http::Uri; +use http::{Request, StatusCode}; use keys::Address; -#[cfg(test)] -use mocktopus::macros::*; -use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json, Transaction as RpcTransaction, VerboseBlockClient}; +#[cfg(test)] use mocktopus::macros::*; +use rpc::v1::types::{Bytes as BytesJson, Transaction as RpcTransaction, VerboseBlockClient, H256 as H256Json}; #[cfg(feature = "native")] use rustls::{self, ClientConfig, Session}; -use script::{Builder}; +use script::Builder; use serde_json::{self as json, Value as Json}; -use serialization::{serialize, deserialize, CompactInteger, Reader}; -use sha2::{Sha256, Digest}; -use std::collections::hash_map::{HashMap, Entry}; -use std::io; -use std::fmt; +use serialization::{deserialize, serialize, CompactInteger, Reader}; +use sha2::{Digest, Sha256}; use std::cmp::Ordering; -use std::net::{ToSocketAddrs, SocketAddr}; +use std::collections::hash_map::{Entry, HashMap}; +use std::fmt; +use std::io; +use std::net::{SocketAddr, ToSocketAddrs}; use std::num::NonZeroU64; use std::ops::Deref; -#[cfg(not(feature = "native"))] -use std::os::raw::c_char; -use std::sync::{Arc}; +#[cfg(not(feature = "native"))] use std::os::raw::c_char; use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering}; use std::time::{Duration}; #[cfg(feature = "native")] @@ -67,11 +65,13 @@ pub struct NoCertificateVerification {} #[cfg(feature = "native")] impl rustls::ServerCertVerifier for NoCertificateVerification { - fn verify_server_cert(&self, - _roots: &rustls::RootCertStore, - _presented_certs: &[rustls::Certificate], - _dns_name: DNSNameRef<'_>, - _ocsp: &[u8]) -> Result { + fn verify_server_cert( + &self, + _roots: &rustls::RootCertStore, + _presented_certs: &[rustls::Certificate], + _dns_name: DNSNameRef<'_>, + _ocsp: &[u8], + ) -> Result { Ok(rustls::ServerCertVerified::assertion()) } } @@ -84,17 +84,15 @@ pub enum UtxoRpcClientEnum { } impl From for UtxoRpcClientEnum { - fn from(client: ElectrumClient) -> UtxoRpcClientEnum { - UtxoRpcClientEnum::Electrum(client) - } + fn from(client: ElectrumClient) -> UtxoRpcClientEnum { UtxoRpcClientEnum::Electrum(client) } } impl Deref for UtxoRpcClientEnum { type Target = dyn UtxoRpcClientOps; fn deref(&self) -> &dyn UtxoRpcClientOps { match self { - &UtxoRpcClientEnum::Native(ref c) => c, - &UtxoRpcClientEnum::Electrum(ref c) => c, + UtxoRpcClientEnum::Native(ref c) => c, + UtxoRpcClientEnum::Electrum(ref c) => c, } } } @@ -109,16 +107,32 @@ impl Clone for UtxoRpcClientEnum { } impl UtxoRpcClientEnum { - pub fn wait_for_confirmations(&self, tx: &UtxoTx, confirmations: u32, requires_notarization: bool, wait_until: u64, check_every: u64) -> Box + Send> { + pub fn wait_for_confirmations( + &self, + tx: &UtxoTx, + confirmations: u32, + requires_notarization: bool, + wait_until: u64, + check_every: u64, + ) -> Box + Send> { let tx = tx.clone(); let selfi = self.clone(); let fut = async move { loop { if now_ms() / 1000 > wait_until { - return ERR!("Waited too long until {} for transaction {:?} to be confirmed {} times", wait_until, tx, confirmations); + return ERR!( + "Waited too long until {} for transaction {:?} to be confirmed {} times", + wait_until, + tx, + confirmations + ); } - match selfi.get_verbose_transaction(tx.hash().reversed().into()).compat().await { + match selfi + .get_verbose_transaction(tx.hash().reversed().into()) + .compat() + .await + { Ok(t) => { let tx_confirmations = if requires_notarization { t.confirmations @@ -131,7 +145,9 @@ impl UtxoRpcClientEnum { log!({ "Waiting for tx {:?} confirmations, now {}, required {}, requires_notarization {}", tx.hash().reversed(), tx_confirmations, confirmations, requires_notarization }); } }, - Err(e) => log!("Error " [e] " getting the transaction " [tx.hash().reversed()] ", retrying in 10 seconds"), + Err(e) => { + log!("Error " [e] " getting the transaction " [tx.hash().reversed()] ", retrying in 10 seconds") + }, } Timer::sleep(check_every as f64).await; @@ -149,7 +165,7 @@ pub struct UnspentInfo { pub value: u64, } -pub type UtxoRpcRes = Box + Send + 'static>; +pub type UtxoRpcRes = Box + Send + 'static>; /// Common operations that both types of UTXO clients have but implement them differently pub trait UtxoRpcClientOps: fmt::Debug + Send + Sync + 'static { @@ -168,14 +184,28 @@ pub trait UtxoRpcClientOps: fmt::Debug + Send + Sync + 'static { fn display_balance(&self, address: Address, decimals: u8) -> RpcRes; /// returns fee estimation per KByte in satoshis - fn estimate_fee_sat(&self, decimals: u8, fee_method: &EstimateFeeMethod, mode: &Option) -> RpcRes; + fn estimate_fee_sat( + &self, + decimals: u8, + fee_method: &EstimateFeeMethod, + mode: &Option, + ) -> RpcRes; fn get_relay_fee(&self) -> RpcRes; - fn find_output_spend(&self, tx: &UtxoTx, vout: usize, from_block: u64) -> Box, Error=String> + Send>; + fn find_output_spend( + &self, + tx: &UtxoTx, + vout: usize, + from_block: u64, + ) -> Box, Error = String> + Send>; /// Get median time past for `count` blocks in the past including `starting_block` - fn get_median_time_past(&self, starting_block: u64, count: NonZeroU64) -> Box + Send>; + fn get_median_time_past( + &self, + starting_block: u64, + count: NonZeroU64, + ) -> Box + Send>; } #[derive(Clone, Deserialize, Debug, PartialEq)] @@ -188,7 +218,7 @@ pub struct NativeUnspent { pub script_pub_key: BytesJson, pub amount: f64, pub confirmations: u64, - pub spendable: bool + pub spendable: bool, } #[derive(Clone, Deserialize, Debug)] @@ -316,7 +346,10 @@ pub struct NativeClientImpl { #[derive(Clone, Debug)] pub struct NativeClient(pub Arc); -impl Deref for NativeClient {type Target = NativeClientImpl; fn deref (&self) -> &NativeClientImpl {&*self.0}} +impl Deref for NativeClient { + type Target = NativeClientImpl; + fn deref(&self) -> &NativeClientImpl { &*self.0 } +} /// The trait provides methods to generate the JsonRpcClient instance info such as name of coin. pub trait UtxoJsonRpcClientInfo: JsonRpcClient { @@ -324,15 +357,11 @@ pub trait UtxoJsonRpcClientInfo: JsonRpcClient { fn coin_name(&self) -> &str; /// Generate client info from coin name - fn client_info(&self) -> String { - format!("coin: {}", self.coin_name()) - } + fn client_info(&self) -> String { format!("coin: {}", self.coin_name()) } } impl UtxoJsonRpcClientInfo for NativeClientImpl { - fn coin_name(&self) -> &str { - self.coin_ticker.as_str() - } + fn coin_name(&self) -> &str { self.coin_ticker.as_str() } } impl JsonRpcClient for NativeClientImpl { @@ -349,33 +378,34 @@ impl JsonRpcClient for NativeClientImpl { let uri = self.uri.clone(); - let http_request = try_fus!( - Request::builder() - .method("POST") - .header( - AUTHORIZATION, - self.auth.clone() - ) - .uri(uri.clone()) - .body(Vec::from(request_body)) - ); + let http_request = try_fus!(Request::builder() + .method("POST") + .header(AUTHORIZATION, self.auth.clone()) + .uri(uri.clone()) + .body(Vec::from(request_body))); let event_handles = self.event_handlers.clone(); - Box::new(slurp_req(http_request).then(move |result| -> Result<(JsonRpcRemoteAddr, JsonRpcResponse), String> { - let res = try_s!(result); - // measure now only body length, because the `hyper` crate doesn't allow to get total HTTP packet length - event_handles.on_incoming_response(&res.2); - - let body = try_s!(std::str::from_utf8(&res.2)); - - if res.0 != StatusCode::OK { - return ERR!("Rpc request {:?} failed with HTTP status code {}, response body: {}", - request, res.0, body); - } + Box::new( + slurp_req(http_request).then(move |result| -> Result<(JsonRpcRemoteAddr, JsonRpcResponse), String> { + let res = try_s!(result); + // measure now only body length, because the `hyper` crate doesn't allow to get total HTTP packet length + event_handles.on_incoming_response(&res.2); + + let body = try_s!(std::str::from_utf8(&res.2)); + + if res.0 != StatusCode::OK { + return ERR!( + "Rpc request {:?} failed with HTTP status code {}, response body: {}", + request, + res.0, + body + ); + } - let response = try_s!(json::from_str(body)); - Ok((uri.into(), response)) - })) + let response = try_s!(json::from_str(body)); + Ok((uri.into(), response)) + }), + ) } } @@ -383,92 +413,106 @@ impl JsonRpcClient for NativeClientImpl { impl UtxoRpcClientOps for NativeClient { fn list_unspent_ordered(&self, address: &Address) -> UtxoRpcRes> { let clone = self.0.clone(); - Box::new(self.list_unspent(0, std::i32::MAX, vec![address.to_string()]).map_err(|e| ERRL!("{}", e)).and_then(move |unspents| { - let mut futures = vec![]; - for unspent in unspents.iter() { - let delay_f = Delay::new(Duration::from_millis(10)).map_err(|e| ERRL!("{}", e)); - let tx_id = unspent.txid.clone(); - let vout = unspent.vout as usize; - let arc = clone.clone(); - // The delay here is required to mitigate "Work queue depth exceeded" error from coin daemon. - // It happens even when we run requests sequentially. - // Seems like daemon need some time to clean up it's queue after response is sent. - futures.push(delay_f.and_then(move |_| arc.output_amount(tx_id, vout).map_err(|e| ERRL!("{}", e)))); - } - - join_all_sequential(futures).map(move |amounts| { - let zip_iter = amounts.iter().zip(unspents.iter()); - let mut result: Vec = zip_iter.map(|(value, unspent)| UnspentInfo { - outpoint: OutPoint { - hash: unspent.txid.reversed().into(), - index: unspent.vout, - }, - value: *value, - }).collect(); - - result.sort_unstable_by(|a, b| { - if a.value < b.value { - Ordering::Less - } else { - Ordering::Greater + Box::new( + self.list_unspent(0, std::i32::MAX, vec![address.to_string()]) + .map_err(|e| ERRL!("{}", e)) + .and_then(move |unspents| { + let mut futures = vec![]; + for unspent in unspents.iter() { + let delay_f = Delay::new(Duration::from_millis(10)).map_err(|e| ERRL!("{}", e)); + let tx_id = unspent.txid.clone(); + let vout = unspent.vout as usize; + let arc = clone.clone(); + // The delay here is required to mitigate "Work queue depth exceeded" error from coin daemon. + // It happens even when we run requests sequentially. + // Seems like daemon need some time to clean up it's queue after response is sent. + futures.push( + delay_f.and_then(move |_| arc.output_amount(tx_id, vout).map_err(|e| ERRL!("{}", e))), + ); } - }); - result - }) - })) + + join_all_sequential(futures).map(move |amounts| { + let zip_iter = amounts.iter().zip(unspents.iter()); + let mut result: Vec = zip_iter + .map(|(value, unspent)| UnspentInfo { + outpoint: OutPoint { + hash: unspent.txid.reversed().into(), + index: unspent.vout, + }, + value: *value, + }) + .collect(); + + result.sort_unstable_by(|a, b| { + if a.value < b.value { + Ordering::Less + } else { + Ordering::Greater + } + }); + result + }) + }), + ) } fn send_transaction(&self, tx: &UtxoTx, _addr: Address) -> UtxoRpcRes { - Box::new(self.send_raw_transaction(BytesJson::from(serialize(tx))).map_err(|e| ERRL!("{}", e))) + Box::new( + self.send_raw_transaction(BytesJson::from(serialize(tx))) + .map_err(|e| ERRL!("{}", e)), + ) } /// https://bitcoin.org/en/developer-reference#sendrawtransaction - fn send_raw_transaction(&self, tx: BytesJson) -> RpcRes { - rpc_func!(self, "sendrawtransaction", tx) - } + fn send_raw_transaction(&self, tx: BytesJson) -> RpcRes { rpc_func!(self, "sendrawtransaction", tx) } - fn get_transaction_bytes(&self, txid: H256Json) -> RpcRes { - self.get_raw_transaction_bytes(txid) - } + fn get_transaction_bytes(&self, txid: H256Json) -> RpcRes { self.get_raw_transaction_bytes(txid) } fn get_verbose_transaction(&self, txid: H256Json) -> RpcRes { self.get_raw_transaction_verbose(txid) } - fn get_block_count(&self) -> RpcRes { - self.0.get_block_count() - } + fn get_block_count(&self) -> RpcRes { self.0.get_block_count() } fn display_balance(&self, address: Address, _decimals: u8) -> RpcRes { - Box::new(self.list_unspent(0, std::i32::MAX, vec![address.to_string()]).map(|unspents| - unspents.iter().fold(0., |sum, unspent| sum + unspent.amount).into() - )) + Box::new( + self.list_unspent(0, std::i32::MAX, vec![address.to_string()]) + .map(|unspents| unspents.iter().fold(0., |sum, unspent| sum + unspent.amount).into()), + ) } - fn estimate_fee_sat(&self, decimals: u8, fee_method: &EstimateFeeMethod, mode: &Option) -> RpcRes { + fn estimate_fee_sat( + &self, + decimals: u8, + fee_method: &EstimateFeeMethod, + mode: &Option, + ) -> RpcRes { match fee_method { - EstimateFeeMethod::Standard => Box::new(self.estimate_fee().map(move |fee| + EstimateFeeMethod::Standard => Box::new(self.estimate_fee().map(move |fee| { if fee > 0.00001 { (fee * 10.0_f64.powf(decimals as f64)) as u64 } else { 1000 } - )), - EstimateFeeMethod::SmartFee => Box::new(self.estimate_smart_fee(mode).map(move |res| + })), + EstimateFeeMethod::SmartFee => Box::new(self.estimate_smart_fee(mode).map(move |res| { if res.fee_rate > 0.00001 { (res.fee_rate * 10.0_f64.powf(decimals as f64)) as u64 } else { 1000 } - )), + })), } } - fn get_relay_fee(&self) -> RpcRes { - Box::new(self.get_network_info().map(|info| info.relay_fee)) - } + fn get_relay_fee(&self) -> RpcRes { Box::new(self.get_network_info().map(|info| info.relay_fee)) } - fn find_output_spend(&self, tx: &UtxoTx, vout: usize, from_block: u64) -> Box, Error=String> + Send> { + fn find_output_spend( + &self, + tx: &UtxoTx, + vout: usize, + from_block: u64, + ) -> Box, Error = String> + Send> { let selfi = self.clone(); let tx = tx.clone(); let fut = async move { @@ -476,7 +520,8 @@ impl UtxoRpcClientOps for NativeClient { let list_since_block: ListSinceBlockRes = try_s!(selfi.list_since_block(from_block_hash).compat().await); for transaction in list_since_block.transactions { let maybe_spend_tx_bytes = try_s!(selfi.get_raw_transaction_bytes(transaction.txid).compat().await); - let maybe_spend_tx: UtxoTx = try_s!(deserialize(maybe_spend_tx_bytes.as_slice()).map_err(|e| ERRL!("{:?}", e))); + let maybe_spend_tx: UtxoTx = + try_s!(deserialize(maybe_spend_tx_bytes.as_slice()).map_err(|e| ERRL!("{:?}", e))); for input in maybe_spend_tx.inputs.iter() { if input.previous_output.hash == tx.hash() && input.previous_output.index == vout as u32 { @@ -489,7 +534,11 @@ impl UtxoRpcClientOps for NativeClient { Box::new(fut.boxed().compat()) } - fn get_median_time_past(&self, starting_block: u64, count: NonZeroU64) -> Box + Send> { + fn get_median_time_past( + &self, + starting_block: u64, + count: NonZeroU64, + ) -> Box + Send> { let selfi = self.clone(); let fut = async move { let starting_block_data = try_s!(selfi.get_block(starting_block.to_string()).compat().await); @@ -534,7 +583,11 @@ impl NativeClientImpl { pub fn output_amount(&self, txid: H256Json, index: usize) -> UtxoRpcRes { let fut = self.get_raw_transaction_bytes(txid).map_err(|e| ERRL!("{}", e)); Box::new(fut.and_then(move |bytes| { - let tx: UtxoTx = try_s!(deserialize(bytes.as_slice()).map_err(|e| ERRL!("Error {:?} trying to deserialize the transaction {:?}", e, bytes))); + let tx: UtxoTx = try_s!(deserialize(bytes.as_slice()).map_err(|e| ERRL!( + "Error {:?} trying to deserialize the transaction {:?}", + e, + bytes + ))); Ok(tx.outputs[index].value) })) } @@ -547,9 +600,7 @@ impl NativeClientImpl { } /// https://bitcoin.org/en/developer-reference#getblockcount - pub fn get_block_count(&self) -> RpcRes { - rpc_func!(self, "getblockcount") - } + pub fn get_block_count(&self) -> RpcRes { rpc_func!(self, "getblockcount") } /// https://bitcoin.org/en/developer-reference#getrawtransaction /// Always returns verbose transaction @@ -590,11 +641,22 @@ impl NativeClientImpl { } /// https://bitcoin.org/en/developer-reference#listreceivedbyaddress - pub fn list_received_by_address(&self, min_conf: u64, include_empty: bool, include_watch_only: bool) -> RpcRes> { - rpc_func!(self, "listreceivedbyaddress", min_conf, include_empty, include_watch_only) + pub fn list_received_by_address( + &self, + min_conf: u64, + include_empty: bool, + include_watch_only: bool, + ) -> RpcRes> { + rpc_func!( + self, + "listreceivedbyaddress", + min_conf, + include_empty, + include_watch_only + ) } - pub fn detect_fee_method(&self) -> impl Future + Send { + pub fn detect_fee_method(&self) -> impl Future + Send { let estimate_fee_fut = self.estimate_fee(); self.estimate_smart_fee(&None).then(move |res| -> Box + Send> { match res { @@ -625,13 +687,17 @@ impl NativeClientImpl { fn list_since_block(&self, block_hash: H256Json) -> RpcRes { let target_confirmations = 1; let include_watch_only = true; - rpc_func!(self, "listsinceblock", block_hash, target_confirmations, include_watch_only) + rpc_func!( + self, + "listsinceblock", + block_hash, + target_confirmations, + include_watch_only + ) } /// https://bitcoin.org/en/developer-reference#getblockhash - fn get_block_hash(&self, block_number: u64) -> RpcRes { - rpc_func!(self, "getblockhash", block_number) - } + fn get_block_hash(&self, block_number: u64) -> RpcRes { rpc_func!(self, "getblockhash", block_number) } /// https://bitcoin.org/en/developer-reference#sendtoaddress pub fn send_to_address(&self, addr: &str, amount: &BigDecimal) -> RpcRes { @@ -639,9 +705,7 @@ impl NativeClientImpl { } /// https://bitcoin.org/en/developer-reference#getnetworkinfo - pub fn get_network_info(&self) -> RpcRes { - rpc_func!(self, "getnetworkinfo") - } + pub fn get_network_info(&self) -> RpcRes { rpc_func!(self, "getnetworkinfo") } } #[derive(Debug, Deserialize)] @@ -761,16 +825,14 @@ pub struct ElectrumRpcRequest { } impl Default for ElectrumProtocol { - fn default() -> Self { - ElectrumProtocol::TCP - } + fn default() -> Self { ElectrumProtocol::TCP } } /// Electrum client configuration #[derive(Clone, Debug, Serialize)] enum ElectrumConfig { TCP, - SSL {dns_name: String, skip_validation: bool} + SSL { dns_name: String, skip_validation: bool }, } fn addr_to_socket_addr(input: &str) -> Result { @@ -799,19 +861,21 @@ pub fn spawn_electrum( /* #[cfg(feature = "native")] fn check(host: &str) -> Result<(), String> { - DNSNameRef::try_from_ascii_str(host).map(|_|()).map_err(|e| fomat!([e])) + DNSNameRef::try_from_ascii_str(host) + .map(|_| ()) + .map_err(|e| fomat!([e])) } */ #[cfg(not(feature = "native"))] - fn check(_host: &str) -> Result<(), String> {Ok(())} + fn check(_host: &str) -> Result<(), String> { Ok(()) } // try_s!(check(host)); ElectrumConfig::SSL { dns_name: host.into(), - skip_validation: req.disable_cert_verification + skip_validation: req.disable_cert_verification, } - } + }, }; Ok(electrum_connect(req.url.clone(), config, event_handlers)) @@ -820,36 +884,42 @@ pub fn spawn_electrum( #[cfg(not(feature = "native"))] #[cfg_attr(feature = "w-bindgen", wasm_bindgen(raw_module = "../../../js/defined-in-js.js"))] extern "C" { - fn host_electrum_connect (ptr: *const c_char, len: i32) -> i32; - fn host_electrum_is_connected (ri: i32) -> i32; - fn host_electrum_request (ri: i32, ptr: *const c_char, len: i32) -> i32; - fn host_electrum_reply (ri: i32, id: i32, rbuf: *mut c_char, rcap: i32) -> i32; + fn host_electrum_connect(ptr: *const c_char, len: i32) -> i32; + fn host_electrum_is_connected(ri: i32) -> i32; + fn host_electrum_request(ri: i32, ptr: *const c_char, len: i32) -> i32; + fn host_electrum_reply(ri: i32, id: i32, rbuf: *mut c_char, rcap: i32) -> i32; } #[cfg(not(feature = "native"))] -pub fn spawn_electrum(req: &ElectrumRpcRequest, _event_handlers: Vec) - -> Result { +pub fn spawn_electrum( + req: &ElectrumRpcRequest, + _event_handlers: Vec, +) -> Result { use std::net::{IpAddr, Ipv4Addr}; - let args = unwrap! (json::to_vec (req)); - let rc = unsafe {host_electrum_connect (args.as_ptr() as *const c_char, args.len() as i32)}; - if rc < 0 {panic! ("!host_electrum_connect: {}", rc)} - let ri = rc; // Random ID assigned by the host to connection. + let args = unwrap!(json::to_vec(req)); + let rc = unsafe { host_electrum_connect(args.as_ptr() as *const c_char, args.len() as i32) }; + if rc < 0 { + panic!("!host_electrum_connect: {}", rc) + } + let ri = rc; // Random ID assigned by the host to connection. - let responses = Arc::new (Mutex::new (HashMap::new())); - let tx = Arc::new (AsyncMutex::new (None)); + let responses = Arc::new(Mutex::new(HashMap::new())); + let tx = Arc::new(AsyncMutex::new(None)); let config = match req.protocol { ElectrumProtocol::TCP => ElectrumConfig::TCP, ElectrumProtocol::SSL => { - let uri: Uri = try_s! (req.url.parse()); - let host = try_s! (uri.host().ok_or ("!host")); + let uri: Uri = try_s!(req.url.parse()); + let host = try_s!(uri.host().ok_or("!host")); ElectrumConfig::SSL { dns_name: host.into(), - skip_validation: req.disable_cert_verification - } } }; + skip_validation: req.disable_cert_verification, + } + }, + }; - Ok (ElectrumConnection { + Ok(ElectrumConnection { addr: req.url.clone(), config, tx, @@ -881,29 +951,34 @@ pub struct ElectrumConnection { impl ElectrumConnection { #[cfg(feature = "native")] - async fn is_connected(&self) -> bool { - self.tx.lock().await.is_some() - } + async fn is_connected(&self) -> bool { self.tx.lock().await.is_some() } #[cfg(not(feature = "native"))] - async fn is_connected (&self) -> bool { - let rc = unsafe {host_electrum_is_connected (self.ri)}; - if rc < 0 {panic! ("!host_electrum_is_connected: {}", rc)} + async fn is_connected(&self) -> bool { + let rc = unsafe { host_electrum_is_connected(self.ri) }; + if rc < 0 { + panic!("!host_electrum_is_connected: {}", rc) + } //log! ("is_connected] host_electrum_is_connected (" [=self.ri] ") " [=rc]); - if rc == 1 {true} else {false} + if rc == 1 { + true + } else { + false + } } - async fn set_protocol_version(&self, version: f32) { - self.protocol_version.lock().await.replace(version); - } + async fn set_protocol_version(&self, version: f32) { self.protocol_version.lock().await.replace(version); } } impl Drop for ElectrumConnection { fn drop(&mut self) { - if let Some (shutdown_tx) = self.shutdown_tx.take() { - if let Err(_) = shutdown_tx.send(()) { - log! ("electrum_connection_drop] Warning, shutdown_tx already closed"); -} } } } + if let Some(shutdown_tx) = self.shutdown_tx.take() { + if shutdown_tx.send(()).is_err() { + log!("electrum_connection_drop] Warning, shutdown_tx already closed"); + } + } + } +} #[derive(Debug)] pub struct ElectrumClientImpl { @@ -935,10 +1010,21 @@ async fn electrum_request_multi( return ERR!("All electrums are currently disconnected"); } if request.method != "server.ping" { - Ok(try_s!(select_ok_sequential(futures).map_err(|e| ERRL!("{:?}", e)).compat().await)) + Ok(try_s!( + select_ok_sequential(futures) + .map_err(|e| ERRL!("{:?}", e)) + .compat() + .await + )) } else { // server.ping must be sent to all servers to keep all connections alive - Ok(try_s!(select_ok(futures).map(|(result, _)| result).map_err(|e| ERRL!("{:?}", e)).compat().await)) + Ok(try_s!( + select_ok(futures) + .map(|(result, _)| result) + .map_err(|e| ERRL!("{:?}", e)) + .compat() + .await + )) } } @@ -949,11 +1035,17 @@ async fn electrum_request_to( to_addr: String, ) -> Result<(JsonRpcRemoteAddr, JsonRpcResponse), String> { let connections = client.connections.lock().await; - let connection = connections.iter().find(|c| c.addr == to_addr) + let connection = connections + .iter() + .find(|c| c.addr == to_addr) .ok_or(ERRL!("Unknown destination address {}", to_addr))?; let response = match &*connection.tx.lock().await { - Some(tx) => try_s!(electrum_request(request.clone(), tx.clone(), connection.responses.clone()).compat().await), + Some(tx) => try_s!( + electrum_request(request.clone(), tx.clone(), connection.responses.clone()) + .compat() + .await + ), None => return ERR!("Connection {} is not established yet", to_addr), }; @@ -962,66 +1054,80 @@ async fn electrum_request_to( #[cfg(not(feature = "native"))] lazy_static! { - static ref ELECTRUM_REPLIES: Mutex>> = Mutex::new (HashMap::new()); + static ref ELECTRUM_REPLIES: Mutex>> = Mutex::new(HashMap::new()); } #[no_mangle] #[cfg(not(feature = "native"))] -pub extern fn electrum_replied (ri: i32, id: i32) { +pub extern "C" fn electrum_replied(ri: i32, id: i32) { //log! ("electrum_replied] " [=ri] ", " [=id]); - let mut electrum_replies = unwrap! (ELECTRUM_REPLIES.lock()); - if let Some (tx) = electrum_replies.remove (&(ri, id)) {let _ = tx.send(());} + let mut electrum_replies = unwrap!(ELECTRUM_REPLIES.lock()); + if let Some(tx) = electrum_replies.remove(&(ri, id)) { + let _ = tx.send(()); + } } /// AG: As of now the pings tend to fail. /// I haven't looked into this because we'll probably use a websocket or Java implementation instead. #[cfg(not(feature = "native"))] -async fn electrum_request_multi (client: ElectrumClient, request: JsonRpcRequest) --> Result<(JsonRpcRemoteAddr, JsonRpcResponse), String> { +async fn electrum_request_multi( + client: ElectrumClient, + request: JsonRpcRequest, +) -> Result<(JsonRpcRemoteAddr, JsonRpcResponse), String> { use futures::future::{select, Either}; use std::mem::MaybeUninit; use std::os::raw::c_char; use std::str::from_utf8; - let req = try_s! (json::to_string (&request)); - let id: i32 = try_s! (request.id.parse()); + let req = try_s!(json::to_string(&request)); + let id: i32 = try_s!(request.id.parse()); let mut jres: Option = None; // address of server from which an Rpc response was received let mut remote_address = JsonRpcRemoteAddr::default(); for connection in client.connections.lock().await.iter() { let (tx, rx) = futures::channel::oneshot::channel(); - try_s! (ELECTRUM_REPLIES.lock()) .insert ((connection.ri, id), tx); - let rc = unsafe {host_electrum_request (connection.ri, req.as_ptr() as *const c_char, req.len() as i32)}; - if rc != 0 {return ERR! ("!host_electrum_request: {}", rc)} + try_s!(ELECTRUM_REPLIES.lock()).insert((connection.ri, id), tx); + let rc = unsafe { host_electrum_request(connection.ri, req.as_ptr() as *const c_char, req.len() as i32) }; + if rc != 0 { + return ERR!("!host_electrum_request: {}", rc); + } // Wait for the host to invoke `fn electrum_replied`. - let timeout = Timer::sleep (10.); - let rc = select (rx, timeout).await; + let timeout = Timer::sleep(10.); + let rc = select(rx, timeout).await; match rc { - Either::Left ((_r, _t)) => (), - Either::Right ((_t, _r)) => {log! ("Electrum " (connection.ri) " timeout"); continue} + Either::Left((_r, _t)) => (), + Either::Right((_t, _r)) => { + log! ("Electrum " (connection.ri) " timeout"); + continue; + }, }; - let mut buf: [u8; 131072] = unsafe {MaybeUninit::uninit().assume_init()}; - let rc = unsafe {host_electrum_reply (connection.ri, id, buf.as_mut_ptr() as *mut c_char, buf.len() as i32)}; - if rc <= 0 {log! ("!host_electrum_reply: " (rc)); continue} // Skip to the next connection. - let res = try_s! (from_utf8 (&buf[0 .. rc as usize])); + let mut buf: [u8; 131072] = unsafe { MaybeUninit::uninit().assume_init() }; + let rc = unsafe { host_electrum_reply(connection.ri, id, buf.as_mut_ptr() as *mut c_char, buf.len() as i32) }; + if rc <= 0 { + log!("!host_electrum_reply: "(rc)); + continue; + } // Skip to the next connection. + let res = try_s!(from_utf8(&buf[0..rc as usize])); //log! ("electrum_request_multi] ri " (connection.ri) ", res: " (res)); - let res: Json = try_s! (json::from_str (res)); + let res: Json = try_s!(json::from_str(res)); // TODO: Detect errors and fill the `error` field somehow? - jres = Some (JsonRpcResponse { + jres = Some(JsonRpcResponse { jsonrpc: req.clone(), id: request.id.clone(), result: res, - error: Json::Null + error: Json::Null, }); remote_address = JsonRpcRemoteAddr(connection.addr.clone()); // server.ping must be sent to all servers to keep all connections alive - if request.method != "server.ping" {break} + if request.method != "server.ping" { + break; + } } - let jres = try_s! (jres.ok_or ("!jres")); - Ok ((remote_address, jres)) + let jres = try_s!(jres.ok_or("!jres")); + Ok((remote_address, jres)) } impl ElectrumClientImpl { @@ -1036,8 +1142,9 @@ impl ElectrumClientImpl { pub async fn remove_server(&self, server_addr: &str) -> Result<(), String> { let mut connections = self.connections.lock().await; // do not use retain, we would have to return an error if we did not find connection by the passd address - let pos = connections.iter() - .position(|con| &con.addr == server_addr) + let pos = connections + .iter() + .position(|con| con.addr == server_addr) .ok_or(ERRL!("Unknown electrum address {}", server_addr))?; // shutdown_tx will be closed immediately on the connection drop connections.remove(pos); @@ -1054,9 +1161,7 @@ impl ElectrumClientImpl { false } - pub async fn count_connections(&self) -> usize { - self.connections.lock().await.len() - } + pub async fn count_connections(&self) -> usize { self.connections.lock().await.len() } /// Check if the protocol version was checked for one of the spawned connections. pub async fn is_protocol_version_checked(&self) -> bool { @@ -1071,36 +1176,35 @@ impl ElectrumClientImpl { /// Set the protocol version for the specified server. pub async fn set_protocol_version(&self, server_addr: &str, version: f32) -> Result<(), String> { let connections = self.connections.lock().await; - let con = connections.iter().find(|con| &con.addr == server_addr) + let con = connections + .iter() + .find(|con| con.addr == server_addr) .ok_or(ERRL!("Unknown electrum address {}", server_addr))?; con.set_protocol_version(version).await; Ok(()) } /// Get available protocol versions. - pub fn protocol_version(&self) -> &OrdRange { - &self.protocol_version - } + pub fn protocol_version(&self) -> &OrdRange { &self.protocol_version } } #[derive(Clone, Debug)] pub struct ElectrumClient(pub Arc); -impl Deref for ElectrumClient {type Target = ElectrumClientImpl; fn deref (&self) -> &ElectrumClientImpl {&*self.0}} +impl Deref for ElectrumClient { + type Target = ElectrumClientImpl; + fn deref(&self) -> &ElectrumClientImpl { &*self.0 } +} -const BLOCKCHAIN_HEADERS_SUB_ID: &'static str = "blockchain.headers.subscribe"; +const BLOCKCHAIN_HEADERS_SUB_ID: &str = "blockchain.headers.subscribe"; impl UtxoJsonRpcClientInfo for ElectrumClient { - fn coin_name(&self) -> &str { - self.coin_ticker.as_str() - } + fn coin_name(&self) -> &str { self.coin_ticker.as_str() } } impl JsonRpcClient for ElectrumClient { fn version(&self) -> &'static str { "2.0" } - fn next_id(&self) -> String { - self.next_id.fetch_add(1, AtomicOrdering::Relaxed).to_string() - } + fn next_id(&self) -> String { self.next_id.fetch_add(1, AtomicOrdering::Relaxed).to_string() } fn client_info(&self) -> String { UtxoJsonRpcClientInfo::client_info(self) } @@ -1117,12 +1221,15 @@ impl JsonRpcMultiClient for ElectrumClient { impl ElectrumClient { /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#server-ping - pub fn server_ping(&self) -> RpcRes<()> { - rpc_func!(self, "server.ping") - } + pub fn server_ping(&self) -> RpcRes<()> { rpc_func!(self, "server.ping") } /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#server-version - pub fn server_version(&self, server_address: &str, client_name: &str, version: &OrdRange) -> RpcRes { + pub fn server_version( + &self, + server_address: &str, + client_name: &str, + version: &OrdRange, + ) -> RpcRes { let protocol_version: Vec = version.flatten().into_iter().map(|v| format!("{}", v)).collect(); rpc_func_from!(self, server_address, "server.version", client_name, protocol_version) } @@ -1131,19 +1238,22 @@ impl ElectrumClient { /// It can return duplicates sometimes: https://github.com/artemii235/SuperNET/issues/269 /// We should remove them to build valid transactions fn scripthash_list_unspent(&self, hash: &str) -> RpcRes> { - Box::new(rpc_func!(self, "blockchain.scripthash.listunspent", hash).and_then(move |unspents: Vec| { - let mut map: HashMap<(H256Json, u32), bool> = HashMap::new(); - let unspents = unspents.into_iter().filter(|unspent| { - match map.entry((unspent.tx_hash.clone(), unspent.tx_pos)) { - Entry::Occupied(_) => false, - Entry::Vacant(e) => { - e.insert(true); - true - }, - } - }).collect(); - Ok(unspents) - })) + Box::new(rpc_func!(self, "blockchain.scripthash.listunspent", hash).and_then( + move |unspents: Vec| { + let mut map: HashMap<(H256Json, u32), bool> = HashMap::new(); + let unspents = unspents + .into_iter() + .filter(|unspent| match map.entry((unspent.tx_hash.clone(), unspent.tx_pos)) { + Entry::Occupied(_) => false, + Entry::Vacant(e) => { + e.insert(true); + true + }, + }) + .collect(); + Ok(unspents) + }, + )) } /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-get-history @@ -1177,8 +1287,7 @@ impl ElectrumClient { } /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-block-headers - pub fn blockchain_block_headers(&self, start_height: u64, count: NonZeroU64) - -> RpcRes { + pub fn blockchain_block_headers(&self, start_height: u64, count: NonZeroU64) -> RpcRes { rpc_func!(self, "blockchain.block.headers", start_height, count) } } @@ -1188,24 +1297,31 @@ impl UtxoRpcClientOps for ElectrumClient { fn list_unspent_ordered(&self, address: &Address) -> UtxoRpcRes> { let script = Builder::build_p2pkh(&address.hash); let script_hash = electrum_script_hash(&script); - Box::new(self.scripthash_list_unspent(&hex::encode(script_hash)).map_err(|e| ERRL!("{}", e)).map(move |unspents| { - let mut result: Vec = unspents.iter().map(|unspent| UnspentInfo { - outpoint: OutPoint { - hash: unspent.tx_hash.reversed().into(), - index: unspent.tx_pos, - }, - value: unspent.value - }).collect(); - - result.sort_unstable_by(|a, b| { - if a.value < b.value { - Ordering::Less - } else { - Ordering::Greater - } - }); - result - })) + Box::new( + self.scripthash_list_unspent(&hex::encode(script_hash)) + .map_err(|e| ERRL!("{}", e)) + .map(move |unspents| { + let mut result: Vec = unspents + .iter() + .map(|unspent| UnspentInfo { + outpoint: OutPoint { + hash: unspent.tx_hash.reversed().into(), + index: unspent.tx_pos, + }, + value: unspent.value, + }) + .collect(); + + result.sort_unstable_by(|a, b| { + if a.value < b.value { + Ordering::Less + } else { + Ordering::Greater + } + }); + result + }), + ) } fn send_transaction(&self, tx: &UtxoTx, my_addr: Address) -> UtxoRpcRes { @@ -1214,36 +1330,44 @@ impl UtxoRpcClientOps for ElectrumClient { let arc = self.clone(); let script = Builder::build_p2pkh(&my_addr.hash); let script_hash = hex::encode(electrum_script_hash(&script)); - Box::new(self.blockchain_transaction_broadcast(bytes).map_err(|e| ERRL!("{}", e)).and_then(move |res| { - // Check every second until Electrum server recognizes that used UTXOs are spent - loop_fn((res, arc, script_hash, inputs), move |(res, arc, script_hash, inputs)| { - let delay_f = Delay::new(Duration::from_secs(1)).map_err(|e| ERRL!("{}", e)); - delay_f.and_then(move |_res| { - arc.scripthash_list_unspent(&script_hash).then(move |unspents| { - let unspents = match unspents { - Ok(unspents) => unspents, - Err(e) => { - log!("Error getting Electrum unspents " [e]); - // we can just keep looping in case of error hoping it will go away - return Ok(Loop::Continue((res, arc, script_hash, inputs))); - } - }; - - for input in inputs.iter() { - let find = unspents.iter().find(|unspent| { - unspent.tx_hash == input.previous_output.hash.reversed().into() && unspent.tx_pos == input.previous_output.index - }); - // Check again if at least 1 spent outpoint is still there - if find.is_some() { - return Ok(Loop::Continue((res, arc, script_hash, inputs))); - } - } - - Ok(Loop::Break(res)) - }) - }) - }) - })) + Box::new( + self.blockchain_transaction_broadcast(bytes) + .map_err(|e| ERRL!("{}", e)) + .and_then(move |res| { + // Check every second until Electrum server recognizes that used UTXOs are spent + loop_fn( + (res, arc, script_hash, inputs), + move |(res, arc, script_hash, inputs)| { + let delay_f = Delay::new(Duration::from_secs(1)).map_err(|e| ERRL!("{}", e)); + delay_f.and_then(move |_res| { + arc.scripthash_list_unspent(&script_hash).then(move |unspents| { + let unspents = match unspents { + Ok(unspents) => unspents, + Err(e) => { + log!("Error getting Electrum unspents "[e]); + // we can just keep looping in case of error hoping it will go away + return Ok(Loop::Continue((res, arc, script_hash, inputs))); + }, + }; + + for input in inputs.iter() { + let find = unspents.iter().find(|unspent| { + unspent.tx_hash == input.previous_output.hash.reversed().into() + && unspent.tx_pos == input.previous_output.index + }); + // Check again if at least 1 spent outpoint is still there + if find.is_some() { + return Ok(Loop::Continue((res, arc, script_hash, inputs))); + } + } + + Ok(Loop::Break(res)) + }) + }) + }, + ) + }), + ) } /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get @@ -1260,9 +1384,7 @@ impl UtxoRpcClientOps for ElectrumClient { rpc_func!(self, "blockchain.transaction.get", txid, verbose) } - fn get_block_count(&self) -> RpcRes { - Box::new(self.blockchain_headers_subscribe().map(|r| r.block_height())) - } + fn get_block_count(&self) -> RpcRes { Box::new(self.blockchain_headers_subscribe().map(|r| r.block_height())) } fn display_balance(&self, address: Address, decimals: u8) -> RpcRes { let hash = electrum_script_hash(&Builder::build_p2pkh(&address.hash)); @@ -1272,25 +1394,31 @@ impl UtxoRpcClientOps for ElectrumClient { })) } - fn estimate_fee_sat(&self, decimals: u8, _fee_method: &EstimateFeeMethod, mode: &Option) -> RpcRes { - Box::new(self.estimate_fee(mode).map(move |fee| + fn estimate_fee_sat( + &self, + decimals: u8, + _fee_method: &EstimateFeeMethod, + mode: &Option, + ) -> RpcRes { + Box::new(self.estimate_fee(mode).map(move |fee| { if fee > 0.00001 { (fee * 10.0_f64.powf(decimals as f64)) as u64 } else { 1000 } - )) + })) } - fn send_raw_transaction(&self, tx: BytesJson) -> RpcRes { - self.blockchain_transaction_broadcast(tx) - } + fn send_raw_transaction(&self, tx: BytesJson) -> RpcRes { self.blockchain_transaction_broadcast(tx) } - fn get_relay_fee(&self) -> RpcRes { - rpc_func!(self, "blockchain.relayfee") - } + fn get_relay_fee(&self) -> RpcRes { rpc_func!(self, "blockchain.relayfee") } - fn find_output_spend(&self, tx: &UtxoTx, vout: usize, _from_block: u64) -> Box, Error=String> + Send> { + fn find_output_spend( + &self, + tx: &UtxoTx, + vout: usize, + _from_block: u64, + ) -> Box, Error = String> + Send> { let selfi = self.clone(); let script_hash = hex::encode(electrum_script_hash(&tx.outputs[vout].script_pubkey)); let tx = tx.clone(); @@ -1317,27 +1445,32 @@ impl UtxoRpcClientOps for ElectrumClient { Box::new(fut.boxed().compat()) } - fn get_median_time_past(&self, starting_block: u64, count: NonZeroU64) -> Box + Send> { + fn get_median_time_past( + &self, + starting_block: u64, + count: NonZeroU64, + ) -> Box + Send> { let from = if starting_block <= count.get() { 0 } else { starting_block - count.get() + 1 }; - Box::new(self.blockchain_block_headers(from, count) - .map_err(|e| ERRL!("{}", e)) - .and_then(|res| { - if res.count == 0 { - return ERR!("Server returned zero count"); - } - let len = CompactInteger::from(res.count); - let mut serialized = serialize(&len).take(); - serialized.extend(res.hex.0.into_iter()); - let mut reader = Reader::new(serialized.as_slice()); - let headers = try_s!(reader.read_list::().map_err(|e| ERRL!("{:?}", e))); - let mut timestamps: Vec<_> = headers.into_iter().map(|block| block.time).collect(); - // can unwrap because count is non zero - Ok(median(timestamps.as_mut_slice()).unwrap()) - }) + Box::new( + self.blockchain_block_headers(from, count) + .map_err(|e| ERRL!("{}", e)) + .and_then(|res| { + if res.count == 0 { + return ERR!("Server returned zero count"); + } + let len = CompactInteger::from(res.count); + let mut serialized = serialize(&len).take(); + serialized.extend(res.hex.0.into_iter()); + let mut reader = Reader::new(serialized.as_slice()); + let headers = try_s!(reader.read_list::().map_err(|e| ERRL!("{:?}", e))); + let mut timestamps: Vec<_> = headers.into_iter().map(|block| block.time).collect(); + // can unwrap because count is non zero + Ok(median(timestamps.as_mut_slice()).unwrap()) + }), ) } } @@ -1356,7 +1489,11 @@ impl ElectrumClientImpl { } #[cfg(test)] - pub fn with_protocol_version(coin_ticker: String, event_handlers: Vec, protocol_version: OrdRange) -> ElectrumClientImpl { + pub fn with_protocol_version( + coin_ticker: String, + event_handlers: Vec, + protocol_version: OrdRange, + ) -> ElectrumClientImpl { ElectrumClientImpl { protocol_version, ..ElectrumClientImpl::new(coin_ticker, event_handlers) @@ -1369,18 +1506,21 @@ fn rx_to_stream(rx: mpsc::Receiver>) -> impl Stream, Erro rx.map_err(|_| panic!("errors not possible on rx")) } -async fn electrum_process_chunk(chunk: BytesMut, arc: Arc>>>) { +async fn electrum_process_chunk( + chunk: BytesMut, + arc: Arc>>>, +) { // we should split the received chunk because we can get several responses in 1 chunk. - let split = chunk.split(|item| *item == '\n' as u8); + let split = chunk.split(|item| *item == b'\n'); for chunk in split { // split returns empty slice if it ends with separator which is our case - if chunk.len() > 0 { + if !chunk.is_empty() { let raw_json: Json = match json::from_slice(chunk) { Ok(json) => json, Err(e) => { log!([e]); return; - } + }, }; // detect if we got standard JSONRPC response or subscription response as JSONRPC request @@ -1390,12 +1530,14 @@ async fn electrum_process_chunk(chunk: BytesMut, arc: Arc { log!([e]); return; - } + }, }; let mut resp = arc.lock().await; // the corresponding sender may not exist, receiver may be dropped // these situations are not considered as errors so we just silently skip them - resp.remove(&response.id.to_string()).map(|tx| tx.send(response).unwrap_or(())); + if let Some(tx) = resp.remove(&response.id.to_string()) { + tx.send(response).unwrap_or(()) + } drop(resp); } else { let request: JsonRpcRequest = match json::from_value(raw_json) { @@ -1403,14 +1545,14 @@ async fn electrum_process_chunk(chunk: BytesMut, arc: Arc { log!([e]); return; - } + }, }; let id = match request.method.as_ref() { BLOCKCHAIN_HEADERS_SUB_ID => BLOCKCHAIN_HEADERS_SUB_ID, _ => { - log!("Couldn't get id of request " [request]); + log!("Couldn't get id of request "[request]); return; - } + }, }; let response = JsonRpcResponse { @@ -1422,7 +1564,9 @@ async fn electrum_process_chunk(chunk: BytesMut, arc: Arc) -> Result<(), Stri Timer::sleep(ELECTRUM_TIMEOUT as f64).await; let last = (last_chunk.load(AtomicOrdering::Relaxed) / 1000) as f64; if now_float() - last > ELECTRUM_TIMEOUT as f64 { - break ERR!("Didn't receive any data since {}. Shutting down the connection.", last as i64); + break ERR!( + "Didn't receive any data since {}. Shutting down the connection.", + last as i64 + ); } } } @@ -1524,7 +1671,9 @@ async fn connect_loop( let mut delay: u64 = 0; loop { - if delay > 0 { Timer::sleep(delay as f64).await; }; + if delay > 0 { + Timer::sleep(delay as f64).await; + }; let socket_addr = try_loop!(addr_to_socket_addr(&addr), addr, delay); @@ -1545,7 +1694,7 @@ async fn connect_loop( Either::B(TcpStream::connect(&socket_addr).and_then(move |stream| { // Can use `unwrap` cause `dns_name` is pre-checked. let dns = unwrap!(DNSNameRef::try_from_ascii_str(&dns_name).map_err(|e| fomat!([e]))); - tls_connector.connect(dns, stream).map(|stream| ElectrumStream::Tls(stream)) + tls_connector.connect(dns, stream).map(ElectrumStream::Tls) })) */ } @@ -1555,18 +1704,17 @@ async fn connect_loop( try_loop!(stream.as_ref().set_nodelay(true), addr, delay); // reset the delay if we've connected successfully delay = 0; - log!("Electrum client connected to " (addr)); + log!("Electrum client connected to "(addr)); try_loop!(event_handlers.on_connected(addr.clone()), addr, delay); let last_chunk = Arc::new(AtomicU64::new(now_ms())); let mut last_chunk_f = electrum_last_chunk_loop(last_chunk.clone()).boxed().fuse(); let (tx, rx) = mpsc::channel(0); *connection_tx.lock().await = Some(tx); - let rx = rx_to_stream(rx) - .inspect(|data| { - // measure the length of each sent packet - event_handlers.on_outgoing_request(&data); - }); + let rx = rx_to_stream(rx).inspect(|data| { + // measure the length of each sent packet + event_handlers.on_outgoing_request(&data); + }); let (sink, stream) = Bytes.framed(stream).split(); let mut recv_f = stream @@ -1575,9 +1723,14 @@ async fn connect_loop( event_handlers.on_incoming_response(&chunk); last_chunk.store(now_ms(), AtomicOrdering::Relaxed); - electrum_process_chunk(chunk, responses.clone()).unit_error().boxed().compat().then(|_| Ok(())) + electrum_process_chunk(chunk, responses.clone()) + .unit_error() + .boxed() + .compat() + .then(|_| Ok(())) }) - .compat().fuse(); + .compat() + .fuse(); // this forwards the messages from rx to sink (write) part of tcp stream let mut send_f = sink.send_all(rx).compat().fuse(); @@ -1611,7 +1764,9 @@ async fn connect_loop( _addr: SocketAddr, _responses: Arc>>, _connection_tx: Arc>>>>, -) -> Result<(), ()> {unimplemented!()} +) -> Result<(), ()> { + unimplemented!() +} /// Builds up the electrum connection, spawns endless loop that attempts to reconnect to the server /// in case of connection errors @@ -1647,8 +1802,13 @@ fn electrum_connect( } #[cfg(not(feature = "native"))] -fn electrum_connect (_addr: SocketAddr, _config: ElectrumConfig, _event_handlers: Vec) - -> ElectrumConnection {unimplemented!()} +fn electrum_connect( + _addr: SocketAddr, + _config: ElectrumConfig, + _event_handlers: Vec, +) -> ElectrumConnection { + unimplemented!() +} /// A simple `Codec` implementation that reads buffer until \n according to Electrum protocol specification: /// https://electrumx.readthedocs.io/en/latest/protocol-basics.html#message-stream @@ -1663,7 +1823,7 @@ impl Decoder for Bytes { fn decode(&mut self, buf: &mut BytesMut) -> io::Result> { let len = buf.len(); - if len > 0 && buf[len - 1] == '\n' as u8 { + if len > 0 && buf[len - 1] == b'\n' { Ok(Some(buf.split_to(len))) } else { Ok(None) @@ -1685,8 +1845,8 @@ impl Encoder for Bytes { fn electrum_request( request: JsonRpcRequest, tx: mpsc::Sender>, - responses: Arc>>> -) -> Box + Send + 'static> { + responses: Arc>>>, +) -> Box + Send + 'static> { let send_fut = async move { let mut json = try_s!(json::to_string(&request)); // Electrum request and responses must end with \n @@ -1702,7 +1862,7 @@ fn electrum_request( let send_fut = send_fut .boxed() .compat() - .map_err(|e| StringError(e)) + .map_err(StringError) .timeout(Duration::from_secs(ELECTRUM_TIMEOUT)); Box::new(send_fut.map_err(|e| ERRL!("{}", e.0))) diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index ff00f9cddc..e8cebf6c69 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -1,15 +1,13 @@ +use super::*; +use crate::{utxo::rpc_clients::{ElectrumProtocol, ListSinceBlockRes, NetworkInfo}, + WithdrawFee}; use bigdecimal::BigDecimal; -use common::{block_on, OrdRange}; use common::mm_ctx::MmCtxBuilder; use common::privkey::key_pair_from_seed; -use crate::{ - WithdrawFee, - utxo::rpc_clients::{ElectrumProtocol, ListSinceBlockRes, NetworkInfo} -}; +use common::{block_on, OrdRange}; use futures::future::join_all; use mocktopus::mocking::*; -use rpc::v1::types::{H256 as H256Json, VerboseBlockClient}; -use super::*; +use rpc::v1::types::{VerboseBlockClient, H256 as H256Json}; const TEST_COIN_NAME: &'static str = "RICK"; @@ -20,7 +18,8 @@ fn electrum_client_for_test(servers: &[&str]) -> ElectrumClient { url: server.to_string(), protocol: ElectrumProtocol::TCP, disable_cert_verification: false, - })).unwrap(); + })) + .unwrap(); } let mut attempts = 0; @@ -42,7 +41,7 @@ fn native_client_for_test() -> NativeClient { coin_ticker: "TEST".into(), uri: "".into(), auth: "".into(), - event_handlers: vec![] + event_handlers: vec![], })) } @@ -52,13 +51,15 @@ fn utxo_coin_for_test(rpc_client: UtxoRpcClientEnum, force_seed: Option<&str>) - let seed = match force_seed { Some(s) => s.into(), None => match std::env::var("BOB_PASSPHRASE") { - Ok(p) => if p.is_empty() { - default_seed.into() - } else { - p + Ok(p) => { + if p.is_empty() { + default_seed.into() + } else { + p + } }, Err(_) => default_seed.into(), - } + }, }; let key_pair = key_pair_from_seed(&seed).unwrap(); let my_address = Address { @@ -138,7 +139,12 @@ fn test_generate_transaction() { value: 98001, }]; - let generated = unwrap!(block_on(coin.generate_transaction(unspents, outputs, FeePolicy::SendExact, None))); + let generated = unwrap!(block_on(coin.generate_transaction( + unspents, + outputs, + FeePolicy::SendExact, + None + ))); // the change that is less than dust must be included to miner fee // so no extra outputs should appear in generated transaction assert_eq!(generated.0.outputs.len(), 1); @@ -158,7 +164,12 @@ fn test_generate_transaction() { }]; // test that fee is properly deducted from output amount equal to input amount (max withdraw case) - let generated = unwrap!(block_on(coin.generate_transaction(unspents, outputs, FeePolicy::DeductFromOutput(0), None))); + let generated = unwrap!(block_on(coin.generate_transaction( + unspents, + outputs, + FeePolicy::DeductFromOutput(0), + None + ))); assert_eq!(generated.0.outputs.len(), 1); assert_eq!(generated.1.fee_amount, 1000); @@ -177,7 +188,12 @@ fn test_generate_transaction() { }]; // test that generate_transaction returns an error when input amount is not sufficient to cover output + fee - unwrap_err!(block_on(coin.generate_transaction(unspents, outputs, FeePolicy::SendExact, None))); + unwrap_err!(block_on(coin.generate_transaction( + unspents, + outputs, + FeePolicy::SendExact, + None + ))); } #[test] @@ -268,7 +284,10 @@ fn test_wait_for_payment_spend_timeout_native() { let client = NativeClientImpl { coin_ticker: "RICK".into(), uri: "http://127.0.0.1:10271".to_owned(), - auth: fomat!("Basic " (base64_encode("user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", URL_SAFE))), + auth: fomat!("Basic "(base64_encode( + "user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", + URL_SAFE + ))), event_handlers: Default::default(), }; @@ -283,7 +302,10 @@ fn test_wait_for_payment_spend_timeout_native() { let wait_until = now_ms() / 1000 - 1; let from_block = 1000; - assert!(coin.wait_for_tx_spend(&transaction, wait_until, from_block).wait().is_err()); + assert!(coin + .wait_for_tx_spend(&transaction, wait_until, from_block) + .wait() + .is_err()); assert!(unsafe { OUTPUT_SPEND_CALLED }); } @@ -302,7 +324,10 @@ fn test_wait_for_payment_spend_timeout_electrum() { let wait_until = now_ms() / 1000 - 1; let from_block = 1000; - assert!(coin.wait_for_tx_spend(&transaction, wait_until, from_block).wait().is_err()); + assert!(coin + .wait_for_tx_spend(&transaction, wait_until, from_block) + .wait() + .is_err()); assert!(unsafe { OUTPUT_SPEND_CALLED }); } @@ -310,7 +335,11 @@ fn test_wait_for_payment_spend_timeout_electrum() { fn test_search_for_swap_tx_spend_electrum_was_spent() { let secret = [0; 32]; let client = electrum_client_for_test(&["electrum1.cipig.net:10017", "electrum2.cipig.net:10017"]); - let coin: UtxoCoin = utxo_coin_for_test(client.into(), Some("spice describe gravity federal blast come thank unfair canal monkey style afraid")).into(); + let coin: UtxoCoin = utxo_coin_for_test( + client.into(), + Some("spice describe gravity federal blast come thank unfair canal monkey style afraid"), + ) + .into(); // raw tx bytes of https://rick.kmd.dev/tx/ba881ecca15b5d4593f14f25debbcdfe25f101fd2e9cf8d0b5d92d19813d4424 let payment_tx_bytes = unwrap!(hex::decode("0400008085202f8902e115acc1b9e26a82f8403c9f81785445cc1285093b63b6246cf45aabac5e0865000000006b483045022100ca578f2d6bae02f839f71619e2ced54538a18d7aa92bd95dcd86ac26479ec9f802206552b6c33b533dd6fc8985415a501ebec89d1f5c59d0c923d1de5280e9827858012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffffb0721bf69163f7a5033fb3d18ba5768621d8c1347ebaa2fddab0d1f63978ea78020000006b483045022100a3309f99167982e97644dbb5cd7279b86630b35fc34855e843f2c5c0cafdc66d02202a8c3257c44e832476b2e2a723dad1bb4ec1903519502a49b936c155cae382ee012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff0300e1f5050000000017a91443fde927a77b3c1d104b78155dc389078c4571b0870000000000000000166a14b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc64b8cd736000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788acba0ce35e000000000000000000000000000000")); @@ -333,7 +362,11 @@ fn test_search_for_swap_tx_spend_electrum_was_spent() { fn test_search_for_swap_tx_spend_electrum_was_refunded() { let secret = [0; 20]; let client = electrum_client_for_test(&["electrum1.cipig.net:10017", "electrum2.cipig.net:10017"]); - let coin: UtxoCoin = utxo_coin_for_test(client.into(), Some("spice describe gravity federal blast come thank unfair canal monkey style afraid")).into(); + let coin: UtxoCoin = utxo_coin_for_test( + client.into(), + Some("spice describe gravity federal blast come thank unfair canal monkey style afraid"), + ) + .into(); // raw tx bytes of https://rick.kmd.dev/tx/78ea7839f6d1b0dafda2ba7e34c1d8218676a58bd1b33f03a5f76391f61b72b0 let payment_tx_bytes = unwrap!(hex::decode("0400008085202f8902bf17bf7d1daace52e08f732a6b8771743ca4b1cb765a187e72fd091a0aabfd52000000006a47304402203eaaa3c4da101240f80f9c5e9de716a22b1ec6d66080de6a0cca32011cd77223022040d9082b6242d6acf9a1a8e658779e1c655d708379862f235e8ba7b8ca4e69c6012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffffff023ca13c0e9e085dd13f481f193e8a3e8fd609020936e98b5587342d994f4d020000006b483045022100c0ba56adb8de923975052312467347d83238bd8d480ce66e8b709a7997373994022048507bcac921fdb2302fa5224ce86e41b7efc1a2e20ae63aa738dfa99b7be826012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff0300e1f5050000000017a9141ee6d4c38a3c078eab87ad1a5e4b00f21259b10d870000000000000000166a1400000000000000000000000000000000000000001b94d736000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac2d08e35e000000000000000000000000000000")); @@ -354,15 +387,24 @@ fn test_search_for_swap_tx_spend_electrum_was_refunded() { #[test] fn test_withdraw_impl_set_fixed_fee() { - NativeClient::list_unspent_ordered.mock_safe(|_,_| { - let unspents = vec![UnspentInfo { outpoint: OutPoint { hash: 1.into(), index: 0 }, value: 1000000000 }]; + NativeClient::list_unspent_ordered.mock_safe(|_, _| { + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + }]; MockResult::Return(Box::new(futures01::future::ok(unspents))) }); let client = NativeClient(Arc::new(NativeClientImpl { coin_ticker: TEST_COIN_NAME.into(), uri: "http://127.0.0.1".to_owned(), - auth: fomat!("Basic " (base64_encode("user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", URL_SAFE))), + auth: fomat!("Basic "(base64_encode( + "user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", + URL_SAFE + ))), event_handlers: Default::default(), })); @@ -373,26 +415,40 @@ fn test_withdraw_impl_set_fixed_fee() { to: "RQq6fWoy8aGGMLjvRfMY5mBNVm2RQxJyLa".to_string(), coin: TEST_COIN_NAME.into(), max: false, - fee: Some(WithdrawFee::UtxoFixed { amount: "0.1".parse().unwrap() }), + fee: Some(WithdrawFee::UtxoFixed { + amount: "0.1".parse().unwrap(), + }), }; - let expected = Some(UtxoFeeDetails { - amount: "0.1".parse().unwrap() - }.into()); + let expected = Some( + UtxoFeeDetails { + amount: "0.1".parse().unwrap(), + } + .into(), + ); let tx_details = unwrap!(block_on(withdraw_impl(coin.clone(), withdraw_req))); assert_eq!(expected, tx_details.fee_details); } #[test] fn test_withdraw_impl_sat_per_kb_fee() { - NativeClient::list_unspent_ordered.mock_safe(|_,_| { - let unspents = vec![UnspentInfo { outpoint: OutPoint { hash: 1.into(), index: 0 }, value: 1000000000 }]; + NativeClient::list_unspent_ordered.mock_safe(|_, _| { + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + }]; MockResult::Return(Box::new(futures01::future::ok(unspents))) }); let client = NativeClient(Arc::new(NativeClientImpl { coin_ticker: TEST_COIN_NAME.into(), uri: "http://127.0.0.1".to_owned(), - auth: fomat!("Basic " (base64_encode("user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", URL_SAFE))), + auth: fomat!("Basic "(base64_encode( + "user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", + URL_SAFE + ))), event_handlers: Default::default(), })); @@ -403,29 +459,43 @@ fn test_withdraw_impl_sat_per_kb_fee() { to: "RQq6fWoy8aGGMLjvRfMY5mBNVm2RQxJyLa".to_string(), coin: TEST_COIN_NAME.into(), max: false, - fee: Some(WithdrawFee::UtxoPerKbyte { amount: "0.1".parse().unwrap() }), + fee: Some(WithdrawFee::UtxoPerKbyte { + amount: "0.1".parse().unwrap(), + }), }; // The resulting transaction size might be 244 or 245 bytes depending on signature size // MM2 always expects the worst case during fee calculation // 0.1 * 245 / 1000 ~ 0.0245 - let expected = Some(UtxoFeeDetails { - amount: "0.0245".parse().unwrap() - }.into()); + let expected = Some( + UtxoFeeDetails { + amount: "0.0245".parse().unwrap(), + } + .into(), + ); let tx_details = unwrap!(block_on(withdraw_impl(coin.clone(), withdraw_req))); assert_eq!(expected, tx_details.fee_details); } #[test] fn test_withdraw_impl_sat_per_kb_fee_amount_equal_to_max() { - NativeClient::list_unspent_ordered.mock_safe(|_,_| { - let unspents = vec![UnspentInfo { outpoint: OutPoint { hash: 1.into(), index: 0 }, value: 1000000000 }]; + NativeClient::list_unspent_ordered.mock_safe(|_, _| { + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + }]; MockResult::Return(Box::new(futures01::future::ok(unspents))) }); let client = NativeClient(Arc::new(NativeClientImpl { coin_ticker: TEST_COIN_NAME.into(), uri: "http://127.0.0.1".to_owned(), - auth: fomat!("Basic " (base64_encode("user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", URL_SAFE))), + auth: fomat!("Basic "(base64_encode( + "user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", + URL_SAFE + ))), event_handlers: Default::default(), })); @@ -436,15 +506,20 @@ fn test_withdraw_impl_sat_per_kb_fee_amount_equal_to_max() { to: "RQq6fWoy8aGGMLjvRfMY5mBNVm2RQxJyLa".to_string(), coin: TEST_COIN_NAME.into(), max: false, - fee: Some(WithdrawFee::UtxoPerKbyte { amount: "0.1".parse().unwrap() }), + fee: Some(WithdrawFee::UtxoPerKbyte { + amount: "0.1".parse().unwrap(), + }), }; let tx_details = unwrap!(block_on(withdraw_impl(coin.clone(), withdraw_req))); // The resulting transaction size might be 210 or 211 bytes depending on signature size // MM2 always expects the worst case during fee calculation // 0.1 * 211 / 1000 = 0.0211 - let expected_fee = Some(UtxoFeeDetails { - amount: "0.0211".parse().unwrap() - }.into()); + let expected_fee = Some( + UtxoFeeDetails { + amount: "0.0211".parse().unwrap(), + } + .into(), + ); assert_eq!(expected_fee, tx_details.fee_details); let expected_balance_change = BigDecimal::from(-10); assert_eq!(expected_balance_change, tx_details.my_balance_change); @@ -452,15 +527,24 @@ fn test_withdraw_impl_sat_per_kb_fee_amount_equal_to_max() { #[test] fn test_withdraw_impl_sat_per_kb_fee_amount_equal_to_max_dust_included_to_fee() { - NativeClient::list_unspent_ordered.mock_safe(|_,_| { - let unspents = vec![UnspentInfo { outpoint: OutPoint { hash: 1.into(), index: 0 }, value: 1000000000 }]; + NativeClient::list_unspent_ordered.mock_safe(|_, _| { + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + }]; MockResult::Return(Box::new(futures01::future::ok(unspents))) }); let client = NativeClient(Arc::new(NativeClientImpl { coin_ticker: TEST_COIN_NAME.into(), uri: "http://127.0.0.1".to_owned(), - auth: fomat!("Basic " (base64_encode("user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", URL_SAFE))), + auth: fomat!("Basic "(base64_encode( + "user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", + URL_SAFE + ))), event_handlers: Default::default(), })); @@ -471,15 +555,20 @@ fn test_withdraw_impl_sat_per_kb_fee_amount_equal_to_max_dust_included_to_fee() to: "RQq6fWoy8aGGMLjvRfMY5mBNVm2RQxJyLa".to_string(), coin: TEST_COIN_NAME.into(), max: false, - fee: Some(WithdrawFee::UtxoPerKbyte { amount: "0.09999999".parse().unwrap() }), + fee: Some(WithdrawFee::UtxoPerKbyte { + amount: "0.09999999".parse().unwrap(), + }), }; let tx_details = unwrap!(block_on(withdraw_impl(coin.clone(), withdraw_req))); // The resulting transaction size might be 210 or 211 bytes depending on signature size // MM2 always expects the worst case during fee calculation // 0.1 * 211 / 1000 = 0.0211 - let expected_fee = Some(UtxoFeeDetails { - amount: "0.0211".parse().unwrap() - }.into()); + let expected_fee = Some( + UtxoFeeDetails { + amount: "0.0211".parse().unwrap(), + } + .into(), + ); assert_eq!(expected_fee, tx_details.fee_details); let expected_balance_change = BigDecimal::from(-10); assert_eq!(expected_balance_change, tx_details.my_balance_change); @@ -487,15 +576,24 @@ fn test_withdraw_impl_sat_per_kb_fee_amount_equal_to_max_dust_included_to_fee() #[test] fn test_withdraw_impl_sat_per_kb_fee_amount_over_max() { - NativeClient::list_unspent_ordered.mock_safe(|_,_| { - let unspents = vec![UnspentInfo { outpoint: OutPoint { hash: 1.into(), index: 0 }, value: 1000000000 }]; + NativeClient::list_unspent_ordered.mock_safe(|_, _| { + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + }]; MockResult::Return(Box::new(futures01::future::ok(unspents))) }); let client = NativeClient(Arc::new(NativeClientImpl { coin_ticker: TEST_COIN_NAME.into(), uri: "http://127.0.0.1".to_owned(), - auth: fomat!("Basic " (base64_encode("user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", URL_SAFE))), + auth: fomat!("Basic "(base64_encode( + "user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", + URL_SAFE + ))), event_handlers: Default::default(), })); @@ -506,22 +604,33 @@ fn test_withdraw_impl_sat_per_kb_fee_amount_over_max() { to: "RQq6fWoy8aGGMLjvRfMY5mBNVm2RQxJyLa".to_string(), coin: TEST_COIN_NAME.into(), max: false, - fee: Some(WithdrawFee::UtxoPerKbyte { amount: "0.1".parse().unwrap() }), + fee: Some(WithdrawFee::UtxoPerKbyte { + amount: "0.1".parse().unwrap(), + }), }; unwrap_err!(block_on(withdraw_impl(coin.clone(), withdraw_req))); } #[test] fn test_withdraw_impl_sat_per_kb_fee_max() { - NativeClient::list_unspent_ordered.mock_safe(|_,_| { - let unspents = vec![UnspentInfo { outpoint: OutPoint { hash: 1.into(), index: 0 }, value: 1000000000 }]; + NativeClient::list_unspent_ordered.mock_safe(|_, _| { + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + }]; MockResult::Return(Box::new(futures01::future::ok(unspents))) }); let client = NativeClient(Arc::new(NativeClientImpl { coin_ticker: TEST_COIN_NAME.into(), uri: "http://127.0.0.1".to_owned(), - auth: fomat!("Basic " (base64_encode("user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", URL_SAFE))), + auth: fomat!("Basic "(base64_encode( + "user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", + URL_SAFE + ))), event_handlers: Default::default(), })); @@ -532,14 +641,19 @@ fn test_withdraw_impl_sat_per_kb_fee_max() { to: "RQq6fWoy8aGGMLjvRfMY5mBNVm2RQxJyLa".to_string(), coin: TEST_COIN_NAME.into(), max: true, - fee: Some(WithdrawFee::UtxoPerKbyte { amount: "0.1".parse().unwrap() }), + fee: Some(WithdrawFee::UtxoPerKbyte { + amount: "0.1".parse().unwrap(), + }), }; // The resulting transaction size might be 210 or 211 bytes depending on signature size // MM2 always expects the worst case during fee calculation // 0.1 * 211 / 1000 = 0.0211 - let expected = Some(UtxoFeeDetails { - amount: "0.0211".parse().unwrap() - }.into()); + let expected = Some( + UtxoFeeDetails { + amount: "0.0211".parse().unwrap(), + } + .into(), + ); let tx_details = unwrap!(block_on(withdraw_impl(coin.clone(), withdraw_req))); assert_eq!(expected, tx_details.fee_details); } @@ -573,8 +687,16 @@ fn list_since_block_btc_serde() { #[test] // https://github.com/KomodoPlatform/atomicDEX-API/issues/587 fn get_tx_details_coinbase_transaction() { - let client = electrum_client_for_test(&["electrum1.cipig.net:10018","electrum2.cipig.net:10018","electrum3.cipig.net:10018"]); - let coin: UtxoCoin = utxo_coin_for_test(client.into(), Some("spice describe gravity federal blast come thank unfair canal monkey style afraid")).into(); + let client = electrum_client_for_test(&[ + "electrum1.cipig.net:10018", + "electrum2.cipig.net:10018", + "electrum3.cipig.net:10018", + ]); + let coin: UtxoCoin = utxo_coin_for_test( + client.into(), + Some("spice describe gravity federal blast come thank unfair canal monkey style afraid"), + ) + .into(); let fut = async move { // hash of coinbase transaction https://morty.explorer.dexstats.info/tx/b59b093ed97c1798f2a88ee3375a0c11d0822b6e4468478777f899891abd34a5 @@ -692,7 +814,10 @@ fn test_generate_transaction_relay_fee_is_used_when_dynamic_fee_is_lower() { let client = NativeClientImpl { coin_ticker: "RICK".into(), uri: "http://127.0.0.1:10271".to_owned(), - auth: fomat!("Basic " (base64_encode("user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", URL_SAFE))), + auth: fomat!("Basic "(base64_encode( + "user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", + URL_SAFE + ))), event_handlers: Default::default(), }; @@ -715,12 +840,7 @@ fn test_generate_transaction_relay_fee_is_used_when_dynamic_fee_is_lower() { value: 900000000, }]; - let fut = coin.generate_transaction( - unspents, - outputs, - FeePolicy::SendExact, - Some(ActualTxFee::Dynamic(100)) - ); + let fut = coin.generate_transaction(unspents, outputs, FeePolicy::SendExact, Some(ActualTxFee::Dynamic(100))); let generated = unwrap!(block_on(fut)); assert_eq!(generated.0.outputs.len(), 1); @@ -737,7 +857,10 @@ fn test_generate_tx_fee_is_correct_when_dynamic_fee_is_larger_than_relay() { let client = NativeClientImpl { coin_ticker: "RICK".into(), uri: "http://127.0.0.1:10271".to_owned(), - auth: fomat!("Basic " (base64_encode("user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", URL_SAFE))), + auth: fomat!("Basic "(base64_encode( + "user481805103:pass97a61c8d048bcf468c6c39a314970e557f57afd1d8a5edee917fb29bafb3a43371", + URL_SAFE + ))), event_handlers: Default::default(), }; @@ -755,7 +878,8 @@ fn test_generate_tx_fee_is_correct_when_dynamic_fee_is_larger_than_relay() { value: 1000000000, outpoint: OutPoint::default(), }; - 20]; + 20 + ]; let outputs = vec![TransactionOutput { script_pubkey: vec![].into(), @@ -766,7 +890,7 @@ fn test_generate_tx_fee_is_correct_when_dynamic_fee_is_larger_than_relay() { unspents, outputs, FeePolicy::SendExact, - Some(ActualTxFee::Dynamic(1000)) + Some(ActualTxFee::Dynamic(1000)), ); let generated = unwrap!(block_on(fut)); assert_eq!(generated.0.outputs.len(), 2); @@ -784,10 +908,13 @@ fn test_get_median_time_past_from_electrum_kmd() { let client = electrum_client_for_test(&[ "electrum1.cipig.net:10001", "electrum2.cipig.net:10001", - "electrum3.cipig.net:10001" + "electrum3.cipig.net:10001", ]); - let mtp = client.get_median_time_past(1773390, KMD_MTP_BLOCK_COUNT).wait().unwrap(); + let mtp = client + .get_median_time_past(1773390, KMD_MTP_BLOCK_COUNT) + .wait() + .unwrap(); // the MTP is block time of 1773385 in this case assert_eq!(1583159915, mtp); } @@ -797,7 +924,7 @@ fn test_get_median_time_past_from_electrum_btc() { let client = electrum_client_for_test(&[ "electrum1.cipig.net:10000", "electrum2.cipig.net:10000", - "electrum3.cipig.net:10000" + "electrum3.cipig.net:10000", ]); let mtp = client.get_median_time_past(632858, KMD_MTP_BLOCK_COUNT).wait().unwrap(); @@ -839,13 +966,14 @@ fn test_get_median_time_past_from_native_does_not_have_median_in_get_block() { "#; let blocks: Vec = json::from_str(blocks_json_str).unwrap(); - let mut blocks: HashMap<_, _> = blocks.into_iter().map(|block| (block.height.unwrap().to_string(), block)).collect(); + let mut blocks: HashMap<_, _> = blocks + .into_iter() + .map(|block| (block.height.unwrap().to_string(), block)) + .collect(); let client = native_client_for_test(); NativeClientImpl::get_block.mock_safe(move |_, block_num| { let block = blocks.remove(&block_num).unwrap(); - MockResult::Return( - Box::new(futures01::future::ok(block)) - ) + MockResult::Return(Box::new(futures01::future::ok(block))) }); let mtp = client.get_median_time_past(632858, KMD_MTP_BLOCK_COUNT).wait().unwrap(); @@ -869,15 +997,22 @@ fn test_cashaddresses_in_tx_details_by_hash() { let ctx = MmCtxBuilder::new().into_mm_arc(); let coin = unwrap!(block_on(utxo_coin_from_conf_and_request( - &ctx, "BCH", &conf, &req, &[1u8; 32]))); + &ctx, "BCH", &conf, &req, &[1u8; 32] + ))); let hash = hex::decode("0f2f6e0c8f440c641895023782783426c3aca1acc78d7c0db7751995e8aa5751").unwrap(); let fut = async { let tx_details = coin.tx_details_by_hash(&hash).compat().await.unwrap(); log!([tx_details]); - assert!(tx_details.from.iter().any(|addr| addr == "bchtest:qze8g4gx3z428jjcxzpycpxl7ke7d947gca2a7n2la")); - assert!(tx_details.to.iter().any(|addr| addr == "bchtest:qr39na5d25wdeecgw3euh9fkd4ygvd4pnsury96597")); + assert!(tx_details + .from + .iter() + .any(|addr| addr == "bchtest:qze8g4gx3z428jjcxzpycpxl7ke7d947gca2a7n2la")); + assert!(tx_details + .to + .iter() + .any(|addr| addr == "bchtest:qr39na5d25wdeecgw3euh9fkd4ygvd4pnsury96597")); }; block_on(fut); @@ -893,9 +1028,11 @@ fn test_network_info_negative_time_offset() { #[test] fn test_unavailable_electrum_proto_version() { ElectrumClientImpl::new.mock_safe(|coin_ticker, event_handlers| { - MockResult::Return( - ElectrumClientImpl::with_protocol_version(coin_ticker, event_handlers, OrdRange::new(1.8, 1.9).unwrap()) - ) + MockResult::Return(ElectrumClientImpl::with_protocol_version( + coin_ticker, + event_handlers, + OrdRange::new(1.8, 1.9).unwrap(), + )) }); let conf = json!({"coin":"RICK","asset":"RICK","rpcport":8923}); @@ -905,8 +1042,7 @@ fn test_unavailable_electrum_proto_version() { }); let ctx = MmCtxBuilder::new().into_mm_arc(); - let error = unwrap!(block_on(utxo_coin_from_conf_and_request( - &ctx, "RICK", &conf, &req, &[1u8; 32])).err()); + let error = unwrap!(block_on(utxo_coin_from_conf_and_request(&ctx, "RICK", &conf, &req, &[1u8; 32])).err()); log!("Error: "(error)); assert!(error.contains("There are no Electrums with the required protocol version")); } @@ -914,15 +1050,27 @@ fn test_unavailable_electrum_proto_version() { #[test] fn test_one_unavailable_electrum_proto_version() { ElectrumClientImpl::new.mock_safe(|coin_ticker, event_handlers| { - MockResult::Return( - ElectrumClientImpl::with_protocol_version(coin_ticker, event_handlers, OrdRange::new(1.4, 1.4).unwrap()) - ) + MockResult::Return(ElectrumClientImpl::with_protocol_version( + coin_ticker, + event_handlers, + OrdRange::new(1.4, 1.4).unwrap(), + )) }); // check if the electrum-mona.bitbank.cc:50001 doesn't support the protocol version 1.4 let client = electrum_client_for_test(&["electrum-mona.bitbank.cc:50001"]); - let result = client.server_version("electrum-mona.bitbank.cc:50001", "AtomicDEX", &OrdRange::new(1.4, 1.4).unwrap()).wait(); - assert!(result.err().unwrap().to_string().contains("unsupported protocol version")); + let result = client + .server_version( + "electrum-mona.bitbank.cc:50001", + "AtomicDEX", + &OrdRange::new(1.4, 1.4).unwrap(), + ) + .wait(); + assert!(result + .err() + .unwrap() + .to_string() + .contains("unsupported protocol version")); drop(client); log!("Run BTC coin to test the server.version loop"); @@ -936,7 +1084,8 @@ fn test_one_unavailable_electrum_proto_version() { let ctx = MmCtxBuilder::new().into_mm_arc(); let coin = unwrap!(block_on(utxo_coin_from_conf_and_request( - &ctx, "BTC", &conf, &req, &[1u8; 32]))); + &ctx, "BTC", &conf, &req, &[1u8; 32] + ))); block_on(async { Timer::sleep(0.5).await }); diff --git a/mm2src/common/big_int_str.rs b/mm2src/common/big_int_str.rs index e0f688fa54..2ac405ab91 100644 --- a/mm2src/common/big_int_str.rs +++ b/mm2src/common/big_int_str.rs @@ -1,8 +1,5 @@ use num_bigint::BigInt; -use serde::{ - de, - Deserialize, Deserializer, Serialize, Serializer, -}; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use std::fmt; /// BigInt wrapper de/serializable from/to string representation @@ -10,27 +7,19 @@ use std::fmt; pub struct BigIntStr(BigInt); impl fmt::Debug for BigIntStr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.0.to_string()) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.0.to_string()) } } impl BigIntStr { - pub fn inner(&self) -> &BigInt { - &self.0 - } + pub fn inner(&self) -> &BigInt { &self.0 } } impl From for BigIntStr { - fn from(num: BigInt) -> BigIntStr { - BigIntStr(num) - } + fn from(num: BigInt) -> BigIntStr { BigIntStr(num) } } impl Into for BigIntStr { - fn into(self) -> BigInt { - self.0 - } + fn into(self) -> BigInt { self.0 } } impl Serialize for BigIntStr { diff --git a/mm2src/common/build.rs b/mm2src/common/build.rs index 3971f7220d..ff7fc96787 100644 --- a/mm2src/common/build.rs +++ b/mm2src/common/build.rs @@ -11,10 +11,8 @@ #![allow(uncommon_codepoints)] #![cfg_attr(not(feature = "native"), allow(dead_code))] -#[macro_use] -extern crate fomat_macros; -#[macro_use] -extern crate unwrap; +#[macro_use] extern crate fomat_macros; +#[macro_use] extern crate unwrap; use bzip2::read::BzDecoder; use futures::Future; @@ -162,10 +160,7 @@ fn mm_version() -> String { let mut buf; let version = if let Ok(mut mm_versionᶠ) = fs::File::open(&mm_versionᵖ) { buf = String::new(); - unwrap!( - mm_versionᶠ.read_to_string(&mut buf), - "Can't read from MM_VERSION" - ); + unwrap!(mm_versionᶠ.read_to_string(&mut buf), "Can't read from MM_VERSION"); buf.trim().to_string() } else { // If the “MM_VERSION” file is absent then we should create it @@ -212,7 +207,7 @@ fn mm_version() -> String { let dt_file = unwrap!(String::from_utf8(slurp(&mm_datetimeᵖ))); let mut dt_file = dt_file.trim().to_string(); if let Some(ref dt_git) = dt_git { - if &dt_git[..] != &dt_file[..] { + if dt_git[..] != dt_file[..] { // Create or update the “MM_DATETIME” file in order to appease the Cargo dependency management. let mut mm_datetimeᶠ = unwrap!(fs::File::create(&mm_datetimeᵖ)); unwrap!(mm_datetimeᶠ.write_all(dt_git.as_bytes())); @@ -241,31 +236,29 @@ fn show_args<'a, I: IntoIterator>(args: I) -> String { } fn forward(stdout: ChildStdout) { - unwrap!(thread::Builder::new() - .name("forward".into()) - .spawn(move || { - let mut buf = Vec::new(); - for ch in stdout.bytes() { - let ch = match ch { - Ok(k) => k, - Err(_) => break, - }; - if ch == b'\n' { - eprintln!("{}", unsafe { from_utf8_unchecked(&buf) }); - } else { - buf.push(ch) - } - } - if !buf.is_empty() { + unwrap!(thread::Builder::new().name("forward".into()).spawn(move || { + let mut buf = Vec::new(); + for ch in stdout.bytes() { + let ch = match ch { + Ok(k) => k, + Err(_) => break, + }; + if ch == b'\n' { eprintln!("{}", unsafe { from_utf8_unchecked(&buf) }); + } else { + buf.push(ch) } - })); + } + if !buf.is_empty() { + eprintln!("{}", unsafe { from_utf8_unchecked(&buf) }); + } + })); } /// Like the `duct` `cmd!` but also prints the command into the standard error stream. macro_rules! ecmd { ( $program:expr ) => {{ - eprintln! ("$ {}", $program); + eprintln!("$ {}", $program); let mut command = Command::new ($program); command.stdout (Stdio::piped()); // Printed to `stderr` in `run!` command.stderr (Stdio::inherit()); // `stderr` is directly visible with "cargo build -vv". @@ -324,8 +317,10 @@ fn windows_requirements() { // `msvcr100.dll` is required by `ftp://sourceware.org/pub/pthreads-win32/prebuilt-dll-2-9-1-release/dll/x64/pthreadVC2.dll` let msvcr100 = system.join("msvcr100.dll"); if !msvcr100.exists() { - panic! ("msvcr100.dll is missing. \ - You can install it from https://www.microsoft.com/en-us/download/details.aspx?id=14632."); + panic!( + "msvcr100.dll is missing. \ + You can install it from https://www.microsoft.com/en-us/download/details.aspx?id=14632." + ); } // I don't exactly know what DLLs this download installs. Probably "msvcp140...". Might prove useful later. @@ -347,12 +342,7 @@ fn root() -> PathBuf { // On Windows we're getting these "\\?\" paths from canonicalize but they aren't any good for CMake. if cfg!(windows) { let s = path2s(super_net); - Path::new(if s.starts_with(r"\\?\") { - &s[4..] - } else { - &s[..] - }) - .into() + Path::new(if s.starts_with(r"\\?\") { &s[4..] } else { &s[..] }).into() } else { super_net } @@ -370,13 +360,9 @@ fn out_dir() -> PathBuf { } /// Absolute path taken from SuperNET's root + `path`. -fn rabs(rrel: &str) -> PathBuf { - root().join(rrel) -} +fn rabs(rrel: &str) -> PathBuf { root().join(rrel) } -fn path2s(path: PathBuf) -> String { - unwrap!(path.to_str(), "Non-stringy path {:?}", path).into() -} +fn path2s(path: PathBuf) -> String { unwrap!(path.to_str(), "Non-stringy path {:?}", path).into() } /// Downloads a file, placing it into the given path /// and sharing the download status on the standard error stream. @@ -410,13 +396,13 @@ fn hget(url: &str, to: PathBuf) { if status == StatusCode::FOUND { let location = unwrap!(res.headers()[LOCATION].to_str()); - epintln!("hget] Redirected to " - if location.len() < 99 { // 99 here is a numerically convenient screen width. - (location) " …" - } else { - (&location[0..33]) '…' (&location[location.len()-44..location.len()]) " …" - } - ); + epintln!("hget] Redirected to " + if location.len() < 99 { // 99 here is a numerically convenient screen width. + (location) " …" + } else { + (&location[0..33]) '…' (&location[location.len()-44..location.len()]) " …" + } + ); let request = unwrap!(Request::builder().uri(location) .body(Body::empty())); rec(client, request, to) @@ -512,13 +498,8 @@ impl Target { t => panic!("Target not (yet) supported: {}", t), } } - fn is_mac(&self) -> bool { - *self == Target::Mac - } - fn cc(&self, _plus_plus: bool) -> cc::Build { - let cc = cc::Build::new(); - cc - } + fn is_mac(&self) -> bool { *self == Target::Mac } + fn cc(&self, _plus_plus: bool) -> cc::Build { cc::Build::new() } } impl fmt::Display for Target { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -726,18 +707,11 @@ fn build_libtorrent(boost: &Path, target: &Target) -> (PathBuf, PathBuf) { } else { "libtorrent.a" }; - let mut lib_paths: Vec<_> = unwrap!(glob(unwrap!(search_from - .join("**") - .join(search_for) - .to_str()))) - .collect(); + let mut lib_paths: Vec<_> = unwrap!(glob(unwrap!(search_from.join("**").join(search_for).to_str()))).collect(); if lib_paths.is_empty() { None } else if lib_paths.len() > 1 { - panic!( - "Multiple versions of {} found in {:?}", - search_for, search_from - ) + panic!("Multiple versions of {} found in {:?}", search_for, search_from) } else { let a = unwrap!(lib_paths.remove(0)); assert!(a.is_file()); @@ -765,7 +739,7 @@ fn build_libtorrent(boost: &Path, target: &Target) -> (PathBuf, PathBuf) { // - https://github.com/arvidn/libtorrent/issues/26#issuecomment-121478708 let boostˢ = unwrap!(boost.to_str()); - let boostᵉ = escape_some(boostˢ.into()); + let boostᵉ = escape_some(boostˢ); // NB: The common compiler flags go to the "cxxflags=" here // and the platform-specific flags go to the jam files or to conditionals below. let mut b2 = fomat!( diff --git a/mm2src/common/common.rs b/mm2src/common/common.rs index 2802d31176..d60c616e3a 100644 --- a/mm2src/common/common.rs +++ b/mm2src/common/common.rs @@ -1,5 +1,5 @@ //! A common dependency for subcrates. -//! +//! //! common //! ^ //! | @@ -15,7 +15,6 @@ #![feature(hash_raw_entry)] #![feature(optin_builtin_traits)] #![feature(const_fn)] - #![allow(uncommon_codepoints)] #![cfg_attr(not(feature = "native"), allow(unused_imports))] #![cfg_attr(not(feature = "native"), allow(dead_code))] @@ -43,19 +42,20 @@ macro_rules! safecopy { } /// Implements a `From` for `enum` with a variant name matching the name of the type stored. -/// +/// /// This is helpful as a workaround for the lack of datasort refinements. /// And also as a simpler alternative to `enum_dispatch` and `enum_derive`. -/// +/// /// enum Color {Red (Red)} /// ifrom! (Color, Red); #[macro_export] macro_rules! ifrom { ($enum: ident, $id: ident) => { impl From<$id> for $enum { - fn from (t: $id) -> $enum { - $enum::$id (t) -} } } } + fn from(t: $id) -> $enum { $enum::$id(t) } + } + }; +} #[macro_use] pub mod jsonrpc_client; @@ -65,82 +65,81 @@ pub mod log; pub mod mm_metrics; pub mod big_int_str; -pub mod file_lock; -#[cfg(feature = "native")] -pub mod for_c; pub mod custom_futures; +pub mod duplex_mutex; +pub mod file_lock; +#[cfg(feature = "native")] pub mod for_c; +pub mod header; pub mod iguana_utils; -pub mod privkey; pub mod mm_ctx; pub mod mm_number; +pub mod privkey; pub mod seri; -pub mod header; -pub mod duplex_mutex; -#[cfg(feature = "native")] -pub mod lift_body; +#[cfg(feature = "native")] pub mod lift_body; #[cfg(not(feature = "native"))] pub mod lift_body { #[derive(Debug)] - pub struct LiftBody {inner: T} + pub struct LiftBody { + inner: T, + } } use atomic::Atomic; use bigdecimal::BigDecimal; #[cfg(all(feature = "native", not(windows)))] -use findshlibs::{Segment, SharedLibrary, TargetSharedLibrary, IterationControl}; -use futures01::{future, task::Task, Future}; -#[cfg(not(feature = "native"))] -use futures::task::{Context, Poll as Poll03}; -use futures::task::Waker; +use findshlibs::{IterationControl, Segment, SharedLibrary, TargetSharedLibrary}; use futures::compat::Future01CompatExt; use futures::future::FutureExt; +use futures::task::Waker; +#[cfg(not(feature = "native"))] +use futures::task::{Context, Poll as Poll03}; +use futures01::{future, task::Task, Future}; use gstuff::binprint; use hex::FromHex; -use http::{Request, Response, StatusCode, HeaderMap}; use http::header::{HeaderValue, CONTENT_TYPE}; -#[cfg(feature = "native")] -use libc::{malloc, free}; +use http::{HeaderMap, Request, Response, StatusCode}; +#[cfg(feature = "native")] use libc::{free, malloc}; use parking_lot::{Mutex as PaMutex, MutexGuard as PaMutexGuard}; -use rand::{SeedableRng, rngs::SmallRng}; -use serde::{ser, de}; +use rand::{rngs::SmallRng, SeedableRng}; +use serde::{de, ser}; #[cfg(not(feature = "native"))] use serde_bencode::de::from_bytes as bdecode; use serde_bytes::ByteBuf; use serde_json::{self as json, Value as Json}; use std::collections::HashMap; use std::env::{self, args}; +use std::ffi::{CStr, OsStr}; use std::fmt::{self, Write as FmtWrite}; use std::fs; use std::fs::DirEntry; -use std::ffi::{CStr, OsStr}; use std::future::Future as Future03; use std::intrinsics::copy; -use std::io::{Write}; +use std::io::Write; use std::mem::{forget, size_of, zeroed}; -use std::os::raw::{c_char, c_void}; use std::ops::{Add, Deref, Div, RangeInclusive}; +use std::os::raw::{c_char, c_void}; use std::path::{Path, PathBuf}; -#[cfg(not(feature = "native"))] -use std::pin::Pin; +#[cfg(not(feature = "native"))] use std::pin::Pin; use std::ptr::{null_mut, read_volatile}; -use std::sync::{Arc, Mutex}; use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex}; use std::time::UNIX_EPOCH; use uuid::Uuid; -#[cfg(feature = "w-bindgen")] -use wasm_bindgen::prelude::*; +#[cfg(feature = "w-bindgen")] use wasm_bindgen::prelude::*; pub use num_bigint::BigInt; #[cfg(feature = "native")] -#[allow(dead_code,non_upper_case_globals,non_camel_case_types,non_snake_case)] -pub mod lp {include! ("c_headers/LP_include.rs");} +#[allow(dead_code, non_upper_case_globals, non_camel_case_types, non_snake_case)] +pub mod lp { + include!("c_headers/LP_include.rs"); +} -pub const MM_DATETIME: &'static str = env! ("MM_DATETIME"); -pub const MM_VERSION: &'static str = env! ("MM_VERSION"); +pub const MM_DATETIME: &str = env!("MM_DATETIME"); +pub const MM_VERSION: &str = env!("MM_VERSION"); -pub const SATOSHIS: u64 = 100000000; +pub const SATOSHIS: u64 = 100_000_000; /// Converts u64 satoshis to f64 pub fn sat_to_f(sat: u64) -> f64 { sat as f64 / SATOSHIS as f64 } @@ -148,120 +147,150 @@ pub fn sat_to_f(sat: u64) -> f64 { sat as f64 / SATOSHIS as f64 } #[allow(non_camel_case_types)] #[derive(Clone, Copy, Eq, Hash, PartialEq)] #[repr(transparent)] -pub struct bits256 {pub bytes: [u8; 32]} +pub struct bits256 { + pub bytes: [u8; 32], +} impl Default for bits256 { fn default() -> bits256 { - bits256 {bytes: unsafe {zeroed()}}}} + bits256 { + bytes: unsafe { zeroed() }, + } + } +} impl fmt::Display for bits256 { - fn fmt (&self, fm: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, fm: &mut fmt::Formatter) -> fmt::Result { for &ch in self.bytes.iter() { - fn hex_from_digit (num: u8) -> char { - if num < 10 {(b'0' + num) as char} else {(b'a' + num - 10) as char}} - fm.write_char (hex_from_digit (ch / 16)) ?; - fm.write_char (hex_from_digit (ch % 16)) ?; + fn hex_from_digit(num: u8) -> char { + if num < 10 { + (b'0' + num) as char + } else { + (b'a' + num - 10) as char + } + } + fm.write_char(hex_from_digit(ch / 16))?; + fm.write_char(hex_from_digit(ch % 16))?; } Ok(()) -} } + } +} impl ser::Serialize for bits256 { - fn serialize (&self, se: S) -> Result where S: ser::Serializer { - se.serialize_bytes (&self.bytes[..]) -} } + fn serialize(&self, se: S) -> Result + where + S: ser::Serializer, + { + se.serialize_bytes(&self.bytes[..]) + } +} impl<'de> de::Deserialize<'de> for bits256 { - fn deserialize (deserializer: D) -> Result where D: de::Deserializer<'de> { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { struct Bits256Visitor; impl<'de> de::Visitor<'de> for Bits256Visitor { type Value = bits256; - fn expecting (&self, fm: &mut fmt::Formatter) -> fmt::Result {fm.write_str ("a byte array")} - fn visit_seq (self, mut seq: S) -> Result where S: de::SeqAccess<'de> { + fn expecting(&self, fm: &mut fmt::Formatter) -> fmt::Result { fm.write_str("a byte array") } + fn visit_seq(self, mut seq: S) -> Result + where + S: de::SeqAccess<'de>, + { let mut bytes: [u8; 32] = [0; 32]; let mut pos = 0; - while let Some (byte) = seq.next_element()? { - if pos >= bytes.len() {return Err (de::Error::custom ("bytes length > 32"))} + while let Some(byte) = seq.next_element()? { + if pos >= bytes.len() { + return Err(de::Error::custom("bytes length > 32")); + } bytes[pos] = byte; pos += 1; } - Ok (bits256 {bytes}) + Ok(bits256 { bytes }) } - fn visit_bytes (self, v: &[u8]) -> Result where E: de::Error { - if v.len() != 32 {return Err (de::Error::custom ("bytes length <> 32"))} - Ok (bits256 {bytes: *array_ref! [v, 0, 32]}) - } } - deserializer.deserialize_bytes (Bits256Visitor) -} } + fn visit_bytes(self, v: &[u8]) -> Result + where + E: de::Error, + { + if v.len() != 32 { + return Err(de::Error::custom("bytes length <> 32")); + } + Ok(bits256 { + bytes: *array_ref![v, 0, 32], + }) + } + } + deserializer.deserialize_bytes(Bits256Visitor) + } +} impl fmt::Debug for bits256 { - fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result { - (self as &dyn fmt::Display) .fmt (f) -} } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { (self as &dyn fmt::Display).fmt(f) } +} impl From<[u8; 32]> for bits256 { - fn from (bytes: [u8; 32]) -> Self {bits256 {bytes}} + fn from(bytes: [u8; 32]) -> Self { bits256 { bytes } } } impl bits256 { /// Returns true if the hash is not zero. /// Port of `#define bits256_nonz`. - pub fn nonz (&self) -> bool { - self.bytes.iter().any (|ch| *ch != 0) - } + pub fn nonz(&self) -> bool { self.bytes.iter().any(|ch| *ch != 0) } } -pub fn nonz (k: [u8; 32]) -> bool { - k.iter().any (|ch| *ch != 0) -} +pub fn nonz(k: [u8; 32]) -> bool { k.iter().any(|ch| *ch != 0) } /// Decodes a HEX string into a 32-bytes array. /// But only if the HEX string is 64 characters long, returning a zeroed array otherwise. /// (Use `fn nonz` to check if the array is zeroed). /// A port of cJSON.c/jbits256. -pub fn jbits256 (json: &Json) -> Result { - if let Some (hex) = json.as_str() { +pub fn jbits256(json: &Json) -> Result { + if let Some(hex) = json.as_str() { if hex.len() == 64 { //try_s! (::common::iguana_utils::decode_hex (unsafe {&mut hash.bytes[..]}, hex.as_bytes())); - let bytes: [u8; 32] = try_s! (FromHex::from_hex (hex)); - return Ok (bits256::from (bytes)) - } } - Ok (unsafe {zeroed()}) + let bytes: [u8; 32] = try_s!(FromHex::from_hex(hex)); + return Ok(bits256::from(bytes)); + } + } + Ok(unsafe { zeroed() }) } -pub const SATOSHIDEN: i64 = 100000000; -pub fn dstr (x: i64, decimals: u8) -> f64 {x as f64 / 10.0_f64.powf(decimals as f64)} +pub const SATOSHIDEN: i64 = 100_000_000; +pub fn dstr(x: i64, decimals: u8) -> f64 { x as f64 / 10.0_f64.powf(decimals as f64) } /// Apparently helps to workaround `double` fluctuations occuring on *certain* systems. /// cf. https://stackoverflow.com/questions/19804472/double-randomly-adds-0-000000000000001. /// Not sure it's needed in Rust, the floating point operations should be determenistic here, /// but better safe than sorry. -pub const SMALLVAL: f64 = 0.000000000000001; // 1e-15f64 +pub const SMALLVAL: f64 = 0.000_000_000_000_001; // 1e-15f64 /// Helps sharing a string slice with C code by allocating a zero-terminated string with the C standard library allocator. -/// +/// /// The difference from `CString` is that the memory is then *owned* by the C code instead of being temporarily borrowed, /// that is it doesn't need to be recycled in Rust. /// Plus we don't check the slice for zeroes, most of our code doesn't need that extra check. #[cfg(feature = "native")] -pub fn str_to_malloc (s: &str) -> *mut c_char { - slice_to_malloc (s.as_bytes()) as *mut c_char -} +pub fn str_to_malloc(s: &str) -> *mut c_char { slice_to_malloc(s.as_bytes()) as *mut c_char } /// Helps sharing a byte slice with C code by allocating a zero-terminated string with the C standard library allocator. #[cfg(feature = "native")] -pub fn slice_to_malloc (bytes: &[u8]) -> *mut u8 {unsafe { - let buf = malloc (bytes.len() + 1) as *mut u8; - copy (bytes.as_ptr(), buf, bytes.len()); - *buf.offset (bytes.len() as isize) = 0; - buf -}} +pub fn slice_to_malloc(bytes: &[u8]) -> *mut u8 { + unsafe { + let buf = malloc(bytes.len() + 1) as *mut u8; + copy(bytes.as_ptr(), buf, bytes.len()); + *buf.add(bytes.len()) = 0; + buf + } +} /// Converts *mut c_char to Rust String /// Doesn't free the allocated memory /// It's responsibility of the caller to free the memory when required /// Returns error in case of null pointer input #[cfg(feature = "native")] -pub fn c_char_to_string(ptr: *mut c_char) -> Result { unsafe { +#[allow(clippy::missing_safety_doc)] +pub unsafe fn c_char_to_string(ptr: *mut c_char) -> Result { if !ptr.is_null() { let res_str = try_s!(CStr::from_ptr(ptr).to_str()); let res_str = String::from(res_str); @@ -269,123 +298,138 @@ pub fn c_char_to_string(ptr: *mut c_char) -> Result { unsafe { } else { ERR!("Tried to convert null pointer to Rust String!") } -}} +} /// Frees C raw pointer /// Does nothing in case of null pointer input #[cfg(feature = "native")] -pub fn free_c_ptr(ptr: *mut c_void) { unsafe { - if !ptr.is_null() { - free(ptr as *mut libc::c_void); +pub fn free_c_ptr(ptr: *mut c_void) { + unsafe { + if !ptr.is_null() { + free(ptr as *mut libc::c_void); + } } -}} +} /// Use the value, preventing the compiler and linker from optimizing it away. -pub fn black_box (v: T) -> T { +pub fn black_box(v: T) -> T { // https://github.com/rust-lang/rfcs/issues/1484#issuecomment-240853111 //std::hint::black_box (v) - let ret = unsafe {read_volatile (&v)}; - forget (v); + let ret = unsafe { read_volatile(&v) }; + forget(v); ret } /// Attempts to remove the `Path` on `drop`. #[derive(Debug)] -pub struct RaiiRm<'a> (pub &'a Path); +pub struct RaiiRm<'a>(pub &'a Path); impl<'a> AsRef for RaiiRm<'a> { - fn as_ref (&self) -> &Path { - self.0 - } + fn as_ref(&self) -> &Path { self.0 } } impl<'a> Drop for RaiiRm<'a> { - fn drop (&mut self) { - let _ = fs::remove_file (self); - } + fn drop(&mut self) { let _ = fs::remove_file(self); } } /// Using a static buffer in order to minimize the chance of heap and stack allocations in the signal handler. fn trace_buf() -> PaMutexGuard<'static, [u8; 256]> { - static TRACE_BUF: PaMutex<[u8; 256]> = PaMutex::new ([0; 256]); + static TRACE_BUF: PaMutex<[u8; 256]> = PaMutex::new([0; 256]); TRACE_BUF.lock() } fn trace_name_buf() -> PaMutexGuard<'static, [u8; 128]> { - static TRACE_NAME_BUF: PaMutex<[u8; 128]> = PaMutex::new ([0; 128]); + static TRACE_NAME_BUF: PaMutex<[u8; 128]> = PaMutex::new([0; 128]); TRACE_NAME_BUF.lock() } /// Formats a stack frame. /// Some common and less than useful frames are skipped. -pub fn stack_trace_frame (instr_ptr: *mut c_void, buf: &mut dyn Write, symbol: &backtrace::Symbol) { +pub fn stack_trace_frame(instr_ptr: *mut c_void, buf: &mut dyn Write, symbol: &backtrace::Symbol) { let filename = match symbol.filename() { - Some (path) => match path.components().rev().next() { - Some (c) => c.as_os_str().to_string_lossy(), + Some(path) => match path.components().rev().next() { + Some(c) => c.as_os_str().to_string_lossy(), None => "??".into(), }, None => "??".into(), }; - let lineno = match symbol.lineno() {Some (lineno) => lineno, None => 0}; - let name = match symbol.name() {Some (name) => name, None => SymbolName::new(&[])}; + let lineno = match symbol.lineno() { + Some(lineno) => lineno, + None => 0, + }; + let name = match symbol.name() { + Some(name) => name, + None => SymbolName::new(&[]), + }; let mut name_buf = trace_name_buf(); - let name = gstring! (name_buf, { - let _ = write! (name_buf, "{}", name); // NB: `fmt` is different from `SymbolName::as_str`. + let name = gstring!(name_buf, { + let _ = write!(name_buf, "{}", name); // NB: `fmt` is different from `SymbolName::as_str`. }); // Skip common and less than informative frames. - if name == "mm2::crash_reports::rust_seh_handler" {return} - if name == "veh_exception_filter" {return} - if name == "common::stack_trace" {return} - if name == "common::log_stacktrace" {return} - if name == "__scrt_common_main_seh" {return} // Super-main on Windows. - - if filename == "boxed.rs" {return} - if filename == "panic.rs" {return} - - // Alphanumerically sorted on first letter. - if name.starts_with ("alloc::") {return} - if name.starts_with ("backtrace::") {return} - if name.starts_with ("common::set_panic_hook") {return} - if name.starts_with ("common::stack_trace") {return} - if name.starts_with ("core::ops::") {return} - if name.starts_with ("futures::") {return} - if name.starts_with ("hyper::") {return} - if name.starts_with ("mm2::crash_reports::signal_handler") {return} - if name.starts_with ("panic_unwind::") {return} - if name.starts_with ("std::") {return} - if name.starts_with ("scoped_tls::") {return} - if name.starts_with ("test::run_test::") {return} - if name.starts_with ("tokio::") {return} - if name.starts_with ("tokio_core::") {return} - if name.starts_with ("tokio_reactor::") {return} - if name.starts_with ("tokio_executor::") {return} - if name.starts_with ("tokio_timer::") {return} - - let _ = writeln! (buf, " {}:{}] {} {:?}", filename, lineno, name, instr_ptr); + match name { + "mm2::crash_reports::rust_seh_handler" + | "veh_exception_filter" + | "common::stack_trace" + | "common::log_stacktrace" + // Super-main on Windows. + | "__scrt_common_main_seh" => return, + _ => (), + } + + match filename.as_ref() { + "boxed.rs" | "panic.rs" => return, + _ => (), + } + + if name.starts_with("alloc::") + || name.starts_with("backtrace::") + || name.starts_with("common::set_panic_hook") + || name.starts_with("common::stack_trace") + || name.starts_with("core::ops::") + || name.starts_with("futures::") + || name.starts_with("hyper::") + || name.starts_with("mm2::crash_reports::signal_handler") + || name.starts_with("panic_unwind::") + || name.starts_with("std::") + || name.starts_with("scoped_tls::") + || name.starts_with("test::run_test::") + || name.starts_with("tokio::") + || name.starts_with("tokio_core::") + || name.starts_with("tokio_reactor::") + || name.starts_with("tokio_executor::") + || name.starts_with("tokio_timer::") + { + return; + } + + let _ = writeln!(buf, " {}:{}] {} {:?}", filename, lineno, name, instr_ptr); } /// Generates a string with the current stack trace. -/// +/// /// To get a simple stack trace: -/// +/// /// let mut trace = String::with_capacity (4096); /// stack_trace (&mut stack_trace_frame, &mut |l| trace.push_str (l)); -/// +/// /// * `format` - Generates the string representation of a frame. /// * `output` - Function used to print the stack trace. /// Printing immediately, without buffering, should make the tracing somewhat more reliable. -pub fn stack_trace (format: &mut dyn FnMut (*mut c_void, &mut dyn Write, &backtrace::Symbol), output: &mut dyn FnMut (&str)) { +pub fn stack_trace( + format: &mut dyn FnMut(*mut c_void, &mut dyn Write, &backtrace::Symbol), + output: &mut dyn FnMut(&str), +) { // cf. https://github.com/rust-lang/rust/pull/64154 (standard library backtrace) - backtrace::trace (|frame| { - backtrace::resolve (frame.ip(), |symbol| { + backtrace::trace(|frame| { + backtrace::resolve(frame.ip(), |symbol| { let mut trace_buf = trace_buf(); - let trace = gstring! (trace_buf, { - // frame.ip() is next instruction pointer typically so offset(-1) points to current instruction - format (frame.ip().wrapping_offset(-1), trace_buf, symbol); + let trace = gstring!(trace_buf, { + // frame.ip() is next instruction pointer typically so offset(-1) points to current instruction + format(frame.ip().wrapping_offset(-1), trace_buf, symbol); }); - output (trace); + output(trace); }); true }); @@ -395,16 +439,25 @@ pub fn stack_trace (format: &mut dyn FnMut (*mut c_void, &mut dyn Write, &backtr } #[cfg(all(feature = "native", not(windows)))] -fn output_pc_mem_addr(output: &mut dyn FnMut (&str)) { +fn output_pc_mem_addr(output: &mut dyn FnMut(&str)) { TargetSharedLibrary::each(|shlib| { let mut trace_buf = trace_buf(); - let name = gstring! (trace_buf, { - let _ = write! (trace_buf, "Virtual memory addresses of {}", shlib.name().to_string_lossy()); + let name = gstring!(trace_buf, { + let _ = write!( + trace_buf, + "Virtual memory addresses of {}", + shlib.name().to_string_lossy() + ); }); output(name); for seg in shlib.segments() { - let segment = gstring! (trace_buf, { - let _ = write! (trace_buf, " {}:{}", seg.name(), seg.actual_virtual_memory_address(shlib)); + let segment = gstring!(trace_buf, { + let _ = write!( + trace_buf, + " {}:{}", + seg.name(), + seg.actual_virtual_memory_address(shlib) + ); }); output(segment); } @@ -423,22 +476,23 @@ pub fn set_panic_hook() { thread_local! {static ENTERED: Atomic = Atomic::new (false);} - set_hook (Box::new (|info: &PanicInfo| { + set_hook(Box::new(|info: &PanicInfo| { // Stack tracing and logging might panic (in `println!` for example). // Let us detect this and do nothing on second panic. // We'll likely still get a crash after the hook is finished // (experimenting with this I'm getting the "thread panicked while panicking. aborting." on Windows) // but that crash will have a better stack trace compared to the one with deep hook recursion. - if let Ok (Err (_)) = ENTERED.try_with ( - |e| e.compare_exchange (false, true, Ordering::Relaxed, Ordering::Relaxed)) { - return} + if let Ok(Err(_)) = ENTERED.try_with(|e| e.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)) + { + return; + } let mut trace = String::new(); - stack_trace (&mut stack_trace_frame, &mut |l| trace.push_str (l)); - log! ((info)); - log! ("backtrace\n" (trace)); + stack_trace(&mut stack_trace_frame, &mut |l| trace.push_str(l)); + log!((info)); + log!("backtrace\n"(trace)); - let _ = ENTERED.try_with (|e| e.compare_exchange (true, false, Ordering::Relaxed, Ordering::Relaxed)); + let _ = ENTERED.try_with(|e| e.compare_exchange(true, false, Ordering::Relaxed, Ordering::Relaxed)); })) } @@ -446,16 +500,17 @@ pub fn set_panic_hook() { pub fn double_panic_crash() { struct Panicker; impl Drop for Panicker { - fn drop (&mut self) { - panic! ("panic in drop") - } } + fn drop(&mut self) { panic!("panic in drop") } + } let panicker = Panicker; - if 1 == 1 {panic! ("first panic")} - drop (panicker) // Delays the drop. + if 1 < 2 { + panic!("first panic") + } + drop(panicker) // Delays the drop. } /// Tries to detect if we're running under a test, allowing us to be lazy and *delay* some costly operations. -/// +/// /// Note that the code SHOULD behave uniformely regardless of where it's invoked from /// (nondeterminism breaks POLA and we don't know how the code will be used in the future) /// but in certain cases we have a leeway of adjusting to being run from a test @@ -464,43 +519,56 @@ pub fn double_panic_crash() { /// we can avoid the unnecessary overhead of DHT initializaion and destruction while maintaining the contract. pub fn is_a_test_drill() -> bool { // Stack tracing would sometimes crash on Windows, doesn't worth the risk here. - if cfg! (windows) {return false} + if cfg!(windows) { + return false; + } - let mut trace = String::with_capacity (1024); - stack_trace ( - &mut |_ptr, mut fwr, sym| {if let Some (name) = sym.name() {let _ = witeln! (fwr, (name));}}, - &mut |tr| {trace.push_str (tr)}); + let mut trace = String::with_capacity(1024); + stack_trace( + &mut |_ptr, mut fwr, sym| { + if let Some(name) = sym.name() { + let _ = witeln!(fwr, (name)); + } + }, + &mut |tr| trace.push_str(tr), + ); - if trace.contains ("\nmm2::main\n") || trace.contains ("\nmm2::run_lp_main\n") {return false} + if trace.contains("\nmm2::main\n") || trace.contains("\nmm2::run_lp_main\n") { + return false; + } - if let Some (executable) = args().next() { - if executable.ends_with (r"\mm2.exe") {return false} - if executable.ends_with ("/mm2") {return false} + if let Some(executable) = args().next() { + if executable.ends_with(r"\mm2.exe") { + return false; + } + if executable.ends_with("/mm2") { + return false; + } } true } -pub type SlurpFut = Box), Error=String> + Send + 'static>; +pub type SlurpFut = Box), Error = String> + Send + 'static>; /// RPC response, returned by the RPC handlers. /// NB: By default the future is executed on the shared asynchronous reactor (`CORE`), /// the handler is responsible for spawning the future on another reactor if it doesn't fit the `CORE` well. -pub type HyRes = Box>, Error=String> + Send>; +pub type HyRes = Box>, Error = String> + Send>; #[derive(Debug, Deserialize, Serialize)] struct HostedHttpRequest { method: String, uri: String, headers: HashMap, - body: Vec + body: Vec, } #[derive(Debug, Deserialize, Serialize)] struct HostedHttpResponse { status: u16, headers: HashMap, - body: Vec + body: Vec, } // To improve git history and ease of exploratory refactoring @@ -510,91 +578,96 @@ struct HostedHttpResponse { #[cfg(not(feature = "native"))] pub mod wio { - use futures01::future::IntoFuture; + use super::SlurpFut; + use futures::channel::oneshot::{channel, Receiver, Sender}; use futures::compat::Compat; use futures::future::FutureExt; use futures::lock::Mutex; - use futures::channel::oneshot::{channel, Receiver, Sender}; - use http::{HeaderMap, Method, Request, StatusCode}; + use futures01::future::IntoFuture; use http::header::{HeaderName, HeaderValue}; + use http::{HeaderMap, Method, Request, StatusCode}; use rand::Rng; - use serde_bencode::ser::to_bytes as bencode; use serde_bencode::de::from_bytes as bdecode; + use serde_bencode::ser::to_bytes as bencode; use std::collections::HashMap; use std::os::raw::c_char; use std::str::FromStr; - use super::SlurpFut; - pub async fn slurp_reqʹ (request: Request>) -> Result<(StatusCode, HeaderMap, Vec), String> { + pub async fn slurp_reqʹ(request: Request>) -> Result<(StatusCode, HeaderMap, Vec), String> { let (parts, body) = request.into_parts(); let hhreq = super::HostedHttpRequest { method: parts.method.as_str().to_owned(), - uri: fomat! ((parts.uri)), - headers: parts.headers.iter().filter_map (|(name, value)| { - let name = name.as_str().to_owned(); - let v = match value.to_str() { - Ok (ascii) => ascii, - Err (err) => {log! ("!ascii '" (name) "': " (err)); return None} - }; - Some ((name, v.to_owned())) - }) .collect(), - body + uri: fomat!((parts.uri)), + headers: parts + .headers + .iter() + .filter_map(|(name, value)| { + let name = name.as_str().to_owned(); + let v = match value.to_str() { + Ok(ascii) => ascii, + Err(err) => { + log! ("!ascii '" (name) "': " (err)); + return None; + }, + }; + Some((name, v.to_owned())) + }) + .collect(), + body, }; - let hhreq = try_s! (bencode (&hhreq)); - let hhres = try_s! (super::helperᶜ ("slurp_req", hhreq) .await); - let hhres: super::HostedHttpResponse = try_s! (bdecode (&hhres)); - let status = try_s! (StatusCode::from_u16 (hhres.status)); - - let mut headers = HeaderMap::::with_capacity (hhres.headers.len()); - for (n, v) in hhres.headers {headers.insert ( - try_s! (HeaderName::from_str (&n[..])), - try_s! (HeaderValue::from_str (&v[..])) - );} + let hhreq = try_s!(bencode(&hhreq)); + let hhres = try_s!(super::helperᶜ("slurp_req", hhreq).await); + let hhres: super::HostedHttpResponse = try_s!(bdecode(&hhres)); + let status = try_s!(StatusCode::from_u16(hhres.status)); + + let mut headers = HeaderMap::::with_capacity(hhres.headers.len()); + for (n, v) in hhres.headers { + headers.insert( + try_s!(HeaderName::from_str(&n[..])), + try_s!(HeaderValue::from_str(&v[..])), + ); + } - Ok ((status, headers, hhres.body)) + Ok((status, headers, hhres.body)) } - pub fn slurp_req (request: Request>) -> SlurpFut { - Box::new (Compat::new (Box::pin (slurp_reqʹ (request)))) - } + pub fn slurp_req(request: Request>) -> SlurpFut { Box::new(Compat::new(Box::pin(slurp_reqʹ(request)))) } } #[cfg(feature = "native")] pub mod wio { - use bytes::Bytes; use crate::lift_body::LiftBody; use crate::SlurpFut; - use futures01::{Async, Future, Poll}; - use futures01::sync::oneshot::{self, Receiver}; + use bytes::Bytes; use futures::compat::Future01CompatExt; use futures::executor::ThreadPool; + use futures01::sync::oneshot::{self, Receiver}; + use futures01::{Async, Future, Poll}; use futures_cpupool::CpuPool; use gstuff::{duration_to_float, now_float}; - use http::{Method, Request, StatusCode, HeaderMap}; - use hyper::Client; + use http::{HeaderMap, Method, Request, StatusCode}; use hyper::client::HttpConnector; use hyper::rt::Stream; use hyper::server::conn::Http; - use hyper_tls::HttpsConnector; - use serde_bencode::ser::to_bytes as bencode; + use hyper::Client; + use hyper_rustls::HttpsConnector; use serde_bencode::de::from_bytes as bdecode; + use serde_bencode::ser::to_bytes as bencode; use std::fmt; + use std::sync::Mutex; use std::thread::JoinHandle; use std::time::Duration; - use std::sync::Mutex; use tokio::runtime::Runtime; - fn start_core_thread() -> Runtime { - unwrap! (tokio::runtime::Builder::new().build()) - } + fn start_core_thread() -> Runtime { unwrap!(tokio::runtime::Builder::new().build()) } lazy_static! { /// Shared asynchronous reactor. pub static ref CORE: Mutex = Mutex::new (start_core_thread()); /// Shared CPU pool to run intensive/sleeping requests on a separate thread. - /// + /// /// Deprecated, prefer the futures 0.3 `POOL` instead. pub static ref CPUPOOL: CpuPool = CpuPool::new(8); /// Shared CPU pool to run intensive/sleeping requests on s separate thread. @@ -612,17 +685,17 @@ pub mod wio { /// If the results are not necessary then a future can be scheduled directly on the reactor: /// /// CORE.spawn (|_| f); - pub fn drive (f: F) -> Receiver> where - F: Future + Send + 'static, - R: Send + 'static, - E: Send + 'static { + pub fn drive(f: F) -> Receiver> + where + F: Future + Send + 'static, + R: Send + 'static, + E: Send + 'static, + { let (sx, rx) = oneshot::channel(); - unwrap! (CORE.lock()) .spawn ( - f.then (move |fr: Result| -> Result<(),()> { - let _ = sx.send (fr); - Ok(()) - }) - ); + unwrap!(CORE.lock()).spawn(f.then(move |fr: Result| -> Result<(), ()> { + let _ = sx.send(fr); + Ok(()) + })); rx } @@ -630,19 +703,21 @@ pub mod wio { /// /// Similar to `fn drive`, but returns a stringified error, /// allowing us to collapse the `Receiver` and return the `R` directly. - pub fn drive_s (f: F) -> impl Future where - F: Future + Send + 'static, - R: Send + 'static, - E: fmt::Display + Send + 'static { - drive (f) .then (move |r| -> Result { - let r = try_s! (r); // Peel the `Receiver`. - let r = try_s! (r); // `E` to `String`. - Ok (r) + pub fn drive_s(f: F) -> impl Future + where + F: Future + Send + 'static, + R: Send + 'static, + E: fmt::Display + Send + 'static, + { + drive(f).then(move |r| -> Result { + let r = try_s!(r); // Peel the `Receiver`. + let r = try_s!(r); // `E` to `String`. + Ok(r) }) } /// Finishes with the "timeout" error if the underlying future isn't ready withing the given timeframe. - /// + /// /// NB: Tokio timers (in `tokio::timer`) only seem to work under the Tokio runtime, /// which is unfortunate as we want the different futures executed on the different reactors /// depending on how much they're I/O-bound, CPU-bound or blocking. @@ -651,64 +726,74 @@ pub mod wio { /// P.S. The older `0.1` version of the `tokio::timer` might work NP, it works in other parts of our code. /// The new version, on the other hand, requires the Tokio runtime (https://tokio.rs/blog/2018-03-timers/). /// P.S. We could try using the `futures-timer` crate instead, but note that it is currently under-maintained, - /// https://github.com/rustasync/futures-timer/issues/9#issuecomment-400802515. + /// https://github.com/rustasync/futures-timer/issues/9#issuecomment-400802515. pub struct Timeout { - fut: Box>, + fut: Box>, started: f64, timeout: f64, - monitor: Option> + monitor: Option>, } impl Future for Timeout { type Item = R; type Error = String; - fn poll (&mut self) -> Poll { + fn poll(&mut self) -> Poll { match self.fut.poll() { - Err (err) => Err (err), - Ok (Async::Ready (r)) => Ok (Async::Ready (r)), - Ok (Async::NotReady) => { + Err(err) => Err(err), + Ok(Async::Ready(r)) => Ok(Async::Ready(r)), + Ok(Async::NotReady) => { let now = now_float(); if now >= self.started + self.timeout { - Err (format! ("timeout ({:.1} > {:.1})", now - self.started, self.timeout)) + Err(format!("timeout ({:.1} > {:.1})", now - self.started, self.timeout)) } else { // Start waking up this future until it has a chance to timeout. // For now it's just a basic separate thread. Will probably optimize later. if self.monitor.is_none() { let task = futures01::task::current(); let deadline = self.started + self.timeout; - self.monitor = Some (unwrap! (std::thread::Builder::new().name ("timeout monitor".into()) .spawn (move || { - loop { - std::thread::sleep (Duration::from_secs (1)); - task.notify(); - if now_float() > deadline + 2. {break} - } - }))); + self.monitor = Some(unwrap!(std::thread::Builder::new() + .name("timeout monitor".into()) + .spawn(move || { + loop { + std::thread::sleep(Duration::from_secs(1)); + task.notify(); + if now_float() > deadline + 2. { + break; + } + } + }))); } - Ok (Async::NotReady) - } } } } } + Ok(Async::NotReady) + } + }, + } + } + } impl Timeout { - pub fn new (fut: Box>, timeout: Duration) -> Timeout { + pub fn new(fut: Box>, timeout: Duration) -> Timeout { Timeout { - fut: fut, + fut, started: now_float(), - timeout: duration_to_float (timeout), - monitor: None - } } } + timeout: duration_to_float(timeout), + monitor: None, + } + } + } unsafe impl Send for Timeout {} /// Initialize the crate. pub fn init() { // Pre-allocate the stack trace buffer in order to avoid allocating it from a signal handler. - super::black_box (&*super::trace_buf()); - super::black_box (&*super::trace_name_buf()); + super::black_box(&*super::trace_buf()); + super::black_box(&*super::trace_name_buf()); } lazy_static! { /// NB: With a shared client there is a possibility that keep-alive connections will be reused. pub static ref HYPER: Client, LiftBody>> = { let dns_threads = 2; - let https = HttpsConnector::new (dns_threads).unwrap(); - let client = Client::builder() + let https = HttpsConnector::new (dns_threads); + Client::builder() .executor (unwrap! (CORE.lock()) .executor()) // Hyper had a lot of Keep-Alive bugs over the years and I suspect // that with the shared client we might be getting errno 10054 @@ -721,198 +806,235 @@ pub mod wio { // Performance of Keep-Alive in the Hyper client is questionable as well, // should measure it on a case-by-case basis when we need it. .keep_alive (false) - .build (https); - client + .build (https) }; } /// Executes a Hyper request, returning the response status, headers and body. - pub fn slurp_req (request: Request>) -> SlurpFut { + pub fn slurp_req(request: Request>) -> SlurpFut { let (head, body) = request.into_parts(); - let request = Request::from_parts (head, LiftBody::from (body)); + let request = Request::from_parts(head, LiftBody::from(body)); - let uri = fomat! ((request.uri())); - let request_f = HYPER.request (request); - let response_f = request_f.then (move |res| -> SlurpFut { + let uri = fomat!((request.uri())); + let request_f = HYPER.request(request); + let response_f = request_f.then(move |res| -> SlurpFut { // Can fail with: // "an IO error occurred: An existing connection was forcibly closed by the remote host. (os error 10054)" (on Windows) // "an error occurred trying to connect: No connection could be made because the target machine actively refused it. (os error 10061)" // "an error occurred trying to connect: Connection refused (os error 111)" let res = match res { - Ok (r) => r, - Err (err) => return Box::new (futures01::future::err ( - ERRL! ("Error accessing '{}': {}", uri, err))) + Ok(r) => r, + Err(err) => return Box::new(futures01::future::err(ERRL!("Error accessing '{}': {}", uri, err))), }; let status = res.status(); let headers = res.headers().clone(); let body = res.into_body(); let body_f = body.concat2(); - let combined_f = body_f.then (move |body| -> Result<(StatusCode, HeaderMap, Vec), String> { - let body = try_s! (body); - Ok ((status, headers, body.to_vec())) + let combined_f = body_f.then(move |body| -> Result<(StatusCode, HeaderMap, Vec), String> { + let body = try_s!(body); + Ok((status, headers, body.to_vec())) }); - Box::new (combined_f) + Box::new(combined_f) }); - Box::new (drive_s (response_f)) + Box::new(drive_s(response_f)) } - pub async fn slurp_reqʹ (request: Request>) -> Result<(StatusCode, HeaderMap, Vec), String> { - slurp_req (request) .compat().await + pub async fn slurp_reqʹ(request: Request>) -> Result<(StatusCode, HeaderMap, Vec), String> { + slurp_req(request).compat().await } - pub async fn slurp_reqʰ (req: Bytes) -> Result, String> { - let hhreq: super::HostedHttpRequest = try_s! (bdecode (&req)); + pub async fn slurp_reqʰ(req: Bytes) -> Result, String> { + let hhreq: super::HostedHttpRequest = try_s!(bdecode(&req)); //log! ("slurp_reqʰ] " [=hhreq]); let mut req = Request::builder(); - req.method (try_s! (Method::from_bytes (hhreq.method.as_bytes()))); - req.uri (hhreq.uri); - for (n, v) in hhreq.headers {req.header (&n[..], &v[..]);} - let req = try_s! (req.body (hhreq.body)); + req.method(try_s!(Method::from_bytes(hhreq.method.as_bytes()))); + req.uri(hhreq.uri); + for (n, v) in hhreq.headers { + req.header(&n[..], &v[..]); + } + let req = try_s!(req.body(hhreq.body)); - let (status, headers, body) = try_s! (slurp_reqʹ (req) .await); + let (status, headers, body) = try_s!(slurp_reqʹ(req).await); let hhres = super::HostedHttpResponse { status: status.as_u16(), - headers: headers.iter().filter_map (|(name, value)| { - let name = name.as_str().to_owned(); - let v = match value.to_str() { - Ok (ascii) => ascii, - Err (err) => {log! ("!ascii '" (name) "': " (err)); return None} - }; - Some ((name, v.to_owned())) - }) .collect(), - body + headers: headers + .iter() + .filter_map(|(name, value)| { + let name = name.as_str().to_owned(); + let v = match value.to_str() { + Ok(ascii) => ascii, + Err(err) => { + log! ("!ascii '" (name) "': " (err)); + return None; + }, + }; + Some((name, v.to_owned())) + }) + .collect(), + body, }; //log! ("HostedHttpResponse: " [=hhres]); - let hhres = try_s! (bencode (&hhres)); - Ok (hhres) + let hhres = try_s!(bencode(&hhres)); + Ok(hhres) } } #[cfg(feature = "native")] pub mod executor { - use futures::{FutureExt, Future as Future03, TryFutureExt}; - use futures::task::{Context, Poll as Poll03}; + use futures::task::Context; + use futures::{Future as Future03, FutureExt, Poll as Poll03, TryFutureExt}; use gstuff::now_float; use std::pin::Pin; - use std::time::Duration; use std::thread; + use std::time::Duration; - pub fn spawn (future: impl Future03 + Send + 'static) { + pub fn spawn(future: impl Future03 + Send + 'static) { let f = future.unit_error().boxed().compat(); - unwrap! (crate::wio::CORE.lock()) .spawn (f); + unwrap!(crate::wio::CORE.lock()).spawn(f); } /// Schedule the given `future` to be executed shortly after the given `utc` time is reached. - pub fn spawn_after (utc: f64, future: impl Future03 + Send + 'static) { + pub fn spawn_after(utc: f64, future: impl Future03 + Send + 'static) { use crossbeam::channel; use gstuff::Constructible; use std::collections::BTreeMap; use std::sync::Once; + type SheduleChannelItem = (f64, Pin + Send + 'static>>); static START: Once = Once::new(); - static SCHEDULE: Constructible + Send + 'static>>)>> = Constructible::new(); - START.call_once (|| { - unwrap! (thread::Builder::new().name ("spawn_after".into()) .spawn (move || { - let (tx, rx) = channel::bounded (0); - unwrap! (SCHEDULE.pin (tx), "spawn_after] Can't pin the channel"); - let mut tasks: BTreeMap + Send + 'static>>>> = BTreeMap::new(); - let mut ready = Vec::with_capacity (4); - loop { - let now = Duration::from_secs_f64 (now_float()); - let mut next_stop = Duration::from_secs_f64 (0.1); - for (utc, _) in tasks.iter() { - if *utc <= now {ready.push (*utc)} - else {next_stop = *utc - now; break} - } - for utc in ready.drain (..) { - let v = match tasks.remove (&utc) {Some (v) => v, None => continue}; - //log! ("spawn_after] spawning " (v.len()) " tasks at " [utc]); - for f in v {spawn (f)} + static SCHEDULE: Constructible> = Constructible::new(); + START.call_once(|| { + unwrap!( + thread::Builder::new().name("spawn_after".into()).spawn(move || { + let (tx, rx) = channel::bounded(0); + unwrap!(SCHEDULE.pin(tx), "spawn_after] Can't pin the channel"); + type Task = Pin + Send + 'static>>; + let mut tasks: BTreeMap> = BTreeMap::new(); + let mut ready = Vec::with_capacity(4); + loop { + let now = Duration::from_secs_f64(now_float()); + let mut next_stop = Duration::from_secs_f64(0.1); + for (utc, _) in tasks.iter() { + if *utc <= now { + ready.push(*utc) + } else { + next_stop = *utc - now; + break; + } + } + for utc in ready.drain(..) { + let v = match tasks.remove(&utc) { + Some(v) => v, + None => continue, + }; + //log! ("spawn_after] spawning " (v.len()) " tasks at " [utc]); + for f in v { + spawn(f) + } + } + let (utc, f) = match rx.recv_timeout(next_stop) { + Ok(t) => t, + Err(channel::RecvTimeoutError::Disconnected) => break, + Err(channel::RecvTimeoutError::Timeout) => continue, + }; + tasks + .entry(Duration::from_secs_f64(utc)) + .or_insert_with(Vec::new) + .push(f) } - let (utc, f) = match rx.recv_timeout (next_stop) { - Ok (t) => t, - Err (channel::RecvTimeoutError::Disconnected) => break, - Err (channel::RecvTimeoutError::Timeout) => continue - }; - tasks.entry (Duration::from_secs_f64 (utc)) .or_insert (Vec::new()) .push (f) - } - }), "Can't spawn a spawn_after thread"); + }), + "Can't spawn a spawn_after thread" + ); }); loop { match SCHEDULE.as_option() { - None => {thread::yield_now(); continue} - Some (tx) => {unwrap! (tx.send ((utc, Box::pin (future))), "Can't reach spawn_after"); break} + None => { + thread::yield_now(); + continue; + }, + Some(tx) => { + unwrap!(tx.send((utc, Box::pin(future))), "Can't reach spawn_after"); + break; + }, } } } /// A future that completes at a given time. - pub struct Timer {till_utc: f64} + pub struct Timer { + till_utc: f64, + } impl Timer { - pub fn till (till_utc: f64) -> Timer {Timer {till_utc}} - pub fn sleep (seconds: f64) -> Timer {Timer {till_utc: now_float() + seconds}} - pub fn till_utc (&self) -> f64 {self.till_utc} + pub fn till(till_utc: f64) -> Timer { Timer { till_utc } } + pub fn sleep(seconds: f64) -> Timer { + Timer { + till_utc: now_float() + seconds, + } + } + pub fn till_utc(&self) -> f64 { self.till_utc } } impl Future03 for Timer { type Output = (); - fn poll (self: Pin<&mut Self>, cx: &mut Context) -> Poll03 { + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll03 { let delta = self.till_utc - now_float(); - if delta <= 0. {return Poll03::Ready(())} + if delta <= 0. { + return Poll03::Ready(()); + } // NB: We should get a new `Waker` on every `poll` in case the future migrates between executors. // cf. https://rust-lang.github.io/async-book/02_execution/03_wakeups.html let waker = cx.waker().clone(); - spawn_after (self.till_utc, async move {waker.wake()}); + spawn_after(self.till_utc, async move { waker.wake() }); Poll03::Pending } } - #[test] fn test_timer() { + #[test] + fn test_timer() { let started = now_float(); - let ti = Timer::sleep (0.2); + let ti = Timer::sleep(0.2); let delta = now_float() - started; - assert! (delta < 0.04, "{}", delta); - super::block_on (ti); + assert!(delta < 0.04, "{}", delta); + super::block_on(ti); let delta = now_float() - started; - println! ("time delta is {}", delta); - assert! (delta > 0.2); - assert! (delta < 0.4) + println!("time delta is {}", delta); + assert!(delta > 0.2); + assert!(delta < 0.4) } } -#[cfg(not(feature = "native"))] -pub mod executor; +#[cfg(not(feature = "native"))] pub mod executor; /// Returns a JSON error HyRes on a failure. #[macro_export] macro_rules! try_h { ($e: expr) => { match $e { - Ok (ok) => ok, - Err (err) => {return $crate::rpc_err_response (500, &ERRL! ("{}", err))} + Ok(ok) => ok, + Err(err) => return $crate::rpc_err_response(500, &ERRL!("{}", err)), } - } + }; } /// Executes a GET request, returning the response status, headers and body. -pub fn slurp_url (url: &str) -> SlurpFut { - wio::slurp_req (try_fus! (Request::builder().uri (url) .body (Vec::new()))) -} +pub fn slurp_url(url: &str) -> SlurpFut { wio::slurp_req(try_fus!(Request::builder().uri(url).body(Vec::new()))) } #[test] #[ignore] fn test_slurp_req() { - let (status, headers, body) = unwrap! (slurp_url ("https://httpbin.org/get") .wait()); - assert! (status.is_success(), format!("{:?} {:?} {:?}", status, headers, body)); + let (status, headers, body) = unwrap!(slurp_url("https://httpbin.org/get").wait()); + assert!(status.is_success(), format!("{:?} {:?} {:?}", status, headers, body)); } /// Fetch URL by HTTPS and parse JSON response -pub fn fetch_json(url: &str) -> Box> -where T: serde::de::DeserializeOwned + Send + 'static { +pub fn fetch_json(url: &str) -> Box> +where + T: serde::de::DeserializeOwned + Send + 'static, +{ Box::new(slurp_url(url).and_then(|result| { // try to parse as json with serde_json let result = try_s!(serde_json::from_slice(&result.2)); @@ -922,17 +1044,15 @@ where T: serde::de::DeserializeOwned + Send + 'static { } /// Send POST JSON HTTPS request and parse response -pub fn post_json(url: &str, json: String) -> Box> -where T: serde::de::DeserializeOwned + Send + 'static { +pub fn post_json(url: &str, json: String) -> Box> +where + T: serde::de::DeserializeOwned + Send + 'static, +{ let request = try_fus!(Request::builder() .method("POST") .uri(url) - .header( - CONTENT_TYPE, - HeaderValue::from_static("application/json") - ) - .body(json.into()) - ); + .header(CONTENT_TYPE, HeaderValue::from_static("application/json")) + .body(json.into())); Box::new(wio::slurp_req(request).and_then(|result| { // try to parse as json with serde_json @@ -943,15 +1063,19 @@ where T: serde::de::DeserializeOwned + Send + 'static { } /// Wraps a JSON string into the `HyRes` RPC response future. -pub fn rpc_response (status: u16, body: T) -> HyRes where Vec: From { +pub fn rpc_response(status: u16, body: T) -> HyRes +where + Vec: From, +{ let rf = match Response::builder() - .status (status) - .header (CONTENT_TYPE, HeaderValue::from_static ("application/json")) - .body (Vec::from (body)) { - Ok (r) => future::ok::>, String> (r), - Err (err) => future::err::>, String> (ERRL! ("{}", err)) - }; - Box::new (rf) + .status(status) + .header(CONTENT_TYPE, HeaderValue::from_static("application/json")) + .body(Vec::from(body)) + { + Ok(r) => future::ok::>, String>(r), + Err(err) => future::err::>, String>(ERRL!("{}", err)), + }; + Box::new(rf) } #[derive(Serialize)] @@ -961,15 +1085,11 @@ struct ErrResponse { /// Converts the given `err` message into the `{error: $err}` JSON string. pub fn err_to_rpc_json_string(err: &str) -> String { - let err = ErrResponse { - error: err.to_owned(), - }; + let err = ErrResponse { error: err.to_owned() }; json::to_string(&err).unwrap() } -pub fn err_tp_rpc_json(error: String) -> Json { - json::to_value(ErrResponse { error }).unwrap() -} +pub fn err_tp_rpc_json(error: String) -> Json { json::to_value(ErrResponse { error }).unwrap() } /// Returns the `{error: $msg}` JSON response with the given HTTP `status`. /// Also logs the error (if possible). @@ -983,14 +1103,15 @@ pub fn rpc_err_response(status: u16, msg: &str) -> HyRes { } /// A closure that would (re)start a `Future` to synchronize with an external resource in `RefreshedExternalResource`. -type ExternalResourceSync = BoxBox + Send + 'static> + Send + 'static>; +type ExternalResourceSync = + Box Box + Send + 'static> + Send + 'static>; /// Memory space accessible to the `Future` tail spawned by the `RefreshedExternalResource`. struct RerShelf { /// The time when the `Future` generated by `sync` has filled this shell. time: f64, /// Results of the `sync`-generated `Future`. - result: Result + result: Result, } /// Often we have an external resource that we need a fresh copy of. @@ -1017,7 +1138,7 @@ pub struct RefreshedExternalResource { shelf: Arc>>>, /// The `Future`s interested in the next update. /// When there is an updated the `Task::notify` gets invoked once and then the `Task` is removed from the `listeners` list. - listeners: Arc>> + listeners: Arc>>, } impl RefreshedExternalResource { /// New instance of the external resource tracker. @@ -1028,77 +1149,95 @@ impl RefreshedExternalResource { /// * `sync` - Generates the `Future` that should synchronize with the external resource in background. /// Note that we'll tail the `Future`, polling the tail from the shared asynchronous reactor; /// *spawn* the `Future` onto a different reactor if the shared asynchronous reactor is not the best option. - pub fn new (every_n_sec: f64, timeout_sec: f64, sync: ExternalResourceSync) -> RefreshedExternalResource { - assert_eq! (size_of::(), 8); + pub fn new(every_n_sec: f64, timeout_sec: f64, sync: ExternalResourceSync) -> RefreshedExternalResource { + assert_eq!(size_of::(), 8); RefreshedExternalResource { - sync: Mutex::new (sync), + sync: Mutex::new(sync), every_n_sec, - timeout_sec: timeout_sec .max (every_n_sec), - last_start: AtomicUsize::new (0f64.to_bits() as usize), - shelf: Arc::new (Mutex::new (None)), - listeners: Arc::new (Mutex::new (Vec::new())) + timeout_sec: timeout_sec.max(every_n_sec), + last_start: AtomicUsize::new(0f64.to_bits() as usize), + shelf: Arc::new(Mutex::new(None)), + listeners: Arc::new(Mutex::new(Vec::new())), } } - pub fn add_listeners (&self, mut tasks: Vec) -> Result<(), String> { - let mut listeners = try_s! (self.listeners.lock()); - listeners.append (&mut tasks); + pub fn add_listeners(&self, mut tasks: Vec) -> Result<(), String> { + let mut listeners = try_s!(self.listeners.lock()); + listeners.append(&mut tasks); Ok(()) } /// Performs the maintenance operations necessary to periodically refresh the resource. - pub fn tick (&self) -> Result<(), String> { + pub fn tick(&self) -> Result<(), String> { let now = now_float(); - let last_finish = match * try_s! (self.shelf.lock()) {Some (ref rer_shelf) => rer_shelf.time, None => 0.}; - let last_start = f64::from_bits (self.last_start.load (Ordering::Relaxed) as u64); + let last_finish = match *try_s!(self.shelf.lock()) { + Some(ref rer_shelf) => rer_shelf.time, + None => 0., + }; + let last_start = f64::from_bits(self.last_start.load(Ordering::Relaxed) as u64); if now - last_start > self.timeout_sec || (last_finish > last_start && now - last_start > self.every_n_sec) { - self.last_start.store (now.to_bits() as usize, Ordering::Relaxed); - let sync = try_s! (self.sync.lock()); + self.last_start.store(now.to_bits() as usize, Ordering::Relaxed); + let sync = try_s!(self.sync.lock()); let f = (*sync)(); let shelf_tx = self.shelf.clone(); let listeners = self.listeners.clone(); - let f = f.then (move |result| -> Result<(), ()> { - let mut shelf = match shelf_tx.lock() {Ok (l) => l, Err (err) => { - log! ({"RefreshedExternalResource::tick] Can't lock the shelf: {}", err}); - return Err(()) - }}; - let shelf_time = match *shelf {Some (ref r) => r.time, None => 0.}; - if now > shelf_time { // This check prevents out-of-order shelf updates. - *shelf = Some (RerShelf { + let f = f.then(move |result| -> Result<(), ()> { + let mut shelf = match shelf_tx.lock() { + Ok(l) => l, + Err(err) => { + log! ({"RefreshedExternalResource::tick] Can't lock the shelf: {}", err}); + return Err(()); + }, + }; + let shelf_time = match *shelf { + Some(ref r) => r.time, + None => 0., + }; + if now > shelf_time { + // This check prevents out-of-order shelf updates. + *shelf = Some(RerShelf { time: now_float(), - result + result, }); - drop (shelf); // Don't hold the lock unnecessarily. + drop(shelf); // Don't hold the lock unnecessarily. { - let mut listeners = match listeners.lock() {Ok (l) => l, Err (err) => { - log! ({"RefreshedExternalResource::tick] Can't lock the listeners: {}", err}); - return Err(()) - }}; - for task in listeners.drain (..) {task.notify()} + let mut listeners = match listeners.lock() { + Ok(l) => l, + Err(err) => { + log! ({"RefreshedExternalResource::tick] Can't lock the listeners: {}", err}); + return Err(()); + }, + }; + for task in listeners.drain(..) { + task.notify() + } } } Ok(()) }); - executor::spawn (f.compat().map(|_|())); // Polls `f` in background. + executor::spawn(f.compat().map(|_| ())); // Polls `f` in background. } Ok(()) } /// The time, in seconds since UNIX epoch, when the refresh `Future` resolved. - pub fn last_finish (&self) -> Result { - Ok (match * try_s! (self.shelf.lock()) { - Some (ref rer_shelf) => rer_shelf.time, - None => 0. + pub fn last_finish(&self) -> Result { + Ok(match *try_s!(self.shelf.lock()) { + Some(ref rer_shelf) => rer_shelf.time, + None => 0., }) } - pub fn with_result>) -> Result> (&self, mut cb: F) -> Result { - let shelf = try_s! (self.shelf.lock()); + pub fn with_result>) -> Result>( + &self, + mut cb: F, + ) -> Result { + let shelf = try_s!(self.shelf.lock()); match *shelf { - Some (ref rer_shelf) => cb (Some (&rer_shelf.result)), - None => cb (None) + Some(ref rer_shelf) => cb(Some(&rer_shelf.result)), + None => cb(None), } } } @@ -1109,9 +1248,7 @@ impl RefreshedExternalResource { pub struct StringError(pub String); impl From for StringError { - fn from(e: std::io::Error) -> StringError { - StringError(ERRL!("{}", e)) - } + fn from(e: std::io::Error) -> StringError { StringError(ERRL!("{}", e)) } } #[derive(Debug)] @@ -1126,30 +1263,42 @@ pub struct QueuedCommand { /// Register an RPC command that came internally or from the peer-to-peer bus. #[no_mangle] #[cfg(feature = "native")] -pub extern "C" fn lp_queue_command_for_c (retstrp: *mut *mut c_char, buf: *mut c_char, response_sock: i32, - stats_json_only: i32, queue_id: u32) -> () { - if retstrp != null_mut() { - unsafe { *retstrp = null_mut() } +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn lp_queue_command_for_c( + retstrp: *mut *mut c_char, + buf: *mut c_char, + response_sock: i32, + stats_json_only: i32, + queue_id: u32, +) { + if !retstrp.is_null() { + *retstrp = null_mut() } - if buf == null_mut() {panic! ("!buf")} - let msg = String::from (unwrap! (unsafe {CStr::from_ptr (buf)} .to_str())); + if buf.is_null() { + panic!("!buf") + } + let msg = String::from(unwrap!(CStr::from_ptr(buf).to_str())); let _cmd = QueuedCommand { msg, queue_id, response_sock, - stats_json_only + stats_json_only, }; - panic! ("We need a context ID"); + panic!("We need a context ID"); //unwrap! ((*COMMAND_QUEUE).0.send (cmd)) } -pub fn lp_queue_command (ctx: &mm_ctx::MmArc, msg: String) -> Result<(), String> { +pub fn lp_queue_command(ctx: &mm_ctx::MmArc, msg: String) -> Result<(), String> { // If we're helping a WASM then leave a copy of the broadcast for them. - if let Some (ref mut cq) = *try_s! (ctx.command_queueʰ.lock()) { + if let Some(ref mut cq) = *try_s!(ctx.command_queueʰ.lock()) { // Monotonic increment. - let now = if let Some (last) = cq.last() {(last.0 + 1) .max (now_ms())} else {now_ms()}; - cq.push ((now, msg.clone())) + let now = if let Some(last) = cq.last() { + (last.0 + 1).max(now_ms()) + } else { + now_ms() + }; + cq.push((now, msg.clone())) } let cmd = QueuedCommand { @@ -1158,176 +1307,222 @@ pub fn lp_queue_command (ctx: &mm_ctx::MmArc, msg: String) -> Result<(), String> response_sock: -1, stats_json_only: 0, }; - try_s! (ctx.command_queue.unbounded_send (cmd)); + try_s!(ctx.command_queue.unbounded_send(cmd)); Ok(()) } -pub fn var (name: &str) -> Result { +pub fn var(name: &str) -> Result { /// Obtains the environment variable `name` from the host, copying it into `rbuf`. /// Returns the length of the value copied to `rbuf` or -1 if there was an error. #[cfg(not(feature = "native"))] #[cfg_attr(feature = "w-bindgen", wasm_bindgen(raw_module = "../../../js/defined-in-js.js"))] - extern "C" {pub fn host_env (name: *const c_char, nameˡ: i32, rbuf: *mut c_char, rcap: i32) -> i32;} + extern "C" { + pub fn host_env(name: *const c_char, nameˡ: i32, rbuf: *mut c_char, rcap: i32) -> i32; + } - #[cfg(feature = "native")] { - match std::env::var (name) { - Ok (v) => Ok (v), - Err (_err) => ERR! ("No {}", name) + #[cfg(feature = "native")] + { + match std::env::var(name) { + Ok(v) => Ok(v), + Err(_err) => ERR!("No {}", name), } } - #[cfg(not(feature = "native"))] { // Get the environment variable from the host. + #[cfg(not(feature = "native"))] + { + // Get the environment variable from the host. use std::mem::zeroed; use std::str::from_utf8; - let mut buf: [u8; 4096] = unsafe {zeroed()}; - let rc = unsafe {host_env ( - name.as_ptr() as *const c_char, name.len() as i32, - buf.as_mut_ptr() as *mut c_char, buf.len() as i32)}; - if rc <= 0 {return ERR! ("No {}", name)} - let s = try_s! (from_utf8 (&buf[0 .. rc as usize])); - Ok (String::from (s)) + let mut buf: [u8; 4096] = unsafe { zeroed() }; + let rc = unsafe { + host_env( + name.as_ptr() as *const c_char, + name.len() as i32, + buf.as_mut_ptr() as *mut c_char, + buf.len() as i32, + ) + }; + if rc <= 0 { + return ERR!("No {}", name); + } + let s = try_s!(from_utf8(&buf[0..rc as usize])); + Ok(String::from(s)) } } -pub fn block_on (f: F) -> F::Output where F: Future03 { - if var ("TRACE_BLOCK_ON") .map (|v| v == "true") == Ok (true) { - let mut trace = String::with_capacity (4096); - stack_trace (&mut stack_trace_frame, &mut |l| trace.push_str (l)); - log! ("block_on at\n" (trace)); +pub fn block_on(f: F) -> F::Output +where + F: Future03, +{ + if var("TRACE_BLOCK_ON").map(|v| v == "true") == Ok(true) { + let mut trace = String::with_capacity(4096); + stack_trace(&mut stack_trace_frame, &mut |l| trace.push_str(l)); + log!("block_on at\n"(trace)); } - futures::executor::block_on (f) + futures::executor::block_on(f) } -#[cfg(feature = "native")] -pub use gstuff::{now_ms, now_float}; use backtrace::SymbolName; +#[cfg(feature = "native")] pub use gstuff::{now_float, now_ms}; #[cfg(not(feature = "native"))] pub fn now_ms() -> u64 { #[cfg_attr(feature = "w-bindgen", wasm_bindgen(raw_module = "../../../js/defined-in-js.js"))] - extern "C" {pub fn date_now() -> f64;} - unsafe {date_now() as u64} + extern "C" { + pub fn date_now() -> f64; + } + unsafe { date_now() as u64 } } #[cfg(not(feature = "native"))] pub fn now_float() -> f64 { use gstuff::duration_to_float; use std::time::Duration; - duration_to_float (Duration::from_millis (now_ms())) + duration_to_float(Duration::from_millis(now_ms())) } #[cfg(feature = "native")] -pub fn slurp (path: &dyn AsRef) -> Result, String> {Ok (gstuff::slurp (path))} +pub fn slurp(path: &dyn AsRef) -> Result, String> { Ok(gstuff::slurp(path)) } #[cfg(not(feature = "native"))] -pub fn slurp (path: &dyn AsRef) -> Result, String> { +pub fn slurp(path: &dyn AsRef) -> Result, String> { use std::mem::MaybeUninit; #[cfg_attr(feature = "w-bindgen", wasm_bindgen(raw_module = "../../../js/defined-in-js.js"))] - extern "C" {pub fn host_slurp (path_p: *const c_char, path_l: i32, rbuf: *mut c_char, rcap: i32) -> i32;} + extern "C" { + pub fn host_slurp(path_p: *const c_char, path_l: i32, rbuf: *mut c_char, rcap: i32) -> i32; + } - let path = try_s! (path.as_ref().to_str().ok_or ("slurp: path not unicode")); - let mut rbuf: [u8; 262144] = unsafe {MaybeUninit::uninit().assume_init()}; - let rc = unsafe {host_slurp (path.as_ptr() as *const c_char, path.len() as i32, rbuf.as_mut_ptr() as *mut c_char, rbuf.len() as i32)}; - if rc < 0 {return ERR! ("!host_slurp: {}", rc)} - Ok (Vec::from (&rbuf[.. rc as usize]))} + let path = try_s!(path.as_ref().to_str().ok_or("slurp: path not unicode")); + let mut rbuf: [u8; 262144] = unsafe { MaybeUninit::uninit().assume_init() }; + let rc = unsafe { + host_slurp( + path.as_ptr() as *const c_char, + path.len() as i32, + rbuf.as_mut_ptr() as *mut c_char, + rbuf.len() as i32, + ) + }; + if rc < 0 { + return ERR!("!host_slurp: {}", rc); + } + Ok(Vec::from(&rbuf[..rc as usize])) +} #[cfg(feature = "native")] -pub fn temp_dir() -> PathBuf {env::temp_dir()} +pub fn temp_dir() -> PathBuf { env::temp_dir() } #[cfg(not(feature = "native"))] pub fn temp_dir() -> PathBuf { #[cfg_attr(feature = "w-bindgen", wasm_bindgen(raw_module = "../../../js/defined-in-js.js"))] - extern "C" {pub fn temp_dir (rbuf: *mut c_char, rcap: i32) -> i32;} - let mut buf: [u8; 4096] = unsafe {zeroed()}; - let rc = unsafe {temp_dir (buf.as_mut_ptr() as *mut c_char, buf.len() as i32)}; - if rc <= 0 {panic! ("!temp_dir")} - let path = unwrap! (std::str::from_utf8 (&buf[0 .. rc as usize])); - Path::new (path) .into() + extern "C" { + pub fn temp_dir(rbuf: *mut c_char, rcap: i32) -> i32; + } + let mut buf: [u8; 4096] = unsafe { zeroed() }; + let rc = unsafe { temp_dir(buf.as_mut_ptr() as *mut c_char, buf.len() as i32) }; + if rc <= 0 { + panic!("!temp_dir") + } + let path = unwrap!(std::str::from_utf8(&buf[0..rc as usize])); + Path::new(path).into() } #[cfg(feature = "native")] -pub fn remove_file (path: &dyn AsRef) -> Result<(), String> { - try_s! (fs::remove_file (path)); +pub fn remove_file(path: &dyn AsRef) -> Result<(), String> { + try_s!(fs::remove_file(path)); Ok(()) } #[cfg(not(feature = "native"))] -pub fn remove_file (path: &dyn AsRef) -> Result<(), String> { +pub fn remove_file(path: &dyn AsRef) -> Result<(), String> { use std::os::raw::c_char; #[cfg_attr(feature = "w-bindgen", wasm_bindgen(raw_module = "../../../js/defined-in-js.js"))] - extern "C" {pub fn host_rm (ptr: *const c_char, len: i32) -> i32;} + extern "C" { + pub fn host_rm(ptr: *const c_char, len: i32) -> i32; + } - let path = try_s! (path.as_ref().to_str().ok_or ("Non-unicode path")); - let rc = unsafe {host_rm (path.as_ptr() as *const c_char, path.len() as i32)}; - if rc != 0 {return ERR! ("!host_rm: {}", rc)} + let path = try_s!(path.as_ref().to_str().ok_or("Non-unicode path")); + let rc = unsafe { host_rm(path.as_ptr() as *const c_char, path.len() as i32) }; + if rc != 0 { + return ERR!("!host_rm: {}", rc); + } Ok(()) } #[cfg(feature = "native")] -pub fn write (path: &dyn AsRef, contents: &dyn AsRef<[u8]>) -> Result<(), String> { - try_s! (fs::write (path, contents)); +pub fn write(path: &dyn AsRef, contents: &dyn AsRef<[u8]>) -> Result<(), String> { + try_s!(fs::write(path, contents)); Ok(()) } #[cfg(not(feature = "native"))] -pub fn write (path: &dyn AsRef, contents: &dyn AsRef<[u8]>) -> Result<(), String> { +pub fn write(path: &dyn AsRef, contents: &dyn AsRef<[u8]>) -> Result<(), String> { use std::os::raw::c_char; #[cfg_attr(feature = "w-bindgen", wasm_bindgen(raw_module = "../../../js/defined-in-js.js"))] - extern "C" {pub fn host_write (path_p: *const c_char, path_l: i32, ptr: *const c_char, len: i32) -> i32;} + extern "C" { + pub fn host_write(path_p: *const c_char, path_l: i32, ptr: *const c_char, len: i32) -> i32; + } - let path = try_s! (path.as_ref().to_str().ok_or ("Non-unicode path")); + let path = try_s!(path.as_ref().to_str().ok_or("Non-unicode path")); let content = contents.as_ref(); - let rc = unsafe {host_write ( - path.as_ptr() as *const c_char, path.len() as i32, - content.as_ptr() as *const c_char, content.len() as i32 - )}; - if rc != 0 {return ERR! ("!host_write: {}", rc)} + let rc = unsafe { + host_write( + path.as_ptr() as *const c_char, + path.len() as i32, + content.as_ptr() as *const c_char, + content.len() as i32, + ) + }; + if rc != 0 { + return ERR!("!host_write: {}", rc); + } Ok(()) } /// Read a folder and return a list of files with their last-modified ms timestamps. #[cfg(feature = "native")] pub fn read_dir(dir: &dyn AsRef) -> Result, String> { - let entries = try_s!(dir.as_ref().read_dir()).filter_map(|dir_entry| { - let entry = match dir_entry { - Ok(ent) => ent, - Err(e) => { - log!("Error " (e) " reading from dir " (dir.as_ref().display())); - return None; - } - }; + let entries = try_s!(dir.as_ref().read_dir()) + .filter_map(|dir_entry| { + let entry = match dir_entry { + Ok(ent) => ent, + Err(e) => { + log!("Error " (e) " reading from dir " (dir.as_ref().display())); + return None; + }, + }; - let metadata = match entry.metadata() { - Ok(m) => m, - Err(e) => { - log!("Error " (e) " getting file " (entry.path().display()) " meta"); - return None; - } - }; + let metadata = match entry.metadata() { + Ok(m) => m, + Err(e) => { + log!("Error " (e) " getting file " (entry.path().display()) " meta"); + return None; + }, + }; - let m_time = match metadata.modified() { - Ok(time) => time, - Err(e) => { - log!("Error " (e) " getting file " (entry.path().display()) " m_time"); - return None; - } - }; + let m_time = match metadata.modified() { + Ok(time) => time, + Err(e) => { + log!("Error " (e) " getting file " (entry.path().display()) " m_time"); + return None; + }, + }; - let lm = unwrap!(m_time.duration_since(UNIX_EPOCH), "!duration_since").as_millis(); - assert!(lm < u64::max_value() as u128); - let lm = lm as u64; + let lm = unwrap!(m_time.duration_since(UNIX_EPOCH), "!duration_since").as_millis(); + assert!(lm < u64::max_value() as u128); + let lm = lm as u64; - let path = entry.path(); - if path.extension() == Some(OsStr::new("json")) { - Some((lm, path)) - } else { - None - } - }).collect(); + let path = entry.path(); + if path.extension() == Some(OsStr::new("json")) { + Some((lm, path)) + } else { + None + } + }) + .collect(); Ok(entries) } @@ -1337,91 +1532,124 @@ pub fn read_dir(dir: &dyn AsRef) -> Result, String> { use std::mem::MaybeUninit; #[cfg_attr(feature = "w-bindgen", wasm_bindgen(raw_module = "../../../js/defined-in-js.js"))] - extern "C" {pub fn host_read_dir (path_p: *const c_char, path_l: i32, rbuf: *mut c_char, rcap: i32) -> i32;} + extern "C" { + pub fn host_read_dir(path_p: *const c_char, path_l: i32, rbuf: *mut c_char, rcap: i32) -> i32; + } - let path = try_s! (dir.as_ref().to_str().ok_or ("read_dir: dir path not unicode")); - let mut rbuf: [u8; 262144] = unsafe {MaybeUninit::uninit().assume_init()}; - let rc = unsafe {host_read_dir (path.as_ptr() as *const c_char, path.len() as i32, rbuf.as_mut_ptr() as *mut c_char, rbuf.len() as i32)}; - if rc <= 0 {return ERR! ("!host_read_dir: {}", rc)} - let jens: Vec<(u64, String)> = try_s! (json::from_slice (&rbuf[.. rc as usize])); + let path = try_s!(dir.as_ref().to_str().ok_or("read_dir: dir path not unicode")); + let mut rbuf: [u8; 262144] = unsafe { MaybeUninit::uninit().assume_init() }; + let rc = unsafe { + host_read_dir( + path.as_ptr() as *const c_char, + path.len() as i32, + rbuf.as_mut_ptr() as *mut c_char, + rbuf.len() as i32, + ) + }; + if rc <= 0 { + return ERR!("!host_read_dir: {}", rc); + } + let jens: Vec<(u64, String)> = try_s!(json::from_slice(&rbuf[..rc as usize])); - let mut entries: Vec<(u64, PathBuf)> = Vec::with_capacity (jens.len()); + let mut entries: Vec<(u64, PathBuf)> = Vec::with_capacity(jens.len()); for (lm, name) in jens { - let path = dir.as_ref().join (name); - entries.push ((lm, path)) + let path = dir.as_ref().join(name); + entries.push((lm, path)) } - Ok (entries) + Ok(entries) } /// If the `MM_LOG` variable is present then tries to open that file. /// Prints a warning to `stdout` if there's a problem opening the file. /// Returns `None` if `MM_LOG` variable is not present or if the specified path can't be opened. fn open_log_file() -> Option { - let mm_log = match var ("MM_LOG") { - Ok (v) => v, - Err (_) => return None + let mm_log = match var("MM_LOG") { + Ok(v) => v, + Err(_) => return None, }; // For security reasons we want the log path to always end with ".log". - if !mm_log.ends_with (".log") {println! ("open_log_file] MM_LOG doesn't end with '.log'"); return None} + if !mm_log.ends_with(".log") { + println!("open_log_file] MM_LOG doesn't end with '.log'"); + return None; + } - match fs::OpenOptions::new().append (true) .create (true) .open (&mm_log) { - Ok (f) => Some (f), - Err (err) => { - println! ("open_log_file] Can't open {}: {}", mm_log, err); + match fs::OpenOptions::new().append(true).create(true).open(&mm_log) { + Ok(f) => Some(f), + Err(err) => { + println!("open_log_file] Can't open {}: {}", mm_log, err); None -} } } + }, + } +} #[cfg(feature = "native")] -pub fn writeln (line: &str) { +pub fn writeln(line: &str) { use std::panic::catch_unwind; - lazy_static! {static ref LOG_FILE: Mutex> = Mutex::new (open_log_file());} + lazy_static! { + static ref LOG_FILE: Mutex> = Mutex::new(open_log_file()); + } // `catch_unwind` protects the tests from error - // + // // thread 'CORE' panicked at 'cannot access stdout during shutdown' - // + // // (which might be related to https://github.com/rust-lang/rust/issues/29488). - let _ = catch_unwind (|| { - if let Ok (mut log_file) = LOG_FILE.lock() { - if let Some (ref mut log_file) = *log_file { - let _ = witeln! (log_file, (line)); - return - } } - println! ("{}", line); + let _ = catch_unwind(|| { + if let Ok(mut log_file) = LOG_FILE.lock() { + if let Some(ref mut log_file) = *log_file { + let _ = witeln!(log_file, (line)); + return; + } + } + println!("{}", line); }); } #[cfg(not(feature = "native"))] -const fn make_tail() -> [u8; 0x10000] {[0; 0x10000]} +const fn make_tail() -> [u8; 0x10000] { [0; 0x10000] } #[cfg(not(feature = "native"))] static mut PROCESS_LOG_TAIL: [u8; 0x10000] = make_tail(); #[cfg(not(feature = "native"))] -static TAIL_CUR: Atomic = Atomic::new (0); +static TAIL_CUR: Atomic = Atomic::new(0); #[cfg(all(not(feature = "native"), not(feature = "w-bindgen")))] -pub fn writeln (line: &str) { +pub fn writeln(line: &str) { use std::ffi::CString; - extern "C" {pub fn console_log (ptr: *const c_char, len: i32);} - let lineᶜ = unwrap! (CString::new (line)); - unsafe {console_log (lineᶜ.as_ptr(), line.len() as i32)} + extern "C" { + pub fn console_log(ptr: *const c_char, len: i32); + } + let lineᶜ = unwrap!(CString::new(line)); + unsafe { console_log(lineᶜ.as_ptr(), line.len() as i32) } // Keep a tail of the log in RAM for the integration tests. unsafe { if line.len() < PROCESS_LOG_TAIL.len() { - let posⁱ = TAIL_CUR.load (Ordering::Relaxed); + let posⁱ = TAIL_CUR.load(Ordering::Relaxed); let posⱼ = posⁱ + line.len(); - let (posˢ, posⱼ) = if posⱼ > PROCESS_LOG_TAIL.len() {(0, line.len())} else {(posⁱ, posⱼ)}; - if TAIL_CUR.compare_exchange (posⁱ, posⱼ, Ordering::Relaxed, Ordering::Relaxed) .is_ok() { - for (cur, ix) in (posˢ..posⱼ) .zip (0..line.len()) {PROCESS_LOG_TAIL[cur] = line.as_bytes()[ix]} -} } } } + let (posˢ, posⱼ) = if posⱼ > PROCESS_LOG_TAIL.len() { + (0, line.len()) + } else { + (posⁱ, posⱼ) + }; + if TAIL_CUR + .compare_exchange(posⁱ, posⱼ, Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + { + for (cur, ix) in (posˢ..posⱼ).zip(0..line.len()) { + PROCESS_LOG_TAIL[cur] = line.as_bytes()[ix] + } + } + } + } +} #[cfg(all(not(feature = "native"), feature = "w-bindgen"))] -pub fn writeln (line: &str) { +pub fn writeln(line: &str) { use web_sys::console; console::log_1(&line.into()); } @@ -1431,47 +1659,45 @@ pub fn writeln (line: &str) { /// Note that we're also getting the stack trace from Node.js and rustfilt). #[cfg(not(feature = "native"))] #[no_mangle] -pub extern fn set_panic_hook() { +pub extern "C" fn set_panic_hook() { use gstuff::filename; use std::panic::{set_hook, PanicInfo}; - set_hook (Box::new (|info: &PanicInfo| { - let mut msg = String::with_capacity (256); - let _ = wite! (&mut msg, ((info))); - writeln (&msg) + set_hook(Box::new(|info: &PanicInfo| { + let mut msg = String::with_capacity(256); + let _ = wite!(&mut msg, (info)); + writeln(&msg) })) } -pub fn small_rng() -> SmallRng { - SmallRng::seed_from_u64 (now_ms()) -} +pub fn small_rng() -> SmallRng { SmallRng::seed_from_u64(now_ms()) } /// Ask the WASM host to send HTTP request to the native helpers. /// Returns request ID used to wait for the reply. #[cfg(not(feature = "native"))] #[cfg_attr(feature = "w-bindgen", wasm_bindgen(raw_module = "../../../js/defined-in-js.js"))] -extern "C" {fn http_helper_if ( - helper: *const u8, helper_len: i32, - payload: *const u8, payload_len: i32, - timeout_ms: i32) -> i32;} +extern "C" { + fn http_helper_if(helper: *const u8, helper_len: i32, payload: *const u8, payload_len: i32, timeout_ms: i32) + -> i32; +} #[cfg(not(feature = "native"))] #[cfg_attr(feature = "w-bindgen", wasm_bindgen(raw_module = "../../../js/defined-in-js.js"))] extern "C" { /// Check with the WASM host to see if the given HTTP request is ready. - /// + /// /// Returns the amount of bytes copied to rbuf, /// or `-1` if the request is not yet finished, /// or `0 - amount of bytes` in case the intended size was larger than the `rcap`. - /// + /// /// The bytes copied to rbuf are in the bencode format, /// `{status: $number, ct: $bytes, cs: $bytes, body: $bytes}` /// (the `HelperResponse`). - /// + /// /// * `helper_request_id` - Request ID previously returned by `http_helper_if`. /// * `rbuf` - The buffer to copy the response payload into if the request is finished. /// * `rcap` - The size of the `rbuf` buffer. - pub fn http_helper_check (helper_request_id: i32, rbuf: *mut u8, rcap: i32) -> i32; + pub fn http_helper_check(helper_request_id: i32, rbuf: *mut u8, rcap: i32) -> i32; } lazy_static! { @@ -1483,9 +1709,11 @@ lazy_static! { /// WASM host invokes this method to signal the readiness of the HTTP request. #[no_mangle] #[cfg(not(feature = "native"))] -pub extern fn http_ready (helper_request_id: i32) { - let mut helper_requests = unwrap! (HELPER_REQUESTS.lock()); - if let Some (waker) = helper_requests.remove (&helper_request_id) {waker.wake()} +pub extern "C" fn http_ready(helper_request_id: i32) { + let mut helper_requests = unwrap!(HELPER_REQUESTS.lock()); + if let Some(waker) = helper_requests.remove(&helper_request_id) { + waker.wake() + } } #[derive(Deserialize, Debug)] @@ -1495,58 +1723,74 @@ pub struct HelperResponse { pub content_type: Option, #[serde(rename = "cs")] pub checksum: Option, - pub body: ByteBuf + pub body: ByteBuf, } /// Mostly used to log the errors coming from the other side. impl fmt::Display for HelperResponse { - fn fmt (&self, ft: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, ft: &mut fmt::Formatter) -> fmt::Result { wite! (ft, (self.status) ", " (binprint (&self.body, b'.'))) -} } + } +} #[cfg(not(feature = "native"))] -pub async fn helperᶜ (helper: &'static str, args: Vec) -> Result, String> { - let helper_request_id = unsafe {http_helper_if ( - helper.as_ptr(), helper.len() as i32, - args.as_ptr(), args.len() as i32, - 9999)}; +pub async fn helperᶜ(helper: &'static str, args: Vec) -> Result, String> { + let helper_request_id = unsafe { + http_helper_if( + helper.as_ptr(), + helper.len() as i32, + args.as_ptr(), + args.len() as i32, + 9999, + ) + }; - struct HelperReply {helper: &'static str, helper_request_id: i32} + struct HelperReply { + helper: &'static str, + helper_request_id: i32, + } impl std::future::Future for HelperReply { type Output = Result, String>; - fn poll (self: Pin<&mut Self>, cx: &mut Context) -> Poll03 { - let mut buf: [u8; 65535] = unsafe {std::mem::MaybeUninit::uninit().assume_init()}; - let rlen = unsafe {http_helper_check (self.helper_request_id, buf.as_mut_ptr(), buf.len() as i32)}; - if rlen < -1 { // Response is larger than capacity. - return Poll03::Ready (ERR! ("Helper result is too large ({})", rlen)) + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll03 { + let mut buf: [u8; 65535] = unsafe { std::mem::MaybeUninit::uninit().assume_init() }; + let rlen = unsafe { http_helper_check(self.helper_request_id, buf.as_mut_ptr(), buf.len() as i32) }; + if rlen < -1 { + // Response is larger than capacity. + return Poll03::Ready(ERR!("Helper result is too large ({})", rlen)); } if rlen >= 0 { - return Poll03::Ready (Ok (Vec::from (&buf[0..rlen as usize]))) + return Poll03::Ready(Ok(Vec::from(&buf[0..rlen as usize]))); } // NB: Need a fresh waker each time `Pending` is returned, to support switching tasks. // cf. https://rust-lang.github.io/async-book/02_execution/03_wakeups.html let waker = cx.waker().clone(); - unwrap! (HELPER_REQUESTS.lock()) .insert (self.helper_request_id, waker); + unwrap!(HELPER_REQUESTS.lock()).insert(self.helper_request_id, waker); Poll03::Pending } } impl Drop for HelperReply { - fn drop (&mut self) { - unwrap! (HELPER_REQUESTS.lock()) .remove (&self.helper_request_id); + fn drop(&mut self) { unwrap!(HELPER_REQUESTS.lock()).remove(&self.helper_request_id); } + } + let rv: Vec = try_s!( + HelperReply { + helper, + helper_request_id } + .await + ); + let rv: HelperResponse = try_s!(bdecode(&rv)); + if rv.status != 200 { + return ERR!("!{}: {}", helper, rv); } - let rv: Vec = try_s! (HelperReply {helper, helper_request_id} .await); - let rv: HelperResponse = try_s! (bdecode (&rv)); - if rv.status != 200 {return ERR! ("!{}: {}", helper, rv)} // TODO: Check `rv.checksum` if present. - Ok (rv.body.into_vec()) + Ok(rv.body.into_vec()) } #[derive(Serialize, Deserialize)] pub struct BroadcastP2pMessageArgs { pub ctx: u32, - pub msg: String + pub msg: String, } #[derive(Debug, Clone)] @@ -1556,9 +1800,7 @@ pub struct OrdRange(RangeInclusive); impl Deref for OrdRange { type Target = RangeInclusive; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { &self.0 } } impl OrdRange { @@ -1574,43 +1816,50 @@ impl OrdRange { impl OrdRange { /// Flatten a start-end pair into the vector. - pub fn flatten(&self) -> Vec { - vec![*self.start(), *self.end()] - } + pub fn flatten(&self) -> Vec { vec![*self.start(), *self.end()] } } /// Invokes callback `cb_id` in the WASM host, passing a `(ptr,len)` string to it. #[cfg(not(feature = "native"))] #[cfg_attr(feature = "w-bindgen", wasm_bindgen(raw_module = "../../../js/defined-in-js.js"))] -extern "C" {pub fn call_back (cb_id: i32, ptr: *const c_char, len: i32);} +extern "C" { + pub fn call_back(cb_id: i32, ptr: *const c_char, len: i32); +} pub mod for_tests; -fn without_trailing_zeroes (decimal: &str, dot: usize) -> &str { +fn without_trailing_zeroes(decimal: &str, dot: usize) -> &str { let mut pos = decimal.len() - 1; loop { let ch = decimal.as_bytes()[pos]; - if ch != b'0' {break &decimal[0..=pos]} - if pos == dot {break &decimal[0..pos]} + if ch != b'0' { + break &decimal[0..=pos]; + } + if pos == dot { + break &decimal[0..pos]; + } pos -= 1 } } /// Round half away from zero (aka commercial rounding). -pub fn round_to (bd: &BigDecimal, places: u8) -> String { +pub fn round_to(bd: &BigDecimal, places: u8) -> String { // Normally we'd do - // + // // let divisor = pow (10, places) // round (self * divisor) / divisor - // + // // But we don't have a `round` function in `BigDecimal` at present, so we're on our own. - let bds = format! ("{}", bd); + let bds = format!("{}", bd); let bda = bds.as_bytes(); - let dot = bda.iter().position (|&ch| ch == b'.'); - let dot = match dot {Some (dot) => dot, None => return bds}; + let dot = bda.iter().position(|&ch| ch == b'.'); + let dot = match dot { + Some(dot) => dot, + None => return bds, + }; if bda.len() - dot <= places as usize { - return String::from (without_trailing_zeroes (&bds, dot)) + return String::from(without_trailing_zeroes(&bds, dot)); } let mut pos = bda.len() - 1; @@ -1618,28 +1867,31 @@ pub fn round_to (bd: &BigDecimal, places: u8) -> String { let mut prev_digit = 0; loop { let digit = ch - b'0'; - let rounded = if prev_digit > 5 {digit + 1} else {digit}; + let rounded = if prev_digit > 5 { digit + 1 } else { digit }; //println! ("{} at {}: prev_digit {}, digit {}, rounded {}", bds, pos, prev_digit, digit, rounded); if pos < dot { //println! ("{}, pos < dot, stopping at pos {}", bds, pos); - let mut integer: i64 = unwrap! ((&bds[0..=pos]).parse()); + let mut integer: i64 = unwrap!((&bds[0..=pos]).parse()); if prev_digit > 5 { if bda[0] == b'-' { - integer = unwrap! (integer.checked_sub (1)) + integer = unwrap!(integer.checked_sub(1)) } else { - integer = unwrap! (integer.checked_add (1)) - } } - return format! ("{}", integer) + integer = unwrap!(integer.checked_add(1)) + } + } + return format!("{}", integer); } if pos == dot + places as usize && rounded < 10 { //println! ("{}, stopping at pos {}", bds, pos); - break format! ("{}{}", &bds[0..pos], rounded) + break format!("{}{}", &bds[0..pos], rounded); } pos -= 1; - if pos == dot {pos -= 1} // Skip over the dot. + if pos == dot { + pos -= 1 + } // Skip over the dot. ch = bda[pos]; prev_digit = rounded } @@ -1647,42 +1899,42 @@ pub fn round_to (bd: &BigDecimal, places: u8) -> String { #[test] fn test_round_to() { - assert_eq! (round_to (&BigDecimal::from (0.999), 2), "1"); - assert_eq! (round_to (&BigDecimal::from (-0.999), 2), "-1"); + assert_eq!(round_to(&BigDecimal::from(0.999), 2), "1"); + assert_eq!(round_to(&BigDecimal::from(-0.999), 2), "-1"); - assert_eq! (round_to (&BigDecimal::from (10.999), 2), "11"); - assert_eq! (round_to (&BigDecimal::from (-10.999), 2), "-11"); + assert_eq!(round_to(&BigDecimal::from(10.999), 2), "11"); + assert_eq!(round_to(&BigDecimal::from(-10.999), 2), "-11"); - assert_eq! (round_to (&BigDecimal::from (99.9), 1), "99.9"); - assert_eq! (round_to (&BigDecimal::from (-99.9), 1), "-99.9"); + assert_eq!(round_to(&BigDecimal::from(99.9), 1), "99.9"); + assert_eq!(round_to(&BigDecimal::from(-99.9), 1), "-99.9"); - assert_eq! (round_to (&BigDecimal::from (99.9), 0), "100"); - assert_eq! (round_to (&BigDecimal::from (-99.9), 0), "-100"); + assert_eq!(round_to(&BigDecimal::from(99.9), 0), "100"); + assert_eq!(round_to(&BigDecimal::from(-99.9), 0), "-100"); - let ouch = BigDecimal::from (1) / BigDecimal::from (7); - assert_eq! (round_to (&ouch, 3), "0.143"); + let ouch = BigDecimal::from(1) / BigDecimal::from(7); + assert_eq!(round_to(&ouch, 3), "0.143"); - let ouch = BigDecimal::from (1) / BigDecimal::from (3); - assert_eq! (round_to (&ouch, 0), "0"); - assert_eq! (round_to (&ouch, 1), "0.3"); - assert_eq! (round_to (&ouch, 2), "0.33"); - assert_eq! (round_to (&ouch, 9), "0.333333333"); + let ouch = BigDecimal::from(1) / BigDecimal::from(3); + assert_eq!(round_to(&ouch, 0), "0"); + assert_eq!(round_to(&ouch, 1), "0.3"); + assert_eq!(round_to(&ouch, 2), "0.33"); + assert_eq!(round_to(&ouch, 9), "0.333333333"); - assert_eq! (round_to (&BigDecimal::from (0.123), 99), "0.123"); - assert_eq! (round_to (&BigDecimal::from (-0.123), 99), "-0.123"); + assert_eq!(round_to(&BigDecimal::from(0.123), 99), "0.123"); + assert_eq!(round_to(&BigDecimal::from(-0.123), 99), "-0.123"); - assert_eq! (round_to (&BigDecimal::from (0), 99), "0"); - assert_eq! (round_to (&BigDecimal::from (-0), 99), "0"); + assert_eq!(round_to(&BigDecimal::from(0), 99), "0"); + assert_eq!(round_to(&BigDecimal::from(-0), 99), "0"); - assert_eq! (round_to (&BigDecimal::from (0.123), 0), "0"); - assert_eq! (round_to (&BigDecimal::from (-0.123), 0), "0"); + assert_eq!(round_to(&BigDecimal::from(0.123), 0), "0"); + assert_eq!(round_to(&BigDecimal::from(-0.123), 0), "0"); - assert_eq! (round_to (&BigDecimal::from (0), 0), "0"); - assert_eq! (round_to (&BigDecimal::from (-0), 0), "0"); + assert_eq!(round_to(&BigDecimal::from(0), 0), "0"); + assert_eq!(round_to(&BigDecimal::from(-0), 0), "0"); } #[cfg(feature = "native")] -pub fn new_uuid() -> Uuid {Uuid::new_v4()} +pub fn new_uuid() -> Uuid { Uuid::new_v4() } #[cfg(not(feature = "native"))] pub fn new_uuid() -> Uuid { @@ -1702,9 +1954,8 @@ pub fn new_uuid() -> Uuid { pub fn first_char_to_upper(input: &str) -> String { let mut v: Vec = input.chars().collect(); - match v.first_mut() { - Some(c) => c.make_ascii_uppercase(), - None => (), + if let Some(c) = v.first_mut() { + c.make_ascii_uppercase() } v.into_iter().collect() } @@ -1718,27 +1969,31 @@ fn test_first_char_to_upper() { } pub fn json_dir_entries(path: &dyn AsRef) -> Result, String> { - Ok(try_s!(path.as_ref().read_dir()).filter_map(|dir_entry| { - let entry = match dir_entry { - Ok(ent) => ent, - Err(e) => { - log!("Error " (e) " reading from dir " (path.as_ref().display())); - return None; - } - }; + Ok(try_s!(path.as_ref().read_dir()) + .filter_map(|dir_entry| { + let entry = match dir_entry { + Ok(ent) => ent, + Err(e) => { + log!("Error " (e) " reading from dir " (path.as_ref().display())); + return None; + }, + }; - if entry.path().extension() == Some(OsStr::new("json")) { - Some(entry) - } else { - None - } - }).collect()) + if entry.path().extension() == Some(OsStr::new("json")) { + Some(entry) + } else { + None + } + }) + .collect()) } /// Calculates the median of the set represented as slice pub fn median + Div + Copy + From + Ord>(input: &mut [T]) -> Option { // median is undefined on empty sets - if input.len() == 0 { return None } + if input.is_empty() { + return None; + } input.sort(); let median_index = input.len() / 2; if input.len() % 2 == 0 { diff --git a/mm2src/common/custom_futures.rs b/mm2src/common/custom_futures.rs index 3365c73106..d1a23c4d1f 100644 --- a/mm2src/common/custom_futures.rs +++ b/mm2src/common/custom_futures.rs @@ -1,14 +1,13 @@ /// Custom future combinators/implementations - some of standard do not match our requirements. - use crate::executor::Timer; use crate::now_float; +use futures01::future::{self, loop_fn, Either as Either01, IntoFuture, Loop}; +use futures01::stream::{Fuse, Stream}; use futures01::{Async, AsyncSink, Future, Poll, Sink}; -use futures01::future::{self, Either as Either01, IntoFuture, Loop, loop_fn}; -use futures01::stream::{Stream, Fuse}; use futures::future::{select, Either}; -use futures::lock::{Mutex as AsyncMutex}; +use futures::lock::Mutex as AsyncMutex; /// The analogue of join_all combinator running futures `sequentially`. /// `join_all` runs futures `concurrently` which cause issues with native coins daemons RPC. @@ -20,14 +19,14 @@ use futures::lock::{Mutex as AsyncMutex}; pub fn join_all_sequential( i: I, ) -> impl Future::Item>, Error = ::Error> - where - I: IntoIterator, - I::Item: IntoFuture, +where + I: IntoIterator, + I::Item: IntoFuture, { let iter = i.into_iter(); loop_fn((vec![], iter), |(mut output, mut iter)| { let fut = if let Some(next) = iter.next() { - Either01::A(next.into_future().map(|v| Some(v))) + Either01::A(next.into_future().map(Some)) } else { Either01::B(future::ok(None)) }; @@ -53,12 +52,13 @@ pub fn join_all_sequential( pub fn select_ok_sequential( i: I, ) -> impl Future::Item, Error = Vec<::Error>> - where I::Item: IntoFuture, +where + I::Item: IntoFuture, { let futures = i.into_iter(); loop_fn((vec![], futures), |(mut errors, mut futures)| { let fut = if let Some(next) = futures.next() { - Either01::A(next.into_future().map(|v| Some(v))) + Either01::A(next.into_future().map(Some)) } else { Either01::B(future::ok(None)) }; @@ -68,7 +68,7 @@ pub fn select_ok_sequential( Ok(val) => val, Err(e) => { errors.push(e); - return Ok(Loop::Continue((errors, futures))) + return Ok(Loop::Continue((errors, futures))); }, }; @@ -96,12 +96,16 @@ pub struct SendAll { } impl SendAll - where T: Sink, - U: Stream, - T::SinkError: From, +where + T: Sink, + U: Stream, + T::SinkError: From, { fn sink_mut(&mut self) -> &mut T { - self.sink.as_mut().take().expect("Attempted to poll SendAll after completion") + self.sink + .as_mut() + .take() + .expect("Attempted to poll SendAll after completion") } pub fn new(sink: T, stream: U) -> SendAll { @@ -113,21 +117,20 @@ impl SendAll } fn stream_mut(&mut self) -> &mut Fuse { - self.stream.as_mut().take() + self.stream + .as_mut() + .take() .expect("Attempted to poll SendAll after completion") } fn take_stream(&mut self) -> U { - let fuse = self.stream.take() - .expect("Attempted to poll Forward after completion"); + let fuse = self.stream.take().expect("Attempted to poll Forward after completion"); fuse.into_inner() } fn take_result(&mut self) -> (T, U) { - let sink = self.sink.take() - .expect("Attempted to poll Forward after completion"); - let fuse = self.stream.take() - .expect("Attempted to poll Forward after completion"); + let sink = self.sink.take().expect("Attempted to poll Forward after completion"); + let fuse = self.stream.take().expect("Attempted to poll Forward after completion"); (sink, fuse.into_inner()) } @@ -135,24 +138,27 @@ impl SendAll debug_assert!(self.buffered.is_none()); if let AsyncSink::NotReady(item) = self.sink_mut().start_send(item)? { self.buffered = Some(item); - return Ok(Async::NotReady) + return Ok(Async::NotReady); } Ok(Async::Ready(())) } } macro_rules! try_ready_send_all { - ($selff: ident, $e:expr) => (match $e { - Ok(Async::Ready(t)) => t, - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(e) => return Err(($selff.take_stream(), From::from(e))), - }) + ($selff: ident, $e:expr) => { + match $e { + Ok(Async::Ready(t)) => t, + Ok(Async::NotReady) => return Ok(Async::NotReady), + Err(e) => return Err(($selff.take_stream(), From::from(e))), + } + }; } impl Future for SendAll - where T: Sink, - U: Stream, - T::SinkError: From, +where + T: Sink, + U: Stream, + T::SinkError: From, { type Item = (T, U); type Error = (U, T::SinkError); @@ -165,49 +171,57 @@ impl Future for SendAll } loop { - match self.stream_mut().poll().map_err(|e| (self.take_stream(), From::from(e)))? { + match self + .stream_mut() + .poll() + .map_err(|e| (self.take_stream(), From::from(e)))? + { Async::Ready(Some(item)) => try_ready_send_all!(self, self.try_start_send(item)), Async::Ready(None) => { try_ready_send_all!(self, self.sink_mut().close()); - return Ok(Async::Ready(self.take_result())) - } + return Ok(Async::Ready(self.take_result())); + }, Async::NotReady => { try_ready_send_all!(self, self.sink_mut().poll_complete()); - return Ok(Async::NotReady) - } + return Ok(Async::NotReady); + }, } } } } -pub struct TimedMutexGuard<'a, T> (futures::lock::MutexGuard<'a, T>); +pub struct TimedMutexGuard<'a, T>(futures::lock::MutexGuard<'a, T>); //impl<'a, T> Drop for TimedMutexGuard<'a, T> {fn drop (&mut self) {}} /// Like `AsyncMutex` but periodically invokes a callback, /// allowing the application to implement timeouts, status updates and shutdowns. -pub struct TimedAsyncMutex (AsyncMutex); +pub struct TimedAsyncMutex(AsyncMutex); impl TimedAsyncMutex { - pub fn new (v: T) -> TimedAsyncMutex {TimedAsyncMutex (AsyncMutex::new (v))} + pub fn new(v: T) -> TimedAsyncMutex { TimedAsyncMutex(AsyncMutex::new(v)) } /// Like `AsyncMutex::lock` but invokes the `tick` callback periodically. /// `tick` returns a time till the next tick, or an error to abort the locking attempt. /// `tick` parameters are the time when the locking attempt has started and the current time /// (they are equal on the first invocation of `tick`). - pub async fn lock (&self, mut tick: F) -> Result, String> - where F: FnMut (f64, f64) -> Result { + pub async fn lock(&self, mut tick: F) -> Result, String> + where + F: FnMut(f64, f64) -> Result, + { let start = now_float(); let mut now = start; let mut l = self.0.lock(); let l = loop { - let tick_after = try_s! (tick (start, now)); - let t = Timer::till (now + tick_after); - let rc = select (l, t) .await; + let tick_after = try_s!(tick(start, now)); + let t = Timer::till(now + tick_after); + let rc = select(l, t).await; match rc { - Either::Left ((l, _t)) => break l, - Either::Right ((_t, lʹ)) => { + Either::Left((l, _t)) => break l, + Either::Right((_t, lʹ)) => { now = now_float(); l = lʹ - } } }; - Ok (TimedMutexGuard (l)) + }, + } + }; + Ok(TimedMutexGuard(l)) } } diff --git a/mm2src/common/duplex_mutex.rs b/mm2src/common/duplex_mutex.rs index 914cfa8b23..87469d074b 100644 --- a/mm2src/common/duplex_mutex.rs +++ b/mm2src/common/duplex_mutex.rs @@ -1,16 +1,16 @@ +use super::executor::Timer; +use super::now_ms; use atomic::Atomic; use parking_lot_core::SpinWait; +use std::cell::UnsafeCell; use std::fmt; use std::ops::{Deref, DerefMut}; -use std::sync::Arc; use std::sync::atomic::Ordering; -use std::cell::UnsafeCell; -use super::now_ms; -use super::executor::Timer; +use std::sync::Arc; #[must_use = "if unused the DuplexMutexGuard will immediately unlock"] pub struct DuplexMutexGuard<'a, T> { - mutex: &'a DuplexMutex + mutex: &'a DuplexMutex, } unsafe impl Send for DuplexMutexGuard<'_, T> {} @@ -18,32 +18,30 @@ unsafe impl Sync for DuplexMutexGuard<'_, T> {} impl Deref for DuplexMutexGuard<'_, T> { type Target = T; - fn deref (&self) -> &T {unsafe {&*self.mutex.pimpl.data.get()}} + fn deref(&self) -> &T { unsafe { &*self.mutex.pimpl.data.get() } } } impl DerefMut for DuplexMutexGuard<'_, T> { - fn deref_mut (&mut self) -> &mut T { - unsafe {&mut *self.mutex.pimpl.data.get()} -} } + fn deref_mut(&mut self) -> &mut T { unsafe { &mut *self.mutex.pimpl.data.get() } } +} impl Drop for DuplexMutexGuard<'_, T> { - fn drop (&mut self) { - unwrap! (self.mutex.pimpl.unlock()); -} } + fn drop(&mut self) { + unwrap!(self.mutex.pimpl.unlock()); + } +} impl fmt::Debug for DuplexMutexGuard<'_, T> { - fn fmt (&self, ft: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt (&**self, ft) -} } + fn fmt(&self, ft: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&**self, ft) } +} impl fmt::Display for DuplexMutexGuard<'_, T> { - fn fmt (&self, ft: &mut fmt::Formatter<'_>) -> fmt::Result { - (**self) .fmt (ft) -} } + fn fmt(&self, ft: &mut fmt::Formatter<'_>) -> fmt::Result { (**self).fmt(ft) } +} struct Impl { locked: Atomic, - data: UnsafeCell + data: UnsafeCell, } // We're only using `Impl::data` behind an `Arc` and a lock. @@ -51,67 +49,110 @@ unsafe impl Send for Impl {} unsafe impl Sync for Impl {} impl Impl { - fn spinlock (&self, timeout_ms: i64) -> Result<(), String> { - if self.locked.compare_exchange (0, 1, Ordering::Relaxed, Ordering::Relaxed) .is_ok() {return Ok(())} + fn spinlock(&self, timeout_ms: i64) -> Result<(), String> { + if self + .locked + .compare_exchange(0, 1, Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + { + return Ok(()); + } let start = now_ms() as i64; let mut spin_wait = SpinWait::new(); loop { let fast = spin_wait.spin(); - if self.locked.compare_exchange (0, 1, Ordering::Relaxed, Ordering::Relaxed) .is_ok() {return Ok(())} + if self + .locked + .compare_exchange(0, 1, Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + { + return Ok(()); + } if !fast { spin_wait.reset(); let delta = now_ms() as i64 - start; - if delta > timeout_ms {return ERR! ("spinlock timeout, {}ms", delta)} + if delta > timeout_ms { + return ERR!("spinlock timeout, {}ms", delta); + } } } } - fn unlock (&self) -> Result<(), String> { - if self.locked.compare_exchange (1, 0, Ordering::Relaxed, Ordering::Relaxed) .is_ok() {return Ok(())} - ERR! ("Not locked") + fn unlock(&self) -> Result<(), String> { + if self + .locked + .compare_exchange(1, 0, Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + { + return Ok(()); + } + ERR!("Not locked") } - async fn sleeplock (&self, timeout_ms: i64) -> Result<(), String> { - if self.locked.compare_exchange (0, 1, Ordering::Relaxed, Ordering::Relaxed) .is_ok() {return Ok(())} + async fn sleeplock(&self, timeout_ms: i64) -> Result<(), String> { + if self + .locked + .compare_exchange(0, 1, Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + { + return Ok(()); + } let start = now_ms() as i64; loop { - Timer::sleep (0.02) .await; - if self.locked.compare_exchange (0, 1, Ordering::Relaxed, Ordering::Relaxed) .is_ok() {return Ok(())} + Timer::sleep(0.02).await; + if self + .locked + .compare_exchange(0, 1, Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + { + return Ok(()); + } let delta = now_ms() as i64 - start; - if delta > timeout_ms {return ERR! ("sleeplock timeout, {}ms", delta)} + if delta > timeout_ms { + return ERR!("sleeplock timeout, {}ms", delta); + } } } } /// A mutual exclusion primitive that can be used from both the synchronous and asynchronous contexts. -/// +/// /// There is a problem with existing primitives: /// Synchronous Mutex can not be used reliably from WASM since threading there is new. /// Asynchronous Mutex is only compatible with fully asynchronous code /// because `block_on` will panic under a layered async->sync->block_on(lock), /// but making everything asynchronous would lead to bloated machine code, /// obscure errors, restraints and slowed compilation speed. -/// +/// /// DuplexMutex bridges this gap by being useable in both contexts: /// In the synchronous context it will spin. /// In the asynchronous context it will wait with the `Timer`, /// allowing the green thread holding the lock to resurface even in situations /// when both the holder and the entrant are on the same system thread. pub struct DuplexMutex { - pimpl: Arc> + pimpl: Arc>, } unsafe impl Send for DuplexMutex {} impl Clone for DuplexMutex { - fn clone (&self) -> DuplexMutex { - DuplexMutex {pimpl: self.pimpl.clone()} -} } + fn clone(&self) -> DuplexMutex { + DuplexMutex { + pimpl: self.pimpl.clone(), + } + } +} impl DuplexMutex { - pub fn new (v: T) -> DuplexMutex { - DuplexMutex {pimpl: Arc::new (Impl {locked: Atomic::new (0), data: UnsafeCell::new (v)})} -} } + pub fn new(v: T) -> DuplexMutex { + DuplexMutex { + pimpl: Arc::new(Impl { + locked: Atomic::new(0), + data: UnsafeCell::new(v), + }), + } + } +} impl DuplexMutex { /// Synchronous spinlock. @@ -119,15 +160,15 @@ impl DuplexMutex { /// (like when the mutex guard does not cross green thread boundaries). /// Using spinlock from two green threads running on the same system thread might result in a deadlock, /// but that might be mitigated by handling the timeout `Err`. - pub fn spinlock (&self, timeout_ms: i64) -> Result, String> { - try_s! (self.pimpl.spinlock (timeout_ms)); - Ok (DuplexMutexGuard {mutex: self}) + pub fn spinlock(&self, timeout_ms: i64) -> Result, String> { + try_s!(self.pimpl.spinlock(timeout_ms)); + Ok(DuplexMutexGuard { mutex: self }) } /// Asynchronous `Timer::sleep` lock. /// Can be used with long-held locks and with locks held across green thread boundaries. - pub async fn sleeplock (&self, timeout_ms: i64) -> Result, String> { - try_s! (self.pimpl.sleeplock (timeout_ms) .await); - Ok (DuplexMutexGuard {mutex: self}) + pub async fn sleeplock(&self, timeout_ms: i64) -> Result, String> { + try_s!(self.pimpl.sleeplock(timeout_ms).await); + Ok(DuplexMutexGuard { mutex: self }) } } diff --git a/mm2src/common/executor.rs b/mm2src/common/executor.rs index 291572ef96..2739cb7e7d 100644 --- a/mm2src/common/executor.rs +++ b/mm2src/common/executor.rs @@ -7,26 +7,25 @@ use crate::now_float; use atomic::Atomic; -use futures::FutureExt; use futures::executor::enter; use futures::future::BoxFuture; use futures::task::{waker_ref, ArcWake, Context, Poll}; +use futures::FutureExt; use std::future::Future; use std::mem::swap; use std::pin::Pin; -use std::sync::{Arc, Mutex}; use std::sync::atomic::Ordering; +use std::sync::{Arc, Mutex}; struct Task { future: Mutex>, /// We can skip running the task till its alarm clock goes off. - alarm_clock: Atomic + alarm_clock: Atomic, } impl ArcWake for Task { - fn wake_by_ref (arc_self: &Arc) { - arc_self.alarm_clock.store (0., Ordering::Relaxed) -} } + fn wake_by_ref(arc_self: &Arc) { arc_self.alarm_clock.store(0., Ordering::Relaxed) } +} lazy_static! { static ref TASKS: Mutex>> = Mutex::new (Vec::new()); @@ -35,71 +34,86 @@ lazy_static! { static ref NEW_TASKS: Mutex>> = Mutex::new (Vec::new()); } -pub fn spawn (future: impl Future + Send + 'static) { - spawn_after (0., future) -} +pub fn spawn(future: impl Future + Send + 'static) { spawn_after(0., future) } /// Schedule the given `future` to be executed shortly after the given `utc` time is reached. -pub fn spawn_after (utc: f64, future: impl Future + Send + 'static) { +pub fn spawn_after(utc: f64, future: impl Future + Send + 'static) { let future = future.boxed(); - let task = Arc::new (Task {future: Mutex::new (future), alarm_clock: Atomic::new (utc)}); - unwrap! (NEW_TASKS.lock()) .push (task) + let task = Arc::new(Task { + future: Mutex::new(future), + alarm_clock: Atomic::new(utc), + }); + unwrap!(NEW_TASKS.lock()).push(task) } pub fn run() { let mut new_tasks = Vec::new(); - swap (&mut new_tasks, &mut* unwrap! (NEW_TASKS.lock())); + swap(&mut new_tasks, &mut *unwrap!(NEW_TASKS.lock())); - let mut tasks = unwrap! (TASKS.lock()); - for new_task in new_tasks {tasks.push (new_task)} - let enter = enter().expect ("!enter"); + let mut tasks = unwrap!(TASKS.lock()); + for new_task in new_tasks { + tasks.push(new_task) + } + let enter = enter().expect("!enter"); let now = now_float(); - tasks.retain (|task| { + tasks.retain(|task| { // As an optimization, and in order to maintain the proper task waking logic, // we're going to skip the tasks which aren't quite ready to run yet. - let alarm_clock = task.alarm_clock.load (Ordering::Relaxed); - if now < alarm_clock {return true} // See you later. + let alarm_clock = task.alarm_clock.load(Ordering::Relaxed); + if now < alarm_clock { + return true; + } // See you later. // Pre-schedule the task into waking up a bit later. // The underlying task future can speed things up by using the `Waker`. - let later = now + 2.; // Bump this up to test the `Waker` code. - let _ = task.alarm_clock.compare_exchange (alarm_clock, later, Ordering::Relaxed, Ordering::Relaxed); - - let mut future = unwrap! (task.future.lock()); - let waker = waker_ref (&task); - let context = &mut Context::from_waker (&*waker); - if let Poll::Pending = future.as_mut().poll (context) { - true // Retain, we're not done yet. + let later = now + 2.; // Bump this up to test the `Waker` code. + let _ = task + .alarm_clock + .compare_exchange(alarm_clock, later, Ordering::Relaxed, Ordering::Relaxed); + + let mut future = unwrap!(task.future.lock()); + let waker = waker_ref(&task); + let context = &mut Context::from_waker(&*waker); + if let Poll::Pending = future.as_mut().poll(context) { + true // Retain, we're not done yet. } else { - false // Evict, we're done here. + false // Evict, we're done here. } }); - drop (enter) + drop(enter) } /// This native export allows the WASM host to run the executor via the WASM FFI. /// TODO: Start a thread from the `start_helpers` instead. #[no_mangle] -pub unsafe extern fn run_executor() {run()} +pub unsafe extern "C" fn run_executor() { run() } /// A future that completes at a given time. -pub struct Timer {till_utc: f64} +pub struct Timer { + till_utc: f64, +} impl Timer { - pub fn till (till_utc: f64) -> Timer {Timer {till_utc}} - pub fn sleep (seconds: f64) -> Timer {Timer {till_utc: now_float() + seconds}} - pub fn till_utc (&self) -> f64 {self.till_utc} + pub fn till(till_utc: f64) -> Timer { Timer { till_utc } } + pub fn sleep(seconds: f64) -> Timer { + Timer { + till_utc: now_float() + seconds, + } + } + pub fn till_utc(&self) -> f64 { self.till_utc } } impl Future for Timer { type Output = (); - fn poll (self: Pin<&mut Self>, cx: &mut Context) -> Poll { - if self.till_utc - now_float() <= 0. {return Poll::Ready(())} + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + if self.till_utc - now_float() <= 0. { + return Poll::Ready(()); + } // NB: We should get a new `Waker` on every `poll` in case the future migrates between executors. // cf. https://rust-lang.github.io/async-book/02_execution/03_wakeups.html let waker = cx.waker().clone(); - spawn_after (self.till_utc, async {waker.wake()}); + spawn_after(self.till_utc, async { waker.wake() }); Poll::Pending } diff --git a/mm2src/common/file_lock.rs b/mm2src/common/file_lock.rs index aa10fb8683..353774eeb0 100644 --- a/mm2src/common/file_lock.rs +++ b/mm2src/common/file_lock.rs @@ -1,10 +1,11 @@ -use crate::{now_ms, now_float}; +use crate::{now_float, now_ms}; use std::path::Path; pub struct FileLock> { /// Filesystem path of the lock file. lock_path: T, /// The time in seconds after which an outdated lock file can be removed. + #[allow(dead_code)] ttl_sec: f64, } @@ -17,13 +18,17 @@ fn touch(path: &dyn AsRef, timestamp: u64) -> Result<(), String> { fn read_timestamp(path: &dyn AsRef) -> Result, String> { match std::fs::read_to_string(path) { Ok(content) => Ok(content.parse().ok()), - Err(e) => ERR!("{:?}", e) + Err(e) => ERR!("{:?}", e), } } impl> FileLock { pub fn lock(lock_path: T, ttl_sec: f64) -> Result>, String> { - match std::fs::OpenOptions::new().write(true).create_new(true).open(lock_path.as_ref()) { + match std::fs::OpenOptions::new() + .write(true) + .create_new(true) + .open(lock_path.as_ref()) + { Ok(_) => { let file_lock = FileLock { lock_path, ttl_sec }; try_s!(file_lock.touch()); @@ -32,43 +37,38 @@ impl> FileLock { Err(ref ie) if ie.kind() == std::io::ErrorKind::AlreadyExists => { // See if the existing lock is old enough to be discarded. match read_timestamp(&lock_path) { - Ok(Some(lm)) => if now_float() - lm as f64 > ttl_sec { - let file_lock = FileLock { lock_path, ttl_sec }; - try_s!(file_lock.touch()); - Ok(Some(file_lock)) - } else { - Ok(None) + Ok(Some(lm)) => { + if now_float() - lm as f64 > ttl_sec { + let file_lock = FileLock { lock_path, ttl_sec }; + try_s!(file_lock.touch()); + Ok(Some(file_lock)) + } else { + Ok(None) + } }, Ok(None) => { let file_lock = FileLock { lock_path, ttl_sec }; try_s!(file_lock.touch()); Ok(Some(file_lock)) }, - Err(ie) => ERR!("Error checking {:?}: {}", lock_path.as_ref(), ie) + Err(ie) => ERR!("Error checking {:?}: {}", lock_path.as_ref(), ie), } }, - Err(ie) => ERR!("Error creating {:?}: {}", lock_path.as_ref(), ie) + Err(ie) => ERR!("Error creating {:?}: {}", lock_path.as_ref(), ie), } } - pub fn touch(&self) -> Result<(), String> { - touch(&self.lock_path, now_ms() / 1000) - } + pub fn touch(&self) -> Result<(), String> { touch(&self.lock_path, now_ms() / 1000) } } impl> Drop for FileLock { - fn drop(&mut self) { - let _ = std::fs::remove_file(&self.lock_path); - } + fn drop(&mut self) { let _ = std::fs::remove_file(&self.lock_path); } } #[cfg(test)] mod file_lock_tests { - use std::{ - thread::sleep, - time::Duration, - }; use super::*; + use std::{thread::sleep, time::Duration}; #[test] fn test_file_lock_should_create_file_and_record_timestamp_and_then_delete_on_drop() { diff --git a/mm2src/common/for_c.rs b/mm2src/common/for_c.rs index a03870452b..5d1ff644cd 100644 --- a/mm2src/common/for_c.rs +++ b/mm2src/common/for_c.rs @@ -1,4 +1,4 @@ -use libc::{c_char}; +use libc::c_char; use std::ffi::CStr; use std::net::IpAddr; @@ -6,36 +6,38 @@ use std::net::IpAddr; //lazy_static! {pub static ref PEERS_SEND_COMPAT: Mutex i32>> = Mutex::new (None);} #[no_mangle] -pub extern fn log_stacktrace (desc: *const c_char) { +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn log_stacktrace(desc: *const c_char) { let desc = if desc.is_null() { "" } else { - match unsafe {CStr::from_ptr (desc)} .to_str() { - Ok (s) => s, - Err (err) => { + match CStr::from_ptr(desc).to_str() { + Ok(s) => s, + Err(err) => { log! ({"log_stacktrace] Bad trace description: {}", err}); "" - } + }, } }; - let mut trace = String::with_capacity (4096); - super::stack_trace (&mut super::stack_trace_frame, &mut |l| trace.push_str (l)); + let mut trace = String::with_capacity(4096); + super::stack_trace(&mut super::stack_trace_frame, &mut |l| trace.push_str(l)); log! ({"Stacktrace. {}\n{}", desc, trace}); } #[no_mangle] -pub extern fn is_loopback_ip (ip: *mut c_char) -> u8 { +#[allow(clippy::missing_safety_doc)] +pub unsafe extern "C" fn is_loopback_ip(ip: *mut c_char) -> u8 { if ip.is_null() { log!("received null ip"); return 0; } - let ip_str = match unsafe { CStr::from_ptr(ip).to_str() } { + let ip_str = match CStr::from_ptr(ip).to_str() { Ok(s) => s, Err(e) => { - log!("Error creating CStr " [e]); + log!("Error creating CStr "[e]); return 0; - } + }, }; let ip: IpAddr = match ip_str.parse() { @@ -43,7 +45,7 @@ pub extern fn is_loopback_ip (ip: *mut c_char) -> u8 { Err(e) => { log!("Error " [e] " parsing ip from str " (ip_str)); return 0; - } + }, }; ip.is_loopback() as u8 diff --git a/mm2src/common/for_tests.rs b/mm2src/common/for_tests.rs index c86294151d..6ee6f09bf9 100644 --- a/mm2src/common/for_tests.rs +++ b/mm2src/common/for_tests.rs @@ -4,54 +4,58 @@ use bytes::Bytes; use chrono::{Local, TimeZone}; -#[cfg(feature = "native")] -use futures01::Future; use futures::channel::oneshot::channel; use futures::task::SpawnExt; +#[cfg(feature = "native")] use futures01::Future; use gstuff::ISATTY; use http::{HeaderMap, Request, StatusCode}; -use serde_json::{self as json, Value as Json}; -use term; use rand::Rng; use regex::Regex; +use serde_json::{self as json, Value as Json}; use std::collections::HashMap; use std::env; use std::fs; use std::io::Write; +#[cfg(not(feature = "native"))] use std::net::SocketAddr; use std::net::{IpAddr, Ipv4Addr}; -#[cfg(not(feature = "native"))] -use std::net::SocketAddr; use std::path::{Path, PathBuf}; -use std::process::{Command, Child}; -#[cfg(feature = "native")] -use std::str::from_utf8; +use std::process::{Child, Command}; +#[cfg(feature = "native")] use std::str::from_utf8; use std::sync::Mutex; use std::thread::sleep; use std::time::Duration; +use term; -use crate::{now_float, slurp}; use crate::executor::Timer; +#[cfg(not(feature = "native"))] use crate::helperᶜ; +use crate::log::{dashboard_path, LogState}; #[cfg(not(feature = "native"))] use crate::mm_ctx::{MmArc, MmCtxBuilder}; -#[cfg(not(feature = "native"))] -use crate::helperᶜ; -#[cfg(feature = "native")] -use crate::wio::{slurp_req, POOL}; -use crate::log::{dashboard_path, LogState}; +#[cfg(feature = "native")] use crate::wio::{slurp_req, POOL}; +use crate::{now_float, slurp}; /// Automatically kill a wrapped process. -pub struct RaiiKill {pub handle: Child, running: bool} +pub struct RaiiKill { + pub handle: Child, + running: bool, +} impl RaiiKill { - pub fn from_handle (handle: Child) -> RaiiKill { - RaiiKill {handle, running: true} - } - pub fn running (&mut self) -> bool { - if !self.running {return false} - match self.handle.try_wait() {Ok (None) => true, _ => {self.running = false; false}} + pub fn from_handle(handle: Child) -> RaiiKill { RaiiKill { handle, running: true } } + pub fn running(&mut self) -> bool { + if !self.running { + return false; + } + match self.handle.try_wait() { + Ok(None) => true, + _ => { + self.running = false; + false + }, + } } } impl Drop for RaiiKill { - fn drop (&mut self) { + fn drop(&mut self) { // The cached `running` check might provide some protection against killing a wrong process under the same PID, // especially if the cached `running` check is also used to monitor the status of the process. if self.running() { @@ -61,34 +65,34 @@ impl Drop for RaiiKill { } /// When `drop`ped, dumps the given file to the stdout. -/// +/// /// Used in the tests, copying the MM log to the test output. -/// +/// /// Note that because of https://github.com/rust-lang/rust/issues/42474 it's currently impossible to share the MM log interactively, /// hence we're doing it in the `drop`. pub struct RaiiDump { #[cfg(feature = "native")] - pub log_path: PathBuf + pub log_path: PathBuf, } #[cfg(feature = "native")] impl Drop for RaiiDump { - fn drop (&mut self) { + fn drop(&mut self) { // `term` bypasses the stdout capturing, we should only use it if the capturing was disabled. - let nocapture = env::args().any (|a| a == "--nocapture"); + let nocapture = env::args().any(|a| a == "--nocapture"); - let log = unwrap! (slurp (&self.log_path)); + let log = unwrap!(slurp(&self.log_path)); // Make sure the log is Unicode. // We'll get the "io error when listing tests: Custom { kind: InvalidData, error: StringError("text was not valid unicode") }" otherwise. - let log = String::from_utf8_lossy (&log); + let log = String::from_utf8_lossy(&log); let log = log.trim(); - if let (true, true, Some (mut t)) = (nocapture, *ISATTY, term::stdout()) { - let _ = t.fg (term::color::BRIGHT_YELLOW); - let _ = t.write (format! ("vvv {:?} vvv\n", self.log_path) .as_bytes()); - let _ = t.fg (term::color::YELLOW); - let _ = t.write (log.as_bytes()); - let _ = t.write (b"\n"); + if let (true, true, Some(mut t)) = (nocapture, *ISATTY, term::stdout()) { + let _ = t.fg(term::color::BRIGHT_YELLOW); + let _ = t.write(format!("vvv {:?} vvv\n", self.log_path).as_bytes()); + let _ = t.fg(term::color::YELLOW); + let _ = t.write(log.as_bytes()); + let _ = t.write(b"\n"); let _ = t.reset(); } else { log! ({"vvv {:?} vvv\n{}", self.log_path, log}); @@ -97,16 +101,16 @@ impl Drop for RaiiDump { } lazy_static! { - /// A singleton with the IPs used by the MarketMakerIt instances created in this session. + /// A singleton with the IPs used by the MarketMakerIt instances created in this session. /// The value is set to `false` when the instance is retired. static ref MM_IPS: Mutex> = Mutex::new (HashMap::new()); } #[cfg(feature = "native")] -pub type LocalStart = fn (PathBuf, PathBuf, Json); +pub type LocalStart = fn(PathBuf, PathBuf, Json); #[cfg(not(feature = "native"))] -pub type LocalStart = fn (MmArc); +pub type LocalStart = fn(MmArc); /// An instance of a MarketMaker process started by and for an integration test. /// Given that [in CI] the tests are executed before the build, the binary of that process is the tests binary. @@ -121,7 +125,7 @@ pub struct MarketMakerIt { /// The PID of the MarketMaker process. pub pc: Option, /// RPC API key. - pub userpass: String + pub userpass: String, } /// A MarketMaker instance started by and for an integration test. @@ -131,45 +135,58 @@ pub struct MarketMakerIt { /// Unique (to run multiple instances) IP, like "127.0.0.$x". pub ip: IpAddr, /// RPC API key. - pub userpass: String + pub userpass: String, } #[cfg(feature = "native")] impl std::fmt::Debug for MarketMakerIt { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "MarketMakerIt {{ folder: {:?}, ip: {}, log_path: {:?}, userpass: {} }}", self.folder, self.ip, self.log_path, self.userpass) -} } + write!( + f, + "MarketMakerIt {{ folder: {:?}, ip: {}, log_path: {:?}, userpass: {} }}", + self.folder, self.ip, self.log_path, self.userpass + ) + } +} impl MarketMakerIt { /// Create a new temporary directory and start a new MarketMaker process there. - /// + /// /// * `conf` - The command-line configuration passed to the MarketMaker. /// Unique local IP address is injected as "myipaddr" unless this field is already present. /// * `userpass` - RPC API key. We should probably extract it automatically from the MM log. /// * `local` - Function to start the MarketMaker in a local thread, instead of spawning a process. /// It's required to manually add 127.0.0.* IPs aliases on Mac to make it properly work. /// cf. https://superuser.com/a/458877, https://superuser.com/a/635327 - pub fn start (mut conf: Json, userpass: String, local: Option) - -> Result { - let ip: IpAddr = if conf["myipaddr"].is_null() { // Generate an unique IP. + pub fn start(mut conf: Json, userpass: String, local: Option) -> Result { + let ip: IpAddr = if conf["myipaddr"].is_null() { + // Generate an unique IP. let mut attempts = 0; let mut rng = super::small_rng(); loop { - let ip4 = Ipv4Addr::new (127, 0, 0, rng.gen_range (1, 255)); - if attempts > 128 {return ERR! ("Out of local IPs?")} + let ip4 = Ipv4Addr::new(127, 0, 0, rng.gen_range(1, 255)); + if attempts > 128 { + return ERR!("Out of local IPs?"); + } let ip: IpAddr = ip4.clone().into(); - let mut mm_ips = try_s! (MM_IPS.lock()); - if mm_ips.contains_key (&ip) {attempts += 1; continue} - mm_ips.insert (ip.clone(), true); - conf["myipaddr"] = format! ("{}", ip) .into(); - conf["rpcip"] = format! ("{}", ip) .into(); - break ip + let mut mm_ips = try_s!(MM_IPS.lock()); + if mm_ips.contains_key(&ip) { + attempts += 1; + continue; + } + mm_ips.insert(ip.clone(), true); + conf["myipaddr"] = format!("{}", ip).into(); + conf["rpcip"] = format!("{}", ip).into(); + break ip; } - } else { // Just use the IP given in the `conf`. - let ip: IpAddr = try_s! (try_s! (conf["myipaddr"].as_str().ok_or ("myipaddr is not a string")) .parse()); - let mut mm_ips = try_s! (MM_IPS.lock()); - if mm_ips.contains_key (&ip) {log! ({"MarketMakerIt] Warning, IP {} was already used.", ip})} - mm_ips.insert (ip.clone(), true); + } else { + // Just use the IP given in the `conf`. + let ip: IpAddr = try_s!(try_s!(conf["myipaddr"].as_str().ok_or("myipaddr is not a string")).parse()); + let mut mm_ips = try_s!(MM_IPS.lock()); + if mm_ips.contains_key(&ip) { + log! ({"MarketMakerIt] Warning, IP {} was already used.", ip}) + } + mm_ips.insert(ip.clone(), true); ip }; @@ -180,19 +197,21 @@ impl MarketMakerIt { let dir = folder.join("DB"); conf["dbdir"] = unwrap!(dir.to_str()).into(); dir - } + }, }; - #[cfg(not(feature = "native"))] { - let ctx = MmCtxBuilder::new().with_conf (conf) .into_mm_arc(); - let local = try_s! (local.ok_or ("!local")); - local (ctx.clone()); - Ok (MarketMakerIt {ctx, ip, userpass}) + #[cfg(not(feature = "native"))] + { + let ctx = MmCtxBuilder::new().with_conf(conf).into_mm_arc(); + let local = try_s!(local.ok_or("!local")); + local(ctx.clone()); + Ok(MarketMakerIt { ctx, ip, userpass }) } - #[cfg(feature = "native")] { - try_s! (fs::create_dir (&folder)); - match fs::create_dir (db_dir) { + #[cfg(feature = "native")] + { + try_s!(fs::create_dir(&folder)); + match fs::create_dir(db_dir) { Ok(_) => (), Err(ref ie) if ie.kind() == std::io::ErrorKind::AlreadyExists => (), Err(e) => return ERR!("{}", e), @@ -203,7 +222,7 @@ impl MarketMakerIt { let path = folder.join("mm2.log"); conf["log"] = unwrap!(path.to_str()).into(); path - } + }, }; // If `local` is provided @@ -211,46 +230,64 @@ impl MarketMakerIt { // allowing us to easily *debug* the tested MarketMaker code. // Note that this should only be used while running a single test, // using this option while running multiple tests (or multiple MarketMaker instances) is currently UB. - let pc = if let Some (local) = local { - local (folder.clone(), log_path.clone(), conf); + let pc = if let Some(local) = local { + local(folder.clone(), log_path.clone(), conf); None } else { - let executable = try_s! (env::args().next().ok_or ("No program name")); - let executable = try_s! (Path::new (&executable) .canonicalize()); - let log = try_s! (fs::File::create (&log_path)); - let child = try_s! (Command::new (&executable) .arg ("test_mm_start") .arg ("--nocapture") - .current_dir (&folder) - .env ("_MM2_TEST_CONF", try_s! (json::to_string (&conf))) - .env ("MM2_UNBUFFERED_OUTPUT", "1") - .stdout (try_s! (log.try_clone())) - .stderr (log) + let executable = try_s!(env::args().next().ok_or("No program name")); + let executable = try_s!(Path::new(&executable).canonicalize()); + let log = try_s!(fs::File::create(&log_path)); + let child = try_s!(Command::new(&executable) + .arg("test_mm_start") + .arg("--nocapture") + .current_dir(&folder) + .env("_MM2_TEST_CONF", try_s!(json::to_string(&conf))) + .env("MM2_UNBUFFERED_OUTPUT", "1") + .stdout(try_s!(log.try_clone())) + .stderr(log) .spawn()); - Some (RaiiKill::from_handle (child)) + Some(RaiiKill::from_handle(child)) }; - Ok (MarketMakerIt {folder, ip, log_path, pc, userpass}) + Ok(MarketMakerIt { + folder, + ip, + log_path, + pc, + userpass, + }) } } #[cfg(feature = "native")] - pub fn log_as_utf8 (&self) -> Result { - let mm_log = try_s! (slurp (&self.log_path)); - let mm_log = unsafe {String::from_utf8_unchecked (mm_log)}; - Ok (mm_log) + pub fn log_as_utf8(&self) -> Result { + let mm_log = try_s!(slurp(&self.log_path)); + let mm_log = unsafe { String::from_utf8_unchecked(mm_log) }; + Ok(mm_log) } /// Busy-wait on the log until the `pred` returns `true` or `timeout_sec` expires. #[cfg(feature = "native")] - pub async fn wait_for_log (&mut self, timeout_sec: f64, pred: F) -> Result<(), String> - where F: Fn (&str) -> bool { + pub async fn wait_for_log(&mut self, timeout_sec: f64, pred: F) -> Result<(), String> + where + F: Fn(&str) -> bool, + { let start = now_float(); - let ms = 50 .min ((timeout_sec * 1000.) as u64 / 20 + 10); + let ms = 50.min((timeout_sec * 1000.) as u64 / 20 + 10); loop { - let mm_log = try_s! (self.log_as_utf8()); - if pred (&mm_log) {return Ok(())} - if now_float() - start > timeout_sec {return ERR! ("Timeout expired waiting for a log condition")} - if let Some (ref mut pc) = self.pc {if !pc.running() {return ERR! ("MM process terminated prematurely.")}} - Timer::sleep (ms as f64 / 1000.) .await + let mm_log = try_s!(self.log_as_utf8()); + if pred(&mm_log) { + return Ok(()); + } + if now_float() - start > timeout_sec { + return ERR!("Timeout expired waiting for a log condition"); + } + if let Some(ref mut pc) = self.pc { + if !pc.running() { + return ERR!("MM process terminated prematurely."); + } + } + Timer::sleep(ms as f64 / 1000.).await } } @@ -258,100 +295,122 @@ impl MarketMakerIt { /// The difference from standard wait_for_log is this function keeps working /// after process is stopped #[cfg(feature = "native")] - pub async fn wait_for_log_after_stop (&mut self, timeout_sec: f64, pred: F) -> Result<(), String> - where F: Fn (&str) -> bool { + pub async fn wait_for_log_after_stop(&mut self, timeout_sec: f64, pred: F) -> Result<(), String> + where + F: Fn(&str) -> bool, + { let start = now_float(); - let ms = 50 .min ((timeout_sec * 1000.) as u64 / 20 + 10); + let ms = 50.min((timeout_sec * 1000.) as u64 / 20 + 10); loop { - let mm_log = try_s! (self.log_as_utf8()); - if pred (&mm_log) {return Ok(())} - if now_float() - start > timeout_sec {return ERR! ("Timeout expired waiting for a log condition")} - Timer::sleep (ms as f64 / 1000.) .await + let mm_log = try_s!(self.log_as_utf8()); + if pred(&mm_log) { + return Ok(()); + } + if now_float() - start > timeout_sec { + return ERR!("Timeout expired waiting for a log condition"); + } + Timer::sleep(ms as f64 / 1000.).await } } /// Busy-wait on the instance in-memory log until the `pred` returns `true` or `timeout_sec` expires. #[cfg(not(feature = "native"))] - pub async fn wait_for_log (&mut self, timeout_sec: f64, pred: F) -> Result<(), String> - where F: Fn (&str) -> bool { + pub async fn wait_for_log(&mut self, timeout_sec: f64, pred: F) -> Result<(), String> + where + F: Fn(&str) -> bool, + { let start = now_float(); loop { - let tail = unsafe {std::str::from_utf8_unchecked (&crate::PROCESS_LOG_TAIL[..])}; - if pred (tail) {return Ok(())} - if now_float() - start > timeout_sec {return ERR! ("Timeout expired waiting for a log condition")} - Timer::sleep (0.1) .await - } } + let tail = unsafe { std::str::from_utf8_unchecked(&crate::PROCESS_LOG_TAIL[..]) }; + if pred(tail) { + return Ok(()); + } + if now_float() - start > timeout_sec { + return ERR!("Timeout expired waiting for a log condition"); + } + Timer::sleep(0.1).await + } + } /// Invokes the locally running MM and returns its reply. - pub async fn rpc (&self, payload: Json) -> Result<(StatusCode, String, HeaderMap), String> { - let uri = format! ("http://{}:7783", self.ip); + pub async fn rpc(&self, payload: Json) -> Result<(StatusCode, String, HeaderMap), String> { + let uri = format!("http://{}:7783", self.ip); log!("sending rpc request " (unwrap!(json::to_string(&payload))) " to " (uri)); - let payload = try_s! (json::to_vec (&payload)); - #[cfg(not(feature = "native"))] let payload = futures01::stream::once (Ok (Bytes::from (payload))); - let request = try_s! (Request::builder().method ("POST") .uri (uri) .body (payload)); - #[cfg(feature = "native")] { - let (status, headers, body) = try_s! (slurp_req (request) .wait()); - Ok ((status, try_s! (from_utf8 (&body)) .trim().into(), headers)) + let payload = try_s!(json::to_vec(&payload)); + #[cfg(not(feature = "native"))] + let payload = futures01::stream::once(Ok(Bytes::from(payload))); + let request = try_s!(Request::builder().method("POST").uri(uri).body(payload)); + #[cfg(feature = "native")] + { + let (status, headers, body) = try_s!(slurp_req(request).wait()); + Ok((status, try_s!(from_utf8(&body)).trim().into(), headers)) } - #[cfg(not(feature = "native"))] { - let rpc_service = try_s! (crate::header::RPC_SERVICE.as_option().ok_or ("!RPC_SERVICE")); + #[cfg(not(feature = "native"))] + { + let rpc_service = try_s!(crate::header::RPC_SERVICE.as_option().ok_or("!RPC_SERVICE")); let (parts, body) = request.into_parts(); - let client: SocketAddr = try_s! ("127.0.0.1:1".parse()); - let f = rpc_service (self.ctx.clone(), parts, Box::new (body), client); - let response = try_s! (f.await); + let client: SocketAddr = try_s!("127.0.0.1:1".parse()); + let f = rpc_service(self.ctx.clone(), parts, Box::new(body), client); + let response = try_s!(f.await); let (parts, body) = response.into_parts(); - Ok ((parts.status, try_s! (String::from_utf8 (body)), parts.headers)) + Ok((parts.status, try_s!(String::from_utf8(body)), parts.headers)) } } /// Sends the &str payload to the locally running MM and returns it's reply. #[cfg(feature = "native")] - pub fn rpc_str (&self, payload: &'static str) -> Result<(StatusCode, String, HeaderMap), String> { - let uri = format! ("http://{}:7783", self.ip); - let request = try_s! (Request::builder().method ("POST") .uri (uri) .body (payload.into())); - let (status, headers, body) = try_s! (slurp_req (request) .wait()); - Ok ((status, try_s! (from_utf8 (&body)) .trim().into(), headers)) + pub fn rpc_str(&self, payload: &'static str) -> Result<(StatusCode, String, HeaderMap), String> { + let uri = format!("http://{}:7783", self.ip); + let request = try_s!(Request::builder().method("POST").uri(uri).body(payload.into())); + let (status, headers, body) = try_s!(slurp_req(request).wait()); + Ok((status, try_s!(from_utf8(&body)).trim().into(), headers)) } #[cfg(not(feature = "native"))] - pub fn rpc_str (&self, _payload: &'static str) -> Result<(StatusCode, String, HeaderMap), String> { + pub fn rpc_str(&self, _payload: &'static str) -> Result<(StatusCode, String, HeaderMap), String> { unimplemented!() } #[cfg(feature = "native")] - pub fn mm_dump (&self) -> (RaiiDump, RaiiDump) {mm_dump (&self.log_path)} + pub fn mm_dump(&self) -> (RaiiDump, RaiiDump) { mm_dump(&self.log_path) } #[cfg(not(feature = "native"))] - pub fn mm_dump (&self) -> (RaiiDump, RaiiDump) {(RaiiDump{}, RaiiDump{})} + pub fn mm_dump(&self) -> (RaiiDump, RaiiDump) { (RaiiDump {}, RaiiDump {}) } /// Send the "stop" request to the locally running MM. - pub async fn stop (&self) -> Result<(), String> { - let (status, body, _headers) = match self.rpc (json! ({"userpass": self.userpass, "method": "stop"})) .await { - Ok (t) => t, - Err (err) => { + pub async fn stop(&self) -> Result<(), String> { + let (status, body, _headers) = match self.rpc(json! ({"userpass": self.userpass, "method": "stop"})).await { + Ok(t) => t, + Err(err) => { // Downgrade the known errors into log warnings, // in order not to spam the unit test logs with confusing panics, obscuring the real issue. - if err.contains ("An existing connection was forcibly closed by the remote host") { - log! ("stop] MM already down? " (err)); - return Ok(()) + if err.contains("An existing connection was forcibly closed by the remote host") { + log!("stop] MM already down? "(err)); + return Ok(()); } else { - return ERR! ("{}", err) - } } }; - if status != StatusCode::OK {return ERR! ("MM didn't accept a stop. body: {}", body)} + return ERR!("{}", err); + } + }, + }; + if status != StatusCode::OK { + return ERR!("MM didn't accept a stop. body: {}", body); + } Ok(()) } } #[cfg(feature = "native")] impl Drop for MarketMakerIt { - fn drop (&mut self) { - if let Ok (mut mm_ips) = MM_IPS.lock() { + fn drop(&mut self) { + if let Ok(mut mm_ips) = MM_IPS.lock() { // The IP addresses might still be used by the libtorrent even after a context is dropped, // hence we're not trying to reuse them but rather just mark them as fried. - if let Some (active) = mm_ips.get_mut (&self.ip) { + if let Some(active) = mm_ips.get_mut(&self.ip) { *active = false } - } else {log! ("MarketMakerIt] Can't lock MM_IPS.")} + } else { + log!("MarketMakerIt] Can't lock MM_IPS.") + } } } @@ -367,81 +426,110 @@ macro_rules! wait_log_re { /// Busy-wait on the log until the `pred` returns `true` or `timeout_sec` expires. #[cfg(feature = "native")] -pub fn wait_for_log (log: &LogState, timeout_sec: f64, pred: &dyn Fn (&str) -> bool) -> Result<(), String> { +pub fn wait_for_log(log: &LogState, timeout_sec: f64, pred: &dyn Fn(&str) -> bool) -> Result<(), String> { let start = now_float(); - let ms = 50 .min ((timeout_sec * 1000.) as u64 / 20 + 10); - let mut buf = String::with_capacity (128); + let ms = 50.min((timeout_sec * 1000.) as u64 / 20 + 10); + let mut buf = String::with_capacity(128); let mut found = false; loop { - log.with_tail (&mut |tail| { + log.with_tail(&mut |tail| { for en in tail { - if en.format (&mut buf) .is_ok() { - if pred (&buf) {found = true; break} + if en.format(&mut buf).is_ok() && pred(&buf) { + found = true; + break; } } }); - if found {return Ok(())} + if found { + return Ok(()); + } - log.with_gravity_tail (&mut |tail| { + log.with_gravity_tail(&mut |tail| { for chunk in tail { - if pred (chunk) {found = true; break} + if pred(chunk) { + found = true; + break; + } } }); - if found {return Ok(())} + if found { + return Ok(()); + } - if now_float() - start > timeout_sec {return ERR! ("Timeout expired waiting for a log condition")} - sleep (Duration::from_millis (ms)); + if now_float() - start > timeout_sec { + return ERR!("Timeout expired waiting for a log condition"); + } + sleep(Duration::from_millis(ms)); } } #[derive(Serialize, Deserialize, Debug)] -struct ToWaitForLogRe {ctx: u32, timeout_sec: f64, re_pred: String} +struct ToWaitForLogRe { + ctx: u32, + timeout_sec: f64, + re_pred: String, +} #[cfg(feature = "native")] -pub async fn common_wait_for_log_re (req: Bytes) -> Result, String> { - let args: ToWaitForLogRe = try_s! (json::from_slice (&req)); - let ctx = try_s! (crate::mm_ctx::MmArc::from_ffi_handle (args.ctx)); - let re = try_s! (Regex::new (&args.re_pred)); +pub async fn common_wait_for_log_re(req: Bytes) -> Result, String> { + let args: ToWaitForLogRe = try_s!(json::from_slice(&req)); + let ctx = try_s!(crate::mm_ctx::MmArc::from_ffi_handle(args.ctx)); + let re = try_s!(Regex::new(&args.re_pred)); // Run the blocking `wait_for_log` in the `POOL`. let (tx, rx) = channel(); - try_s! (try_s! (POOL.lock()) .spawn (async move { - let _ = tx.send (wait_for_log (&ctx.log, args.timeout_sec, &|line| re.is_match (line))); + try_s!(try_s!(POOL.lock()).spawn(async move { + let _ = tx.send(wait_for_log(&ctx.log, args.timeout_sec, &|line| re.is_match(line))); })); - try_s! (try_s! (rx.await)); + try_s!(try_s!(rx.await)); - Ok (Vec::new()) + Ok(Vec::new()) } #[cfg(feature = "native")] -pub async fn wait_for_log_re (ctx: &crate::mm_ctx::MmArc, timeout_sec: f64, re_pred: &str) -> Result<(), String> { - let re = try_s! (Regex::new (re_pred)); - wait_for_log (&ctx.log, timeout_sec, &|line| re.is_match (line)) +pub async fn wait_for_log_re(ctx: &crate::mm_ctx::MmArc, timeout_sec: f64, re_pred: &str) -> Result<(), String> { + let re = try_s!(Regex::new(re_pred)); + wait_for_log(&ctx.log, timeout_sec, &|line| re.is_match(line)) } #[cfg(not(feature = "native"))] -pub async fn wait_for_log_re (ctx: &crate::mm_ctx::MmArc, timeout_sec: f64, re_pred: &str) -> Result<(), String> { - try_s! (helperᶜ ("common_wait_for_log_re", try_s! (json::to_vec (&ToWaitForLogRe { - ctx: try_s! (ctx.ffi_handle()), - timeout_sec: timeout_sec, - re_pred: re_pred.into() - }))) .await); +pub async fn wait_for_log_re(ctx: &crate::mm_ctx::MmArc, timeout_sec: f64, re_pred: &str) -> Result<(), String> { + try_s!( + helperᶜ( + "common_wait_for_log_re", + try_s!(json::to_vec(&ToWaitForLogRe { + ctx: try_s!(ctx.ffi_handle()), + timeout_sec, + re_pred: re_pred.into() + })) + ) + .await + ); Ok(()) } /// Create RAII variables to the effect of dumping the log and the status dashboard at the end of the scope. #[cfg(feature = "native")] -pub fn mm_dump (log_path: &Path) -> (RaiiDump, RaiiDump) {( - RaiiDump {log_path: log_path.to_path_buf()}, - RaiiDump {log_path: unwrap! (dashboard_path (log_path))} -)} +pub fn mm_dump(log_path: &Path) -> (RaiiDump, RaiiDump) { + ( + RaiiDump { + log_path: log_path.to_path_buf(), + }, + RaiiDump { + log_path: unwrap!(dashboard_path(log_path)), + }, + ) +} /// A typical MM instance. #[cfg(feature = "native")] -pub fn mm_spat (local_start: LocalStart, conf_mod: &dyn Fn(Json)->Json) -> (&'static str, MarketMakerIt, RaiiDump, RaiiDump) { +pub fn mm_spat( + local_start: LocalStart, + conf_mod: &dyn Fn(Json) -> Json, +) -> (&'static str, MarketMakerIt, RaiiDump, RaiiDump) { let passphrase = "SPATsRps3dhEtXwtnpRCKF"; - let mm = unwrap! (MarketMakerIt::start ( - conf_mod (json! ({ + let mm = unwrap!(MarketMakerIt::start( + conf_mod(json! ({ "gui": "nogui", "passphrase": passphrase, "rpccors": "http://localhost:4000", @@ -453,77 +541,96 @@ pub fn mm_spat (local_start: LocalStart, conf_mod: &dyn Fn(Json)->Json) -> (&'st "rpc_password": "pass", })), "pass".into(), - match super::var ("LOCAL_THREAD_MM") {Ok (ref e) if e == "1" => Some (local_start), _ => None} + match super::var("LOCAL_THREAD_MM") { + Ok(ref e) if e == "1" => Some(local_start), + _ => None, + } )); - let (dump_log, dump_dashboard) = mm_dump (&mm.log_path); + let (dump_log, dump_dashboard) = mm_dump(&mm.log_path); (passphrase, mm, dump_log, dump_dashboard) } #[cfg(not(feature = "native"))] -pub fn mm_spat (_local_start: LocalStart, _conf_mod: &dyn Fn(Json)->Json) -> (&'static str, MarketMakerIt, RaiiDump, RaiiDump) { +pub fn mm_spat( + _local_start: LocalStart, + _conf_mod: &dyn Fn(Json) -> Json, +) -> (&'static str, MarketMakerIt, RaiiDump, RaiiDump) { unimplemented!() } /// Asks MM to enable the given currency in electrum mode /// fresh list of servers at https://github.com/jl777/coins/blob/master/electrums/. -pub async fn enable_electrum (mm: &MarketMakerIt, coin: &str, urls: Vec<&str>) -> Json { - let servers: Vec<_> = urls.into_iter().map(|url| json!({"url": url})).collect(); - let electrum = unwrap! (mm.rpc (json! ({ - "userpass": mm.userpass, - "method": "electrum", - "coin": coin, - "servers": servers, - "mm2": 1, - })) .await); - assert_eq! (electrum.0, StatusCode::OK, "RPC «electrum» failed with {} {}", electrum.0, electrum.1); +pub async fn enable_electrum(mm: &MarketMakerIt, coin: &str, urls: Vec<&str>) -> Json { + let servers: Vec<_> = urls.into_iter().map(|url| json!({ "url": url })).collect(); + let electrum = unwrap!( + mm.rpc(json! ({ + "userpass": mm.userpass, + "method": "electrum", + "coin": coin, + "servers": servers, + "mm2": 1, + })) + .await + ); + assert_eq!( + electrum.0, + StatusCode::OK, + "RPC «electrum» failed with {} {}", + electrum.0, + electrum.1 + ); unwrap!(json::from_str(&electrum.1)) } /// Reads passphrase and userpass from .env file -pub fn from_env_file (env: Vec) -> (Option, Option) { +pub fn from_env_file(env: Vec) -> (Option, Option) { use regex::bytes::Regex; let (mut passphrase, mut userpass) = (None, None); - for cap in unwrap! (Regex::new (r"(?m)^(PASSPHRASE|USERPASS)=(\w[\w ]+)$")) .captures_iter (&env) { - match cap.get (1) { - Some (name) if name.as_bytes() == b"PASSPHRASE" => - passphrase = cap.get (2) .map (|v| unwrap! (String::from_utf8 (v.as_bytes().into()))), - Some (name) if name.as_bytes() == b"USERPASS" => - userpass = cap.get (2) .map (|v| unwrap! (String::from_utf8 (v.as_bytes().into()))), - _ => () + for cap in unwrap!(Regex::new(r"(?m)^(PASSPHRASE|USERPASS)=(\w[\w ]+)$")).captures_iter(&env) { + match cap.get(1) { + Some(name) if name.as_bytes() == b"PASSPHRASE" => { + passphrase = cap.get(2).map(|v| unwrap!(String::from_utf8(v.as_bytes().into()))) + }, + Some(name) if name.as_bytes() == b"USERPASS" => { + userpass = cap.get(2).map(|v| unwrap!(String::from_utf8(v.as_bytes().into()))) + }, + _ => (), } } (passphrase, userpass) } -#[cfg(not(feature = "native"))] -use std::os::raw::c_char; +#[cfg(not(feature = "native"))] use std::os::raw::c_char; /// Reads passphrase from file or environment. -pub fn get_passphrase (path: &dyn AsRef, env: &str) -> Result { - if let (Some (file_passphrase), _file_userpass) = from_env_file (try_s! (slurp (path))) { - return Ok (file_passphrase) +pub fn get_passphrase(path: &dyn AsRef, env: &str) -> Result { + if let (Some(file_passphrase), _file_userpass) = from_env_file(try_s!(slurp(path))) { + return Ok(file_passphrase); } - if let Ok (v) = super::var (env) { - Ok (v) + if let Ok(v) = super::var(env) { + Ok(v) } else { - ERR! ("No {} or {}", env, path.as_ref().display()) + ERR!("No {} or {}", env, path.as_ref().display()) } } /// Asks MM to enable the given currency in native mode. /// Returns the RPC reply containing the corresponding wallet address. pub async fn enable_native(mm: &MarketMakerIt, coin: &str, urls: Vec<&str>) -> Json { - let native = unwrap! (mm.rpc (json! ({ - "userpass": mm.userpass, - "method": "enable", - "coin": coin, - "urls": urls, - // Dev chain swap contract address - "swap_contract_address": "0xa09ad3cd7e96586ebd05a2607ee56b56fb2db8fd", - "mm2": 1, - })) .await); - assert_eq! (native.0, StatusCode::OK, "'enable' failed: {}", native.1); + let native = unwrap!( + mm.rpc(json! ({ + "userpass": mm.userpass, + "method": "enable", + "coin": coin, + "urls": urls, + // Dev chain swap contract address + "swap_contract_address": "0xa09ad3cd7e96586ebd05a2607ee56b56fb2db8fd", + "mm2": 1, + })) + .await + ); + assert_eq!(native.0, StatusCode::OK, "'enable' failed: {}", native.1); unwrap!(json::from_str(&native.1)) } @@ -533,10 +640,10 @@ pub async fn enable_native(mm: &MarketMakerIt, coin: &str, urls: Vec<&str>) -> J /// Appends IpAddr if it is pre-known pub fn new_mm2_temp_folder_path(ip: Option) -> PathBuf { let now = super::now_ms(); - let now = Local.timestamp ((now / 1000) as i64, (now % 1000) as u32 * 1000000); + let now = Local.timestamp((now / 1000) as i64, (now % 1000) as u32 * 1_000_000); let folder = match ip { - Some(ip) => format! ("mm2_{}_{}", now.format ("%Y-%m-%d_%H-%M-%S-%3f"), ip), - None => format! ("mm2_{}", now.format ("%Y-%m-%d_%H-%M-%S-%3f")), + Some(ip) => format!("mm2_{}_{}", now.format("%Y-%m-%d_%H-%M-%S-%3f"), ip), + None => format!("mm2_{}", now.format("%Y-%m-%d_%H-%M-%S-%3f")), }; - super::temp_dir().join (folder) + super::temp_dir().join(folder) } diff --git a/mm2src/common/header.rs b/mm2src/common/header.rs index 903086c437..1441259ad3 100644 --- a/mm2src/common/header.rs +++ b/mm2src/common/header.rs @@ -1,34 +1,29 @@ //! Indirect routing between crates. -//! +//! //! Sometimes we need to call downstream, from a dependency and into a dependent crate, -//! such as when calling `mm2::rpc::rpc_serviceʹ` from `common::MarketMakerIt::rpc`. +//! such as when calling `mm2::rpc::rpc_serviceʹ` from `common::MarketMakerIt::rpc`. //! Here we can use C-like headers and/or constructible slots for that. -#[cfg(not(feature = "native"))] -use bytes::Bytes; -#[cfg(not(feature = "native"))] -use crate::mm_ctx::MmArc; -#[cfg(not(feature = "native"))] -use futures01::Stream; -#[cfg(not(feature = "native"))] -use gstuff::Constructible; -#[cfg(not(feature = "native"))] -use http::Response; -#[cfg(not(feature = "native"))] -use http::request::Parts; -#[cfg(not(feature = "native"))] -use std::future::Future; +#[cfg(not(feature = "native"))] use crate::mm_ctx::MmArc; +#[cfg(not(feature = "native"))] use bytes::Bytes; +#[cfg(not(feature = "native"))] use futures01::Stream; +#[cfg(not(feature = "native"))] use gstuff::Constructible; +#[cfg(not(feature = "native"))] use http::request::Parts; +#[cfg(not(feature = "native"))] use http::Response; +#[cfg(not(feature = "native"))] use std::future::Future; -#[cfg(not(feature = "native"))] -use std::net::SocketAddr; +#[cfg(not(feature = "native"))] use std::net::SocketAddr; -#[cfg(not(feature = "native"))] -use std::pin::Pin; +#[cfg(not(feature = "native"))] use std::pin::Pin; /// Access to `rpc::rpc_serviceʹ` defined downstream. /// Initialized in `rpc::init_header_slots`. #[cfg(not(feature = "native"))] pub static RPC_SERVICE: Constructible< - fn (ctx: MmArc, req: Parts, reqᵇ: Box + Send>, client: SocketAddr) - -> Pin>, String>> + Send>> + fn( + ctx: MmArc, + req: Parts, + reqᵇ: Box + Send>, + client: SocketAddr, + ) -> Pin>, String>> + Send>>, > = Constructible::new(); diff --git a/mm2src/common/iguana_utils.rs b/mm2src/common/iguana_utils.rs index 47b83d4f98..f42da258d7 100755 --- a/mm2src/common/iguana_utils.rs +++ b/mm2src/common/iguana_utils.rs @@ -22,23 +22,23 @@ use hex::FromHex; int32_t smallprimes[168] = { - 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, - 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, - 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, - 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, - 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, - 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, - 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, - 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, - 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, - 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, - 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, - 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, - 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, - 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, - 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, - 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, - 947, 953, 967, 971, 977, 983, 991, 997 + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, + 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, + 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, + 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, + 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, + 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, + 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, + 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, + 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, + 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, + 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, + 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, + 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, + 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, + 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, + 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, + 947, 953, 967, 971, 977, 983, 991, 997 }; bits256 bits256_doublesha256(char *hashstr,uint8_t *data,int32_t datalen) @@ -138,8 +138,7 @@ bits256 bits256_ave(bits256 a,bits256 b) bits256 bits256_from_compact(uint32_t c) { - - uint32_t nbytes,nbits,i; bits256 x; + uint32_t nbytes,nbits,i; bits256 x; memset(x.bytes,0,sizeof(x)); nbytes = (c >> 24) & 0xFF; if ( nbytes >= 3 ) @@ -201,44 +200,44 @@ void calc_OP_HASH160(char hexstr[41],uint8_t rmd160[20],char *pubkey) double _xblend(float *destp,double val,double decay) { double oldval; - if ( (oldval = *destp) != 0. ) - return((oldval * decay) + ((1. - decay) * val)); - else return(val); + if ( (oldval = *destp) != 0. ) + return((oldval * decay) + ((1. - decay) * val)); + else return(val); } double _dxblend(double *destp,double val,double decay) { double oldval; - if ( (oldval = *destp) != 0. ) - return((oldval * decay) + ((1. - decay) * val)); - else return(val); + if ( (oldval = *destp) != 0. ) + return((oldval * decay) + ((1. - decay) * val)); + else return(val); } double dxblend(double *destp,double val,double decay) { - double newval,slope; - if ( isnan(*destp) != 0 ) - *destp = 0.; - if ( isnan(val) != 0 ) - return(0.); - if ( *destp == 0 ) - { - *destp = val; - return(0); - } - newval = _dxblend(destp,val,decay); - if ( newval < SMALLVAL && newval > -SMALLVAL ) - { - // non-zero marker for actual values close to or even equal to zero - if ( newval < 0. ) - newval = -SMALLVAL; - else newval = SMALLVAL; - } - if ( *destp != 0. && newval != 0. ) - slope = (newval - *destp); - else slope = 0.; - *destp = newval; - return(slope); + double newval,slope; + if ( isnan(*destp) != 0 ) + *destp = 0.; + if ( isnan(val) != 0 ) + return(0.); + if ( *destp == 0 ) + { + *destp = val; + return(0); + } + newval = _dxblend(destp,val,decay); + if ( newval < SMALLVAL && newval > -SMALLVAL ) + { + // non-zero marker for actual values close to or even equal to zero + if ( newval < 0. ) + newval = -SMALLVAL; + else newval = SMALLVAL; + } + if ( *destp != 0. && newval != 0. ) + slope = (newval - *destp); + else slope = 0.; + *destp = newval; + return(slope); } int32_t TerminateQ_queued; queue_t TerminateQ; @@ -371,19 +370,21 @@ unsigned char _decode_hex(char *hex) { return((unhex(hex[0])<<4) | unhex(hex[1]) */ /// Unpacks a `hex` string into the corresponding array of `bytes`. -pub fn decode_hex (bytes: &mut [u8], hex: &[u8]) -> Result<(), String> { +pub fn decode_hex(bytes: &mut [u8], hex: &[u8]) -> Result<(), String> { if bytes.len() == 32 { // This length is often used (cf. bits256) and we decode it without a temporary vector. // Consider also using `from_hex` directly. - let buf: [u8; 32] = try_s! (FromHex::from_hex (hex)); - bytes.copy_from_slice (&buf); + let buf: [u8; 32] = try_s!(FromHex::from_hex(hex)); + bytes.copy_from_slice(&buf); } else { // Refactoring vector: Hex decoding shouldn't be more complex than a simple loop, // there's no need for temporary buffers, // should take some time eventually to reimplement and test this as such. - let vec: Vec = try_s! (FromHex::from_hex (hex)); - if vec.len() != bytes.len() {return ERR! ("Unpacked hex length {} is not {}", vec.len(), bytes.len())} - bytes.copy_from_slice (&vec); + let vec: Vec = try_s!(FromHex::from_hex(hex)); + if vec.len() != bytes.len() { + return ERR!("Unpacked hex length {} is not {}", vec.len(), bytes.len()); + } + bytes.copy_from_slice(&vec); } Ok(()) } @@ -541,11 +542,11 @@ int _increasing_uint64(const void *a,const void *b) { #define uint64_a (*(uint64_t *)a) #define uint64_b (*(uint64_t *)b) - if ( uint64_b > uint64_a ) - return(-1); - else if ( uint64_b < uint64_a ) - return(1); - return(0); + if ( uint64_b > uint64_a ) + return(-1); + else if ( uint64_b < uint64_a ) + return(1); + return(0); #undef uint64_a #undef uint64_b } @@ -554,11 +555,11 @@ int _decreasing_uint64(const void *a,const void *b) { #define uint64_a (*(uint64_t *)a) #define uint64_b (*(uint64_t *)b) - if ( uint64_b > uint64_a ) - return(1); - else if ( uint64_b < uint64_a ) - return(-1); - return(0); + if ( uint64_b > uint64_a ) + return(1); + else if ( uint64_b < uint64_a ) + return(-1); + return(0); #undef uint64_a #undef uint64_b } @@ -567,11 +568,11 @@ int _decreasing_uint32(const void *a,const void *b) { #define uint32_a (*(uint32_t *)a) #define uint32_b (*(uint32_t *)b) - if ( uint32_b > uint32_a ) - return(1); - else if ( uint32_b < uint32_a ) - return(-1); - return(0); + if ( uint32_b > uint32_a ) + return(1); + else if ( uint32_b < uint32_a ) + return(-1); + return(0); #undef uint32_a #undef uint32_b } @@ -590,20 +591,20 @@ int32_t revsortds(double *buf,uint32_t num,int32_t size) int32_t sort64s(uint64_t *buf,uint32_t num,int32_t size) { - qsort(buf,num,size,_increasing_uint64); - return(0); + qsort(buf,num,size,_increasing_uint64); + return(0); } int32_t revsort64s(uint64_t *buf,uint32_t num,int32_t size) { - qsort(buf,num,size,_decreasing_uint64); - return(0); + qsort(buf,num,size,_decreasing_uint64); + return(0); } int32_t revsort32(uint32_t *buf,uint32_t num,int32_t size) { - qsort(buf,num,size,_decreasing_uint32); - return(0); + qsort(buf,num,size,_decreasing_uint32); + return(0); } /*int32_t iguana_sortbignum(void *buf,int32_t size,uint32_t num,int32_t structsize,int32_t dir) @@ -627,7 +628,7 @@ int32_t revsort32(uint32_t *buf,uint32_t num,int32_t size) } if ( retval < 0 ) printf("iguana_sortbignum only does bits256 and rmd160 for now\n"); - return(retval); + return(retval); }*/ void touppercase(char *str) diff --git a/mm2src/common/jsonrpc_client.rs b/mm2src/common/jsonrpc_client.rs index 4c83b93040..03a714242f 100644 --- a/mm2src/common/jsonrpc_client.rs +++ b/mm2src/common/jsonrpc_client.rs @@ -49,21 +49,15 @@ macro_rules! rpc_func_from { pub struct JsonRpcRemoteAddr(pub String); impl fmt::Debug for JsonRpcRemoteAddr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl From for String { - fn from(addr: JsonRpcRemoteAddr) -> Self { - addr.0 - } + fn from(addr: JsonRpcRemoteAddr) -> Self { addr.0 } } impl From for JsonRpcRemoteAddr { - fn from(addr: String) -> Self { - JsonRpcRemoteAddr(addr) - } + fn from(addr: String) -> Self { JsonRpcRemoteAddr(addr) } } /// Serializable RPC request @@ -77,9 +71,7 @@ pub struct JsonRpcRequest { } impl JsonRpcRequest { - pub fn get_id(&self) -> &str { - &self.id - } + pub fn get_id(&self) -> &str { &self.id } } #[derive(Deserialize, Debug, Clone)] @@ -95,7 +87,7 @@ pub struct JsonRpcResponse { } #[derive(Debug)] -pub struct JsonRpcError { +pub struct JsonRpcError { /// Additional member contains an instance info that implements the JsonRpcClient trait. /// The info is used in particular to supplement the error info. client_info: String, @@ -112,17 +104,16 @@ pub enum JsonRpcErrorType { /// Response parse error Parse(JsonRpcRemoteAddr, String), /// The JSON-RPC error returned from server - Response(JsonRpcRemoteAddr, Json) + Response(JsonRpcRemoteAddr, Json), } impl fmt::Display for JsonRpcError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self) - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } } -pub type JsonRpcResponseFut = Box + Send + 'static>; -pub type RpcRes = Box + Send + 'static>; +pub type JsonRpcResponseFut = + Box + Send + 'static>; +pub type RpcRes = Box + Send + 'static>; pub trait JsonRpcClient { fn version(&self) -> &'static str; @@ -136,8 +127,10 @@ pub trait JsonRpcClient { fn send_request(&self, request: JsonRpcRequest) -> RpcRes { let client_info = self.client_info(); - Box::new(self.transport(request.clone()) - .then(move |result| process_transport_result(result, client_info, request))) + Box::new( + self.transport(request.clone()) + .then(move |result| process_transport_result(result, client_info, request)), + ) } } @@ -145,10 +138,16 @@ pub trait JsonRpcClient { pub trait JsonRpcMultiClient: JsonRpcClient { fn transport_exact(&self, to_addr: String, request: JsonRpcRequest) -> JsonRpcResponseFut; - fn send_request_to(&self, to_addr: &str, request: JsonRpcRequest) -> RpcRes { + fn send_request_to( + &self, + to_addr: &str, + request: JsonRpcRequest, + ) -> RpcRes { let client_info = self.client_info(); - Box::new(self.transport_exact(to_addr.to_owned(), request.clone()) - .then(move |result| process_transport_result(result, client_info, request))) + Box::new( + self.transport_exact(to_addr.to_owned(), request.clone()) + .then(move |result| process_transport_result(result, client_info, request)), + ) } } @@ -159,11 +158,13 @@ fn process_transport_result( ) -> Result { let (remote_addr, response) = match result { Ok(r) => r, - Err(e) => return Err(JsonRpcError { - client_info, - request, - error: JsonRpcErrorType::Transport(e), - }) + Err(e) => { + return Err(JsonRpcError { + client_info, + request, + error: JsonRpcErrorType::Transport(e), + }) + }, }; if !response.error.is_null() { @@ -174,10 +175,12 @@ fn process_transport_result( }); } - json::from_value(response.result.clone()) - .map_err(|e| JsonRpcError { - client_info, - request, - error: JsonRpcErrorType::Parse(remote_addr, ERRL!("error {:?} parsing result from response {:?}", e, response)), - }) + json::from_value(response.result.clone()).map_err(|e| JsonRpcError { + client_info, + request, + error: JsonRpcErrorType::Parse( + remote_addr, + ERRL!("error {:?} parsing result from response {:?}", e, response), + ), + }) } diff --git a/mm2src/common/lift_body.rs b/mm2src/common/lift_body.rs index 606d58d22f..d7e4d3ca73 100644 --- a/mm2src/common/lift_body.rs +++ b/mm2src/common/lift_body.rs @@ -12,34 +12,30 @@ pub type Error = Box; /// Lifts a body to support `Payload` #[derive(Debug)] -pub struct LiftBody {inner: T} +pub struct LiftBody { + inner: T, +} impl LiftBody { - pub fn into_inner (self) -> T {self.inner} + pub fn into_inner(self) -> T { self.inner } } impl From for LiftBody { - fn from (inner: T) -> Self {LiftBody {inner}} + fn from(inner: T) -> Self { LiftBody { inner } } } impl Payload for LiftBody where T: HttpBody + Send + 'static, T::Data: Send, - T::Error: Into + T::Error: Into, { type Data = T::Data; type Error = T::Error; - fn poll_data (&mut self) -> Poll, Self::Error> { - self.inner.poll_data() - } + fn poll_data(&mut self) -> Poll, Self::Error> { self.inner.poll_data() } - fn poll_trailers (&mut self) -> Poll, Self::Error> { - self.inner.poll_trailers() - } + fn poll_trailers(&mut self) -> Poll, Self::Error> { self.inner.poll_trailers() } - fn is_end_stream (&self) -> bool { - self.inner.is_end_stream() - } + fn is_end_stream(&self) -> bool { self.inner.is_end_stream() } } diff --git a/mm2src/common/log.rs b/mm2src/common/log.rs index 9260c6bb3c..b3c971a528 100644 --- a/mm2src/common/log.rs +++ b/mm2src/common/log.rs @@ -1,15 +1,18 @@ //! Human-readable logging and statuses. +use super::duplex_mutex::DuplexMutex; +use super::executor::{spawn, Timer}; +use super::{now_ms, writeln}; use atomic::Atomic; -use chrono::{Local, TimeZone, Utc}; -use chrono::format::DelayedFormat; use chrono::format::strftime::StrftimeItems; +use chrono::format::DelayedFormat; +use chrono::{Local, TimeZone, Utc}; use crossbeam::queue::SegQueue; use parking_lot::Mutex; -use serde_json::{Value as Json}; +use serde_json::Value as Json; use std::cell::RefCell; -use std::collections::VecDeque; use std::collections::hash_map::DefaultHasher; +use std::collections::VecDeque; use std::default::Default; use std::fmt; use std::fmt::Write as WriteFmt; @@ -18,18 +21,15 @@ use std::mem::swap; use std::ops::Deref; use std::os::raw::c_char; use std::path::{Path, PathBuf}; -use std::sync::{Arc, Weak}; use std::sync::atomic::Ordering; +use std::sync::{Arc, Weak}; use std::thread; -use super::{now_ms, writeln}; -use super::executor::{spawn, Timer}; -use super::duplex_mutex::DuplexMutex; #[cfg(feature = "native")] lazy_static! { static ref PRINTF_LOCK: Mutex<()> = Mutex::new(()); /// If this C callback is present then all the logging output should happen through it - /// (and leaving stdout untouched). + /// (and leaving stdout untouched). /// The *gravity* logging still gets a copy in order for the log-based tests to work. pub static ref LOG_OUTPUT: Mutex> = Mutex::new (None); } @@ -41,39 +41,42 @@ struct Gravity { /// Log chunks received from satellite threads. landing: SegQueue, /// Keeps a portiong of a recently flushed gravity log in RAM for inspection by the unit tests. - tail: DuplexMutex> + tail: DuplexMutex>, } impl Gravity { /// Files a log chunk to be logged from the center of gravity thread. #[cfg(feature = "native")] - fn chunk2log (&self, chunk: String) { - self.landing.push (chunk); + fn chunk2log(&self, chunk: String) { + self.landing.push(chunk); if thread::current().id() == self.target_thread_id { self.flush() } } #[cfg(not(feature = "native"))] - fn chunk2log (&self, chunk: String) { - writeln (&chunk); - self.landing.push (chunk); + fn chunk2log(&self, chunk: String) { + writeln(&chunk); + self.landing.push(chunk); } /// Prints the collected log chunks. /// `println!` is used for compatibility with unit test stdout capturing. #[cfg(feature = "native")] - fn flush (&self) { - let mut tail = unwrap! (self.tail.spinlock (77)); - while let Ok (chunk) = self.landing.pop() { + fn flush(&self) { + let mut tail = unwrap!(self.tail.spinlock(77)); + while let Ok(chunk) = self.landing.pop() { let logged_with_log_output = LOG_OUTPUT.lock().is_some(); if !logged_with_log_output { - writeln (&chunk) + writeln(&chunk) } - if tail.len() == tail.capacity() {let _ = tail.pop_front();} - tail.push_back (chunk) - } } + if tail.len() == tail.capacity() { + let _ = tail.pop_front(); + } + tail.push_back(chunk) + } + } #[cfg(not(feature = "native"))] - fn flush (&self) {} + fn flush(&self) {} } thread_local! { @@ -83,55 +86,63 @@ thread_local! { #[cfg(feature = "native")] #[doc(hidden)] -pub fn chunk2log (mut chunk: String) { - let used_log_output = if let Some (log_cb) = *LOG_OUTPUT.lock() { - chunk.push ('\0'); - log_cb (chunk.as_ptr() as *const c_char); +pub fn chunk2log(mut chunk: String) { + let used_log_output = if let Some(log_cb) = *LOG_OUTPUT.lock() { + chunk.push('\0'); + log_cb(chunk.as_ptr() as *const c_char); true - } else {false}; + } else { + false + }; // NB: Using gravity even in the non-capturing tests in order to give the tests access to the gravity tail. - let rc = GRAVITY.try_with (|gravity| { - if let Some (ref gravity) = *gravity.borrow() { - if let Some (gravity) = gravity.upgrade() { + let rc = GRAVITY.try_with(|gravity| { + if let Some(ref gravity) = *gravity.borrow() { + if let Some(gravity) = gravity.upgrade() { let mut chunkʹ = String::new(); - swap (&mut chunk, &mut chunkʹ); - gravity.chunk2log (chunkʹ); + swap(&mut chunk, &mut chunkʹ); + gravity.chunk2log(chunkʹ); true - } else {false} - } else {false} + } else { + false + } + } else { + false + } }); - match rc {Ok (true) => return, _ => ()} + if let Ok(true) = rc { + return; + } - if used_log_output {return} + if used_log_output { + return; + } - writeln (&chunk) + writeln(&chunk) } #[cfg(not(feature = "native"))] #[doc(hidden)] -pub fn chunk2log (chunk: String) { - writeln (&chunk) -} +pub fn chunk2log(chunk: String) { writeln(&chunk) } #[doc(hidden)] -pub fn short_log_time (ms: u64) -> DelayedFormat> { +pub fn short_log_time(ms: u64) -> DelayedFormat> { // NB: Given that the debugging logs are targeted at the developers and not the users // I think it's better to output the time in GMT here // in order for the developers to more easily match the events between the various parts of the peer-to-peer system. - let time = Utc.timestamp_millis (ms as i64); - time.format ("%d %H:%M:%S") + let time = Utc.timestamp_millis(ms as i64); + time.format("%d %H:%M:%S") } /// Debug logging. -/// +/// /// This logging SHOULD be human-readable but it is not intended for the end users specifically. /// Rather, it's being used as debugging and testing tool. -/// +/// /// (As such it doesn't have to be a text paragraph, the capital letters and end marks are not necessary). -/// +/// /// For the user-targeted logging use the `LogState::log` instead. -/// +/// /// On Windows the Rust standard output and the standard output of the MM1 C library are not compatible, /// they will overlap and overwrite each other if used togather. /// In order to avoid it, all logging MUST be done through this macros and NOT through `println!` or `eprintln!`. @@ -154,57 +165,57 @@ macro_rules! log { } pub trait TagParam<'a> { - fn key (&self) -> String; - fn val (&self) -> Option; + fn key(&self) -> String; + fn val(&self) -> Option; } impl<'a> TagParam<'a> for &'a str { - fn key (&self) -> String {String::from (&self[..])} - fn val (&self) -> Option {None} + fn key(&self) -> String { String::from(&self[..]) } + fn val(&self) -> Option { None } } impl<'a> TagParam<'a> for String { - fn key (&self) -> String {self.clone()} - fn val (&self) -> Option {None} + fn key(&self) -> String { self.clone() } + fn val(&self) -> Option { None } } impl<'a> TagParam<'a> for (&'a str, &'a str) { - fn key (&self) -> String {String::from (self.0)} - fn val (&self) -> Option {Some (String::from (self.1))} + fn key(&self) -> String { String::from(self.0) } + fn val(&self) -> Option { Some(String::from(self.1)) } } impl<'a> TagParam<'a> for (String, &'a str) { - fn key (&self) -> String { self.0.clone() } - fn val (&self) -> Option {Some (String::from (self.1))} + fn key(&self) -> String { self.0.clone() } + fn val(&self) -> Option { Some(String::from(self.1)) } } impl<'a> TagParam<'a> for (&'a str, i32) { - fn key (&self) -> String {String::from (self.0)} - fn val (&self) -> Option {Some (fomat! ((self.1)))} + fn key(&self) -> String { String::from(self.0) } + fn val(&self) -> Option { Some(fomat!((self.1))) } } #[derive(Clone, Eq, Hash, PartialEq)] pub struct Tag { pub key: String, - pub val: Option + pub val: Option, } impl Tag { /// Returns the tag's value or the empty string if there is no value. - pub fn val_s (&self) -> &str { + pub fn val_s(&self) -> &str { match self.val { - Some (ref s) => &s[..], - None => "" + Some(ref s) => &s[..], + None => "", } } } impl fmt::Debug for Tag { - fn fmt (&self, ft: &mut fmt::Formatter) -> fmt::Result { - ft.write_str (&self.key) ?; - if let Some (ref val) = self.val { - ft.write_str ("=") ?; - ft.write_str (val) ?; + fn fmt(&self, ft: &mut fmt::Formatter) -> fmt::Result { + ft.write_str(&self.key)?; + if let Some(ref val) = self.val { + ft.write_str("=")?; + ft.write_str(val)?; } Ok(()) } @@ -218,52 +229,67 @@ pub struct Status { pub start: Atomic, /// Expected time limit of the tracked operation, in milliseconds since UNIX epoch. /// 0 if no deadline is set. - pub deadline: Atomic + pub deadline: Atomic, } impl Clone for Status { - fn clone (&self) -> Status { - let tags = unwrap! (self.tags.spinlock (77)) .clone(); - let line = unwrap! (self.line.spinlock (77)) .clone(); + fn clone(&self) -> Status { + let tags = unwrap!(self.tags.spinlock(77)).clone(); + let line = unwrap!(self.line.spinlock(77)).clone(); Status { - tags: DuplexMutex::new (tags), - line: DuplexMutex::new (line), - start: Atomic::new (self.start.load (Ordering::Relaxed)), - deadline: Atomic::new (self.deadline.load (Ordering::Relaxed)) -} } } + tags: DuplexMutex::new(tags), + line: DuplexMutex::new(line), + start: Atomic::new(self.start.load(Ordering::Relaxed)), + deadline: Atomic::new(self.deadline.load(Ordering::Relaxed)), + } + } +} impl Hash for Status { fn hash(&self, state: &mut H) { - if let Ok (tags) = self.tags.spinlock (77) {for tag in tags.iter() {tag.hash (state)}} - if let Ok (line) = self.line.spinlock (77) {line.hash (state)} - self.start.load (Ordering::Relaxed) .hash (state); - self.deadline.load (Ordering::Relaxed) .hash (state); -} } + if let Ok(tags) = self.tags.spinlock(77) { + for tag in tags.iter() { + tag.hash(state) + } + } + if let Ok(line) = self.line.spinlock(77) { + line.hash(state) + } + self.start.load(Ordering::Relaxed).hash(state); + self.deadline.load(Ordering::Relaxed).hash(state); + } +} impl Status { /// Invoked when the `StatusHandle` is dropped, marking the status as finished. - fn finished (status: &Arc, dashboard: &Arc>>>, tail: &Arc>>) { - let mut dashboard = unwrap! (dashboard.spinlock (77)); - if let Some (idx) = dashboard.iter().position (|e| Arc::ptr_eq (e, status)) { - dashboard.swap_remove (idx); + fn finished( + status: &Arc, + dashboard: &Arc>>>, + tail: &Arc>>, + ) { + let mut dashboard = unwrap!(dashboard.spinlock(77)); + if let Some(idx) = dashboard.iter().position(|e| Arc::ptr_eq(e, status)) { + dashboard.swap_remove(idx); } else { - log! ("log] Warning, a finished StatusHandle was missing from the dashboard."); + log!("log] Warning, a finished StatusHandle was missing from the dashboard."); } - drop (dashboard); + drop(dashboard); - let mut tail = unwrap! (tail.spinlock (77)); - if tail.len() == tail.capacity() {let _ = tail.pop_front();} + let mut tail = unwrap!(tail.spinlock(77)); + if tail.len() == tail.capacity() { + let _ = tail.pop_front(); + } let mut log = LogEntry::default(); - swap (&mut log.tags, &mut *unwrap! (status.tags.spinlock (77))); - swap (&mut log.line, &mut *unwrap! (status.line.spinlock (77))); - let mut chunk = String::with_capacity (256); - if let Err (err) = log.format (&mut chunk) { + swap(&mut log.tags, &mut *unwrap!(status.tags.spinlock(77))); + swap(&mut log.line, &mut *unwrap!(status.line.spinlock(77))); + let mut chunk = String::with_capacity(256); + if let Err(err) = log.format(&mut chunk) { log! ({"log] Error formatting log entry: {}", err}); } - tail.push_back (log); - drop (tail); + tail.push_back(log); + drop(tail); - self::chunk2log (chunk) + self::chunk2log(chunk) } } @@ -272,7 +298,7 @@ pub struct LogEntry { pub time: u64, pub emotion: String, pub tags: Vec, - pub line: String + pub line: String, } impl Default for LogEntry { @@ -281,14 +307,14 @@ impl Default for LogEntry { time: now_ms(), emotion: Default::default(), tags: Default::default(), - line: Default::default() + line: Default::default(), } } } impl LogEntry { - pub fn format (&self, buf: &mut String) -> Result<(), fmt::Error> { - let time = Local.timestamp_millis (self.time as i64); + pub fn format(&self, buf: &mut String) -> Result<(), fmt::Error> { + let time = Local.timestamp_millis(self.time as i64); wite! (buf, if self.emotion.is_empty() {'·'} else {(self.emotion)} @@ -303,88 +329,100 @@ impl LogEntry { } /// Tracks the status of an ongoing operation, allowing us to inform the user of the status updates. -/// +/// /// Dropping the handle tells us that the operation was "finished" and that we can dump the final status into the log. pub struct StatusHandle { status: Option>, dashboard: Arc>>>, - tail: Arc>> + tail: Arc>>, } impl StatusHandle { /// Creates the status or rewrites it. - /// + /// /// The `tags` can be changed as well: /// with `StatusHandle` the status line is directly identified by the handle and doesn't use the tags to lookup the status line. - pub fn status (&mut self, tags: &[&dyn TagParam], line: &str) { - let tagsʹ: Vec = tags.iter().map (|t| Tag {key: t.key(), val: t.val()}) .collect(); - if let Some (ref status) = self.status { + pub fn status(&mut self, tags: &[&dyn TagParam], line: &str) { + let tagsʹ: Vec = tags + .iter() + .map(|t| Tag { + key: t.key(), + val: t.val(), + }) + .collect(); + if let Some(ref status) = self.status { // Skip a status update if it is equal to the previous update. - if unwrap! (status.line.spinlock (77)) .as_str() == line - && *unwrap! (status.tags.spinlock (77)) == tagsʹ {return} + if unwrap!(status.line.spinlock(77)).as_str() == line && *unwrap!(status.tags.spinlock(77)) == tagsʹ { + return; + } - *unwrap! (status.tags.spinlock (77)) = tagsʹ; - *unwrap! (status.line.spinlock (77)) = String::from (line); + *unwrap!(status.tags.spinlock(77)) = tagsʹ; + *unwrap!(status.line.spinlock(77)) = String::from(line); } else { - let status = Arc::new (Status { - tags: DuplexMutex::new (tagsʹ), - line: DuplexMutex::new (line.into()), - start: Atomic::new (now_ms()), - deadline: Atomic::new (0) + let status = Arc::new(Status { + tags: DuplexMutex::new(tagsʹ), + line: DuplexMutex::new(line.into()), + start: Atomic::new(now_ms()), + deadline: Atomic::new(0), }); - self.status = Some (status.clone()); - unwrap! (self.dashboard.spinlock (77)) .push (status); + self.status = Some(status.clone()); + unwrap!(self.dashboard.spinlock(77)).push(status); } } /// Adds new text into the status line. /// Does nothing if the status handle is empty (if the status wasn't created yet). - pub fn append (&self, suffix: &str) { - if let Some (ref status) = self.status { - unwrap! (status.line.spinlock (77)) .push_str (suffix) - } } + pub fn append(&self, suffix: &str) { + if let Some(ref status) = self.status { + unwrap!(status.line.spinlock(77)).push_str(suffix) + } + } /// Detach the handle from the status, allowing the status to remain in the dashboard when the handle is dropped. - /// + /// /// The code should later manually finish the status (finding it with `LogState::find_status`). - pub fn detach (&mut self) -> &mut Self { + pub fn detach(&mut self) -> &mut Self { self.status = None; self } /// Sets the deadline for the operation tracked by the status. - /// + /// /// The deadline is used to inform the user of the time constaints of the operation. /// It is not enforced by the logging/dashboard subsystem. - /// + /// /// * `ms` - The time, in milliseconds since UNIX epoch, /// when the operation is bound to end regardless of its status (aka a timeout). - pub fn deadline (&self, ms: u64) { - if let Some (ref status) = self.status { - status.deadline.store (ms, Ordering::Relaxed) + pub fn deadline(&self, ms: u64) { + if let Some(ref status) = self.status { + status.deadline.store(ms, Ordering::Relaxed) } } /// Sets the deadline for the operation tracked by the status. - /// + /// /// The deadline is used to inform the user of the time constaints of the operation. /// It is not enforced by the logging/dashboard subsystem. - /// + /// /// * `ms` - The time, in milliseconds since the creation of the status, /// when the operation is bound to end (aka a timeout). - pub fn timeframe (&self, ms: u64) { - if let Some (ref status) = self.status { - let start = status.start.load (Ordering::Relaxed); - status.deadline.store (start + ms, Ordering::Relaxed) + pub fn timeframe(&self, ms: u64) { + if let Some(ref status) = self.status { + let start = status.start.load(Ordering::Relaxed); + status.deadline.store(start + ms, Ordering::Relaxed) } } /// The number of milliseconds remaining till the deadline. /// Negative if the deadline is in the past. - pub fn ms2deadline (&self) -> Option { - if let Some (ref status) = self.status { - let deadline = status.deadline.load (Ordering::Relaxed); - if deadline == 0 {None} else {Some (deadline as i64 - now_ms() as i64)} + pub fn ms2deadline(&self) -> Option { + if let Some(ref status) = self.status { + let deadline = status.deadline.load(Ordering::Relaxed); + if deadline == 0 { + None + } else { + Some(deadline as i64 - now_ms() as i64) + } } else { None } @@ -392,15 +430,17 @@ impl StatusHandle { } impl Drop for StatusHandle { - fn drop (&mut self) { - if let Some (ref status) = self.status { - Status::finished (status, &self.dashboard, &self.tail) -} } } + fn drop(&mut self) { + if let Some(ref status) = self.status { + Status::finished(status, &self.dashboard, &self.tail) + } + } +} /// Generates a MM dashboard file path from the MM log file path. -pub fn dashboard_path (log_path: &Path) -> Result { - let log_path = try_s! (log_path.to_str().ok_or ("Non-unicode log_path?")); - Ok (format! ("{}.dashboard", log_path) .into()) +pub fn dashboard_path(log_path: &Path) -> Result { + let log_path = try_s!(log_path.to_str().ok_or("Non-unicode log_path?")); + Ok(format!("{}.dashboard", log_path).into()) } /// The shared log state of a MarketMaker instance. @@ -414,7 +454,7 @@ pub struct LogState { /// Initialized when we need the logging to happen through a certain thread /// (this thread becomes a center of gravity for the other registered threads). /// In the future we might also use `gravity` to log into a file. - gravity: DuplexMutex>> + gravity: DuplexMutex>>, } #[derive(Clone)] @@ -427,32 +467,23 @@ impl Deref for LogArc { impl LogArc { /// Create LogArc from real `LogState`. - pub fn new(state: LogState) -> LogArc { - LogArc(Arc::new(state)) - } + pub fn new(state: LogState) -> LogArc { LogArc(Arc::new(state)) } /// Try to obtain the `LogState` from the weak pointer. - pub fn from_weak(weak: &LogWeak) -> Option { - weak.0.upgrade().map(|arc| LogArc(arc)) - } + pub fn from_weak(weak: &LogWeak) -> Option { weak.0.upgrade().map(LogArc) } /// Create a weak pointer to `LogState`. - pub fn weak(&self) -> LogWeak { - LogWeak(Arc::downgrade(&self.0)) - } + pub fn weak(&self) -> LogWeak { LogWeak(Arc::downgrade(&self.0)) } } +#[derive(Default)] pub struct LogWeak(pub Weak); impl LogWeak { /// Create a default MmWeak without allocating any memory. - pub fn new() -> LogWeak { - LogWeak(Default::default()) - } + pub fn new() -> LogWeak { Default::default() } - pub fn dropped(&self) -> bool { - self.0.strong_count() == 0 - } + pub fn dropped(&self) -> bool { self.0.strong_count() == 0 } } /// The state used to periodically log the dashboard. @@ -461,57 +492,78 @@ struct DashboardLogging { last_log_ms: Atomic, /// Checksum of the dashboard that was last printed into the log. /// Allows us to detect whether the dashboard has changed since then. - last_hash: Atomic + last_hash: Atomic, } impl Default for DashboardLogging { fn default() -> DashboardLogging { DashboardLogging { - last_log_ms: Atomic::new (0), - last_hash: Atomic::new (0) -} } } + last_log_ms: Atomic::new(0), + last_hash: Atomic::new(0), + } + } +} -fn log_dashboard_sometimesʹ (dashboard: &Vec>, dl: &mut DashboardLogging) { +fn log_dashboard_sometimesʹ(dashboard: &[Arc], dl: &mut DashboardLogging) { // See if it's time to log the dashboard. - if dashboard.is_empty() {return} + if dashboard.is_empty() { + return; + } let mut hasher = DefaultHasher::new(); - for status in dashboard.iter() {status.hash (&mut hasher)} + for status in dashboard.iter() { + status.hash(&mut hasher) + } let hash = hasher.finish(); let now = now_ms(); - let delta = now as i64 - dl.last_log_ms.load (Ordering::Relaxed) as i64; - let last_hash = dl.last_hash.load (Ordering::Relaxed); - let itʹs_time = if hash != last_hash {delta > 7777} else {delta > 7777 * 3}; - if !itʹs_time {return} - - dl.last_hash.store (hash, Ordering::Relaxed); - dl.last_log_ms.store (now, Ordering::Relaxed); - let mut buf = String::with_capacity (7777); - unwrap! (wite! (buf, "+--- " (short_log_time (now)) " -------")); + let delta = now as i64 - dl.last_log_ms.load(Ordering::Relaxed) as i64; + let last_hash = dl.last_hash.load(Ordering::Relaxed); + let itʹs_time = if hash != last_hash { + delta > 7777 + } else { + delta > 7777 * 3 + }; + if !itʹs_time { + return; + } + + dl.last_hash.store(hash, Ordering::Relaxed); + dl.last_log_ms.store(now, Ordering::Relaxed); + let mut buf = String::with_capacity(7777); + unwrap!(wite! (buf, "+--- " (short_log_time (now)) " -------")); for status in dashboard.iter() { - let start = status.start.load (Ordering::Relaxed); - let deadline = status.deadline.load (Ordering::Relaxed); + let start = status.start.load(Ordering::Relaxed); + let deadline = status.deadline.load(Ordering::Relaxed); let passed = (now as i64 - start as i64) / 1000; let timeframe = (deadline as i64 - start as i64) / 1000; - let tags = match status.tags.spinlock (77) {Ok (t) => t.clone(), Err (_) => Vec::new()}; - let line = match status.line.spinlock (77) {Ok (l) => l.clone(), Err (_) => "-locked-".into()}; - unwrap! (wite! (buf, + let tags = match status.tags.spinlock(77) { + Ok(t) => t.clone(), + Err(_) => Vec::new(), + }; + let line = match status.line.spinlock(77) { + Ok(l) => l.clone(), + Err(_) => "-locked-".into(), + }; + unwrap!(wite! (buf, "\n| (" if passed >= 0 {(passed / 60) ':' {"{:0>2}", passed % 60}} else {'-'} if deadline > 0 {'/' (timeframe / 60) ':' {"{:0>2}", timeframe % 60}} ") " '[' for t in tags {(t.key) if let Some (ref v) = t.val {'=' (v)}} separated {' '} "] " (line))); } - chunk2log (buf) + chunk2log(buf) } -async fn log_dashboard_sometimes (dashboardʷ: Weak>>>) { +async fn log_dashboard_sometimes(dashboardʷ: Weak>>>) { let mut dashboard_logging = DashboardLogging::default(); loop { - Timer::sleep (0.777) .await; + Timer::sleep(0.777).await; // The loop stops when the `LogState::dashboard` is dropped. - let dashboard = match dashboardʷ.upgrade() {Some (arc) => arc, None => break}; - let dashboard = unwrap! (dashboard.sleeplock (77) .await); - log_dashboard_sometimesʹ (&*dashboard, &mut dashboard_logging); + let dashboard = match dashboardʷ.upgrade() { + Some(arc) => arc, + None => break, + }; + let dashboard = unwrap!(dashboard.sleeplock(77).await); + log_dashboard_sometimesʹ(&*dashboard, &mut dashboard_logging); } } @@ -519,95 +571,118 @@ impl LogState { /// Log into memory, for unit testing. pub fn in_memory() -> LogState { LogState { - dashboard: Arc::new (DuplexMutex::new (Vec::new())), - tail: Arc::new (DuplexMutex::new (VecDeque::with_capacity (64))), - gravity: DuplexMutex::new (None) - } } + dashboard: Arc::new(DuplexMutex::new(Vec::new())), + tail: Arc::new(DuplexMutex::new(VecDeque::with_capacity(64))), + gravity: DuplexMutex::new(None), + } + } /// Initialize according to the MM command-line configuration. - pub fn mm (_conf: &Json) -> LogState { - let dashboard = Arc::new (DuplexMutex::new (Vec::new())); + pub fn mm(_conf: &Json) -> LogState { + let dashboard = Arc::new(DuplexMutex::new(Vec::new())); - spawn (log_dashboard_sometimes (Arc::downgrade (&dashboard))); + spawn(log_dashboard_sometimes(Arc::downgrade(&dashboard))); LogState { dashboard, - tail: Arc::new (DuplexMutex::new (VecDeque::with_capacity (64))), - gravity: DuplexMutex::new (None) + tail: Arc::new(DuplexMutex::new(VecDeque::with_capacity(64))), + gravity: DuplexMutex::new(None), } } /// The operation is considered "in progress" while the `StatusHandle` exists. - /// + /// /// When the `StatusHandle` is dropped the operation is considered "finished" (possibly with a failure) /// and the status summary is dumped into the log. - pub fn status_handle (&self) -> StatusHandle { + pub fn status_handle(&self) -> StatusHandle { StatusHandle { status: None, dashboard: self.dashboard.clone(), - tail: self.tail.clone() - } } + tail: self.tail.clone(), + } + } /// Read-only access to the status dashboard. - pub fn with_dashboard (&self, cb: &mut dyn FnMut (&[Arc])) { - let dashboard = unwrap! (self.dashboard.spinlock (77)); - cb (&dashboard[..]) + pub fn with_dashboard(&self, cb: &mut dyn FnMut(&[Arc])) { + let dashboard = unwrap!(self.dashboard.spinlock(77)); + cb(&dashboard[..]) } - pub fn with_tail (&self, cb: &mut dyn FnMut (&VecDeque)) { - match self.tail.spinlock (77) { - Ok (tail) => cb (&*tail), - Err (_err) => writeln ("with_tail] !spinlock") + pub fn with_tail(&self, cb: &mut dyn FnMut(&VecDeque)) { + match self.tail.spinlock(77) { + Ok(tail) => cb(&*tail), + Err(_err) => writeln("with_tail] !spinlock"), } } - pub fn with_gravity_tail (&self, cb: &mut dyn FnMut (&VecDeque)) { - let gravity = match self.gravity.spinlock (77) { - Ok (guard) => guard, - Err (_err) => {writeln ("with_gravity_tail] !spinlock"); return} + pub fn with_gravity_tail(&self, cb: &mut dyn FnMut(&VecDeque)) { + let gravity = match self.gravity.spinlock(77) { + Ok(guard) => guard, + Err(_err) => { + writeln("with_gravity_tail] !spinlock"); + return; + }, }; - if let Some (ref gravity) = *gravity { + if let Some(ref gravity) = *gravity { gravity.flush(); - match gravity.tail.spinlock (77) { - Ok (tail) => cb (&*tail), - Err (_err) => writeln ("with_gravity_tail] !spinlock") + match gravity.tail.spinlock(77) { + Ok(tail) => cb(&*tail), + Err(_err) => writeln("with_gravity_tail] !spinlock"), } - } } + } + } /// Creates the status or rewrites it if the tags match. - pub fn status<'b> (&self, tags: &[&dyn TagParam], line: &str) -> StatusHandle { - let mut status = self.claim_status (tags) .unwrap_or (self.status_handle()); - status.status (tags, line); + pub fn status(&self, tags: &[&dyn TagParam], line: &str) -> StatusHandle { + let mut status = self.claim_status(tags).unwrap_or_else(|| self.status_handle()); + status.status(tags, line); status } /// Search dashboard for status matching the tags. - /// + /// /// Note that returned status handle represent an ownership of the status and on the `drop` will mark the status as finished. - pub fn claim_status (&self, tags: &[&dyn TagParam]) -> Option { + pub fn claim_status(&self, tags: &[&dyn TagParam]) -> Option { let mut found = Vec::new(); - let tags: Vec = tags.iter().map (|t| Tag {key: t.key(), val: t.val()}) .collect(); - let dashboard = unwrap! (self.dashboard.spinlock (77)); + let tags: Vec = tags + .iter() + .map(|t| Tag { + key: t.key(), + val: t.val(), + }) + .collect(); + let dashboard = unwrap!(self.dashboard.spinlock(77)); for status_arc in &*dashboard { - if *unwrap! (status_arc.tags.spinlock (77)) == tags {found.push (StatusHandle { - status: Some (status_arc.clone()), - dashboard: self.dashboard.clone(), - tail: self.tail.clone() - })} - } - drop (dashboard); // Unlock the dashboard before lock-waiting on statuses, avoiding a chance of deadlock. - if found.len() > 1 {log! ("log] Dashboard tags not unique!")} + if *unwrap!(status_arc.tags.spinlock(77)) == tags { + found.push(StatusHandle { + status: Some(status_arc.clone()), + dashboard: self.dashboard.clone(), + tail: self.tail.clone(), + }) + } + } + drop(dashboard); // Unlock the dashboard before lock-waiting on statuses, avoiding a chance of deadlock. + if found.len() > 1 { + log!("log] Dashboard tags not unique!") + } found.pop() } /// Returns `true` if there are recent log entries exactly matching the tags. - pub fn tail_any (&self, tags: &[&dyn TagParam]) -> bool { - let tags: Vec = tags.iter().map (|t| Tag {key: t.key(), val: t.val()}) .collect(); - for en in unwrap! (self.tail.spinlock (77)) .iter() { + pub fn tail_any(&self, tags: &[&dyn TagParam]) -> bool { + let tags: Vec = tags + .iter() + .map(|t| Tag { + key: t.key(), + val: t.val(), + }) + .collect(); + for en in unwrap!(self.tail.spinlock(77)).iter() { if en.tags == tags { - return true - } } - return false + return true; + } + } + false } /// Creates a new human-readable log entry. @@ -617,7 +692,13 @@ impl LogState { let entry = LogEntry { time: now_ms(), emotion: emotion.into(), - tags: tags.iter().map(|t| Tag { key: t.key(), val: t.val() }).collect(), + tags: tags + .iter() + .map(|t| Tag { + key: t.key(), + val: t.val(), + }) + .collect(), line: line.into(), }; @@ -625,16 +706,16 @@ impl LogState { } /// Creates a new human-readable log entry. - /// + /// /// This is a bit different from the `println!` logging /// (https://www.reddit.com/r/rust/comments/9hpk65/which_tools_are_you_using_to_debug_rust_projects/e6dkciz/) /// as the information here is intended for the end users /// (and to be shared through the GUI), /// explaining what's going on with MM. - /// + /// /// Since the focus here is on human-readability, the log entry SHOULD be treated /// as a text paragraph, namely starting with a capital letter and ending with an end mark. - /// + /// /// * `emotion` - We might use a unicode smiley here /// (https://unicode.org/emoji/charts/full-emoji-list.html) /// to emotionally color the event (the good, the bad and the ugly) @@ -659,19 +740,21 @@ impl LogState { let mut chunk = String::with_capacity(256); if let Err(err) = entry.format(&mut chunk) { log!({ "log] Error formatting log entry: {}", err }); - return + return; } - let mut tail = unwrap!(self.tail.spinlock (77)); - if tail.len() == tail.capacity() { let _ = tail.pop_front(); } + let mut tail = unwrap!(self.tail.spinlock(77)); + if tail.len() == tail.capacity() { + let _ = tail.pop_front(); + } tail.push_back(entry); drop(tail); self.chunk2log(chunk) } - fn chunk2log (&self, chunk: String) { - self::chunk2log (chunk) + fn chunk2log(&self, chunk: String) { + self::chunk2log(chunk) /* match self.log_file { Some (ref f) => match f.lock() { @@ -692,9 +775,9 @@ impl LogState { } /// Writes into the *raw* portion of the log, the one not shared with the UI. - pub fn rawln (&self, mut line: String) { - line.push ('\n'); - self.chunk2log (line); + pub fn rawln(&self, mut line: String) { + line.push('\n'); + self.chunk2log(line); } /// Binds the logger to the current thread, @@ -703,34 +786,33 @@ impl LogState { /// (https://github.com/rust-lang/rust/issues/12309, /// https://github.com/rust-lang/rust/issues/50297#issuecomment-388988381). #[cfg(feature = "native")] - pub fn thread_gravity_on (&self) -> Result<(), String> { - let mut gravity = try_s! (self.gravity.spinlock (77)); - if let Some (ref gravity) = *gravity { + pub fn thread_gravity_on(&self) -> Result<(), String> { + let mut gravity = try_s!(self.gravity.spinlock(77)); + if let Some(ref gravity) = *gravity { if gravity.target_thread_id == thread::current().id() { Ok(()) } else { - ERR! ("Gravity already enabled and for a different thread") + ERR!("Gravity already enabled and for a different thread") } } else { - *gravity = Some (Arc::new (Gravity { + *gravity = Some(Arc::new(Gravity { target_thread_id: thread::current().id(), landing: SegQueue::new(), - tail: DuplexMutex::new (VecDeque::with_capacity (64)) + tail: DuplexMutex::new(VecDeque::with_capacity(64)), })); Ok(()) } } #[cfg(not(feature = "native"))] - pub fn thread_gravity_on (&self) -> Result<(), String> {Ok(())} + pub fn thread_gravity_on(&self) -> Result<(), String> { Ok(()) } /// Start intercepting the `log!` invocations happening on the current thread. #[cfg(feature = "native")] - pub fn register_my_thread (&self) -> Result<(), String> { - let gravity = try_s! (self.gravity.spinlock (77)); - if let Some (ref gravity) = *gravity { - try_s! (GRAVITY.try_with (|thread_local_gravity| { - thread_local_gravity.replace (Some (Arc::downgrade (gravity))) - })); + pub fn register_my_thread(&self) -> Result<(), String> { + let gravity = try_s!(self.gravity.spinlock(77)); + if let Some(ref gravity) = *gravity { + try_s!(GRAVITY + .try_with(|thread_local_gravity| { thread_local_gravity.replace(Some(Arc::downgrade(gravity))) })); } else { // If no gravity thread is registered then `register_my_thread` is currently a no-op. // In the future we might implement a version of `Gravity` that pulls log entries into a file @@ -739,33 +821,35 @@ impl LogState { Ok(()) } #[cfg(not(feature = "native"))] - pub fn register_my_thread (&self) -> Result<(), String> {Ok(())} + pub fn register_my_thread(&self) -> Result<(), String> { Ok(()) } } #[cfg(feature = "native")] impl Drop for LogState { - fn drop (&mut self) { + fn drop(&mut self) { // Make sure to log the chunks received from the satellite threads. // NB: The `drop` might happen in a thread that is not the center of gravity, // resulting in log chunks escaping the unit test capture. // One way to fight this might be adding a flushing RAII struct into a unit test. // NB: The `drop` will not be happening if some of the satellite threads still hold to the context. - let mut gravity_arc = None; // Variable is used in order not to hold two locks. - if let Ok (gravity) = self.gravity.spinlock (77) { - if let Some (ref gravity) = *gravity { - gravity_arc = Some (gravity.clone()) + let mut gravity_arc = None; // Variable is used in order not to hold two locks. + if let Ok(gravity) = self.gravity.spinlock(77) { + if let Some(ref gravity) = *gravity { + gravity_arc = Some(gravity.clone()) } } - if let Some (gravity) = gravity_arc { + if let Some(gravity) = gravity_arc { gravity.flush() } - let dashboard_copy = unwrap! (self.dashboard.spinlock (77)) .clone(); - if dashboard_copy.len() > 0 { - log! ("--- LogState] Bye! Remaining status entries. ---"); - for status in &*dashboard_copy {Status::finished (status, &self.dashboard, &self.tail)} + let dashboard_copy = unwrap!(self.dashboard.spinlock(77)).clone(); + if !dashboard_copy.is_empty() { + log!("--- LogState] Bye! Remaining status entries. ---"); + for status in &*dashboard_copy { + Status::finished(status, &self.dashboard, &self.tail) + } } else { - log! ("LogState] Bye!"); + log!("LogState] Bye!"); } } } @@ -775,52 +859,54 @@ pub mod tests { use super::LogState; pub fn test_status() { - crate::writeln (""); // Begin from a new line in the --nocapture mode. + crate::writeln(""); // Begin from a new line in the --nocapture mode. let log = LogState::in_memory(); - log.with_dashboard (&mut |dashboard| assert_eq! (dashboard.len(), 0)); + log.with_dashboard(&mut |dashboard| assert_eq!(dashboard.len(), 0)); let mut handle = log.status_handle(); for n in 1..=3 { - handle.status (&[&"tag1", &"tag2"], &format! ("line {}", n)); + handle.status(&[&"tag1", &"tag2"], &format!("line {}", n)); - log.with_dashboard (&mut |dashboard| { - assert_eq! (dashboard.len(), 1); + log.with_dashboard(&mut |dashboard| { + assert_eq!(dashboard.len(), 1); let status = &dashboard[0]; - assert! (unwrap! (status.tags.spinlock (77)) .iter().any (|tag| tag.key == "tag1")); - assert! (unwrap! (status.tags.spinlock (77)) .iter().any (|tag| tag.key == "tag2")); - assert_eq! (unwrap! (status.tags.spinlock (77)) .len(), 2); - assert_eq! (*unwrap! (status.line.spinlock (77)), format! ("line {}", n)); + assert!(unwrap!(status.tags.spinlock(77)).iter().any(|tag| tag.key == "tag1")); + assert!(unwrap!(status.tags.spinlock(77)).iter().any(|tag| tag.key == "tag2")); + assert_eq!(unwrap!(status.tags.spinlock(77)).len(), 2); + assert_eq!(*unwrap!(status.line.spinlock(77)), format!("line {}", n)); }); } - drop (handle); + drop(handle); - log.with_dashboard (&mut |dashboard| assert_eq! (dashboard.len(), 0)); // The status was dumped into the log. - log.with_tail (&mut |tail| { - assert_eq! (tail.len(), 1); - assert_eq! (tail[0].line, "line 3"); + log.with_dashboard(&mut |dashboard| assert_eq!(dashboard.len(), 0)); // The status was dumped into the log. + log.with_tail(&mut |tail| { + assert_eq!(tail.len(), 1); + assert_eq!(tail[0].line, "line 3"); - assert! (tail[0].tags.iter().any (|tag| tag.key == "tag1")); - assert! (tail[0].tags.iter().any (|tag| tag.key == "tag2")); - assert_eq! (tail[0].tags.len(), 2); + assert!(tail[0].tags.iter().any(|tag| tag.key == "tag1")); + assert!(tail[0].tags.iter().any(|tag| tag.key == "tag2")); + assert_eq!(tail[0].tags.len(), 2); }) } pub fn test_printed_dashboard() { - crate::writeln (""); // Begin from a new line in the --nocapture mode. + crate::writeln(""); // Begin from a new line in the --nocapture mode. let log = LogState::in_memory(); - unwrap! (log.thread_gravity_on()); - unwrap! (log.register_my_thread()); + unwrap!(log.thread_gravity_on()); + unwrap!(log.register_my_thread()); let mut status = log.status_handle(); - status.status (&[&"tag"], "status 1%…"); - status.timeframe ((3 * 60 + 33) * 1000); + status.status(&[&"tag"], "status 1%…"); + status.timeframe((3 * 60 + 33) * 1000); - { let dashboard = unwrap! (log.dashboard.spinlock (77)); + { + let dashboard = unwrap!(log.dashboard.spinlock(77)); let mut dashboard_logging = super::DashboardLogging::default(); - super::log_dashboard_sometimesʹ (&*dashboard, &mut dashboard_logging); } + super::log_dashboard_sometimesʹ(&*dashboard, &mut dashboard_logging); + } - log.with_gravity_tail (&mut |tail| { - assert! (tail[0].ends_with ("/3:33) [tag] status 1%…")); + log.with_gravity_tail(&mut |tail| { + assert!(tail[0].ends_with("/3:33) [tag] status 1%…")); }); } } diff --git a/mm2src/common/mm_ctx.rs b/mm2src/common/mm_ctx.rs index 08a3aa264c..240a19ab9d 100644 --- a/mm2src/common/mm_ctx.rs +++ b/mm2src/common/mm_ctx.rs @@ -9,31 +9,29 @@ use futures::{ prelude::*, }; use gstuff::Constructible; -#[cfg(not(feature = "native"))] -use http::Response; +#[cfg(not(feature = "native"))] use http::Response; use keys::{DisplayLayout, KeyPair, Private}; use primitives::hash::H160; use rand::Rng; -use serde_bencode::ser::to_bytes as bencode; use serde_bencode::de::from_bytes as bdecode; +use serde_bencode::ser::to_bytes as bencode; use serde_bytes::ByteBuf; use serde_json::{self as json, Value as Json}; use std::any::Any; -use std::collections::HashSet; use std::collections::hash_map::{Entry, HashMap}; +use std::collections::HashSet; use std::fmt; use std::net::IpAddr; -#[cfg(feature = "native")] -use std::net::SocketAddr; +#[cfg(feature = "native")] use std::net::SocketAddr; use std::ops::Deref; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex, Weak}; use std::time::Duration; -use crate::{bits256, small_rng, QueuedCommand}; -use crate::log::{self, LogState}; -use crate::mm_metrics::{MetricsArc, prometheus}; use crate::executor::Timer; +use crate::log::{self, LogState}; +use crate::mm_metrics::{prometheus, MetricsArc}; +use crate::{bits256, small_rng, QueuedCommand}; /// Default interval to export and record metrics to log. const EXPORT_METRICS_INTERVAL: f64 = 5. * 60.; @@ -48,6 +46,8 @@ pub enum P2PCommand { SendToPeers(Vec<(String, Vec)>, Vec), } +type StopListenerCallback = Box Result<(), String>>; + /// MarketMaker state, shared between the various MarketMaker threads. /// /// Every MarketMaker has one and only one instance of `MmCtx`. @@ -60,12 +60,12 @@ pub enum P2PCommand { /// In the future we might want to replace direct state access with traceable and replayable /// state modifications /// (cf. https://github.com/artemii235/SuperNET/blob/mm2-dice/mm2src/README.md#purely-functional-core). -/// +/// /// `MmCtx` never moves in memory (and it isn't `Send`), it is created and then destroyed in place /// (this invariant should make it a bit simpler thinking about aliasing and thread-safety, /// particularly of the C structures during the gradual port). /// Only the pointers (`MmArc`, `MmWeak`) can be moved around. -/// +/// /// Threads only have the non-`mut` access to `MmCtx`, allowing us to directly share certain fields. pub struct MmCtx { /// MM command-line configuration. @@ -75,7 +75,7 @@ pub struct MmCtx { /// Tools and methods and to collect and export the MM metrics. pub metrics: MetricsArc, /// Set to true after `lp_passphrase_init`, indicating that we have a usable state. - /// + /// /// Should be refactored away in the future. State should always be valid. /// If there are things that are loaded in background then they should be separately optional, /// without invalidating the entire state. @@ -88,7 +88,7 @@ pub struct MmCtx { /// 0 if the handler ID is allocated yet. pub ffi_handle: Constructible, /// Callbacks to invoke from `fn stop`. - pub stop_listeners: MutexResult<(), String>>>>, + pub stop_listeners: Mutex>, /// The context belonging to the `portfolio` crate: `PortfolioContext`. pub portfolio_ctx: Mutex>>, /// The context belonging to the `ordermatch` mod: `OrdermatchContext`. @@ -130,10 +130,10 @@ pub struct MmCtx { pub gossipsub_ctx: Mutex>>, } impl MmCtx { - pub fn with_log_state (log: LogState) -> MmCtx { + pub fn with_log_state(log: LogState) -> MmCtx { let (command_queue, command_queueʳ) = mpsc::unbounded(); MmCtx { - conf: Json::Object (json::Map::new()), + conf: Json::Object(json::Map::new()), log: log::LogArc::new(log), metrics: MetricsArc::new(), initialized: Constructible::default(), @@ -155,7 +155,7 @@ impl MmCtx { command_queueʳ: Mutex::new (Some (command_queueʳ)), command_queueʰ: Mutex::new (None), rmd160: Constructible::default(), - seeds: Mutex::new (Vec::new()), + seeds: Mutex::new(Vec::new()), secp256k1_key_pair: Constructible::default(), coins_needed_for_kick_start: Mutex::new (HashSet::new()), swaps_ctx: Mutex::new (None), @@ -163,81 +163,92 @@ impl MmCtx { } } - pub fn rmd160 (&self) -> &H160 { - lazy_static! {static ref DEFAULT: H160 = [0; 20].into();} - self.rmd160.or (&|| &*DEFAULT) + pub fn rmd160(&self) -> &H160 { + lazy_static! { + static ref DEFAULT: H160 = [0; 20].into(); + } + self.rmd160.or(&|| &*DEFAULT) } #[cfg(feature = "native")] - pub fn rpc_ip_port (&self) -> Result { - let port = self.conf["rpcport"].as_u64().unwrap_or (7783); - if port < 1000 {return ERR! ("rpcport < 1000")} - if port > u16::max_value() as u64 {return ERR! ("rpcport > u16")} + pub fn rpc_ip_port(&self) -> Result { + let port = self.conf["rpcport"].as_u64().unwrap_or(7783); + if port < 1000 { + return ERR!("rpcport < 1000"); + } + if port > u16::max_value() as u64 { + return ERR!("rpcport > u16"); + } let rpcip = if !self.conf["rpcip"].is_null() { - try_s! (self.conf["rpcip"].as_str().ok_or ("rpcip is not a string")) + try_s!(self.conf["rpcip"].as_str().ok_or("rpcip is not a string")) } else { "127.0.0.1" - } .to_string(); - let ip: IpAddr = try_s! (rpcip.parse()); - Ok (SocketAddr::new (ip, port as u16)) + } + .to_string(); + let ip: IpAddr = try_s!(rpcip.parse()); + Ok(SocketAddr::new(ip, port as u16)) } /// MM database path. /// Defaults to a relative "DB". - /// + /// /// Can be changed via the "dbdir" configuration field, for example: - /// + /// /// "dbdir": "c:/Users/mm2user/.mm2-db" - /// + /// /// No checks in this method, the paths should be checked in the `fn fix_directories` instead. - pub fn dbdir (&self) -> PathBuf { - let path = if let Some (dbdir) = self.conf["dbdir"].as_str() { + pub fn dbdir(&self) -> PathBuf { + let path = if let Some(dbdir) = self.conf["dbdir"].as_str() { let dbdir = dbdir.trim(); if !dbdir.is_empty() { - Path::new (dbdir) + Path::new(dbdir) } else { - Path::new ("DB") + Path::new("DB") } } else { - Path::new ("DB") + Path::new("DB") }; - path.join (hex::encode (&**self.rmd160())) + path.join(hex::encode(&**self.rmd160())) } - pub fn netid (&self) -> u16 { - let big = self.conf["netid"].as_u64().unwrap_or (0); - if big > u16::max_value().into() {panic! ("netid {} is too big", big)} + pub fn netid(&self) -> u16 { + let big = self.conf["netid"].as_u64().unwrap_or(0); + if big > u16::max_value().into() { + panic!("netid {} is too big", big) + } big as u16 } - pub fn stop (&self) { - if self.stop.pin (true) .is_ok() { - let mut stop_listeners = unwrap! (self.stop_listeners.lock(), "Can't lock stop_listeners"); + pub fn stop(&self) { + if self.stop.pin(true).is_ok() { + let mut stop_listeners = unwrap!(self.stop_listeners.lock(), "Can't lock stop_listeners"); // NB: It is important that we `drain` the `stop_listeners` rather than simply iterating over them // because otherwise there might be reference counting instances remaining in a listener // that would prevent the contexts from properly `Drop`ping. - for mut listener in stop_listeners.drain (..) { - if let Err (err) = listener() { + for mut listener in stop_listeners.drain(..) { + if let Err(err) = listener() { log! ({"MmCtx::stop] Listener error: {}", err}) - } } } } + } + } + } + } /// True if the MarketMaker instance needs to stop. - pub fn is_stopping (&self) -> bool { - self.stop.copy_or (false) - } + pub fn is_stopping(&self) -> bool { self.stop.copy_or(false) } /// Register a callback to be invoked when the MM receives the "stop" request. /// The callback is invoked immediately if the MM is stopped already. - pub fn on_stop (&self, mut cb: BoxResult<(), String>>) { - let mut stop_listeners = unwrap! (self.stop_listeners.lock(), "Can't lock stop_listeners"); - if self.stop.copy_or (false) { - if let Err (err) = cb() { + pub fn on_stop(&self, mut cb: Box Result<(), String>>) { + let mut stop_listeners = unwrap!(self.stop_listeners.lock(), "Can't lock stop_listeners"); + if self.stop.copy_or(false) { + if let Err(err) = cb() { log! ({"MmCtx::on_stop] Listener error: {}", err}) } } else { - stop_listeners.push (cb) - } } + stop_listeners.push(cb) + } + } /// Sends the P2P message to a processing thread #[cfg(feature = "native")] @@ -249,45 +260,51 @@ impl MmCtx { } #[cfg(not(feature = "native"))] - pub fn broadcast_p2p_msg (&self, msg: &str) { - use crate::{helperᶜ, BroadcastP2pMessageArgs}; + pub fn broadcast_p2p_msg(&self, msg: &str) { use crate::executor::spawn; + use crate::{helperᶜ, BroadcastP2pMessageArgs}; - let args = BroadcastP2pMessageArgs {ctx: self.ffi_handle.copy_or (0), msg: msg.into()}; - let args = unwrap! (bencode (&args)); - spawn (async move { - let rc = helperᶜ ("broadcast_p2p_msg", args) .await; - if let Err (err) = rc {log! ("!broadcast_p2p_msg: " (err))} + let args = BroadcastP2pMessageArgs { + ctx: self.ffi_handle.copy_or(0), + msg: msg.into(), + }; + let args = unwrap!(bencode(&args)); + spawn(async move { + let rc = helperᶜ("broadcast_p2p_msg", args).await; + if let Err(err) = rc { + log!("!broadcast_p2p_msg: "(err)) + } }); } /// Get a reference to the secp256k1 key pair. /// Panics if the key pair is not available. - pub fn secp256k1_key_pair (&self) -> &KeyPair { + pub fn secp256k1_key_pair(&self) -> &KeyPair { match self.secp256k1_key_pair.as_option() { - Some (pair) => pair, - None => panic! ("secp256k1_key_pair not available") - } } + Some(pair) => pair, + None => panic!("secp256k1_key_pair not available"), + } + } /// This is our public ID, allowing us to be different from other peers. /// This should also be our public key which we'd use for message verification. - pub fn public_id (&self) -> Result { - for pair in &self.secp256k1_key_pair { - let public = pair.public(); // Compressed public key is going to be 33 bytes. - // First byte is a prefix, https://davidederosa.com/basic-blockchain-programming/elliptic-curve-keys/. - return Ok (bits256 {bytes: *array_ref! (public, 1, 32)}) - } - ERR! ("Public ID is not yet available") + pub fn public_id(&self) -> Result { + self.secp256k1_key_pair + .ok_or(ERRL!("Public ID is not yet available")) + .map(|keypair| { + let public = keypair.public(); // Compressed public key is going to be 33 bytes. + // First byte is a prefix, https://davidederosa.com/basic-blockchain-programming/elliptic-curve-keys/. + bits256 { + bytes: *array_ref!(public, 1, 32), + } + }) } - pub fn gui (&self) -> Option<&str> { - self.conf["gui"].as_str() - } + pub fn gui(&self) -> Option<&str> { self.conf["gui"].as_str() } } impl Default for MmCtx { - fn default() -> Self { - Self::with_log_state (LogState::in_memory()) -} } + fn default() -> Self { Self::with_log_state(LogState::in_memory()) } +} // We don't want to send `MmCtx` across threads, it will only obstruct the normal use case // (and might result in undefined behavior if there's a C struct or value in the context that is aliased from the various MM threads). @@ -296,13 +313,18 @@ impl Default for MmCtx { // which will likely come useful during the gradual port. //not-implemented-on-stable// impl !Send for MmCtx {} -pub struct MmArc (pub Arc); +pub struct MmArc(pub Arc); // NB: Explicit `Send` and `Sync` marks here should become unnecessary later, // after we finish the initial port and replace the C values with the corresponding Rust alternatives. unsafe impl Send for MmArc {} unsafe impl Sync for MmArc {} -impl Clone for MmArc {fn clone (&self) -> MmArc {MmArc (self.0.clone())}} -impl Deref for MmArc {type Target = MmCtx; fn deref (&self) -> &MmCtx {&*self.0}} +impl Clone for MmArc { + fn clone(&self) -> MmArc { MmArc(self.0.clone()) } +} +impl Deref for MmArc { + type Target = MmCtx; + fn deref(&self) -> &MmCtx { &*self.0 } +} impl MmArc { #[cfg(feature = "native")] @@ -315,25 +337,22 @@ impl MmArc { } } -#[derive(Clone)] -pub struct MmWeak (Weak); +#[derive(Clone, Default)] +pub struct MmWeak(Weak); // Same as `MmArc`. unsafe impl Send for MmWeak {} unsafe impl Sync for MmWeak {} impl MmWeak { /// Create a default MmWeak without allocating any memory. - pub fn new() -> MmWeak { - MmWeak(Default::default()) - } + pub fn new() -> MmWeak { MmWeak::default() } - pub fn dropped (&self) -> bool { - self.0.strong_count() == 0 -} } + pub fn dropped(&self) -> bool { self.0.strong_count() == 0 } +} impl fmt::Debug for MmWeak { - fn fmt (&self, ft: &mut fmt::Formatter) -> Result<(), fmt::Error> { - let ctx = MmArc::from_weak (self); + fn fmt(&self, ft: &mut fmt::Formatter) -> Result<(), fmt::Error> { + let ctx = MmArc::from_weak(self); wite! (ft, "MmWeak(" if let Some (ctx) = ctx { match ctx.ffi_handle() { @@ -342,70 +361,84 @@ impl fmt::Debug for MmWeak { } } else {'-'} ')') -} } + } +} lazy_static! { - /// A map from a unique context ID to the corresponding MM context, facilitating context access across the FFI boundaries. + /// A map from a unique context ID to the corresponding MM context, facilitating context access across the FFI boundaries. /// NB: The entries are not removed in order to keep the FFI handlers unique. pub static ref MM_CTX_FFI: Mutex> = Mutex::new (HashMap::default()); } /// Portable core sharing its context with the native helpers. -/// +/// /// In the integration tests we're using this to create new native contexts. -#[derive (Serialize, Deserialize)] +#[derive(Serialize, Deserialize)] struct PortableCtx { // Sending the `conf` as a string in order for bencode not to mess with JSON, and for wire readability. conf: String, secp256k1_key_pair: ByteBuf, - ffi_handle: Option + ffi_handle: Option, } -#[derive (Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug)] struct NativeCtx { - ffi_handle: u32 + ffi_handle: u32, } impl MmArc { /// Unique context identifier, allowing us to more easily pass the context through the FFI boundaries. - pub fn ffi_handle (&self) -> Result { - let mut mm_ctx_ffi = try_s! (MM_CTX_FFI.lock()); - for &have in &self.ffi_handle {return Ok (have)} + pub fn ffi_handle(&self) -> Result { + let mut mm_ctx_ffi = try_s!(MM_CTX_FFI.lock()); + if let Some(have) = self.ffi_handle.as_option() { + return Ok(*have); + } let mut tries = 0; let mut rng = small_rng(); loop { - if tries > 999 {panic! ("MmArc] out of RIDs")} else {tries += 1} + if tries > 999 { + panic!("MmArc] out of RIDs") + } else { + tries += 1 + } let rid: u32 = rng.gen(); - if rid == 0 {continue} - match mm_ctx_ffi.entry (rid) { - Entry::Occupied (_) => continue, // Try another ID. - Entry::Vacant (ve) => { - ve.insert (self.weak()); - try_s! (self.ffi_handle.pin (rid)); - return Ok (rid) - } } } } + if rid == 0 { + continue; + } + match mm_ctx_ffi.entry(rid) { + Entry::Occupied(_) => continue, // Try another ID. + Entry::Vacant(ve) => { + ve.insert(self.weak()); + try_s!(self.ffi_handle.pin(rid)); + return Ok(rid); + }, + } + } + } #[cfg(not(feature = "native"))] - pub async fn send_to_helpers (&self) -> Result<(), String> { + pub async fn send_to_helpers(&self) -> Result<(), String> { use crate::helperᶜ; let ctxʷ = PortableCtx { - conf: try_s! (json::to_string (&self.conf)), + conf: try_s!(json::to_string(&self.conf)), secp256k1_key_pair: match self.secp256k1_key_pair.as_option() { - Some (k) => ByteBuf::from (k.private().layout()), - None => ByteBuf::new() + Some(k) => ByteBuf::from(k.private().layout()), + None => ByteBuf::new(), }, - ffi_handle: self.ffi_handle.as_option().copied() + ffi_handle: self.ffi_handle.as_option().copied(), }; - let ctxᵇ = try_s! (bencode (&ctxʷ)); - let hr = try_s! (helperᶜ ("ctx2helpers", ctxᵇ) .await); + let ctxᵇ = try_s!(bencode(&ctxʷ)); + let hr = try_s!(helperᶜ("ctx2helpers", ctxᵇ).await); // Remember the context ID used by the native helpers in order to simplify consecutive syncs. - let ctxⁿ: NativeCtx = try_s! (bdecode (&hr)); - if let Some (ffi_handle) = self.ffi_handle.as_option().copied() { - if ffi_handle != ctxⁿ.ffi_handle {return ERR! ("ffi_handle mismatch")} + let ctxⁿ: NativeCtx = try_s!(bdecode(&hr)); + if let Some(ffi_handle) = self.ffi_handle.as_option().copied() { + if ffi_handle != ctxⁿ.ffi_handle { + return ERR!("ffi_handle mismatch"); + } } else { - try_s! (self.ffi_handle.pin (ctxⁿ.ffi_handle)); + try_s!(self.ffi_handle.pin(ctxⁿ.ffi_handle)); } Ok(()) @@ -413,30 +446,31 @@ impl MmArc { /// Tries getting access to the MM context. /// Fails if an invalid MM context handler is passed (no such context or dropped context). - pub fn from_ffi_handle (ffi_handle: u32) -> Result { - if ffi_handle == 0 {return ERR! ("MmArc] Zeroed ffi_handle")} - let mm_ctx_ffi = try_s! (MM_CTX_FFI.lock()); - match mm_ctx_ffi.get (&ffi_handle) { - Some (weak) => match MmArc::from_weak (weak) { - Some (ctx) => Ok (ctx), - None => ERR! ("MmArc] ffi_handle {} is dead", ffi_handle) + pub fn from_ffi_handle(ffi_handle: u32) -> Result { + if ffi_handle == 0 { + return ERR!("MmArc] Zeroed ffi_handle"); + } + let mm_ctx_ffi = try_s!(MM_CTX_FFI.lock()); + match mm_ctx_ffi.get(&ffi_handle) { + Some(weak) => match MmArc::from_weak(weak) { + Some(ctx) => Ok(ctx), + None => ERR!("MmArc] ffi_handle {} is dead", ffi_handle), }, - None => ERR! ("MmArc] ffi_handle {} does not exists", ffi_handle) - } } + None => ERR!("MmArc] ffi_handle {} does not exists", ffi_handle), + } + } /// Generates a weak link, to track the context without prolonging its life. - pub fn weak (&self) -> MmWeak { - MmWeak (Arc::downgrade (&self.0)) - } + pub fn weak(&self) -> MmWeak { MmWeak(Arc::downgrade(&self.0)) } /// Tries to obtain the MM context from the weak link. - pub fn from_weak (weak: &MmWeak) -> Option { - weak.0.upgrade().map (|arc| MmArc (arc)) - } + pub fn from_weak(weak: &MmWeak) -> Option { weak.0.upgrade().map(MmArc) } /// Init metrics with dashboard. pub fn init_metrics(&self) -> Result<(), String> { - let interval = self.conf["metrics_interval"].as_f64().unwrap_or(EXPORT_METRICS_INTERVAL); + let interval = self.conf["metrics_interval"] + .as_f64() + .unwrap_or(EXPORT_METRICS_INTERVAL); if interval == 0.0 { try_s!(self.metrics.init()); @@ -451,9 +485,12 @@ impl MmArc { let address: SocketAddr = try_s!(format!("127.0.0.1:{}", prometheusport).parse()); - let credentials = self.conf["prometheus_credentials"] - .as_str() - .map(|userpass| prometheus::PrometheusCredentials { userpass: userpass.into() }); + let credentials = + self.conf["prometheus_credentials"] + .as_str() + .map(|userpass| prometheus::PrometheusCredentials { + userpass: userpass.into(), + }); let ctx = self.weak(); @@ -476,110 +513,125 @@ impl MmArc { /// As of now we're expecting a one-to-one pairing between the portable and the native versions of MM /// so the uniqueness of the `ffi_handle` is not a concern yet. #[cfg(feature = "native")] -pub async fn ctx2helpers (main_ctx: MmArc, req: Bytes) -> Result, String> { - let ctxʷ: PortableCtx = try_s! (bdecode (&req)); - let private = try_s! (Private::from_layout (&ctxʷ.secp256k1_key_pair[..])); - let main_key = try_s! (main_ctx.secp256k1_key_pair.as_option().ok_or ("No key")); +pub async fn ctx2helpers(main_ctx: MmArc, req: Bytes) -> Result, String> { + let ctxʷ: PortableCtx = try_s!(bdecode(&req)); + let private = try_s!(Private::from_layout(&ctxʷ.secp256k1_key_pair[..])); + let main_key = try_s!(main_ctx.secp256k1_key_pair.as_option().ok_or("No key")); if *main_key.private() == private { // We have a match with the primary native context, the one configured on the command line. - let res = try_s! (bencode (&NativeCtx { - ffi_handle: try_s! (main_ctx.ffi_handle()) + let res = try_s!(bencode(&NativeCtx { + ffi_handle: try_s!(main_ctx.ffi_handle()) })); - return Ok (res) + return Ok(res); } - if let Some (ffi_handle) = ctxʷ.ffi_handle { - if let Ok (ctx) = MmArc::from_ffi_handle (ffi_handle) { - let key = try_s! (ctx.secp256k1_key_pair.as_option().ok_or ("No key")); - if *key.private() != private {return ERR! ("key mismatch")} - let res = try_s! (bencode (&NativeCtx { - ffi_handle: try_s! (ctx.ffi_handle()) + if let Some(ffi_handle) = ctxʷ.ffi_handle { + if let Ok(ctx) = MmArc::from_ffi_handle(ffi_handle) { + let key = try_s!(ctx.secp256k1_key_pair.as_option().ok_or("No key")); + if *key.private() != private { + return ERR!("key mismatch"); + } + let res = try_s!(bencode(&NativeCtx { + ffi_handle: try_s!(ctx.ffi_handle()) })); - return Ok (res) - } } + return Ok(res); + } + } // Create a native copy of the portable context. - let pair: Option = if ctxʷ.secp256k1_key_pair.is_empty() {None} else { - let private = try_s! (Private::from_layout (&ctxʷ.secp256k1_key_pair[..])); - Some (try_s! (KeyPair::from_private (private))) + let pair: Option = if ctxʷ.secp256k1_key_pair.is_empty() { + None + } else { + let private = try_s!(Private::from_layout(&ctxʷ.secp256k1_key_pair[..])); + Some(try_s!(KeyPair::from_private(private))) }; let ctx = MmCtx { - conf: try_s! (json::from_str (&ctxʷ.conf)), + conf: try_s!(json::from_str(&ctxʷ.conf)), secp256k1_key_pair: pair.into(), ffi_handle: ctxʷ.ffi_handle.into(), - ..MmCtx::with_log_state (LogState::in_memory()) + ..MmCtx::with_log_state(LogState::in_memory()) }; - let ctx = MmArc (Arc::new (ctx)); - if let Some (ffi_handle) = ctxʷ.ffi_handle { - let mut ctx_ffi = try_s! (MM_CTX_FFI.lock()); - if ctx_ffi.contains_key (&ffi_handle) {return ERR! ("ID race")} - ctx_ffi.insert (ffi_handle, ctx.weak()); + let ctx = MmArc(Arc::new(ctx)); + if let Some(ffi_handle) = ctxʷ.ffi_handle { + let mut ctx_ffi = try_s!(MM_CTX_FFI.lock()); + if ctx_ffi.contains_key(&ffi_handle) { + return ERR!("ID race"); + } + ctx_ffi.insert(ffi_handle, ctx.weak()); } - let res = try_s! (bencode (&NativeCtx { - ffi_handle: try_s! (ctx.ffi_handle()) + let res = try_s!(bencode(&NativeCtx { + ffi_handle: try_s!(ctx.ffi_handle()) })); - Arc::into_raw (ctx.0); // Leak. + Arc::into_raw(ctx.0); // Leak. - Ok (res) + Ok(res) } /// Helps getting a crate context from a corresponding `MmCtx` field. -/// +/// /// * `ctx_field` - A dedicated crate context field in `MmCtx`, such as the `MmCtx::portfolio_ctx`. /// * `constructor` - Generates the initial crate context. -pub fn from_ctx (ctx_field: &Mutex>>, constructor: C) -> Result, String> -where C: FnOnce()->Result, T: 'static + Send + Sync { - let mut ctx_field = try_s! (ctx_field.lock()); - if let Some (ref ctx) = *ctx_field { +pub fn from_ctx( + ctx_field: &Mutex>>, + constructor: C, +) -> Result, String> +where + C: FnOnce() -> Result, + T: 'static + Send + Sync, +{ + let mut ctx_field = try_s!(ctx_field.lock()); + if let Some(ref ctx) = *ctx_field { let ctx: Arc = match ctx.clone().downcast() { - Ok (p) => p, - Err (_) => return ERR! ("Error casting the context field") + Ok(p) => p, + Err(_) => return ERR!("Error casting the context field"), }; - return Ok (ctx) + return Ok(ctx); } - let arc = Arc::new (try_s! (constructor())); - *ctx_field = Some (arc.clone()); - return Ok (arc) + let arc = Arc::new(try_s!(constructor())); + *ctx_field = Some(arc.clone()); + Ok(arc) } #[derive(Default)] pub struct MmCtxBuilder { conf: Option, - key_pair: Option + key_pair: Option, } impl MmCtxBuilder { - pub fn new() -> Self { - MmCtxBuilder::default() - } + pub fn new() -> Self { MmCtxBuilder::default() } pub fn with_conf(mut self, conf: Json) -> Self { - self.conf = Some (conf); + self.conf = Some(conf); self } pub fn with_secp256k1_key_pair(mut self, key_pair: KeyPair) -> Self { - self.key_pair = Some (key_pair); + self.key_pair = Some(key_pair); self } pub fn into_mm_arc(self) -> MmArc { // NB: We avoid recreating LogState // in order not to interfere with the integration tests checking LogState drop on shutdown. - let log = if let Some (ref conf) = self.conf {LogState::mm (conf)} else {LogState::in_memory()}; - let mut ctx = MmCtx::with_log_state (log); - if let Some (conf) = self.conf { + let log = if let Some(ref conf) = self.conf { + LogState::mm(conf) + } else { + LogState::in_memory() + }; + let mut ctx = MmCtx::with_log_state(log); + if let Some(conf) = self.conf { ctx.conf = conf } - if let Some (key_pair) = self.key_pair { - unwrap! (ctx.rmd160.pin (key_pair.public().address_hash())); - unwrap! (ctx.secp256k1_key_pair.pin (key_pair)); + if let Some(key_pair) = self.key_pair { + unwrap!(ctx.rmd160.pin(key_pair.public().address_hash())); + unwrap!(ctx.secp256k1_key_pair.pin(key_pair)); } - MmArc (Arc::new (ctx)) + MmArc(Arc::new(ctx)) } } diff --git a/mm2src/common/mm_metrics.rs b/mm2src/common/mm_metrics.rs index 239a930f88..7ed2d840cc 100644 --- a/mm2src/common/mm_metrics.rs +++ b/mm2src/common/mm_metrics.rs @@ -3,9 +3,9 @@ use crate::log::{LogArc, LogWeak, Tag}; use gstuff::Constructible; use hdrhistogram::Histogram; use itertools::Itertools; -use metrics_core::{Key, Label, Drain, Observe, Observer, ScopedString, Builder}; -use metrics_runtime::{Receiver, observers::PrometheusBuilder}; +use metrics_core::{Builder, Drain, Key, Label, Observe, Observer, ScopedString}; pub use metrics_runtime::Sink; +use metrics_runtime::{observers::PrometheusBuilder, Receiver}; use metrics_util::{parse_quantiles, Quantile}; use serde_json::{self as json, Value as Json}; use std::collections::HashMap; @@ -70,35 +70,34 @@ macro_rules! mm_timing { #[cfg(feature = "native")] pub mod prometheus { + use super::*; use crate::wio::CORE; - use futures01::{self, future, Future}; use futures::compat::Future01CompatExt; use futures::future::FutureExt; + use futures01::{self, future, Future}; use hyper::http::{self, header, Request, Response, StatusCode}; - use hyper::{Body, Server}; use hyper::service::{make_service_fn, service_fn}; + use hyper::{Body, Server}; use std::convert::Infallible; use std::net::SocketAddr; - use super::*; #[derive(Clone)] pub struct PrometheusCredentials { pub userpass: String, } - pub fn spawn_prometheus_exporter(metrics: MetricsWeak, - address: SocketAddr, - shutdown_detector: impl Future + 'static + Send, - credentials: Option) - -> Result<(), String> { + pub fn spawn_prometheus_exporter( + metrics: MetricsWeak, + address: SocketAddr, + shutdown_detector: impl Future + 'static + Send, + credentials: Option, + ) -> Result<(), String> { let make_svc = make_service_fn(move |_conn| { let metrics = metrics.clone(); let credentials = credentials.clone(); - Ok::<_, Infallible>( - service_fn(move |req| { - future::result(scrape_handle(req, metrics.clone(), credentials.clone())) - }) - ) + Ok::<_, Infallible>(service_fn(move |req| { + future::result(scrape_handle(req, metrics.clone(), credentials.clone())) + })) }); let server = try_s!(Server::try_bind(&address)) @@ -118,23 +117,24 @@ pub mod prometheus { Ok(()) } - fn scrape_handle(req: Request, - metrics: MetricsWeak, - credentials: Option) - -> Result, http::Error> { + fn scrape_handle( + req: Request, + metrics: MetricsWeak, + credentials: Option, + ) -> Result, http::Error> { fn on_error(status: StatusCode, error: String) -> Result, http::Error> { log!((error)); - Response::builder() - .status(status) - .body(Body::empty()) - .map_err(|err| { - log!((err)); - err - }) + Response::builder().status(status).body(Body::empty()).map_err(|err| { + log!((err)); + err + }) } if req.uri() != "/metrics" { - return on_error(StatusCode::BAD_REQUEST, ERRL!("Warning Prometheus: unexpected URI {}", req.uri())); + return on_error( + StatusCode::BAD_REQUEST, + ERRL!("Warning Prometheus: unexpected URI {}", req.uri()), + ); } if let Some(credentials) = credentials { @@ -145,12 +145,22 @@ pub mod prometheus { let metrics = match MetricsArc::from_weak(&metrics) { Some(m) => m, - _ => return on_error(StatusCode::BAD_REQUEST, ERRL!("Warning Prometheus: metrics system unavailable")), + _ => { + return on_error( + StatusCode::BAD_REQUEST, + ERRL!("Warning Prometheus: metrics system unavailable"), + ) + }, }; let body = match metrics.collect_prometheus_format() { Ok(body) => Body::from(body), - _ => return on_error(StatusCode::BAD_REQUEST, ERRL!("Warning Prometheus: metrics system is not initialized yet")), + _ => { + return on_error( + StatusCode::BAD_REQUEST, + ERRL!("Warning Prometheus: metrics system is not initialized yet"), + ) + }, }; Response::builder() @@ -164,11 +174,11 @@ pub mod prometheus { } fn check_auth_credentials(req: &Request, expected: PrometheusCredentials) -> Result<(), String> { - let header_value = req.headers() + let header_value = req + .headers() .get(header::AUTHORIZATION) .ok_or(ERRL!("Warning Prometheus: authorization required")) - .and_then(|header| Ok(try_s!(header.to_str()))) - ?; + .and_then(|header| Ok(try_s!(header.to_str())))?; let expected = format!("Basic {}", base64::encode_config(&expected.userpass, base64::URL_SAFE)); @@ -214,7 +224,11 @@ impl Metrics { let controller = self.receiver.as_option().unwrap().controller(); let observer = TagObserver::new(QUANTILES); - let exporter = TagExporter { log_state, controller, observer }; + let exporter = TagExporter { + log_state, + controller, + observer, + }; spawn(exporter.run(record_interval)); @@ -222,9 +236,7 @@ impl Metrics { } /// Handle for sending metric samples. - pub fn sink(&self) -> Result { - Ok(try_s!(self.try_receiver()).sink()) - } + pub fn sink(&self) -> Result { Ok(try_s!(self.try_receiver()).sink()) } /// Collect the metrics as Json. pub fn collect_json(&self) -> Result { @@ -282,40 +294,30 @@ pub enum MetricType { }, } -#[derive(Clone)] +#[derive(Clone, Default)] pub struct MetricsArc(pub Arc); impl Deref for MetricsArc { type Target = Metrics; - fn deref(&self) -> &Metrics { - &*self.0 - } + fn deref(&self) -> &Metrics { &*self.0 } } impl TrySink for MetricsArc { - fn try_sink(&self) -> Option { - self.sink().ok() - } + fn try_sink(&self) -> Option { self.sink().ok() } } impl MetricsArc { /// Create new `Metrics` instance - pub fn new() -> MetricsArc { - MetricsArc(Arc::new(Default::default())) - } + pub fn new() -> MetricsArc { MetricsArc(Arc::new(Default::default())) } /// Try to obtain the `Metrics` from the weak pointer. - pub fn from_weak(weak: &MetricsWeak) -> Option { - weak.0.upgrade().map(|arc| MetricsArc(arc)) - } + pub fn from_weak(weak: &MetricsWeak) -> Option { weak.0.upgrade().map(MetricsArc) } /// Create a weak pointer from `MetricsWeak`. - pub fn weak(&self) -> MetricsWeak { - MetricsWeak(Arc::downgrade(&self.0)) - } + pub fn weak(&self) -> MetricsWeak { MetricsWeak(Arc::downgrade(&self.0)) } } -#[derive(Clone)] +#[derive(Clone, Default)] pub struct MetricsWeak(pub Weak); impl TrySink for MetricsWeak { @@ -327,13 +329,9 @@ impl TrySink for MetricsWeak { impl MetricsWeak { /// Create a default MmWeak without allocating any memory. - pub fn new() -> MetricsWeak { - MetricsWeak(Default::default()) - } + pub fn new() -> MetricsWeak { MetricsWeak::default() } - pub fn dropped(&self) -> bool { - self.0.strong_count() == 0 - } + pub fn dropped(&self) -> bool { self.0.strong_count() == 0 } } type MetricName = ScopedString; @@ -382,7 +380,8 @@ impl TagObserver { } fn prepare_metrics(&self) -> Vec { - self.metrics.iter() + self.metrics + .iter() .map(|(labels, name_value_map)| { let tags = labels_to_tags(labels.iter()); let message = name_value_map_to_message(name_value_map); @@ -393,7 +392,8 @@ impl TagObserver { } fn prepare_histograms(&self) -> Vec { - self.histograms.iter() + self.histograms + .iter() .map(|(key, hist)| { let tags = labels_to_tags(key.labels()); let message = format!("{}: {}", key.name(), hist_to_message(hist, &self.quantiles)); @@ -405,7 +405,8 @@ impl TagObserver { fn insert_metric(&mut self, key: Key, value: Integer) { let (name, labels) = key.into_parts(); - self.metrics.entry(labels) + self.metrics + .entry(labels) .and_modify(|name_value_map| { name_value_map.insert(name.clone(), value.clone()); }) @@ -426,30 +427,24 @@ impl TagObserver { } impl Observer for TagObserver { - fn observe_counter(&mut self, key: Key, value: u64) { - self.insert_metric(key, Integer::Unsigned(value)) - } + fn observe_counter(&mut self, key: Key, value: u64) { self.insert_metric(key, Integer::Unsigned(value)) } - fn observe_gauge(&mut self, key: Key, value: i64) { - self.insert_metric(key, Integer::Signed(value)) - } + fn observe_gauge(&mut self, key: Key, value: i64) { self.insert_metric(key, Integer::Signed(value)) } fn observe_histogram(&mut self, key: Key, values: &[u64]) { - let entry = self.histograms - .entry(key) - .or_insert({ - // Use default significant figures value. - // For more info on `sigfig` see the Historgam::new_with_bounds(). - let sigfig = 3; - match Histogram::new(sigfig) { - Ok(x) => x, - Err(err) => { - log!("failed to create histogram: "(err)); - // do nothing on error - return; - } - } - }); + let entry = self.histograms.entry(key).or_insert({ + // Use default significant figures value. + // For more info on `sigfig` see the Historgam::new_with_bounds(). + let sigfig = 3; + match Histogram::new(sigfig) { + Ok(x) => x, + Err(err) => { + log!("failed to create histogram: "(err)); + // do nothing on error + return; + }, + } + }); for value in values { if let Err(err) = entry.record(*value) { @@ -504,7 +499,7 @@ impl Observer for JsonObserver { log!("failed to create histogram: "(err)); // do nothing on error return; - } + }, }; for value in values { @@ -536,14 +531,11 @@ impl JsonObserver { } } - fn into_json(self) -> Result { - json::to_value(self.metrics).map_err(|err| ERRL!("{}", err)) - } + fn into_json(self) -> Result { json::to_value(self.metrics).map_err(|err| ERRL!("{}", err)) } } /// Exports metrics by converting them to a Tag format and log them using log::Status. -struct TagExporter -{ +struct TagExporter { /// Using a weak reference by default in order to avoid circular references and leaks. log_state: LogWeak, /// Handle for acquiring metric snapshots. @@ -553,7 +545,9 @@ struct TagExporter } impl TagExporter - where C: Observe { +where + C: Observe, +{ /// Run endless async loop async fn run(mut self, interval: f64) { loop { @@ -567,7 +561,7 @@ impl TagExporter let log_state = match LogArc::from_weak(&self.log_state) { Some(x) => x, // MmCtx is dropped already - _ => return + _ => return, }; log!(">>>>>>>>>> DEX metrics <<<<<<<<<"); @@ -604,18 +598,19 @@ fn labels_into_parts(labels: Iter