diff --git a/Cargo.lock b/Cargo.lock index 080ed2d8bc..9d9db4b6cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,6 +48,40 @@ name = "arrayvec" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "async-std" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "async-task 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "broadcaster 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-timer 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "kv-log-macro 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "async-task" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "atomic" version = "0.4.5" @@ -229,6 +263,19 @@ dependencies = [ "byte-tools 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "broadcaster" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "build_const" version = "0.2.1" @@ -545,6 +592,15 @@ dependencies = [ "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam-channel" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam-deque" version = "0.2.0" @@ -937,6 +993,15 @@ name = "futures" version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "futures-channel" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "futures-channel-preview" version = "0.3.0-alpha.18" @@ -946,6 +1011,11 @@ dependencies = [ "futures-sink-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "futures-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "futures-core-preview" version = "0.3.0-alpha.18" @@ -970,6 +1040,11 @@ dependencies = [ "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "futures-io" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "futures-io-preview" version = "0.3.0-alpha.18" @@ -980,12 +1055,23 @@ name = "futures-join-macro-preview" version = "0.3.0-alpha.18" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro-hack 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.42 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "futures-macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "futures-preview" version = "0.3.0-alpha.18" @@ -1004,12 +1090,17 @@ name = "futures-select-macro-preview" version = "0.3.0-alpha.18" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro-hack 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.42 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "futures-sink" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "futures-sink-preview" version = "0.3.0-alpha.18" @@ -1018,6 +1109,11 @@ dependencies = [ "futures-core-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "futures-task" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "futures-timer" version = "0.1.1" @@ -1026,6 +1122,26 @@ dependencies = [ "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "futures-timer" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-util" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-macro 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "futures-util-preview" version = "0.3.0-alpha.18" @@ -1040,7 +1156,7 @@ dependencies = [ "futures-sink-preview 0.3.0-alpha.18 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-hack 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1359,6 +1475,14 @@ dependencies = [ "rustc-hex 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "kv-log-macro" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1416,7 +1540,7 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1513,6 +1637,7 @@ dependencies = [ name = "mm2" version = "0.1.0" dependencies = [ + "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "atomic 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "bigdecimal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "bitcrypto 0.1.0 (git+https://github.com/artemii235/parity-bitcoin.git)", @@ -1662,6 +1787,11 @@ dependencies = [ "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "once_cell" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "opaque-debug" version = "0.2.1" @@ -1689,11 +1819,20 @@ name = "parking_lot" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lock_api 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parking_lot" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot_core" version = "0.4.0" @@ -1720,6 +1859,19 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parking_lot_core" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -1767,6 +1919,11 @@ name = "percent-encoding" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "pin-project-lite" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "pin-utils" version = "0.1.0-alpha.4" @@ -1789,13 +1946,8 @@ dependencies = [ [[package]] name = "proc-macro-hack" -version = "0.5.8" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.42 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "proc-macro-nested" @@ -2363,6 +2515,11 @@ name = "smallvec" version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "smallvec" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "sourcefile" version = "0.1.4" @@ -3225,6 +3382,8 @@ dependencies = [ "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" @@ -3245,6 +3404,7 @@ dependencies = [ "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" @@ -3269,6 +3429,7 @@ dependencies = [ "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" @@ -3312,16 +3473,24 @@ dependencies = [ "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" @@ -3356,13 +3525,14 @@ dependencies = [ "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 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.3 (registry+https://github.com/rust-lang/crates.io-index)" = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b" +"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" @@ -3385,18 +3555,22 @@ dependencies = [ "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.8 (registry+https://github.com/rust-lang/crates.io-index)" = "982a35d1194084ba319d65c4a68d24ca28f5fdb5b8bc20899e4eef8641ea5178" +"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" @@ -3465,6 +3639,7 @@ dependencies = [ "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" diff --git a/Cargo.toml b/Cargo.toml index 1410b1db99..845d7073f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,7 @@ debug = true debug = false [dependencies] +async-std = {version = "1.5", features = ["unstable"]} atomic = "0.4" bigdecimal = { version = "0.1", features = ["serde"] } bitcrypto = { git = "https://github.com/artemii235/parity-bitcoin.git" } diff --git a/etomic_build/client/buy_ONE_ANOTHER b/etomic_build/client/buy_ONE_ANOTHER index 1f80a63dd0..a7fd784624 100755 --- a/etomic_build/client/buy_ONE_ANOTHER +++ b/etomic_build/client/buy_ONE_ANOTHER @@ -1,3 +1,3 @@ #!/bin/bash source userpass -curl --url "http://127.0.0.1:7783" --data "{\"userpass\":\"$userpass\",\"method\":\"buy\",\"base\":\"$1\",\"rel\":\"$2\",\"volume\":\"0.1\",\"price\":\"2\"}" +curl --url "http://127.0.0.1:7783" --data "{\"userpass\":\"$userpass\",\"method\":\"buy\",\"base\":\"$1\",\"rel\":\"$2\",\"volume\":\"0.1\",\"price\":\"3\"}" diff --git a/mm2src/common/common.rs b/mm2src/common/common.rs index 075b7dbc01..4c68bd11c6 100644 --- a/mm2src/common/common.rs +++ b/mm2src/common/common.rs @@ -61,6 +61,7 @@ pub mod jsonrpc_client; #[macro_use] pub mod log; +pub mod file_lock; #[cfg(feature = "native")] pub mod for_c; pub mod custom_futures; diff --git a/mm2src/common/file_lock.rs b/mm2src/common/file_lock.rs new file mode 100644 index 0000000000..aa10fb8683 --- /dev/null +++ b/mm2src/common/file_lock.rs @@ -0,0 +1,123 @@ +use crate::{now_ms, now_float}; +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. + ttl_sec: f64, +} + +/// Records timestamp to a file contents. +fn touch(path: &dyn AsRef, timestamp: u64) -> Result<(), String> { + std::fs::write(path.as_ref(), timestamp.to_string()).map_err(|e| ERRL!("{:?}", e)) +} + +/// Attempts to read timestamp recorded to a file +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) + } +} + +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()) { + Ok(_) => { + let file_lock = FileLock { lock_path, ttl_sec }; + try_s!(file_lock.touch()); + Ok(Some(file_lock)) + }, + 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(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 creating {:?}: {}", lock_path.as_ref(), ie) + } + } + + 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); + } +} + +#[cfg(test)] +mod file_lock_tests { + use std::{ + thread::sleep, + time::Duration, + }; + use super::*; + + #[test] + fn test_file_lock_should_create_file_and_record_timestamp_and_then_delete_on_drop() { + let now = now_ms() / 1000; + let path = Path::new("test1.lock"); + let lock = FileLock::lock(&path, 1000.).unwrap().unwrap(); + assert!(path.exists()); + let timestamp = read_timestamp(&path).unwrap().unwrap(); + assert!(timestamp >= now); + drop(lock); + assert!(!path.exists()); + } + + #[test] + fn test_file_lock_should_return_none_if_lock_acquired() { + let path = Path::new("test2.lock"); + let _lock = FileLock::lock(&path, 1000.).unwrap().unwrap(); + let new_lock = FileLock::lock(&path, 1000.).unwrap(); + assert!(new_lock.is_none()); + } + + #[test] + fn test_file_lock_should_acquire_if_ttl_expired_and_update_timestamp() { + let path = Path::new("test3.lock"); + let _lock = FileLock::lock(&path, 1.).unwrap().unwrap(); + sleep(Duration::from_secs(2)); + let old_timestamp = read_timestamp(&path).unwrap(); + let _new_lock = FileLock::lock(&path, 1.).unwrap().unwrap(); + let new_timestamp = read_timestamp(&path).unwrap(); + assert!(new_timestamp > old_timestamp); + } + + #[test] + fn test_file_lock_should_acquire_if_file_is_empty() { + let now = now_ms() / 1000; + let path = Path::new("test4.lock"); + std::fs::write(&path, &[]).unwrap(); + let _new_lock = FileLock::lock(&path, 1.).unwrap().unwrap(); + let timestamp = read_timestamp(&path).unwrap().unwrap(); + assert!(timestamp >= now); + } + + #[test] + fn test_file_lock_should_acquire_if_file_does_not_contain_parsable_timestamp() { + let now = now_ms() / 1000; + let path = Path::new("test5.lock"); + std::fs::write(&path, &[12, 13]).unwrap(); + let _new_lock = FileLock::lock(&path, 1.).unwrap().unwrap(); + let timestamp = read_timestamp(&path).unwrap().unwrap(); + assert!(timestamp >= now); + } +} diff --git a/mm2src/common/for_tests.rs b/mm2src/common/for_tests.rs index d618231658..cc2a15ca13 100644 --- a/mm2src/common/for_tests.rs +++ b/mm2src/common/for_tests.rs @@ -173,15 +173,15 @@ impl MarketMakerIt { ip }; - // Use a separate (unique) temporary folder for each MM. - // (We could also remove the old folders after some time in order not to spam the temporary folder. - // Though we don't always want to remove them right away, allowing developers to check the files). - let now = super::now_ms(); - let now = Local.timestamp ((now / 1000) as i64, (now % 1000) as u32 * 1000000); - let folder = format! ("mm2_{}_{}", now.format ("%Y-%m-%d_%H-%M-%S-%3f"), ip); - let folder = super::temp_dir().join (folder); - let db_dir = folder.join ("DB"); - conf["dbdir"] = unwrap! (db_dir.to_str()) .into(); + let folder = new_mm2_temp_folder_path(Some(ip)); + let db_dir = match conf["dbdir"].as_str() { + Some(path) => path.into(), + None => { + 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(); @@ -192,9 +192,19 @@ impl MarketMakerIt { #[cfg(feature = "native")] { try_s! (fs::create_dir (&folder)); - try_s! (fs::create_dir (db_dir)); - let log_path = folder.join ("mm2.log"); - conf["log"] = unwrap! (log_path.to_str()) .into(); + match fs::create_dir (db_dir) { + Ok(_) => (), + Err(ref ie) if ie.kind() == std::io::ErrorKind::AlreadyExists => (), + Err(e) => return ERR!("{}", e), + }; + let log_path = match conf["log"].as_str() { + Some(path) => path.into(), + None => { + let path = folder.join("mm2.log"); + conf["log"] = unwrap!(path.to_str()).into(); + path + } + }; // If `local` is provided // then instead of spawning a process we start the MarketMaker in a local thread, @@ -244,6 +254,22 @@ impl MarketMakerIt { } } + /// Busy-wait on the log until the `pred` returns `true` or `timeout_sec` expires. + /// 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 { + let start = now_float(); + 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 + } + } + /// 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> @@ -500,3 +526,17 @@ pub async fn enable_native(mm: &MarketMakerIt, coin: &str, urls: Vec<&str>) -> J assert_eq! (native.0, StatusCode::OK, "'enable' failed: {}", native.1); unwrap!(json::from_str(&native.1)) } + +/// Use a separate (unique) temporary folder for each MM. +/// We could also remove the old folders after some time in order not to spam the temporary folder. +/// Though we don't always want to remove them right away, allowing developers to check the files). +/// 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 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")), + }; + super::temp_dir().join (folder) +} diff --git a/mm2src/docker_tests.rs b/mm2src/docker_tests.rs index e82cf4a7f9..8be45bf729 100644 --- a/mm2src/docker_tests.rs +++ b/mm2src/docker_tests.rs @@ -25,13 +25,20 @@ fn main() { #[cfg(all(test, feature = "native"))] mod docker_tests { + mod swaps_file_lock_tests; + + use bitcrypto::ChecksumType; use common::block_on; - use common::for_tests::{enable_native, MarketMakerIt, mm_dump}; + use common::{ + file_lock::FileLock, + for_tests::{enable_native, MarketMakerIt, new_mm2_temp_folder_path, mm_dump} + }; use coins::{FoundSwapTxSpend, MarketCoinOps, SwapOps}; use coins::utxo::{coin_daemon_data_dir, dhash160, utxo_coin_from_conf_and_request, zcash_params_path, UtxoCoin}; use coins::utxo::rpc_clients::{UtxoRpcClientEnum, UtxoRpcClientOps}; use futures01::Future; use gstuff::now_ms; + use keys::{KeyPair, Private}; use secp256k1::SecretKey; use serde_json::{self as json, Value as Json}; use std::env; @@ -535,4 +542,88 @@ mod docker_tests { unwrap!(block_on(mm_bob.stop())); unwrap!(block_on(mm_alice.stop())); } + + #[test] + fn swaps_should_stop_on_stop_rpc() { + let (_, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000); + let (_, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN1", 2000); + let coins = json! ([ + {"coin":"MYCOIN","asset":"MYCOIN","txversion":4,"overwintered":1,"txfee":1000}, + {"coin":"MYCOIN1","asset":"MYCOIN1","txversion":4,"overwintered":1,"txfee":1000}, + ]); + let mut mm_bob = unwrap! (MarketMakerIt::start ( + json! ({ + "gui": "nogui", + "netid": 9000, + "dht": "on", // Enable DHT without delay. + "passphrase": format!("0x{}", hex::encode(bob_priv_key)), + "coins": coins, + "rpc_password": "pass", + "i_am_seed": true, + }), + "pass".to_string(), + None, + )); + let (_bob_dump_log, _bob_dump_dashboard) = mm_dump (&mm_bob.log_path); + unwrap! (block_on (mm_bob.wait_for_log (22., |log| log.contains (">>>>>>>>> DEX stats ")))); + + let mut mm_alice = unwrap! (MarketMakerIt::start ( + json! ({ + "gui": "nogui", + "netid": 9000, + "dht": "on", // Enable DHT without delay. + "passphrase": format!("0x{}", hex::encode(alice_priv_key)), + "coins": coins, + "rpc_password": "pass", + "seednodes": vec![format!("{}", mm_bob.ip)], + }), + "pass".to_string(), + None, + )); + let (_alice_dump_log, _alice_dump_dashboard) = mm_dump (&mm_alice.log_path); + unwrap! (block_on (mm_alice.wait_for_log (22., |log| log.contains (">>>>>>>>> DEX stats ")))); + + log!([block_on(enable_native(&mm_bob, "MYCOIN", vec![]))]); + log!([block_on(enable_native(&mm_bob, "MYCOIN1", vec![]))]); + log!([block_on(enable_native(&mm_alice, "MYCOIN", vec![]))]); + log!([block_on(enable_native(&mm_alice, "MYCOIN1", vec![]))]); + let rc = unwrap! (block_on (mm_bob.rpc (json! ({ + "userpass": mm_bob.userpass, + "method": "setprice", + "base": "MYCOIN", + "rel": "MYCOIN1", + "price": 1, + "max": true, + })))); + assert! (rc.0.is_success(), "!setprice: {}", rc.1); + let mut uuids = Vec::with_capacity(3); + + for _ in 0..3 { + let rc = unwrap!(block_on (mm_alice.rpc (json! ({ + "userpass": mm_alice.userpass, + "method": "buy", + "base": "MYCOIN", + "rel": "MYCOIN1", + "price": 1, + "volume": "1", + })))); + assert!(rc.0.is_success(), "!buy: {}", rc.1); + let buy: Json = json::from_str(&rc.1).unwrap(); + uuids.push(buy["result"]["uuid"].as_str().unwrap().to_owned()); + } + for uuid in uuids.iter() { + unwrap!(block_on (mm_bob.wait_for_log (22., + |log| log.contains (&format!("Entering the maker_swap_loop MYCOIN/MYCOIN1 with uuid: {}", uuid)) + ))); + unwrap!(block_on (mm_alice.wait_for_log (22., + |log| log.contains (&format!("Entering the taker_swap_loop MYCOIN/MYCOIN1 with uuid: {}", uuid)) + ))); + } + unwrap!(block_on(mm_bob.stop())); + unwrap!(block_on(mm_alice.stop())); + for uuid in uuids { + unwrap!(block_on (mm_bob.wait_for_log_after_stop (22., |log| log.contains (&format!("swap {} stopped", uuid))))); + unwrap!(block_on (mm_alice.wait_for_log_after_stop (22., |log| log.contains (&format!("swap {} stopped", uuid))))); + } + } } diff --git a/mm2src/docker_tests/swaps_file_lock_tests.rs b/mm2src/docker_tests/swaps_file_lock_tests.rs new file mode 100644 index 0000000000..f45cd7891f --- /dev/null +++ b/mm2src/docker_tests/swaps_file_lock_tests.rs @@ -0,0 +1,244 @@ +use super::*; + +const UNFINISHED_MAKER_SWAP: &str = r#"{"type":"Maker","uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","events":[{"timestamp":1588244036250,"event":{"type":"Started","data":{"taker_coin":"MYCOIN1","maker_coin":"MYCOIN","taker":"859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521","secret":"dedf3c8dcfff9ee2787b4bf211f960fd044fdab7fa8e922ef613a0115848a498","secret_hash":"9304bce3196f344b2a22dc99db406e95ab6f3107","my_persistent_pub":"02987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4d","lock_duration":7800,"maker_amount":"999.99999","taker_amount":"999.99999","maker_payment_confirmations":1,"maker_payment_requires_nota":false,"taker_payment_confirmations":1,"taker_payment_requires_nota":false,"maker_payment_lock":1588259636,"uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","started_at":1588244036,"maker_coin_start_block":12,"taker_coin_start_block":11}}},{"timestamp":1588244038035,"event":{"type":"Negotiated","data":{"taker_payment_locktime":1588251836,"taker_pubkey":"02859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521"}}}],"maker_amount":"999.99999","maker_coin":"MYCOIN","taker_amount":"999.99999","taker_coin":"MYCOIN1","gui":"nogui","mm_version":"UNKNOWN","success_events":["Started","Negotiated","TakerFeeValidated","MakerPaymentSent","TakerPaymentReceived","TakerPaymentWaitConfirmStarted","TakerPaymentValidatedAndConfirmed","TakerPaymentSpent","Finished"],"error_events":["StartFailed","NegotiateFailed","TakerFeeValidateFailed","MakerPaymentTransactionFailed","MakerPaymentDataSendFailed","MakerPaymentWaitConfirmFailed","TakerPaymentValidateFailed","TakerPaymentWaitConfirmFailed","TakerPaymentSpendFailed","MakerPaymentWaitRefundStarted","MakerPaymentRefunded","MakerPaymentRefundFailed"]}"#; +const FINISHED_MAKER_SWAP: &str = r#"{"type":"Maker","uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","events":[{"timestamp":1588244036250,"event":{"type":"Started","data":{"taker_coin":"MYCOIN1","maker_coin":"MYCOIN","taker":"859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521","secret":"dedf3c8dcfff9ee2787b4bf211f960fd044fdab7fa8e922ef613a0115848a498","secret_hash":"9304bce3196f344b2a22dc99db406e95ab6f3107","my_persistent_pub":"02987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4d","lock_duration":7800,"maker_amount":"999.99999","taker_amount":"999.99999","maker_payment_confirmations":1,"maker_payment_requires_nota":false,"taker_payment_confirmations":1,"taker_payment_requires_nota":false,"maker_payment_lock":1588259636,"uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","started_at":1588244036,"maker_coin_start_block":12,"taker_coin_start_block":11}}},{"timestamp":1588244038035,"event":{"type":"Negotiated","data":{"taker_payment_locktime":1588251836,"taker_pubkey":"02859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521"}}},{"timestamp":1588244038463,"event":{"type":"TakerFeeValidated","data":{"tx_hex":"0400008085202f8901bdde9bca02870787441f6068e4c2a869a3aac3d1d0925f6a6e27874343544d0a010000006a47304402206694a794693b55fbe8205cb1cbb992d92fa2a9a851763ad1bf1628c16deaf73e02203cfff465504bdd6c51e8fbd45dd2e1187142fdc29e82f36f577616d9d6097d7a012102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ffffffff02dfceab07000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac39fd41892e0000001976a914d24e799df360da3ca3158d63b89ffaff27722c1588ac46aeaa5e000000000000000000000000000000","tx_hash":"7118d7484d1cbfdd6126673a848a228856f8748d946f6a9a440c90f0d62e27c6","from":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"to":["RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf","RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"total_amount":"2000","spent_by_me":"0","received_by_me":"0","my_balance_change":"0","block_height":13,"timestamp":1588244038,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"7118d7484d1cbfdd6126673a848a228856f8748d946f6a9a440c90f0d62e27c6"}}},{"timestamp":1588244038505,"event":{"type":"MakerPaymentSent","data":{"tx_hex":"0400008085202f890163cc0aceb3b432f84c1407991a0389b74b7842f030aa261203aa0b4b9a9a15fd010000006b483045022100f3239794b7b0e1c75aae084a65535270f154231ce86a4739976ff69eeff1ebd402206c0aeb28b7b4b2e77e98b3c3efd9a20d85c2cd0627acc3f45d577c0e88c34fb4012102987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4dffffffff0118e476481700000017a914dc4ed4686174503b85374bfb0aefe07a9fb37bcf8746aeaa5e000000000000000000000000000000","tx_hash":"9a4c0b3f85ed0bb24dc9575ce7c2fd6bc50ad9d37c91c478946f9c33d15abfdf","from":["RAzSdYQhjCFdyhjrBz1AZQDVg3Hu8DrzYc"],"to":["bYp9ncp3V7FYsymipriVbdd3QL72hK9hio"],"total_amount":"1000","spent_by_me":"1000","received_by_me":"0","my_balance_change":"-1000","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN","internal_id":"9a4c0b3f85ed0bb24dc9575ce7c2fd6bc50ad9d37c91c478946f9c33d15abfdf"}}},{"timestamp":1588244053938,"event":{"type":"TakerPaymentReceived","data":{"tx_hex":"0400008085202f8901c6272ed6f0900c449a6a6f948d74f85688228a843a672661ddbf1c4d48d71871010000006b483045022100c37627385c66b7bdf466b4dd4e7095b7551e6f5ea35f9bcc6344eb629f2edcb202203280eaba64b4d72010500166fab62cf34a687a516b2fe83d4eceaf8572cb37a7012102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ffffffff0218e476481700000017a91431ff75ed72cd135a7ce50d121e71efc37066b9f9873915cb40170000001976a914d24e799df360da3ca3158d63b89ffaff27722c1588ac55aeaa5e000000000000000000000000000000","tx_hash":"b4718ce94aa43f9073ab0b70c18ab4ea4b587338fb7110f20ff7d1bb452df08f","from":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"to":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC","bHHdqM8XWDHee2oyzydwUJKEE2BjofEtZH"],"total_amount":"1998.71298873","spent_by_me":"0","received_by_me":"0","my_balance_change":"0","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"b4718ce94aa43f9073ab0b70c18ab4ea4b587338fb7110f20ff7d1bb452df08f"}}},{"timestamp":1588244053939,"event":{"type":"TakerPaymentWaitConfirmStarted"}},{"timestamp":1588244068951,"event":{"type":"TakerPaymentValidatedAndConfirmed"}},{"timestamp":1588244068959,"event":{"type":"TakerPaymentSpent","data":{"tx_hex":"0400008085202f89018ff02d45bbd1f70ff21071fb3873584beab48ac1700bab73903fa44ae98c71b400000000d747304402204f0d641a3916e54d6788744c3229110a431eff18634c66fbd1741f9ca7dba99d02202315ee1d9317cc4d5d75d01f066f2c8a59876f790106d310144cdc03c25f985e0120dedf3c8dcfff9ee2787b4bf211f960fd044fdab7fa8e922ef613a0115848a498004c6b6304bcccaa5eb1752102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ac6782012088a9149304bce3196f344b2a22dc99db406e95ab6f3107882102987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4dac68ffffffff0130e07648170000001976a91412c553e8469363f2d30268c475af1e9186cc90af88ac54a0aa5e000000000000000000000000000000","tx_hash":"e7aed7a77e47b44dc9d12166589bbade70faea10b64888f73ed4be04bcc9f9a9","from":["bHHdqM8XWDHee2oyzydwUJKEE2BjofEtZH"],"to":["RAzSdYQhjCFdyhjrBz1AZQDVg3Hu8DrzYc"],"total_amount":"999.99999","spent_by_me":"0","received_by_me":"999.99998","my_balance_change":"999.99998","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"e7aed7a77e47b44dc9d12166589bbade70faea10b64888f73ed4be04bcc9f9a9"}}},{"timestamp":1588244068960,"event":{"type":"Finished"}}],"maker_amount":"999.99999","maker_coin":"MYCOIN","taker_amount":"999.99999","taker_coin":"MYCOIN1","gui":"nogui","mm_version":"UNKNOWN","success_events":["Started","Negotiated","TakerFeeValidated","MakerPaymentSent","TakerPaymentReceived","TakerPaymentWaitConfirmStarted","TakerPaymentValidatedAndConfirmed","TakerPaymentSpent","Finished"],"error_events":["StartFailed","NegotiateFailed","TakerFeeValidateFailed","MakerPaymentTransactionFailed","MakerPaymentDataSendFailed","MakerPaymentWaitConfirmFailed","TakerPaymentValidateFailed","TakerPaymentWaitConfirmFailed","TakerPaymentSpendFailed","MakerPaymentWaitRefundStarted","MakerPaymentRefunded","MakerPaymentRefundFailed"]}"#; + +const UNFINISHED_TAKER_SWAP: &str = r#"{"type":"Taker","uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","events":[{"timestamp":1588244036253,"event":{"type":"Started","data":{"taker_coin":"MYCOIN1","maker_coin":"MYCOIN","maker":"987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4d","my_persistent_pub":"02859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521","lock_duration":7800,"maker_amount":"999.99999","taker_amount":"999.99999","maker_payment_confirmations":1,"maker_payment_requires_nota":false,"taker_payment_confirmations":1,"taker_payment_requires_nota":false,"taker_payment_lock":1588251836,"uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","started_at":1588244036,"maker_payment_wait":1588247156,"maker_coin_start_block":12,"taker_coin_start_block":11}}},{"timestamp":1588244038239,"event":{"type":"Negotiated","data":{"maker_payment_locktime":1588259636,"maker_pubkey":"02987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4d","secret_hash":"9304bce3196f344b2a22dc99db406e95ab6f3107"}}},{"timestamp":1588244038271,"event":{"type":"TakerFeeSent","data":{"tx_hex":"0400008085202f8901bdde9bca02870787441f6068e4c2a869a3aac3d1d0925f6a6e27874343544d0a010000006a47304402206694a794693b55fbe8205cb1cbb992d92fa2a9a851763ad1bf1628c16deaf73e02203cfff465504bdd6c51e8fbd45dd2e1187142fdc29e82f36f577616d9d6097d7a012102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ffffffff02dfceab07000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac39fd41892e0000001976a914d24e799df360da3ca3158d63b89ffaff27722c1588ac46aeaa5e000000000000000000000000000000","tx_hash":"7118d7484d1cbfdd6126673a848a228856f8748d946f6a9a440c90f0d62e27c6","from":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"to":["RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf","RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"total_amount":"2000","spent_by_me":"2000","received_by_me":"1998.71298873","my_balance_change":"-1.28701127","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"7118d7484d1cbfdd6126673a848a228856f8748d946f6a9a440c90f0d62e27c6"}}}],"maker_amount":"999.99999","maker_coin":"MYCOIN","taker_amount":"999.99999","taker_coin":"MYCOIN1","gui":"nogui","mm_version":"UNKNOWN","success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundFailed"]}"#; +const FINISHED_TAKER_SWAP: &str = r#"{"type":"Taker","uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","events":[{"timestamp":1588244036253,"event":{"type":"Started","data":{"taker_coin":"MYCOIN1","maker_coin":"MYCOIN","maker":"987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4d","my_persistent_pub":"02859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521","lock_duration":7800,"maker_amount":"999.99999","taker_amount":"999.99999","maker_payment_confirmations":1,"maker_payment_requires_nota":false,"taker_payment_confirmations":1,"taker_payment_requires_nota":false,"taker_payment_lock":1588251836,"uuid":"5acb0e63-8b26-469e-81df-7dd9e4a9ad15","started_at":1588244036,"maker_payment_wait":1588247156,"maker_coin_start_block":12,"taker_coin_start_block":11}}},{"timestamp":1588244038239,"event":{"type":"Negotiated","data":{"maker_payment_locktime":1588259636,"maker_pubkey":"02987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4d","secret_hash":"9304bce3196f344b2a22dc99db406e95ab6f3107"}}},{"timestamp":1588244038271,"event":{"type":"TakerFeeSent","data":{"tx_hex":"0400008085202f8901bdde9bca02870787441f6068e4c2a869a3aac3d1d0925f6a6e27874343544d0a010000006a47304402206694a794693b55fbe8205cb1cbb992d92fa2a9a851763ad1bf1628c16deaf73e02203cfff465504bdd6c51e8fbd45dd2e1187142fdc29e82f36f577616d9d6097d7a012102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ffffffff02dfceab07000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac39fd41892e0000001976a914d24e799df360da3ca3158d63b89ffaff27722c1588ac46aeaa5e000000000000000000000000000000","tx_hash":"7118d7484d1cbfdd6126673a848a228856f8748d946f6a9a440c90f0d62e27c6","from":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"to":["RThtXup6Zo7LZAi8kRWgjAyi1s4u6U9Cpf","RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"total_amount":"2000","spent_by_me":"2000","received_by_me":"1998.71298873","my_balance_change":"-1.28701127","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"7118d7484d1cbfdd6126673a848a228856f8748d946f6a9a440c90f0d62e27c6"}}},{"timestamp":1588244038698,"event":{"type":"MakerPaymentReceived","data":{"tx_hex":"0400008085202f890163cc0aceb3b432f84c1407991a0389b74b7842f030aa261203aa0b4b9a9a15fd010000006b483045022100f3239794b7b0e1c75aae084a65535270f154231ce86a4739976ff69eeff1ebd402206c0aeb28b7b4b2e77e98b3c3efd9a20d85c2cd0627acc3f45d577c0e88c34fb4012102987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4dffffffff0118e476481700000017a914dc4ed4686174503b85374bfb0aefe07a9fb37bcf8746aeaa5e000000000000000000000000000000","tx_hash":"9a4c0b3f85ed0bb24dc9575ce7c2fd6bc50ad9d37c91c478946f9c33d15abfdf","from":["RAzSdYQhjCFdyhjrBz1AZQDVg3Hu8DrzYc"],"to":["bYp9ncp3V7FYsymipriVbdd3QL72hK9hio"],"total_amount":"1000","spent_by_me":"0","received_by_me":"0","my_balance_change":"0","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN","internal_id":"9a4c0b3f85ed0bb24dc9575ce7c2fd6bc50ad9d37c91c478946f9c33d15abfdf"}}},{"timestamp":1588244038699,"event":{"type":"MakerPaymentWaitConfirmStarted"}},{"timestamp":1588244053712,"event":{"type":"MakerPaymentValidatedAndConfirmed"}},{"timestamp":1588244053745,"event":{"type":"TakerPaymentSent","data":{"tx_hex":"0400008085202f8901c6272ed6f0900c449a6a6f948d74f85688228a843a672661ddbf1c4d48d71871010000006b483045022100c37627385c66b7bdf466b4dd4e7095b7551e6f5ea35f9bcc6344eb629f2edcb202203280eaba64b4d72010500166fab62cf34a687a516b2fe83d4eceaf8572cb37a7012102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ffffffff0218e476481700000017a91431ff75ed72cd135a7ce50d121e71efc37066b9f9873915cb40170000001976a914d24e799df360da3ca3158d63b89ffaff27722c1588ac55aeaa5e000000000000000000000000000000","tx_hash":"b4718ce94aa43f9073ab0b70c18ab4ea4b587338fb7110f20ff7d1bb452df08f","from":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"to":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC","bHHdqM8XWDHee2oyzydwUJKEE2BjofEtZH"],"total_amount":"1998.71298873","spent_by_me":"1998.71298873","received_by_me":"998.71298873","my_balance_change":"-1000","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"b4718ce94aa43f9073ab0b70c18ab4ea4b587338fb7110f20ff7d1bb452df08f"}}},{"timestamp":1588244078860,"event":{"type":"TakerPaymentSpent","data":{"transaction":{"tx_hex":"0400008085202f89018ff02d45bbd1f70ff21071fb3873584beab48ac1700bab73903fa44ae98c71b400000000d747304402204f0d641a3916e54d6788744c3229110a431eff18634c66fbd1741f9ca7dba99d02202315ee1d9317cc4d5d75d01f066f2c8a59876f790106d310144cdc03c25f985e0120dedf3c8dcfff9ee2787b4bf211f960fd044fdab7fa8e922ef613a0115848a498004c6b6304bcccaa5eb1752102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ac6782012088a9149304bce3196f344b2a22dc99db406e95ab6f3107882102987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4dac68ffffffff0130e07648170000001976a91412c553e8469363f2d30268c475af1e9186cc90af88ac54a0aa5e000000000000000000000000000000","tx_hash":"e7aed7a77e47b44dc9d12166589bbade70faea10b64888f73ed4be04bcc9f9a9","from":["bHHdqM8XWDHee2oyzydwUJKEE2BjofEtZH"],"to":["RAzSdYQhjCFdyhjrBz1AZQDVg3Hu8DrzYc"],"total_amount":"999.99999","spent_by_me":"0","received_by_me":"0","my_balance_change":"0","block_height":29,"timestamp":1588244070,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN1","internal_id":"e7aed7a77e47b44dc9d12166589bbade70faea10b64888f73ed4be04bcc9f9a9"},"secret":"dedf3c8dcfff9ee2787b4bf211f960fd044fdab7fa8e922ef613a0115848a498"}}},{"timestamp":1588244078870,"event":{"type":"MakerPaymentSpent","data":{"tx_hex":"0400008085202f8901dfbf5ad1339c6f9478c4917cd3d90ac56bfdc2e75c57c94db20bed853f0b4c9a00000000d848304502210092535c081325ba5261699d7cfd4c503fb6125dde86389b83f40f3e2c006039bb022063cfd72aa15558dee874cac08b22dbcf11d3f06c8e48b0ddaf75b86887d604410120dedf3c8dcfff9ee2787b4bf211f960fd044fdab7fa8e922ef613a0115848a498004c6b630434ebaa5eb1752102987d5f82205a55d789616f470ae9df48537f050ee050d501aa316651642a0a4dac6782012088a9149304bce3196f344b2a22dc99db406e95ab6f3107882102859a80b83941e4e8ff2f511080e3ea5021db4ba95caec30eb37864e71ae73521ac68ffffffff0130e07648170000001976a914d24e799df360da3ca3158d63b89ffaff27722c1588ac5ea0aa5e000000000000000000000000000000","tx_hash":"caea128b1c85a88abd5924e512780ee18952dadc217b0c06f4b2820eb71d03bc","from":["bYp9ncp3V7FYsymipriVbdd3QL72hK9hio"],"to":["RUTBzLtJNTn89Wkb6oZocbesKrjBDTRMrC"],"total_amount":"999.99999","spent_by_me":"0","received_by_me":"999.99998","my_balance_change":"999.99998","block_height":0,"timestamp":0,"fee_details":{"amount":"0.00001"},"coin":"MYCOIN","internal_id":"caea128b1c85a88abd5924e512780ee18952dadc217b0c06f4b2820eb71d03bc"}}},{"timestamp":1588244078871,"event":{"type":"Finished"}}],"maker_amount":"999.99999","maker_coin":"MYCOIN","taker_amount":"999.99999","taker_coin":"MYCOIN1","gui":"nogui","mm_version":"UNKNOWN","success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundFailed"]}"#; + +fn swap_file_lock_prevents_double_swap_start_on_kick_start(swap_json: &str) { + let (_, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000); + let addr_hash = addr_hash_for_privkey(bob_priv_key); + let db_folder = new_mm2_temp_folder_path(None).join("DB"); + let swaps_db_folder = db_folder.join(addr_hash).join("SWAPS").join("MY"); + std::fs::create_dir_all(&swaps_db_folder).unwrap(); + let swap_path = swaps_db_folder.join("5acb0e63-8b26-469e-81df-7dd9e4a9ad15.json"); + let swap_lock_path = swaps_db_folder.join("5acb0e63-8b26-469e-81df-7dd9e4a9ad15.lock"); + let lock = FileLock::lock(swap_lock_path, 10.).unwrap().unwrap(); + std::fs::write(&swap_path, swap_json).unwrap(); + thread::spawn(move || loop { + // touch the lock file to simulate that other process is running the swap already + lock.touch().unwrap(); + thread::sleep(Duration::from_secs(10)); + }); + + let coins = json! ([ + {"coin":"MYCOIN","asset":"MYCOIN","txversion":4,"overwintered":1,"txfee":1000}, + {"coin":"MYCOIN1","asset":"MYCOIN1","txversion":4,"overwintered":1,"txfee":1000}, + ]); + let bob_conf = json! ({ + "gui": "nogui", + "netid": 9000, + "dht": "on", // Enable DHT without delay. + "passphrase": format!("0x{}", hex::encode(bob_priv_key)), + "coins": coins, + "rpc_password": "pass", + "i_am_seed": true, + "dbdir": db_folder.to_str().unwrap(), + }); + let mut mm_bob = unwrap! (MarketMakerIt::start ( + bob_conf, + "pass".to_string(), + None, + )); + let (_bob_dump_log, _bob_dump_dashboard) = mm_dump (&mm_bob.log_path); + unwrap! (block_on (mm_bob.wait_for_log (22., |log| log.contains (">>>>>>>>> DEX stats ")))); + log!([block_on(enable_native(&mm_bob, "MYCOIN", vec![]))]); + log!([block_on(enable_native(&mm_bob, "MYCOIN1", vec![]))]); + unwrap! (block_on (mm_bob.wait_for_log (22., |log| log.contains ("Kick starting the swap 5acb0e63-8b26-469e-81df-7dd9e4a9ad15")))); + unwrap! (block_on (mm_bob.wait_for_log (50., |log| log.contains ("Swap 5acb0e63-8b26-469e-81df-7dd9e4a9ad15 file lock is acquired by another process/thread, aborting")))); +} + +#[test] +fn test_swap_file_lock_prevents_double_swap_start_on_kick_start_maker() { + swap_file_lock_prevents_double_swap_start_on_kick_start(UNFINISHED_MAKER_SWAP); +} + +#[test] +fn test_swap_file_lock_prevents_double_swap_start_on_kick_start_taker() { + swap_file_lock_prevents_double_swap_start_on_kick_start(UNFINISHED_TAKER_SWAP); +} + +#[test] +fn test_swaps_should_kick_start_if_process_was_killed() { + let (_, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000); + let (_, alice_priv_key) = generate_coin_with_random_privkey("MYCOIN1", 2000); + let coins = json! ([ + {"coin":"MYCOIN","asset":"MYCOIN","txversion":4,"overwintered":1,"txfee":1000}, + {"coin":"MYCOIN1","asset":"MYCOIN1","txversion":4,"overwintered":1,"txfee":1000}, + ]); + let mut bob_conf = json! ({ + "gui": "nogui", + "netid": 9000, + "dht": "on", // Enable DHT without delay. + "passphrase": format!("0x{}", hex::encode(bob_priv_key)), + "coins": coins, + "rpc_password": "pass", + "i_am_seed": true, + }); + let mut mm_bob = unwrap! (MarketMakerIt::start ( + bob_conf.clone(), + "pass".to_string(), + None, + )); + let (_bob_dump_log, _bob_dump_dashboard) = mm_dump (&mm_bob.log_path); + unwrap! (block_on (mm_bob.wait_for_log (22., |log| log.contains (">>>>>>>>> DEX stats ")))); + + let mut alice_conf = json! ({ + "gui": "nogui", + "netid": 9000, + "dht": "on", // Enable DHT without delay. + "passphrase": format!("0x{}", hex::encode(alice_priv_key)), + "coins": coins, + "rpc_password": "pass", + "seednodes": vec![format!("{}", mm_bob.ip)], + }); + let mut mm_alice = unwrap! (MarketMakerIt::start ( + alice_conf.clone(), + "pass".to_string(), + None, + )); + let (_alice_dump_log, _alice_dump_dashboard) = mm_dump (&mm_alice.log_path); + unwrap! (block_on (mm_alice.wait_for_log (22., |log| log.contains (">>>>>>>>> DEX stats ")))); + + log!([block_on(enable_native(&mm_bob, "MYCOIN", vec![]))]); + log!([block_on(enable_native(&mm_bob, "MYCOIN1", vec![]))]); + log!([block_on(enable_native(&mm_alice, "MYCOIN", vec![]))]); + log!([block_on(enable_native(&mm_alice, "MYCOIN1", vec![]))]); + let rc = unwrap! (block_on (mm_bob.rpc (json! ({ + "userpass": mm_bob.userpass, + "method": "setprice", + "base": "MYCOIN", + "rel": "MYCOIN1", + "price": 1, + "max": true, + })))); + assert! (rc.0.is_success(), "!setprice: {}", rc.1); + let rc = unwrap!(block_on (mm_alice.rpc (json! ({ + "userpass": mm_alice.userpass, + "method": "buy", + "base": "MYCOIN", + "rel": "MYCOIN1", + "price": 1, + "volume": "1", + })))); + assert!(rc.0.is_success(), "!buy: {}", rc.1); + let buy: Json = json::from_str(&rc.1).unwrap(); + let uuid = buy["result"]["uuid"].as_str().unwrap().to_owned(); + unwrap!(block_on (mm_bob.wait_for_log (22., + |log| log.contains (&format!("Entering the maker_swap_loop MYCOIN/MYCOIN1 with uuid: {}", uuid)) + ))); + unwrap!(block_on (mm_alice.wait_for_log (22., + |log| log.contains (&format!("Entering the taker_swap_loop MYCOIN/MYCOIN1 with uuid: {}", uuid)) + ))); + log!("Give swaps some time to start and save the first event by sleeping for 4 seconds"); + thread::sleep(Duration::from_secs(4)); + // mm_bob using same DB dir that should kick start the swap + bob_conf["dbdir"] = mm_bob.folder.join("DB").to_str().unwrap().into(); + bob_conf["log"] = mm_bob.folder.join("mm2_dup.log").to_str().unwrap().into(); + // dropping instead of graceful stop to retain swap file locks + drop(mm_bob); + let mut mm_bob_dup = unwrap! (MarketMakerIt::start ( + bob_conf, + "pass".to_string(), + None, + )); + let (_bob_dup_dump_log, _bob_dup_dump_dashboard) = mm_dump (&mm_bob_dup.log_path); + unwrap! (block_on (mm_bob_dup.wait_for_log (22., |log| log.contains (">>>>>>>>> DEX stats ")))); + log!([block_on(enable_native(&mm_bob_dup, "MYCOIN", vec![]))]); + log!([block_on(enable_native(&mm_bob_dup, "MYCOIN1", vec![]))]); + + unwrap! (block_on (mm_bob_dup.wait_for_log (50., + |log| log.contains (&format!("Swap {} kick started.", uuid)) + ))); + + // mm_alice using same DB dir that should kick start the swap + alice_conf["dbdir"] = mm_alice.folder.join("DB").to_str().unwrap().into(); + alice_conf["log"] = mm_alice.folder.join("mm2_dup.log").to_str().unwrap().into(); + // dropping instead of graceful stop to retain swap file locks + drop(mm_alice); + + let mut mm_alice_dup = unwrap! (MarketMakerIt::start ( + alice_conf, + "pass".to_string(), + None, + )); + let (_alice_dup_dump_log, _alice_dup_dump_dashboard) = mm_dump (&mm_alice_dup.log_path); + unwrap! (block_on (mm_alice_dup.wait_for_log (22., |log| log.contains (">>>>>>>>> DEX stats ")))); + log!([block_on(enable_native(&mm_alice_dup, "MYCOIN", vec![]))]); + log!([block_on(enable_native(&mm_alice_dup, "MYCOIN1", vec![]))]); + + unwrap! (block_on (mm_alice_dup.wait_for_log (50., + |log| log.contains (&format!("Swap {} kick started.", uuid)) + ))); +} + +fn addr_hash_for_privkey(priv_key: [u8; 32]) -> String { + let private = Private { + prefix: 1, + secret: priv_key.into(), + compressed: true, + checksum_type: ChecksumType::DSHA256, + }; + let key_pair = KeyPair::from_private(private).unwrap(); + hex::encode(&*key_pair.public().address_hash()) +} + +fn swap_should_not_kick_start_if_finished_during_waiting_for_file_lock( + unfinished_swap_json: &str, + finished_swap_json: &str, +) { + let (_, bob_priv_key) = generate_coin_with_random_privkey("MYCOIN", 1000); + let addr_hash = addr_hash_for_privkey(bob_priv_key); + let db_folder = new_mm2_temp_folder_path(None).join("DB"); + let swaps_db_folder = db_folder.join(addr_hash).join("SWAPS").join("MY"); + std::fs::create_dir_all(&swaps_db_folder).unwrap(); + let swap_path = swaps_db_folder.join("5acb0e63-8b26-469e-81df-7dd9e4a9ad15.json"); + let swap_lock_path = swaps_db_folder.join("5acb0e63-8b26-469e-81df-7dd9e4a9ad15.lock"); + let _lock = FileLock::lock(swap_lock_path, 10.).unwrap().unwrap(); + std::fs::write(&swap_path, unfinished_swap_json).unwrap(); + + let coins = json! ([ + {"coin":"MYCOIN","asset":"MYCOIN","txversion":4,"overwintered":1,"txfee":1000}, + {"coin":"MYCOIN1","asset":"MYCOIN1","txversion":4,"overwintered":1,"txfee":1000}, + ]); + let bob_conf = json! ({ + "gui": "nogui", + "netid": 9000, + "dht": "on", // Enable DHT without delay. + "passphrase": format!("0x{}", hex::encode(bob_priv_key)), + "coins": coins, + "rpc_password": "pass", + "i_am_seed": true, + "dbdir": db_folder.to_str().unwrap(), + }); + let mut mm_bob = unwrap! (MarketMakerIt::start ( + bob_conf, + "pass".to_string(), + None, + )); + let (_bob_dump_log, _bob_dump_dashboard) = mm_dump (&mm_bob.log_path); + unwrap! (block_on (mm_bob.wait_for_log (22., |log| log.contains (">>>>>>>>> DEX stats ")))); + log!([block_on(enable_native(&mm_bob, "MYCOIN", vec![]))]); + log!([block_on(enable_native(&mm_bob, "MYCOIN1", vec![]))]); + unwrap! (block_on (mm_bob.wait_for_log (22., |log| log.contains ("Kick starting the swap 5acb0e63-8b26-469e-81df-7dd9e4a9ad15")))); + thread::sleep(Duration::from_secs(10)); + std::fs::write(swap_path, finished_swap_json).unwrap(); + unwrap! (block_on (mm_bob.wait_for_log (50., |log| log.contains ("Swap 5acb0e63-8b26-469e-81df-7dd9e4a9ad15 has been finished already, aborting.")))); +} + +#[test] +fn maker_swap_should_not_kick_start_if_finished_during_waiting_for_file_lock() { + swap_should_not_kick_start_if_finished_during_waiting_for_file_lock( + UNFINISHED_MAKER_SWAP, + FINISHED_MAKER_SWAP, + ); +} + +#[test] +fn taker_swap_should_not_kick_start_if_finished_during_waiting_for_file_lock() { + swap_should_not_kick_start_if_finished_during_waiting_for_file_lock( + UNFINISHED_TAKER_SWAP, + FINISHED_TAKER_SWAP, + ); +} diff --git a/mm2src/lp_native_dex.rs b/mm2src/lp_native_dex.rs index e48bdf23ab..0b232520db 100644 --- a/mm2src/lp_native_dex.rs +++ b/mm2src/lp_native_dex.rs @@ -48,7 +48,7 @@ use crate::common::mm_ctx::{MmCtx, MmArc}; use crate::common::privkey::key_pair_from_seed; use crate::mm2::lp_network::{lp_command_q_loop, start_seednode_loop, start_client_p2p_loop}; use crate::mm2::lp_ordermatch::{lp_ordermatch_loop, lp_trade_command, migrate_saved_orders, orders_kick_start}; -use crate::mm2::lp_swap::swap_kick_starts; +use crate::mm2::lp_swap::{running_swaps_num, swap_kick_starts}; use crate::mm2::rpc::{spawn_rpc}; /// Process a previously queued command that wasn't handled by the RPC `dispatcher`. @@ -1345,5 +1345,7 @@ pub async fn lp_init (mypubport: u16, ctx: MmArc) -> Result<(), String> { // In the mobile version we might depend on `lp_init` staying around until the context stops. loop {if ctx.is_stopping() {break}; Timer::sleep (0.2) .await} + // wait for swaps to stop + loop { if running_swaps_num(&ctx) == 0 { break }; Timer::sleep (0.2) .await } Ok(()) } diff --git a/mm2src/lp_ordermatch.rs b/mm2src/lp_ordermatch.rs index c44fcb3444..232e63a240 100644 --- a/mm2src/lp_ordermatch.rs +++ b/mm2src/lp_ordermatch.rs @@ -50,7 +50,7 @@ use std::sync::{Arc, Mutex}; use uuid::Uuid; use crate::mm2::lp_swap::{dex_fee_amount, get_locked_amount, is_pubkey_banned, MakerSwap, - run_maker_swap, run_taker_swap, TakerSwap}; + RunMakerSwapInput, RunTakerSwapInput, run_maker_swap, run_taker_swap, TakerSwap}; #[cfg(test)] #[cfg(feature = "native")] @@ -361,7 +361,7 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch) { let my_persistent_pub = unwrap!(compressed_pub_key_from_priv_raw(&privkey[..], ChecksumType::DSHA256)); let uuid = maker_match.request.uuid.to_string(); - log!("Entering the maker_swap_loop " (maker_coin.ticker()) "/" (taker_coin.ticker())); + log!("Entering the maker_swap_loop " (maker_coin.ticker()) "/" (taker_coin.ticker()) " with uuid: " (uuid)); let maker_swap = MakerSwap::new( ctx.clone(), alice.into(), @@ -372,7 +372,7 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch) { my_persistent_pub, uuid, ); - run_maker_swap(maker_swap, None).await; + run_maker_swap(RunMakerSwapInput::StartNew(maker_swap), ctx).await; }); } @@ -410,9 +410,9 @@ fn lp_connected_alice(ctx: MmArc, taker_match: TakerMatch) { let taker_amount = taker_match.reserved.get_rel_amount().into(); let uuid = taker_match.reserved.taker_order_uuid.to_string(); - log!("Entering the taker_swap_loop " (maker_coin.ticker()) "/" (taker_coin.ticker())); + log!("Entering the taker_swap_loop " (maker_coin.ticker()) "/" (taker_coin.ticker()) " with uuid: " (uuid)); let taker_swap = TakerSwap::new( - ctx, + ctx.clone(), maker.into(), maker_coin, taker_coin, @@ -421,7 +421,7 @@ fn lp_connected_alice(ctx: MmArc, taker_match: TakerMatch) { my_persistent_pub, uuid, ); - run_taker_swap(taker_swap, None).await + run_taker_swap(RunTakerSwapInput::StartNew(taker_swap), ctx).await }); } diff --git a/mm2src/lp_swap.rs b/mm2src/lp_swap.rs index be3045545f..966b3c1afe 100644 --- a/mm2src/lp_swap.rs +++ b/mm2src/lp_swap.rs @@ -57,17 +57,21 @@ #![allow(uncommon_codepoints)] #![cfg_attr(not(feature = "native"), allow(dead_code))] +use async_std::{sync as async_std_sync}; use bigdecimal::BigDecimal; use coins::{lp_coinfind, TransactionEnum}; -use common::{block_on, read_dir, rpc_response, slurp, write, HyRes}; -use common::mm_ctx::{from_ctx, MmArc}; +use common::{ + block_on, HyRes, read_dir, rpc_response, slurp, write, + executor::spawn, + mm_ctx::{from_ctx, MmArc} +}; use http::Response; use primitives::hash::{H160, H256, H264}; use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json}; use serde_json::{self as json, Value as Json}; use std::collections::{HashSet, HashMap}; use std::ffi::OsStr; -use std::path::PathBuf; +use std::path::{PathBuf}; use std::sync::{Arc, Mutex, Weak}; use std::thread; use std::time::Duration; @@ -131,8 +135,8 @@ mod taker_swap; use maker_swap::{MakerSavedSwap, MakerSwapEvent, stats_maker_swap_file_path}; use taker_swap::{TakerSavedSwap, TakerSwapEvent, stats_taker_swap_file_path}; -pub use maker_swap::{MakerSwap, run_maker_swap}; -pub use taker_swap::{TakerSwap, run_taker_swap}; +pub use maker_swap::{MakerSwap, RunMakerSwapInput, run_maker_swap}; +pub use taker_swap::{RunTakerSwapInput, TakerSwap, run_taker_swap}; /// Includes the grace time we add to the "normal" timeouts /// in order to give different and/or heavy communication channels a chance. @@ -204,15 +208,34 @@ struct BanReason { struct SwapsContext { running_swaps: Mutex>>, banned_pubkeys: Mutex>, + /// The clonable receiver of multi-consumer async channel awaiting for shutdown_tx.send() to be + /// invoked to stop all running swaps. + /// MM2 is used as static lib on some platforms e.g. iOS so it doesn't run as separate process. + /// So when stop was invoked the swaps could stay running on shared executors causing + /// Very unpleasant consequences + shutdown_rx: async_std_sync::Receiver<()>, } impl SwapsContext { /// Obtains a reference to this crate context, creating it if necessary. fn from_ctx (ctx: &MmArc) -> Result, String> { Ok (try_s! (from_ctx (&ctx.swaps_ctx, move || { + let (shutdown_tx, shutdown_rx) = async_std_sync::channel(1); + let mut shutdown_tx = Some(shutdown_tx); + ctx.on_stop (Box::new (move || { + if let Some (shutdown_tx) = shutdown_tx.take() { + log! ("on_stop] firing shutdown_tx!"); + spawn(async move { + shutdown_tx.send(()).await; + }); + Ok(()) + } else {ERR! ("on_stop callback called twice!")} + })); + Ok (SwapsContext { running_swaps: Mutex::new(vec![]), banned_pubkeys: Mutex::new(HashMap::new()), + shutdown_rx, }) }))) } @@ -259,6 +282,21 @@ pub fn get_locked_amount(ctx: &MmArc, coin: &str) -> BigDecimal { ) } +/// Get number of currently running swaps +pub fn running_swaps_num(ctx: &MmArc) -> u64 { + let swap_ctx = unwrap!(SwapsContext::from_ctx(&ctx)); + let swaps = unwrap!(swap_ctx.running_swaps.lock()); + swaps.iter().fold( + 0, + |total, swap| { + match swap.upgrade() { + Some(_) => total + 1, + None => total, + } + } + ) +} + /// Get total amount of selected coin locked by all currently ongoing swaps except the one with selected uuid fn get_locked_amount_by_other_swaps(ctx: &MmArc, except_uuid: &str, coin: &str) -> BigDecimal { let swap_ctx = unwrap!(SwapsContext::from_ctx(&ctx)); @@ -634,7 +672,7 @@ pub fn swap_kick_starts(ctx: MmArc) -> HashSet { match json::from_slice::(&unwrap!(slurp(&path))) { Ok(swap) => { if !swap.is_finished() { - log!("Kick starting the swap " [swap.uuid()]); + log!("Kick starting the swap " (swap.uuid())); let maker_coin_ticker = match swap.maker_coin_ticker() { Ok(t) => t, Err(e) => { @@ -654,55 +692,47 @@ pub fn swap_kick_starts(ctx: MmArc) -> HashSet { thread::spawn({ let ctx = ctx.clone(); move || { - let mut taker_coin; - loop { - taker_coin = match lp_coinfind(&ctx, &taker_coin_ticker) { - Ok(c) => c, + let taker_coin = loop { + match lp_coinfind(&ctx, &taker_coin_ticker) { + Ok(Some(c)) => break c, + Ok(None) => { + log!("Can't kickstart the swap " (swap.uuid()) " until the coin " (taker_coin_ticker) " is activated"); + thread::sleep(Duration::from_secs(5)); + }, Err(e) => { log!("Error " (e) " on " (taker_coin_ticker) " find attempt"); return; - } + }, }; - if taker_coin.is_some() { - break; - } - log!("Can't kickstart the swap " (swap.uuid()) " until the coin " (taker_coin_ticker) " is activated"); - thread::sleep(Duration::from_secs(5)); }; - let mut maker_coin; - loop { - maker_coin = match lp_coinfind(&ctx, &maker_coin_ticker) { - Ok(c) => c, + let maker_coin = loop { + match lp_coinfind(&ctx, &maker_coin_ticker) { + Ok(Some(c)) => break c, + Ok(None) => { + log!("Can't kickstart the swap " (swap.uuid()) " until the coin " (maker_coin_ticker) " is activated"); + thread::sleep(Duration::from_secs(5)); + } Err(e) => { log!("Error " (e) " on " (maker_coin_ticker) " find attempt"); return; } }; - if maker_coin.is_some() { - break; - } - log!("Can't kickstart the swap " (swap.uuid()) " until the coin " (maker_coin_ticker) " is activated"); - thread::sleep(Duration::from_secs(5)); }; match swap { - SavedSwap::Maker(swap) => match MakerSwap::load_from_saved( - ctx, - maker_coin.unwrap(), - taker_coin.unwrap(), - swap, - ) { - Ok((maker, command)) => block_on(run_maker_swap(maker, command)), - Err(e) => log!([e]), + SavedSwap::Maker(saved_swap) => { + block_on(run_maker_swap(RunMakerSwapInput::KickStart { + maker_coin, + taker_coin, + swap_uuid: saved_swap.uuid, + }, ctx)); }, - SavedSwap::Taker(swap) => match TakerSwap::load_from_saved( - ctx, - maker_coin.unwrap(), - taker_coin.unwrap(), - swap, - ) { - Ok((taker, command)) => block_on(run_taker_swap(taker, command)), - Err(e) => log!([e]), + SavedSwap::Taker(saved_swap) => { + block_on(run_taker_swap(RunTakerSwapInput::KickStart { + maker_coin, + taker_coin, + swap_uuid: saved_swap.uuid, + }, ctx)); }, } } diff --git a/mm2src/lp_swap/maker_swap.rs b/mm2src/lp_swap/maker_swap.rs index 5c5a06cc8f..c0c0f2e1aa 100644 --- a/mm2src/lp_swap/maker_swap.rs +++ b/mm2src/lp_swap/maker_swap.rs @@ -3,13 +3,19 @@ use atomic::Atomic; use bigdecimal::BigDecimal; use bitcrypto::dhash160; -use common::executor::Timer; -use common::{bits256, now_ms, now_float, slurp, write, MM_VERSION}; -use common::mm_ctx::MmArc; +use common::{ + bits256, now_ms, now_float, slurp, write, MM_VERSION, + executor::Timer, + file_lock::FileLock, + mm_ctx::MmArc, +}; use coins::{FoundSwapTxSpend, MmCoinEnum, TradeInfo, TransactionDetails}; use crc::crc32; -use futures::compat::Future01CompatExt; -use futures::future::Either; +use futures::{ + FutureExt, select, + compat::Future01CompatExt, + future::Either, +}; use futures01::Future; use parking_lot::Mutex as PaMutex; use peers::FixedValidator; @@ -22,7 +28,7 @@ use std::path::PathBuf; use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::sync::atomic::Ordering; use super::{ban_pubkey, broadcast_my_swap_status, dex_fee_amount, get_locked_amount_by_other_swaps, - lp_atomic_locktime, my_swap_file_path, + lp_atomic_locktime, my_swaps_dir, my_swap_file_path, AtomicSwap, LockedAmount, MySwapInfo, RecoveredSwap, RecoveredSwapAction, SavedSwap, SwapsContext, SwapError, SwapNegotiationData, BASIC_COMM_TIMEOUT, WAIT_CONFIRM_INTERVAL}; @@ -187,7 +193,7 @@ impl MakerSwap { uuid: String, ) -> Self { MakerSwap { - ctx: ctx.clone(), + ctx, maker_coin, taker_coin, maker_amount, @@ -699,11 +705,26 @@ impl MakerSwap { )) } + pub fn load_from_db_by_uuid( + ctx: MmArc, + maker_coin: MmCoinEnum, + taker_coin: MmCoinEnum, + swap_uuid: &str, + ) -> Result<(Self, Option), String> { + let path = my_swap_file_path(&ctx, swap_uuid); + let saved: SavedSwap = try_s!(json::from_slice(&try_s!(slurp(&path)))); + let saved = match saved { + SavedSwap::Maker(swap) => swap, + SavedSwap::Taker(_) => return ERR!("Can not load MakerSwap from SavedSwap::Taker uuid: {}", swap_uuid), + }; + Self::load_from_saved(ctx, maker_coin, taker_coin, saved) + } + pub fn load_from_saved( ctx: MmArc, maker_coin: MmCoinEnum, taker_coin: MmCoinEnum, - saved: MakerSavedSwap + saved: MakerSavedSwap, ) -> Result<(Self, Option), String> { if saved.events.is_empty() { return ERR!("Can't restore swap from empty events set"); @@ -1007,13 +1028,81 @@ impl MakerSavedSwap { } } +pub enum RunMakerSwapInput { + StartNew(MakerSwap), + KickStart { + maker_coin: MmCoinEnum, + taker_coin: MmCoinEnum, + swap_uuid: String, + }, +} + +impl RunMakerSwapInput { + fn uuid(&self) -> &str { + match self { + RunMakerSwapInput::StartNew(swap) => &swap.uuid, + RunMakerSwapInput::KickStart { swap_uuid, .. } => &swap_uuid, + } + } +} + /// Starts the maker swap and drives it to completion (until None next command received). /// Panics in case of command or event apply fails, not sure yet how to handle such situations /// because it's usually means that swap is in invalid state which is possible only if there's developer error. /// Every produced event is saved to local DB. Swap status is broadcasted to P2P network after completion. -pub async fn run_maker_swap(swap: MakerSwap, initial_command: Option) { - let mut command = initial_command.unwrap_or(MakerSwapCommand::Start); - let mut events; +pub async fn run_maker_swap(swap: RunMakerSwapInput, ctx: MmArc) { + let uuid = swap.uuid().to_owned(); + let lock_path = my_swaps_dir(&ctx).join(fomat!((uuid) ".lock")); + let mut attempts = 0; + let file_lock = loop { + match FileLock::lock(&lock_path, 40.) { + Ok(Some(l)) => break l, + Ok(None) => if attempts >= 1 { + log!("Swap " (uuid) " file lock is acquired by another process/thread, aborting"); + return; + } else { + attempts += 1; + Timer::sleep(40.).await; + }, + Err(e) => { + log!("Swap " (uuid) " file lock error " (e)); + return; + } + }; + }; + + let (swap, mut command) = match swap { + RunMakerSwapInput::StartNew(swap) => (swap, MakerSwapCommand::Start), + RunMakerSwapInput::KickStart { + maker_coin, taker_coin, swap_uuid + } => match MakerSwap::load_from_db_by_uuid(ctx, maker_coin, taker_coin, &swap_uuid) { + Ok((swap, command)) => match command { + Some(c) => { + log!("Swap " (uuid) " kick started."); + (swap, c) + }, + None => { + log!("Swap " (uuid) " has been finished already, aborting."); + return + }, + }, + Err(e) => { + log!("Error " (e) " loading swap " (uuid)); + return; + } + } + }; + + let mut touch_loop = Box::pin(async move { + loop { + match file_lock.touch() { + Ok(_) => (), + Err(e) => log!("Warning, touch error " (e) " for swap " (uuid)), + }; + Timer::sleep(30.).await; + } + }.fuse()); + let ctx = swap.ctx.clone(); let mut status = ctx.log.status_handle(); let uuid = swap.uuid.clone(); @@ -1022,30 +1111,40 @@ pub async fn run_maker_swap(swap: MakerSwap, initial_command: Option { command = c; }, - None => { - if let Err(e) = broadcast_my_swap_status(&uuid, &ctx) { - log!("!broadcast_my_swap_status(" (uuid) "): " (e)); - } - break; - }, + let shutdown_rx = swap_ctx.shutdown_rx.clone(); + let swap_for_log = running_swap.clone(); + let mut swap_fut = Box::pin(async move { + let mut events; + loop { + let res = unwrap!(running_swap.handle_command(command).await, "!handle_command"); + events = res.1; + for event in events { + let to_save = MakerSavedEvent { + timestamp: now_ms(), + event: event.clone(), + }; + unwrap!(save_my_maker_swap_event(&ctx, &running_swap, to_save), "!save_my_maker_swap_event"); + if event.should_ban_taker() { ban_pubkey(&ctx, running_swap.taker.bytes.into(), &running_swap.uuid, event.clone().into()) } + status.status(swap_tags!(), &event.status_str()); + unwrap!(running_swap.apply_event(event), "!apply_event"); + } + match res.0 { + Some(c) => { command = c; }, + None => { + if let Err(e) = broadcast_my_swap_status(&uuid, &ctx) { + log!("!broadcast_my_swap_status(" (uuid) "): " (e)); + } + break; + }, + } } - } + }.fuse()); + let mut shutdown_fut = Box::pin(shutdown_rx.recv().fuse()); + select! { + swap = swap_fut => (), // swap finished normally + shutdown = shutdown_fut => log!("on_stop] swap " (swap_for_log.uuid) " stopped!"), + touch = touch_loop => unreachable!("Touch loop can not stop!"), + }; } #[cfg(test)] diff --git a/mm2src/lp_swap/taker_swap.rs b/mm2src/lp_swap/taker_swap.rs index a15f0756e9..c1ae0eca98 100644 --- a/mm2src/lp_swap/taker_swap.rs +++ b/mm2src/lp_swap/taker_swap.rs @@ -2,13 +2,19 @@ use atomic::Atomic; use bigdecimal::BigDecimal; -use common::executor::Timer; -use common::{bits256, now_ms, now_float, slurp, write, MM_VERSION}; -use common::mm_ctx::MmArc; +use common::{ + bits256, now_ms, now_float, slurp, write, MM_VERSION, + executor::Timer, + file_lock::FileLock, + mm_ctx::MmArc, +}; use coins::{FoundSwapTxSpend, MmCoinEnum, TradeInfo, TransactionDetails}; use crc::crc32; -use futures::compat::Future01CompatExt; -use futures::future::Either; +use futures::{ + FutureExt, select, + compat::Future01CompatExt, + future::Either, +}; use futures01::Future; use parking_lot::Mutex as PaMutex; use peers::FixedValidator; @@ -20,7 +26,7 @@ use std::path::PathBuf; use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::sync::atomic::Ordering; use super::{ban_pubkey, broadcast_my_swap_status, dex_fee_amount, get_locked_amount_by_other_swaps, - lp_atomic_locktime, my_swap_file_path, + lp_atomic_locktime, my_swap_file_path, my_swaps_dir, AtomicSwap, LockedAmount, MySwapInfo, RecoveredSwap, RecoveredSwapAction, SavedSwap, SwapsContext, SwapError, SwapNegotiationData, BASIC_COMM_TIMEOUT, WAIT_CONFIRM_INTERVAL}; @@ -180,13 +186,81 @@ impl TakerSavedSwap { } } +pub enum RunTakerSwapInput { + StartNew(TakerSwap), + KickStart { + maker_coin: MmCoinEnum, + taker_coin: MmCoinEnum, + swap_uuid: String, + }, +} + +impl RunTakerSwapInput { + fn uuid(&self) -> &str { + match self { + RunTakerSwapInput::StartNew(swap) => &swap.uuid, + RunTakerSwapInput::KickStart { swap_uuid, .. } => &swap_uuid, + } + } +} + /// Starts the taker swap and drives it to completion (until None next command received). /// Panics in case of command or event apply fails, not sure yet how to handle such situations /// because it's usually means that swap is in invalid state which is possible only if there's developer error /// Every produced event is saved to local DB. Swap status is broadcasted to P2P network after completion. -pub async fn run_taker_swap(swap: TakerSwap, initial_command: Option) { - let mut command = initial_command.unwrap_or(TakerSwapCommand::Start); - let mut events; +pub async fn run_taker_swap(swap: RunTakerSwapInput, ctx: MmArc) { + let uuid = swap.uuid().to_owned(); + let lock_path = my_swaps_dir(&ctx).join(fomat!((uuid) ".lock")); + let mut attempts = 0; + let file_lock = loop { + match FileLock::lock(&lock_path, 40.) { + Ok(Some(l)) => break l, + Ok(None) => if attempts >= 1 { + log!("Swap " (uuid) " file lock is acquired by another process/thread, aborting"); + return; + } else { + attempts += 1; + Timer::sleep(40.).await; + }, + Err(e) => { + log!("Swap " (uuid) " file lock error " (e)); + return; + } + }; + }; + + let (swap, mut command) = match swap { + RunTakerSwapInput::StartNew(swap) => (swap, TakerSwapCommand::Start), + RunTakerSwapInput::KickStart { + maker_coin, taker_coin, swap_uuid + } => match TakerSwap::load_from_db_by_uuid(ctx, maker_coin, taker_coin, &swap_uuid) { + Ok((swap, command)) => match command { + Some(c) => { + log!("Swap " (uuid) " kick started."); + (swap, c) + }, + None => { + log!("Swap " (uuid) " has been finished already, aborting."); + return + }, + }, + Err(e) => { + log!("Error " (e) " loading swap " (uuid)); + return; + } + } + }; + + let mut touch_loop = Box::pin(async move { + loop { + match file_lock.touch() { + Ok(_) => (), + Err(e) => log!("Warning, touch error " (e) " for swap " (uuid)), + }; + Timer::sleep(30.).await; + } + }.fuse()); + let ctx = swap.ctx.clone(); let mut status = ctx.log.status_handle(); let uuid = swap.uuid.clone(); @@ -194,30 +268,41 @@ pub async fn run_taker_swap(swap: TakerSwap, initial_command: Option { command = c; }, - None => { - if let Err(e) = broadcast_my_swap_status(&uuid, &ctx) { - log!("!broadcast_my_swap_status(" (uuid) "): " (e)); - } - break; - }, + let mut swap_fut = Box::pin(async move { + let mut events; + loop { + let res = unwrap!(running_swap.handle_command(command).await, "!handle_command"); + events = res.1; + for event in events { + let to_save = TakerSavedEvent { + timestamp: now_ms(), + event: event.clone(), + }; + unwrap!(save_my_taker_swap_event(&ctx, &running_swap, to_save), "!save_my_taker_swap_event"); + if event.should_ban_maker() { ban_pubkey(&ctx, running_swap.maker.bytes.into(), &running_swap.uuid, event.clone().into()) } + status.status(&[&"swap", &("uuid", &uuid[..])], &event.status_str()); + unwrap!(running_swap.apply_event(event), "!apply_event"); + } + match res.0 { + Some(c) => { command = c; }, + None => { + if let Err(e) = broadcast_my_swap_status(&uuid, &ctx) { + log!("!broadcast_my_swap_status(" (uuid) "): " (e)); + } + break; + }, + } } - } + }.fuse()); + let mut shutdown_fut = Box::pin(shutdown_rx.recv().fuse()); + select! { + swap = swap_fut => (), // swap finished normally + shutdown = shutdown_fut => log!("on_stop] swap " (swap_for_log.uuid) " stopped!"), + touch = touch_loop => unreachable!("Touch loop can not stop!"), + }; } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -970,11 +1055,26 @@ impl TakerSwap { )) } + pub fn load_from_db_by_uuid( + ctx: MmArc, + maker_coin: MmCoinEnum, + taker_coin: MmCoinEnum, + swap_uuid: &str, + ) -> Result<(Self, Option), String> { + let path = my_swap_file_path(&ctx, swap_uuid); + let saved: SavedSwap = try_s!(json::from_slice(&try_s!(slurp(&path)))); + let saved = match saved { + SavedSwap::Taker(swap) => swap, + SavedSwap::Maker(_) => return ERR!("Can not load TakerSwap from SavedSwap::Maker uuid: {}", swap_uuid), + }; + Self::load_from_saved(ctx, maker_coin, taker_coin, saved) + } + pub fn load_from_saved( ctx: MmArc, maker_coin: MmCoinEnum, taker_coin: MmCoinEnum, - saved: TakerSavedSwap + saved: TakerSavedSwap, ) -> Result<(Self, Option), String> { if saved.events.is_empty() { return ERR!("Can't restore swap from empty events set");