diff --git a/.docker/container-state/atom-testnet-data/data/application.db/000122.ldb b/.docker/container-state/atom-testnet-data/data/application.db/000122.ldb deleted file mode 100644 index 5979f2fdb6..0000000000 Binary files a/.docker/container-state/atom-testnet-data/data/application.db/000122.ldb and /dev/null differ diff --git a/.docker/container-state/atom-testnet-data/data/application.db/000123.ldb b/.docker/container-state/atom-testnet-data/data/application.db/000123.ldb deleted file mode 100644 index b03a2ddeb6..0000000000 Binary files a/.docker/container-state/atom-testnet-data/data/application.db/000123.ldb and /dev/null differ diff --git a/.docker/container-state/atom-testnet-data/data/application.db/000124.ldb b/.docker/container-state/atom-testnet-data/data/application.db/000124.ldb deleted file mode 100644 index 415499ade2..0000000000 Binary files a/.docker/container-state/atom-testnet-data/data/application.db/000124.ldb and /dev/null differ diff --git a/.docker/container-state/atom-testnet-data/data/application.db/000125.ldb b/.docker/container-state/atom-testnet-data/data/application.db/000125.ldb deleted file mode 100644 index 90619e3797..0000000000 Binary files a/.docker/container-state/atom-testnet-data/data/application.db/000125.ldb and /dev/null differ diff --git a/.docker/container-state/atom-testnet-data/data/application.db/000126.log b/.docker/container-state/atom-testnet-data/data/application.db/000126.log deleted file mode 100644 index 69d85e6d61..0000000000 Binary files a/.docker/container-state/atom-testnet-data/data/application.db/000126.log and /dev/null differ diff --git a/.docker/container-state/atom-testnet-data/data/application.db/000145.ldb b/.docker/container-state/atom-testnet-data/data/application.db/000145.ldb new file mode 100644 index 0000000000..ffa94a328e Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/application.db/000145.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/application.db/000146.ldb b/.docker/container-state/atom-testnet-data/data/application.db/000146.ldb new file mode 100644 index 0000000000..305c23033d Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/application.db/000146.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/application.db/000147.ldb b/.docker/container-state/atom-testnet-data/data/application.db/000147.ldb new file mode 100644 index 0000000000..4c895dfd75 Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/application.db/000147.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/application.db/000148.ldb b/.docker/container-state/atom-testnet-data/data/application.db/000148.ldb new file mode 100644 index 0000000000..44b0a978d0 Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/application.db/000148.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/application.db/000149.ldb b/.docker/container-state/atom-testnet-data/data/application.db/000149.ldb new file mode 100644 index 0000000000..a79ab5bddf Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/application.db/000149.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/application.db/000150.log b/.docker/container-state/atom-testnet-data/data/application.db/000150.log new file mode 100644 index 0000000000..19f3ee4d7e Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/application.db/000150.log differ diff --git a/.docker/container-state/atom-testnet-data/data/application.db/CURRENT b/.docker/container-state/atom-testnet-data/data/application.db/CURRENT index 224d52afe7..3f137c99d1 100644 --- a/.docker/container-state/atom-testnet-data/data/application.db/CURRENT +++ b/.docker/container-state/atom-testnet-data/data/application.db/CURRENT @@ -1 +1 @@ -MANIFEST-000127 +MANIFEST-000151 diff --git a/.docker/container-state/atom-testnet-data/data/application.db/CURRENT.bak b/.docker/container-state/atom-testnet-data/data/application.db/CURRENT.bak index 2b465edb20..3e77273f6e 100644 --- a/.docker/container-state/atom-testnet-data/data/application.db/CURRENT.bak +++ b/.docker/container-state/atom-testnet-data/data/application.db/CURRENT.bak @@ -1 +1 @@ -MANIFEST-000121 +MANIFEST-000144 diff --git a/.docker/container-state/atom-testnet-data/data/application.db/LOG b/.docker/container-state/atom-testnet-data/data/application.db/LOG index 40eb63ea2c..0e35591395 100644 --- a/.docker/container-state/atom-testnet-data/data/application.db/LOG +++ b/.docker/container-state/atom-testnet-data/data/application.db/LOG @@ -470,3 +470,79 @@ 17:59:01.710322 version@stat F·[1 3] S·6MiB[739KiB 5MiB] Sc·[0.25 0.06] 17:59:01.715180 db@janitor F·6 G·0 17:59:01.715196 db@open done T·17.361833ms +=============== Oct 17, 2024 (UTC) =============== +05:10:40.858576 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.858710 version@stat F·[1 3] S·6MiB[739KiB 5MiB] Sc·[0.25 0.06] +05:10:40.858723 db@open opening +05:10:40.858766 journal@recovery F·1 +05:10:40.858847 journal@recovery recovering @126 +05:10:40.860765 memdb@flush created L0@128 N·905 S·48KiB "s/713,v122410":"s/p..hts,v122409" +05:10:40.862727 version@stat F·[2 3] S·6MiB[788KiB 5MiB] Sc·[0.50 0.06] +05:10:40.865397 db@janitor F·7 G·0 +05:10:40.865410 db@open done T·6.680542ms +05:10:40.972856 table@compaction L0·2 -> L1·3 S·6MiB Q·123136 +05:10:40.990144 table@build created L1@131 N·26043 S·2MiB "s/1,v430":"s/k..Z\xe2w,v101980" +05:10:41.012132 table@build created L1@132 N·39970 S·2MiB "s/k..\x8e\xd2\x7f,v43561":"s/k..\x86S\xad,v103471" +05:10:41.027607 table@build created L1@133 N·18990 S·2MiB "s/k..\x82\xb9\x82,v24251":"s/k..\x1a\xf6y,v40651" +05:10:41.034441 table@build created L1@134 N·9675 S·376KiB "s/k..\x9b\xb8\x1d,v40639":"s/p..hts,v123133" +05:10:41.034500 version@stat F·[0 4] S·6MiB[0B 6MiB] Sc·[0.00 0.06] +05:10:41.035116 table@compaction committed F-1 S-28KiB Ke·0 D·3011 T·62.239547ms +05:10:41.035304 table@remove removed @125 +05:10:41.035687 table@remove removed @122 +05:10:41.036200 table@remove removed @123 +05:10:41.036511 table@remove removed @124 +=============== Oct 17, 2024 (UTC) =============== +06:44:58.533830 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.533977 version@stat F·[0 4] S·6MiB[0B 6MiB] Sc·[0.00 0.06] +06:44:58.533988 db@open opening +06:44:58.534025 journal@recovery F·1 +06:44:58.534163 journal@recovery recovering @129 +06:44:58.588185 memdb@flush created L0@135 N·26580 S·1MiB "s/718,v123316":"s/p..hts,v123315" +06:44:58.588321 version@stat F·[1 4] S·7MiB[1MiB 6MiB] Sc·[0.25 0.06] +06:44:58.609803 db@janitor F·8 G·1 +06:44:58.609810 db@janitor removing table-128 +06:44:58.609853 db@open done T·75.860223ms +06:45:28.811214 table@compaction L0·1 -> L1·4 S·7MiB Q·150786 +06:45:28.852586 table@build created L1@138 N·26523 S·2MiB "s/1,v430":"s/k..\xfaW\xa8,v68382" +06:45:28.897007 table@build created L1@139 N·36709 S·2MiB "s/k..>Uu,v62163":"s/k..\x00\x01\xa4,v70055" +06:45:28.935643 table@build created L1@140 N·27416 S·2MiB "s/k..\x00\x01\xa5,v70226":"s/k..i\xf8\x8a,v22133" +06:45:28.974020 table@build created L1@141 N·24774 S·1MiB "s/k..e^\xef,v12056":"s/p..hts,v149714" +06:45:28.974046 version@stat F·[0 4] S·7MiB[0B 7MiB] Sc·[0.00 0.08] +06:45:28.981030 table@compaction committed F-1 S-26KiB Ke·0 D·5836 T·169.796253ms +06:45:28.981494 table@remove removed @131 +06:45:28.981958 table@remove removed @132 +06:45:28.982402 table@remove removed @133 +06:45:28.982499 table@remove removed @134 +=============== Oct 17, 2024 (UTC) =============== +06:45:46.585282 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.585428 version@stat F·[0 4] S·7MiB[0B 7MiB] Sc·[0.00 0.08] +06:45:46.585445 db@open opening +06:45:46.585484 journal@recovery F·1 +06:45:46.585567 journal@recovery recovering @136 +06:45:46.587472 memdb@flush created L0@142 N·1427 S·80KiB "s/862,v149897":"s/p..hts,v149896" +06:45:46.588280 version@stat F·[1 4] S·7MiB[80KiB 7MiB] Sc·[0.25 0.08] +06:45:46.591569 db@janitor F·8 G·1 +06:45:46.591580 db@janitor removing table-135 +06:45:46.591873 db@open done T·6.422715ms +06:46:51.810714 table@compaction L0·1 -> L1·4 S·7MiB Q·153293 +06:46:51.853844 table@build created L1@145 N·26596 S·2MiB "s/1,v430":"s/k..R\xa9',v46917" +06:46:51.895701 table@build created L1@146 N·36341 S·2MiB "s/k..\xc8A\xba,v114547":"s/k..\xa2*\xbf,v100466" +06:46:51.941201 table@build created L1@147 N·28140 S·2MiB "s/k..\xee^\xb5,v100467":"s/k..\xabk`,v122744" +06:46:51.976205 table@build created L1@148 N·25470 S·1MiB "s/k..̵\xd4,v106125":"s/p..hts,v151142" +06:46:51.976239 version@stat F·[0 4] S·7MiB[0B 7MiB] Sc·[0.00 0.08] +06:46:51.983945 table@compaction committed F-1 S-1KiB Ke·0 D·302 T·173.208828ms +06:46:51.984409 table@remove removed @138 +06:46:51.984907 table@remove removed @139 +06:46:51.985406 table@remove removed @140 +06:46:51.985884 table@remove removed @141 +=============== Oct 17, 2024 (UTC) =============== +06:47:26.207760 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.207883 version@stat F·[0 4] S·7MiB[0B 7MiB] Sc·[0.00 0.08] +06:47:26.207895 db@open opening +06:47:26.207934 journal@recovery F·1 +06:47:26.208025 journal@recovery recovering @143 +06:47:26.210939 memdb@flush created L0@149 N·2685 S·147KiB "s/869,v151323":"s/p..hts,v151322" +06:47:26.211271 version@stat F·[1 4] S·8MiB[147KiB 7MiB] Sc·[0.25 0.08] +06:47:26.214624 db@janitor F·8 G·1 +06:47:26.214639 db@janitor removing table-142 +06:47:26.214702 db@open done T·6.802848ms diff --git a/.docker/container-state/atom-testnet-data/data/application.db/MANIFEST-000127 b/.docker/container-state/atom-testnet-data/data/application.db/MANIFEST-000127 deleted file mode 100644 index 28e3f506f3..0000000000 Binary files a/.docker/container-state/atom-testnet-data/data/application.db/MANIFEST-000127 and /dev/null differ diff --git a/.docker/container-state/atom-testnet-data/data/application.db/MANIFEST-000151 b/.docker/container-state/atom-testnet-data/data/application.db/MANIFEST-000151 new file mode 100644 index 0000000000..12bea3a66a Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/application.db/MANIFEST-000151 differ diff --git a/.docker/container-state/atom-testnet-data/data/blockstore.db/000093.ldb b/.docker/container-state/atom-testnet-data/data/blockstore.db/000093.ldb deleted file mode 100644 index 8ddf4ae7fb..0000000000 Binary files a/.docker/container-state/atom-testnet-data/data/blockstore.db/000093.ldb and /dev/null differ diff --git a/.docker/container-state/atom-testnet-data/data/blockstore.db/000096.ldb b/.docker/container-state/atom-testnet-data/data/blockstore.db/000096.ldb deleted file mode 100644 index 854fba8071..0000000000 Binary files a/.docker/container-state/atom-testnet-data/data/blockstore.db/000096.ldb and /dev/null differ diff --git a/.docker/container-state/atom-testnet-data/data/blockstore.db/000097.log b/.docker/container-state/atom-testnet-data/data/blockstore.db/000097.log deleted file mode 100644 index b5adbff40d..0000000000 Binary files a/.docker/container-state/atom-testnet-data/data/blockstore.db/000097.log and /dev/null differ diff --git a/.docker/container-state/atom-testnet-data/data/blockstore.db/000109.ldb b/.docker/container-state/atom-testnet-data/data/blockstore.db/000109.ldb new file mode 100644 index 0000000000..f850f5a1ef Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/blockstore.db/000109.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/blockstore.db/000110.ldb b/.docker/container-state/atom-testnet-data/data/blockstore.db/000110.ldb new file mode 100644 index 0000000000..6587bbe2bb Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/blockstore.db/000110.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/blockstore.db/000111.log b/.docker/container-state/atom-testnet-data/data/blockstore.db/000111.log new file mode 100644 index 0000000000..0b0f0c9313 Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/blockstore.db/000111.log differ diff --git a/.docker/container-state/atom-testnet-data/data/blockstore.db/CURRENT b/.docker/container-state/atom-testnet-data/data/blockstore.db/CURRENT index 95395b28bd..b59a6ba248 100644 --- a/.docker/container-state/atom-testnet-data/data/blockstore.db/CURRENT +++ b/.docker/container-state/atom-testnet-data/data/blockstore.db/CURRENT @@ -1 +1 @@ -MANIFEST-000098 +MANIFEST-000112 diff --git a/.docker/container-state/atom-testnet-data/data/blockstore.db/CURRENT.bak b/.docker/container-state/atom-testnet-data/data/blockstore.db/CURRENT.bak index b993e6cda5..db6fa61f96 100644 --- a/.docker/container-state/atom-testnet-data/data/blockstore.db/CURRENT.bak +++ b/.docker/container-state/atom-testnet-data/data/blockstore.db/CURRENT.bak @@ -1 +1 @@ -MANIFEST-000095 +MANIFEST-000108 diff --git a/.docker/container-state/atom-testnet-data/data/blockstore.db/LOG b/.docker/container-state/atom-testnet-data/data/blockstore.db/LOG index 969bf1ea7a..8d007b4ffd 100644 --- a/.docker/container-state/atom-testnet-data/data/blockstore.db/LOG +++ b/.docker/container-state/atom-testnet-data/data/blockstore.db/LOG @@ -384,3 +384,57 @@ 17:59:01.758668 version@stat F·[1 1] S·684KiB[72KiB 611KiB] Sc·[0.25 0.01] 17:59:01.761454 db@janitor F·4 G·0 17:59:01.761470 db@open done T·5.254513ms +=============== Oct 17, 2024 (UTC) =============== +05:10:40.909640 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.909735 version@stat F·[1 1] S·684KiB[72KiB 611KiB] Sc·[0.25 0.01] +05:10:40.909748 db@open opening +05:10:40.909778 journal@recovery F·1 +05:10:40.909866 journal@recovery recovering @97 +05:10:40.910854 memdb@flush created L0@99 N·30 S·3KiB "BH:..eed,v4304":"blo..ore,v4301" +05:10:40.911054 version@stat F·[2 1] S·688KiB[76KiB 611KiB] Sc·[0.50 0.01] +05:10:40.915558 db@janitor F·5 G·0 +05:10:40.915581 db@open done T·5.827674ms +05:11:03.981547 table@compaction L0·2 -> L1·1 S·688KiB Q·4350 +05:11:03.987164 table@build created L1@102 N·3586 S·689KiB "BH:..c56,v1346":"blo..ore,v4325" +05:11:03.987190 version@stat F·[0 1] S·689KiB[0B 689KiB] Sc·[0.00 0.01] +05:11:03.987766 table@compaction committed F-2 S+1KiB Ke·0 D·74 T·6.185418ms +05:11:03.987844 table@remove removed @96 +05:11:03.987972 table@remove removed @93 +=============== Oct 17, 2024 (UTC) =============== +06:44:58.661274 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.661360 version@stat F·[0 1] S·689KiB[0B 689KiB] Sc·[0.00 0.01] +06:44:58.661370 db@open opening +06:44:58.661399 journal@recovery F·1 +06:44:58.661518 journal@recovery recovering @100 +06:44:58.681937 memdb@flush created L0@103 N·864 S·144KiB "BH:..573,v4461":"blo..ore,v4332" +06:44:58.682057 version@stat F·[1 1] S·834KiB[144KiB 689KiB] Sc·[0.25 0.01] +06:44:58.690133 db@janitor F·5 G·1 +06:44:58.690138 db@janitor removing table-99 +06:44:58.690162 db@open done T·28.789108ms +=============== Oct 17, 2024 (UTC) =============== +06:45:46.638683 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.638799 version@stat F·[1 1] S·834KiB[144KiB 689KiB] Sc·[0.25 0.01] +06:45:46.638816 db@open opening +06:45:46.638854 journal@recovery F·1 +06:45:46.638937 journal@recovery recovering @104 +06:45:46.639741 memdb@flush created L0@106 N·42 S·10KiB "BH:..d0b,v5194":"blo..ore,v5197" +06:45:46.639858 version@stat F·[2 1] S·844KiB[154KiB 689KiB] Sc·[0.50 0.01] +06:45:46.642453 db@janitor F·5 G·0 +06:45:46.642465 db@open done T·3.643621ms +06:46:17.600570 table@compaction L0·2 -> L1·1 S·844KiB Q·5270 +06:46:17.605912 table@build created L1@109 N·4341 S·845KiB "BH:..c56,v1346":"blo..ore,v5233" +06:46:17.605934 version@stat F·[0 1] S·845KiB[0B 845KiB] Sc·[0.00 0.01] +06:46:17.607466 table@compaction committed F-2 S+552B Ke·0 D·151 T·6.876138ms +06:46:17.607551 table@remove removed @103 +06:46:17.607706 table@remove removed @102 +=============== Oct 17, 2024 (UTC) =============== +06:47:26.263473 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.263569 version@stat F·[0 1] S·845KiB[0B 845KiB] Sc·[0.00 0.01] +06:47:26.263581 db@open opening +06:47:26.263610 journal@recovery F·1 +06:47:26.263695 journal@recovery recovering @107 +06:47:26.264737 memdb@flush created L0@110 N·90 S·14KiB "BH:..5c8,v5321":"blo..ore,v5240" +06:47:26.264916 version@stat F·[1 1] S·859KiB[14KiB 845KiB] Sc·[0.25 0.01] +06:47:26.269660 db@janitor F·5 G·1 +06:47:26.269667 db@janitor removing table-106 +06:47:26.269704 db@open done T·6.119582ms diff --git a/.docker/container-state/atom-testnet-data/data/blockstore.db/MANIFEST-000098 b/.docker/container-state/atom-testnet-data/data/blockstore.db/MANIFEST-000098 deleted file mode 100644 index a7d73d0ef2..0000000000 Binary files a/.docker/container-state/atom-testnet-data/data/blockstore.db/MANIFEST-000098 and /dev/null differ diff --git a/.docker/container-state/atom-testnet-data/data/blockstore.db/MANIFEST-000112 b/.docker/container-state/atom-testnet-data/data/blockstore.db/MANIFEST-000112 new file mode 100644 index 0000000000..07d83114fb Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/blockstore.db/MANIFEST-000112 differ diff --git a/.docker/container-state/atom-testnet-data/data/cs.wal/wal b/.docker/container-state/atom-testnet-data/data/cs.wal/wal index 61a997fc44..58106e91fa 100644 Binary files a/.docker/container-state/atom-testnet-data/data/cs.wal/wal and b/.docker/container-state/atom-testnet-data/data/cs.wal/wal differ diff --git a/.docker/container-state/atom-testnet-data/data/evidence.db/000068.log b/.docker/container-state/atom-testnet-data/data/evidence.db/000076.log similarity index 100% rename from .docker/container-state/atom-testnet-data/data/evidence.db/000068.log rename to .docker/container-state/atom-testnet-data/data/evidence.db/000076.log diff --git a/.docker/container-state/atom-testnet-data/data/evidence.db/CURRENT b/.docker/container-state/atom-testnet-data/data/evidence.db/CURRENT index 5893b8f83b..c7a124bfc3 100644 --- a/.docker/container-state/atom-testnet-data/data/evidence.db/CURRENT +++ b/.docker/container-state/atom-testnet-data/data/evidence.db/CURRENT @@ -1 +1 @@ -MANIFEST-000069 +MANIFEST-000077 diff --git a/.docker/container-state/atom-testnet-data/data/evidence.db/CURRENT.bak b/.docker/container-state/atom-testnet-data/data/evidence.db/CURRENT.bak index 0094dacbb8..d2ea14ced0 100644 --- a/.docker/container-state/atom-testnet-data/data/evidence.db/CURRENT.bak +++ b/.docker/container-state/atom-testnet-data/data/evidence.db/CURRENT.bak @@ -1 +1 @@ -MANIFEST-000067 +MANIFEST-000075 diff --git a/.docker/container-state/atom-testnet-data/data/evidence.db/LOG b/.docker/container-state/atom-testnet-data/data/evidence.db/LOG index 5ab563f5bc..e4d4d0631a 100644 --- a/.docker/container-state/atom-testnet-data/data/evidence.db/LOG +++ b/.docker/container-state/atom-testnet-data/data/evidence.db/LOG @@ -310,3 +310,39 @@ 17:59:01.774745 version@stat F·[] S·0B[] Sc·[] 17:59:01.777793 db@janitor F·2 G·0 17:59:01.777805 db@open done T·4.373546ms +=============== Oct 17, 2024 (UTC) =============== +05:10:40.928961 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.929028 version@stat F·[] S·0B[] Sc·[] +05:10:40.929037 db@open opening +05:10:40.929066 journal@recovery F·1 +05:10:40.929428 journal@recovery recovering @68 +05:10:40.931225 version@stat F·[] S·0B[] Sc·[] +05:10:40.934658 db@janitor F·2 G·0 +05:10:40.934670 db@open done T·5.628153ms +=============== Oct 17, 2024 (UTC) =============== +06:44:58.730742 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.730819 version@stat F·[] S·0B[] Sc·[] +06:44:58.730838 db@open opening +06:44:58.730866 journal@recovery F·1 +06:44:58.732739 journal@recovery recovering @70 +06:44:58.734542 version@stat F·[] S·0B[] Sc·[] +06:44:58.749764 db@janitor F·2 G·0 +06:44:58.749779 db@open done T·18.938744ms +=============== Oct 17, 2024 (UTC) =============== +06:45:46.656897 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.656961 version@stat F·[] S·0B[] Sc·[] +06:45:46.656972 db@open opening +06:45:46.656996 journal@recovery F·1 +06:45:46.657075 journal@recovery recovering @72 +06:45:46.657200 version@stat F·[] S·0B[] Sc·[] +06:45:46.659741 db@janitor F·2 G·0 +06:45:46.659750 db@open done T·2.774794ms +=============== Oct 17, 2024 (UTC) =============== +06:47:26.282862 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.282927 version@stat F·[] S·0B[] Sc·[] +06:47:26.282937 db@open opening +06:47:26.282964 journal@recovery F·1 +06:47:26.283046 journal@recovery recovering @74 +06:47:26.283185 version@stat F·[] S·0B[] Sc·[] +06:47:26.285765 db@janitor F·2 G·0 +06:47:26.285779 db@open done T·2.837874ms diff --git a/.docker/container-state/atom-testnet-data/data/evidence.db/MANIFEST-000069 b/.docker/container-state/atom-testnet-data/data/evidence.db/MANIFEST-000069 deleted file mode 100644 index 3833569575..0000000000 Binary files a/.docker/container-state/atom-testnet-data/data/evidence.db/MANIFEST-000069 and /dev/null differ diff --git a/.docker/container-state/atom-testnet-data/data/evidence.db/MANIFEST-000077 b/.docker/container-state/atom-testnet-data/data/evidence.db/MANIFEST-000077 new file mode 100644 index 0000000000..dd7fb6cd3d Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/evidence.db/MANIFEST-000077 differ diff --git a/.docker/container-state/atom-testnet-data/data/priv_validator_state.json b/.docker/container-state/atom-testnet-data/data/priv_validator_state.json index 99546706e3..56899e7a9a 100644 --- a/.docker/container-state/atom-testnet-data/data/priv_validator_state.json +++ b/.docker/container-state/atom-testnet-data/data/priv_validator_state.json @@ -1,7 +1,7 @@ { - "height": "717", + "height": "888", "round": 0, "step": 3, - "signature": "XLwGIxJpiVU0AYl646G7zqXk0v/ihlfAwKP5BvHmMfy6UQCzeW/rjtuVoS+2i7KT0RVUfXc7dvGYOnpuAvSTAA==", - "signbytes": "76080211CD0200000000000022480A20911EABA0078B62D4B55F3B349F0719F53B0D39EEE4064EA626437B86D219A37F122408011220AB2248EBDDC8ACA32BCF90D60E19DA9B4A42F2DD6A8957FA3D7337C1AC73151A2A0C08FEA296B40610F7CFDA82033211636F736D6F736875622D746573746E6574" + "signature": "XskRDe70STOW5qxh60WQ2GR/2x2sDaGqPnAsTb7icGjekGky1w7kO/b3nU+XzmQVDaQcYHjb8W1JRO4ue9TeAA==", + "signbytes": "76080211780300000000000022480A20F5676C89CEA03B617C53FCD05015F465FB2C14107310553F186D423A022E05E2122408011220AA62CEBCC34EFE61C30D5917935B2224E39D5019F92CE313CB8514F584F6D54E2A0C0897E6C2B80610EBD7E698013211636F736D6F736875622D746573746E6574" } \ No newline at end of file diff --git a/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/000068.log b/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/000076.log similarity index 100% rename from .docker/container-state/atom-testnet-data/data/snapshots/metadata.db/000068.log rename to .docker/container-state/atom-testnet-data/data/snapshots/metadata.db/000076.log diff --git a/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/CURRENT b/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/CURRENT index 5893b8f83b..c7a124bfc3 100644 --- a/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/CURRENT +++ b/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/CURRENT @@ -1 +1 @@ -MANIFEST-000069 +MANIFEST-000077 diff --git a/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/CURRENT.bak b/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/CURRENT.bak index 0094dacbb8..d2ea14ced0 100644 --- a/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/CURRENT.bak +++ b/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/CURRENT.bak @@ -1 +1 @@ -MANIFEST-000067 +MANIFEST-000075 diff --git a/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/LOG b/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/LOG index 2638185812..108e3e10d4 100644 --- a/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/LOG +++ b/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/LOG @@ -310,3 +310,39 @@ 17:59:01.716921 version@stat F·[] S·0B[] Sc·[] 17:59:01.720432 db@janitor F·2 G·0 17:59:01.720442 db@open done T·3.774542ms +=============== Oct 17, 2024 (UTC) =============== +05:10:40.866907 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.866974 version@stat F·[] S·0B[] Sc·[] +05:10:40.866984 db@open opening +05:10:40.867009 journal@recovery F·1 +05:10:40.867092 journal@recovery recovering @68 +05:10:40.867215 version@stat F·[] S·0B[] Sc·[] +05:10:40.871101 db@janitor F·2 G·0 +05:10:40.871109 db@open done T·4.121771ms +=============== Oct 17, 2024 (UTC) =============== +06:44:58.611297 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.611385 version@stat F·[] S·0B[] Sc·[] +06:44:58.611394 db@open opening +06:44:58.611420 journal@recovery F·1 +06:44:58.611708 journal@recovery recovering @70 +06:44:58.613594 version@stat F·[] S·0B[] Sc·[] +06:44:58.621731 db@janitor F·2 G·0 +06:44:58.621742 db@open done T·10.34492ms +=============== Oct 17, 2024 (UTC) =============== +06:45:46.593245 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.593314 version@stat F·[] S·0B[] Sc·[] +06:45:46.593325 db@open opening +06:45:46.593352 journal@recovery F·1 +06:45:46.595203 journal@recovery recovering @72 +06:45:46.597207 version@stat F·[] S·0B[] Sc·[] +06:45:46.599821 db@janitor F·2 G·0 +06:45:46.599833 db@open done T·6.503955ms +=============== Oct 17, 2024 (UTC) =============== +06:47:26.215962 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.216036 version@stat F·[] S·0B[] Sc·[] +06:47:26.216047 db@open opening +06:47:26.216083 journal@recovery F·1 +06:47:26.217966 journal@recovery recovering @74 +06:47:26.220147 version@stat F·[] S·0B[] Sc·[] +06:47:26.222797 db@janitor F·2 G·0 +06:47:26.222807 db@open done T·6.756568ms diff --git a/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/MANIFEST-000069 b/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/MANIFEST-000069 deleted file mode 100644 index 3833569575..0000000000 Binary files a/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/MANIFEST-000069 and /dev/null differ diff --git a/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/MANIFEST-000077 b/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/MANIFEST-000077 new file mode 100644 index 0000000000..dd7fb6cd3d Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/snapshots/metadata.db/MANIFEST-000077 differ diff --git a/.docker/container-state/atom-testnet-data/data/state.db/000114.ldb b/.docker/container-state/atom-testnet-data/data/state.db/000114.ldb deleted file mode 100644 index fb954e1eb4..0000000000 Binary files a/.docker/container-state/atom-testnet-data/data/state.db/000114.ldb and /dev/null differ diff --git a/.docker/container-state/atom-testnet-data/data/state.db/000129.ldb b/.docker/container-state/atom-testnet-data/data/state.db/000129.ldb new file mode 100644 index 0000000000..b38bc5d97d Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/state.db/000129.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/state.db/000115.log b/.docker/container-state/atom-testnet-data/data/state.db/000130.log similarity index 68% rename from .docker/container-state/atom-testnet-data/data/state.db/000115.log rename to .docker/container-state/atom-testnet-data/data/state.db/000130.log index 5eec6803eb..bbb9c9ebd2 100644 Binary files a/.docker/container-state/atom-testnet-data/data/state.db/000115.log and b/.docker/container-state/atom-testnet-data/data/state.db/000130.log differ diff --git a/.docker/container-state/atom-testnet-data/data/state.db/000113.ldb b/.docker/container-state/atom-testnet-data/data/state.db/000132.ldb similarity index 63% rename from .docker/container-state/atom-testnet-data/data/state.db/000113.ldb rename to .docker/container-state/atom-testnet-data/data/state.db/000132.ldb index 4775c1b3ee..e589a7bb5d 100644 Binary files a/.docker/container-state/atom-testnet-data/data/state.db/000113.ldb and b/.docker/container-state/atom-testnet-data/data/state.db/000132.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/state.db/CURRENT b/.docker/container-state/atom-testnet-data/data/state.db/CURRENT index 2b6390e186..d5731e9f98 100644 --- a/.docker/container-state/atom-testnet-data/data/state.db/CURRENT +++ b/.docker/container-state/atom-testnet-data/data/state.db/CURRENT @@ -1 +1 @@ -MANIFEST-000116 +MANIFEST-000131 diff --git a/.docker/container-state/atom-testnet-data/data/state.db/CURRENT.bak b/.docker/container-state/atom-testnet-data/data/state.db/CURRENT.bak index b59a6ba248..224d52afe7 100644 --- a/.docker/container-state/atom-testnet-data/data/state.db/CURRENT.bak +++ b/.docker/container-state/atom-testnet-data/data/state.db/CURRENT.bak @@ -1 +1 @@ -MANIFEST-000112 +MANIFEST-000127 diff --git a/.docker/container-state/atom-testnet-data/data/state.db/LOG b/.docker/container-state/atom-testnet-data/data/state.db/LOG index 2c9dc925a4..333f86a5c7 100644 --- a/.docker/container-state/atom-testnet-data/data/state.db/LOG +++ b/.docker/container-state/atom-testnet-data/data/state.db/LOG @@ -441,3 +441,67 @@ 17:59:01.766489 db@janitor F·5 G·1 17:59:01.766499 db@janitor removing table-110 17:59:01.766531 db@open done T·4.87064ms +=============== Oct 17, 2024 (UTC) =============== +05:10:40.915711 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.915810 version@stat F·[1 1] S·327KiB[83KiB 243KiB] Sc·[0.25 0.00] +05:10:40.915825 db@open opening +05:10:40.915868 journal@recovery F·1 +05:10:40.918284 journal@recovery recovering @115 +05:10:40.919542 memdb@flush created L0@117 N·26 S·5KiB "abc..713,v3635":"val..719,v3657" +05:10:40.919668 version@stat F·[2 1] S·332KiB[88KiB 243KiB] Sc·[0.50 0.00] +05:10:40.923785 db@janitor F·5 G·0 +05:10:40.923796 db@open done T·7.966161ms +05:11:00.958934 table@compaction L0·2 -> L1·1 S·332KiB Q·3676 +05:11:00.962394 table@build created L1@120 N·2158 S·280KiB "abc..y:1,v7":"val..:99,v491" +05:11:00.962424 version@stat F·[0 1] S·280KiB[0B 280KiB] Sc·[0.00 0.00] +05:11:00.964049 table@compaction committed F-2 S-51KiB Ke·0 D·150 T·5.093449ms +05:11:00.964119 table@remove removed @114 +05:11:00.964186 table@remove removed @113 +=============== Oct 17, 2024 (UTC) =============== +06:44:58.690228 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.690292 version@stat F·[0 1] S·280KiB[0B 280KiB] Sc·[0.00 0.00] +06:44:58.690301 db@open opening +06:44:58.690327 journal@recovery F·1 +06:44:58.690411 journal@recovery recovering @118 +06:44:58.698458 memdb@flush created L0@121 N·721 S·152KiB "abc..718,v3662":"val..863,v4379" +06:44:58.698595 version@stat F·[1 1] S·433KiB[152KiB 280KiB] Sc·[0.25 0.00] +06:44:58.713491 db@janitor F·5 G·1 +06:44:58.713496 db@janitor removing table-117 +06:44:58.713521 db@open done T·23.21798ms +06:45:33.812957 table@compaction L0·1 -> L1·1 S·433KiB Q·4413 +06:45:33.817194 table@build created L1@124 N·2590 S·345KiB "abc..y:1,v7":"val..:99,v491" +06:45:33.817219 version@stat F·[0 1] S·345KiB[0B 345KiB] Sc·[0.00 0.00] +06:45:33.818740 table@compaction committed F-1 S-88KiB Ke·0 D·289 T·5.758129ms +06:45:33.818856 table@remove removed @120 +=============== Oct 17, 2024 (UTC) =============== +06:45:46.642590 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.642660 version@stat F·[0 1] S·345KiB[0B 345KiB] Sc·[0.00 0.00] +06:45:46.642670 db@open opening +06:45:46.642701 journal@recovery F·1 +06:45:46.642793 journal@recovery recovering @122 +06:45:46.643923 memdb@flush created L0@125 N·36 S·14KiB "abc..862,v4384":"val..870,v4416" +06:45:46.644184 version@stat F·[1 1] S·359KiB[14KiB 345KiB] Sc·[0.25 0.00] +06:45:46.648159 db@janitor F·5 G·1 +06:45:46.648166 db@janitor removing table-121 +06:45:46.648229 db@open done T·5.554767ms +06:46:12.624281 table@compaction L0·1 -> L1·1 S·359KiB Q·4445 +06:46:12.628139 table@build created L1@128 N·2611 S·351KiB "abc..y:1,v7":"val..:99,v491" +06:46:12.628162 version@stat F·[0 1] S·351KiB[0B 351KiB] Sc·[0.00 0.00] +06:46:12.628755 table@compaction committed F-1 S-7KiB Ke·0 D·15 T·4.453088ms +06:46:12.628879 table@remove removed @124 +=============== Oct 17, 2024 (UTC) =============== +06:47:26.269795 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.269871 version@stat F·[0 1] S·351KiB[0B 351KiB] Sc·[0.00 0.00] +06:47:26.269882 db@open opening +06:47:26.269915 journal@recovery F·1 +06:47:26.270011 journal@recovery recovering @126 +06:47:26.270915 memdb@flush created L0@129 N·76 S·13KiB "abc..869,v4421":"val..885,v4493" +06:47:26.271251 version@stat F·[1 1] S·364KiB[13KiB 351KiB] Sc·[0.25 0.00] +06:47:26.274125 db@janitor F·5 G·1 +06:47:26.274133 db@janitor removing table-125 +06:47:26.274169 db@open done T·4.283147ms +06:47:51.337900 table@compaction L0·1 -> L1·1 S·364KiB Q·4522 +06:47:51.342188 table@build created L1@132 N·2656 S·356KiB "abc..y:1,v7":"val..:99,v491" +06:47:51.342209 version@stat F·[0 1] S·356KiB[0B 356KiB] Sc·[0.00 0.00] +06:47:51.343789 table@compaction committed F-1 S-7KiB Ke·0 D·31 T·5.87117ms +06:47:51.343944 table@remove removed @128 diff --git a/.docker/container-state/atom-testnet-data/data/state.db/MANIFEST-000116 b/.docker/container-state/atom-testnet-data/data/state.db/MANIFEST-000116 deleted file mode 100644 index 41565f0c98..0000000000 Binary files a/.docker/container-state/atom-testnet-data/data/state.db/MANIFEST-000116 and /dev/null differ diff --git a/.docker/container-state/atom-testnet-data/data/state.db/MANIFEST-000131 b/.docker/container-state/atom-testnet-data/data/state.db/MANIFEST-000131 new file mode 100644 index 0000000000..f5239ae69c Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/state.db/MANIFEST-000131 differ diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/000070.ldb b/.docker/container-state/atom-testnet-data/data/tx_index.db/000070.ldb deleted file mode 100644 index 13cc2bd346..0000000000 Binary files a/.docker/container-state/atom-testnet-data/data/tx_index.db/000070.ldb and /dev/null differ diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/000071.ldb b/.docker/container-state/atom-testnet-data/data/tx_index.db/000071.ldb deleted file mode 100644 index c7dcbe51d4..0000000000 Binary files a/.docker/container-state/atom-testnet-data/data/tx_index.db/000071.ldb and /dev/null differ diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/000090.ldb b/.docker/container-state/atom-testnet-data/data/tx_index.db/000090.ldb deleted file mode 100644 index 32bb2004b9..0000000000 Binary files a/.docker/container-state/atom-testnet-data/data/tx_index.db/000090.ldb and /dev/null differ diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/000095.ldb b/.docker/container-state/atom-testnet-data/data/tx_index.db/000095.ldb deleted file mode 100644 index f22e4b3e9b..0000000000 Binary files a/.docker/container-state/atom-testnet-data/data/tx_index.db/000095.ldb and /dev/null differ diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/000096.log b/.docker/container-state/atom-testnet-data/data/tx_index.db/000096.log deleted file mode 100644 index a57e507e95..0000000000 Binary files a/.docker/container-state/atom-testnet-data/data/tx_index.db/000096.log and /dev/null differ diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/000101.ldb b/.docker/container-state/atom-testnet-data/data/tx_index.db/000101.ldb new file mode 100644 index 0000000000..708a3e9b1f Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/tx_index.db/000101.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/000102.ldb b/.docker/container-state/atom-testnet-data/data/tx_index.db/000102.ldb new file mode 100644 index 0000000000..881b5395e2 Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/tx_index.db/000102.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/000105.ldb b/.docker/container-state/atom-testnet-data/data/tx_index.db/000105.ldb new file mode 100644 index 0000000000..f6ef7556d8 Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/tx_index.db/000105.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/000108.ldb b/.docker/container-state/atom-testnet-data/data/tx_index.db/000108.ldb new file mode 100644 index 0000000000..5e5fa0b9d8 Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/tx_index.db/000108.ldb differ diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/000109.log b/.docker/container-state/atom-testnet-data/data/tx_index.db/000109.log new file mode 100644 index 0000000000..d42d24bdef Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/tx_index.db/000109.log differ diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/CURRENT b/.docker/container-state/atom-testnet-data/data/tx_index.db/CURRENT index af00d34b29..a451d53d04 100644 --- a/.docker/container-state/atom-testnet-data/data/tx_index.db/CURRENT +++ b/.docker/container-state/atom-testnet-data/data/tx_index.db/CURRENT @@ -1 +1 @@ -MANIFEST-000097 +MANIFEST-000110 diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/CURRENT.bak b/.docker/container-state/atom-testnet-data/data/tx_index.db/CURRENT.bak index 0ab25fa07f..aecd689375 100644 --- a/.docker/container-state/atom-testnet-data/data/tx_index.db/CURRENT.bak +++ b/.docker/container-state/atom-testnet-data/data/tx_index.db/CURRENT.bak @@ -1 +1 @@ -MANIFEST-000094 +MANIFEST-000107 diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/LOG b/.docker/container-state/atom-testnet-data/data/tx_index.db/LOG index 58ce5d3c39..30dd8dbfc5 100644 --- a/.docker/container-state/atom-testnet-data/data/tx_index.db/LOG +++ b/.docker/container-state/atom-testnet-data/data/tx_index.db/LOG @@ -377,3 +377,52 @@ 17:59:01.769561 version@stat F·[3 1] S·414KiB[61KiB 352KiB] Sc·[0.75 0.00] 17:59:01.773003 db@janitor F·6 G·0 17:59:01.773016 db@open done T·5.882999ms +=============== Oct 17, 2024 (UTC) =============== +05:10:40.924218 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.924296 version@stat F·[3 1] S·414KiB[61KiB 352KiB] Sc·[0.75 0.00] +05:10:40.924308 db@open opening +05:10:40.924344 journal@recovery F·1 +05:10:40.924448 journal@recovery recovering @96 +05:10:40.925246 memdb@flush created L0@98 N·145 S·2KiB "blo..\x01\xc2\xc9,v21136":"blo..\x00\x01\xb9,v21262" +05:10:40.925383 version@stat F·[4 1] S·417KiB[64KiB 352KiB] Sc·[1.00 0.00] +05:10:40.928590 db@janitor F·7 G·0 +05:10:40.928600 db@open done T·4.288823ms +05:10:40.928646 table@compaction L0·4 -> L1·1 S·417KiB Q·21281 +05:10:40.938446 table@build created L1@101 N·21257 S·415KiB "\x04\x92\xf2..\v,\x8e,v12512":"\xef\xb9<..\x9a\x1b\x86,v4043" +05:10:40.938478 version@stat F·[0 1] S·415KiB[0B 415KiB] Sc·[0.00 0.00] +05:10:40.939072 table@compaction committed F-4 S-1KiB Ke·0 D·0 T·10.406849ms +05:10:40.939148 table@remove removed @95 +05:10:40.939176 table@remove removed @90 +05:10:40.939200 table@remove removed @71 +05:10:40.939286 table@remove removed @70 +=============== Oct 17, 2024 (UTC) =============== +06:44:58.713909 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.714036 version@stat F·[0 1] S·415KiB[0B 415KiB] Sc·[0.00 0.00] +06:44:58.714046 db@open opening +06:44:58.714080 journal@recovery F·1 +06:44:58.714171 journal@recovery recovering @99 +06:44:58.717466 memdb@flush created L0@102 N·4372 S·103KiB "!\xe6\x14..CN\x88,v25566":"\xf8@\xbc..Ï\xfe,v23666" +06:44:58.717707 version@stat F·[1 1] S·518KiB[103KiB 415KiB] Sc·[0.25 0.00] +06:44:58.730090 db@janitor F·5 G·1 +06:44:58.730096 db@janitor removing table-98 +06:44:58.730122 db@open done T·16.072878ms +=============== Oct 17, 2024 (UTC) =============== +06:45:46.648667 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.648738 version@stat F·[1 1] S·518KiB[103KiB 415KiB] Sc·[0.25 0.00] +06:45:46.648749 db@open opening +06:45:46.648777 journal@recovery F·1 +06:45:46.650719 journal@recovery recovering @103 +06:45:46.651755 memdb@flush created L0@105 N·264 S·12KiB "blo..\x01\xc3^,v25655":"\xa0i\xe8..f\x92\x12,v25726" +06:45:46.653729 version@stat F·[2 1] S·531KiB[116KiB 415KiB] Sc·[0.50 0.00] +06:45:46.656293 db@janitor F·5 G·0 +06:45:46.656305 db@open done T·7.551795ms +=============== Oct 17, 2024 (UTC) =============== +06:47:26.274594 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.274675 version@stat F·[2 1] S·531KiB[116KiB 415KiB] Sc·[0.50 0.00] +06:47:26.274686 db@open opening +06:47:26.274721 journal@recovery F·1 +06:47:26.276698 journal@recovery recovering @106 +06:47:26.277708 memdb@flush created L0@108 N·435 S·7KiB "blo..\x01\xc3e,v25920":"blo..\x01\xc0\xbb,v26336" +06:47:26.279711 version@stat F·[3 1] S·538KiB[123KiB 415KiB] Sc·[0.75 0.00] +06:47:26.282380 db@janitor F·6 G·0 +06:47:26.282394 db@open done T·7.703855ms diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/MANIFEST-000097 b/.docker/container-state/atom-testnet-data/data/tx_index.db/MANIFEST-000097 deleted file mode 100644 index bbde422037..0000000000 Binary files a/.docker/container-state/atom-testnet-data/data/tx_index.db/MANIFEST-000097 and /dev/null differ diff --git a/.docker/container-state/atom-testnet-data/data/tx_index.db/MANIFEST-000110 b/.docker/container-state/atom-testnet-data/data/tx_index.db/MANIFEST-000110 new file mode 100644 index 0000000000..422b4dc63e Binary files /dev/null and b/.docker/container-state/atom-testnet-data/data/tx_index.db/MANIFEST-000110 differ diff --git a/.docker/container-state/ibc-relayer-data/config/config.yaml b/.docker/container-state/ibc-relayer-data/config/config.yaml index b31a94b30c..77886d2f40 100755 --- a/.docker/container-state/ibc-relayer-data/config/config.yaml +++ b/.docker/container-state/ibc-relayer-data/config/config.yaml @@ -10,12 +10,13 @@ chains: atom: type: cosmos value: - key-directory: /home/nimda/.relayer/keys/cosmoshub-testnet + key-directory: /root/.relayer/keys/cosmoshub-testnet key: test2 chain-id: cosmoshub-testnet rpc-addr: http://127.0.0.1:26658 account-prefix: cosmos keyring-backend: test + dynamic-gas-price: false gas-adjustment: 1.8 gas-prices: 0.5uatom min-gas-amount: 0 @@ -35,12 +36,13 @@ chains: nucleus: type: cosmos value: - key-directory: /home/nimda/.relayer/keys/nucleus-testnet + key-directory: /root/.relayer/keys/nucleus-testnet key: test1 chain-id: nucleus-testnet rpc-addr: http://127.0.0.1:26657 account-prefix: nuc keyring-backend: test + dynamic-gas-price: false gas-adjustment: 1.8 gas-prices: 0.5unucl min-gas-amount: 0 @@ -61,12 +63,12 @@ paths: nucleus-atom: src: chain-id: nucleus-testnet - client-id: 07-tendermint-1 - connection-id: connection-1 + client-id: 07-tendermint-2 + connection-id: connection-2 dst: chain-id: cosmoshub-testnet - client-id: 07-tendermint-1 - connection-id: connection-1 + client-id: 07-tendermint-2 + connection-id: connection-2 src-channel-filter: rule: "" channel-list: [] diff --git a/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-01059173227530771155 b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-01059173227530771155 new file mode 100644 index 0000000000..256a04df6e --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-01059173227530771155 @@ -0,0 +1,4 @@ +{ + "key": "f431aeec40157170c0988400", + "addrs": [] +} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-02330869765942450674 b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-02330869765942450674 new file mode 100644 index 0000000000..256a04df6e --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-02330869765942450674 @@ -0,0 +1,4 @@ +{ + "key": "f431aeec40157170c0988400", + "addrs": [] +} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-02840196522853014193 b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-02840196522853014193 new file mode 100644 index 0000000000..256a04df6e --- /dev/null +++ b/.docker/container-state/nucleus-testnet-data/config/write-file-atomic-02840196522853014193 @@ -0,0 +1,4 @@ +{ + "key": "f431aeec40157170c0988400", + "addrs": [] +} \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/000124.ldb b/.docker/container-state/nucleus-testnet-data/data/application.db/000124.ldb deleted file mode 100644 index 626e16207a..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/application.db/000124.ldb and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/000125.ldb b/.docker/container-state/nucleus-testnet-data/data/application.db/000125.ldb deleted file mode 100644 index 3590e1577d..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/application.db/000125.ldb and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/000126.ldb b/.docker/container-state/nucleus-testnet-data/data/application.db/000126.ldb deleted file mode 100644 index c9145d6ba1..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/application.db/000126.ldb and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/000127.ldb b/.docker/container-state/nucleus-testnet-data/data/application.db/000127.ldb deleted file mode 100644 index d35ee8f42a..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/application.db/000127.ldb and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/000130.ldb b/.docker/container-state/nucleus-testnet-data/data/application.db/000130.ldb deleted file mode 100644 index 70efc2fb72..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/application.db/000130.ldb and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/000131.log b/.docker/container-state/nucleus-testnet-data/data/application.db/000131.log deleted file mode 100644 index 84e0725816..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/application.db/000131.log and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/000154.ldb b/.docker/container-state/nucleus-testnet-data/data/application.db/000154.ldb new file mode 100644 index 0000000000..a8a9d5d14a Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/application.db/000154.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/000155.log b/.docker/container-state/nucleus-testnet-data/data/application.db/000155.log new file mode 100644 index 0000000000..eb4ce2123b Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/application.db/000155.log differ diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/000157.ldb b/.docker/container-state/nucleus-testnet-data/data/application.db/000157.ldb new file mode 100644 index 0000000000..d2efabcda6 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/application.db/000157.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/000158.ldb b/.docker/container-state/nucleus-testnet-data/data/application.db/000158.ldb new file mode 100644 index 0000000000..0f54f43bcb Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/application.db/000158.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/000159.ldb b/.docker/container-state/nucleus-testnet-data/data/application.db/000159.ldb new file mode 100644 index 0000000000..cb9fafd62b Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/application.db/000159.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/000160.ldb b/.docker/container-state/nucleus-testnet-data/data/application.db/000160.ldb new file mode 100644 index 0000000000..a8417c6b23 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/application.db/000160.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/CURRENT b/.docker/container-state/nucleus-testnet-data/data/application.db/CURRENT index c39c670731..c16f179ffd 100644 --- a/.docker/container-state/nucleus-testnet-data/data/application.db/CURRENT +++ b/.docker/container-state/nucleus-testnet-data/data/application.db/CURRENT @@ -1 +1 @@ -MANIFEST-000132 +MANIFEST-000156 diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/CURRENT.bak b/.docker/container-state/nucleus-testnet-data/data/application.db/CURRENT.bak index ea072ca932..1dec270d08 100644 --- a/.docker/container-state/nucleus-testnet-data/data/application.db/CURRENT.bak +++ b/.docker/container-state/nucleus-testnet-data/data/application.db/CURRENT.bak @@ -1 +1 @@ -MANIFEST-000129 +MANIFEST-000149 diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/LOG b/.docker/container-state/nucleus-testnet-data/data/application.db/LOG index a806f60292..d5977bfc9f 100644 --- a/.docker/container-state/nucleus-testnet-data/data/application.db/LOG +++ b/.docker/container-state/nucleus-testnet-data/data/application.db/LOG @@ -461,3 +461,91 @@ 17:59:01.664278 version@stat F·[1 4] S·6MiB[526KiB 6MiB] Sc·[0.25 0.06] 17:59:01.667135 db@janitor F·7 G·0 17:59:01.667155 db@open done T·11.159872ms +=============== Oct 17, 2024 (UTC) =============== +05:10:40.905554 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.905681 version@stat F·[1 4] S·6MiB[526KiB 6MiB] Sc·[0.25 0.06] +05:10:40.905693 db@open opening +05:10:40.905729 journal@recovery F·1 +05:10:40.905932 journal@recovery recovering @131 +05:10:40.907646 memdb@flush created L0@133 N·625 S·34KiB "s/1032,v129146":"s/p..hts,v129145" +05:10:40.907787 version@stat F·[2 4] S·6MiB[560KiB 6MiB] Sc·[0.50 0.06] +05:10:40.912478 db@janitor F·8 G·0 +05:10:40.912492 db@open done T·6.794072ms +05:10:40.980773 table@compaction L0·2 -> L1·4 S·6MiB Q·129648 +05:10:40.997373 table@build created L1@136 N·24265 S·2MiB "s/1,v499":"s/k..\xbb\x1da,v114109" +05:10:41.023402 table@build created L1@137 N·42517 S·2MiB "s/k..\v\xd1\xd9,v114104":"s/k..\x926\x81,v45485" +05:10:41.037226 table@build created L1@138 N·15792 S·2MiB "s/k..\xce\t\xe8,v127197":"s/k..A\xb8j,v10829" +05:10:41.043103 table@build created L1@139 N·14633 S·724KiB "s/k..\x0f\xbb',v65315":"s/p..hts,v129645" +05:10:41.043137 version@stat F·[0 4] S·6MiB[0B 6MiB] Sc·[0.00 0.07] +05:10:41.043726 table@compaction committed F-2 S-8KiB Ke·0 D·2346 T·62.926263ms +05:10:41.043888 table@remove removed @130 +05:10:41.044256 table@remove removed @124 +05:10:41.044737 table@remove removed @125 +05:10:41.045200 table@remove removed @126 +05:10:41.045256 table@remove removed @127 +=============== Oct 17, 2024 (UTC) =============== +06:44:58.635757 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.635904 version@stat F·[0 4] S·6MiB[0B 6MiB] Sc·[0.00 0.07] +06:44:58.635919 db@open opening +06:44:58.635952 journal@recovery F·1 +06:44:58.636043 journal@recovery recovering @134 +06:44:58.675589 memdb@flush created L0@140 N·18960 S·1MiB "s/1037,v129772":"s/p..hts,v129771" +06:44:58.675735 version@stat F·[1 4] S·7MiB[1MiB 6MiB] Sc·[0.25 0.07] +06:44:58.690561 db@janitor F·8 G·1 +06:44:58.690567 db@janitor removing table-133 +06:44:58.690608 db@open done T·54.685771ms +06:45:23.851285 table@compaction L0·1 -> L1·4 S·7MiB Q·149292 +06:45:23.863708 table@build created L1@143 N·20853 S·2MiB "s/1,v499":"s/k..\x90\xb6\xf1,v46843" +06:45:23.878194 table@build created L1@144 N·34793 S·2MiB "s/k..^\xb3\xfc,v46849":"s/k..\xf5X*,v124880" +06:45:23.891282 table@build created L1@145 N·32649 S·2MiB "s/k..\\\x8d?,v124877":"s/k..\xf6OO,v108777" +06:45:23.901184 table@build created L1@146 N·23324 S·1MiB "s/k..\xbe\x90\x84,v72822":"s/p..hts,v148606" +06:45:23.901206 version@stat F·[0 4] S·7MiB[0B 7MiB] Sc·[0.00 0.08] +06:45:23.901783 table@compaction committed F-1 S-22KiB Ke·0 D·4548 T·50.479285ms +06:45:23.902188 table@remove removed @136 +06:45:23.902594 table@remove removed @137 +06:45:23.903127 table@remove removed @138 +06:45:23.903277 table@remove removed @139 +=============== Oct 17, 2024 (UTC) =============== +06:45:46.632694 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.632829 version@stat F·[0 4] S·7MiB[0B 7MiB] Sc·[0.00 0.08] +06:45:46.632841 db@open opening +06:45:46.632873 journal@recovery F·1 +06:45:46.633065 journal@recovery recovering @141 +06:45:46.634967 memdb@flush created L0@147 N·1058 S·59KiB "s/1181,v148733":"s/p..hts,v148732" +06:45:46.635106 version@stat F·[1 4] S·7MiB[59KiB 7MiB] Sc·[0.25 0.08] +06:45:46.637686 db@janitor F·8 G·1 +06:45:46.637692 db@janitor removing table-140 +06:45:46.637923 db@open done T·5.078403ms +06:46:51.996792 table@compaction L0·1 -> L1·4 S·7MiB Q·151169 +06:46:52.036950 table@build created L1@150 N·20616 S·2MiB "s/1,v499":"s/k..w\x17D,v41895" +06:46:52.085978 table@build created L1@151 N·34742 S·2MiB "s/k..\x02F\xf2,v41900":"s/k..\xffQ{,v117451" +06:46:52.135406 table@build created L1@152 N·33250 S·2MiB "s/k..\x96\xeeu,v117450":"s/k..\x1c\xb9t,v75679" +06:46:52.173716 table@build created L1@153 N·23832 S·1MiB "s/k..{\x9f\xd9,v41367":"s/p..hts,v149665" +06:46:52.173757 version@stat F·[0 4] S·7MiB[0B 7MiB] Sc·[0.00 0.08] +06:46:52.181047 table@compaction committed F-1 S+1KiB Ke·0 D·237 T·184.227992ms +06:46:52.181530 table@remove removed @143 +06:46:52.182010 table@remove removed @144 +06:46:52.182474 table@remove removed @145 +06:46:52.182906 table@remove removed @146 +=============== Oct 17, 2024 (UTC) =============== +06:47:26.253548 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.253681 version@stat F·[0 4] S·7MiB[0B 7MiB] Sc·[0.00 0.08] +06:47:26.253693 db@open opening +06:47:26.253724 journal@recovery F·1 +06:47:26.253926 journal@recovery recovering @148 +06:47:26.256527 memdb@flush created L0@154 N·1873 S·100KiB "s/1188,v149792":"s/p..hts,v149791" +06:47:26.256677 version@stat F·[1 4] S·7MiB[100KiB 7MiB] Sc·[0.25 0.08] +06:47:26.259838 db@janitor F·8 G·1 +06:47:26.259845 db@janitor removing table-147 +06:47:26.259895 db@open done T·6.196873ms +06:47:51.346605 table@compaction L0·1 -> L1·4 S·7MiB Q·152042 +06:47:51.358754 table@build created L1@157 N·20319 S·2MiB "s/1,v499":"s/k..7\xcc\xf2,v36191" +06:47:51.372848 table@build created L1@158 N·34591 S·2MiB "s/k..8\x00\x1e,v36192":"s/k..\xfd<\x97,v106679" +06:47:51.386025 table@build created L1@159 N·34293 S·2MiB "s/k..\xe4\x8ej,v106681":"s/k..@m\xdb,v129528" +06:47:51.396388 table@build created L1@160 N·24643 S·1MiB "s/k..\xdb\xee\x8f,v145753":"s/p..hts,v151539" +06:47:51.396408 version@stat F·[0 4] S·7MiB[0B 7MiB] Sc·[0.00 0.08] +06:47:51.396980 table@compaction committed F-1 S-1KiB Ke·0 D·467 T·50.358509ms +06:47:51.397411 table@remove removed @150 +06:47:51.397858 table@remove removed @151 +06:47:51.398295 table@remove removed @152 +06:47:51.398626 table@remove removed @153 diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/MANIFEST-000132 b/.docker/container-state/nucleus-testnet-data/data/application.db/MANIFEST-000132 deleted file mode 100644 index 50463c21d5..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/application.db/MANIFEST-000132 and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/application.db/MANIFEST-000156 b/.docker/container-state/nucleus-testnet-data/data/application.db/MANIFEST-000156 new file mode 100644 index 0000000000..7da12a9d2f Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/application.db/MANIFEST-000156 differ diff --git a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000089.ldb b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000089.ldb deleted file mode 100644 index 139161de2b..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000089.ldb and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000090.log b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000090.log deleted file mode 100644 index 39218b184a..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000090.log and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000092.ldb b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000092.ldb deleted file mode 100644 index f0670e0093..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000092.ldb and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000104.ldb b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000104.ldb new file mode 100644 index 0000000000..c4e0ac3f50 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000104.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000105.log b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000105.log new file mode 100644 index 0000000000..b8f80b5b12 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000105.log differ diff --git a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000107.ldb b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000107.ldb new file mode 100644 index 0000000000..33c48c0b3e Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/000107.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/CURRENT b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/CURRENT index 00f4669871..abdfdfe276 100644 --- a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/CURRENT +++ b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/CURRENT @@ -1 +1 @@ -MANIFEST-000091 +MANIFEST-000106 diff --git a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/CURRENT.bak b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/CURRENT.bak index 948a0b647f..e333c89b48 100644 --- a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/CURRENT.bak +++ b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/CURRENT.bak @@ -1 +1 @@ -MANIFEST-000088 +MANIFEST-000102 diff --git a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/LOG b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/LOG index 1b30a4e429..f9e576d734 100644 --- a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/LOG +++ b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/LOG @@ -389,3 +389,70 @@ 17:59:01.708096 table@remove removed @73 17:59:27.006678 db@close closing 17:59:27.006727 db@close done T·48.84µs +=============== Oct 17, 2024 (UTC) =============== +05:10:40.941929 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.942055 version@stat F·[0 1] S·979KiB[0B 979KiB] Sc·[0.00 0.01] +05:10:40.942068 db@open opening +05:10:40.942101 journal@recovery F·1 +05:10:40.943919 journal@recovery recovering @90 +05:10:40.944706 memdb@flush created L0@93 N·30 S·3KiB "BH:..e6d,v6218":"blo..ore,v6215" +05:10:40.945097 version@stat F·[1 1] S·983KiB[3KiB 979KiB] Sc·[0.25 0.01] +05:10:40.947901 db@janitor F·5 G·1 +05:10:40.947908 db@janitor removing table-89 +05:10:40.947956 db@open done T·5.883755ms +05:11:11.986457 table@compaction L0·1 -> L1·1 S·983KiB Q·6276 +05:11:11.992423 table@build created L1@96 N·5181 S·982KiB "BH:..c5c,v363":"blo..ore,v6239" +05:11:11.992451 version@stat F·[0 1] S·982KiB[0B 982KiB] Sc·[0.00 0.01] +05:11:11.993034 table@compaction committed F-1 S-280B Ke·0 D·5 T·6.5542ms +05:11:11.993266 table@remove removed @92 +05:22:45.688799 db@close closing +05:22:45.688852 db@close done T·52.32µs +=============== Oct 17, 2024 (UTC) =============== +06:44:58.731477 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.731572 version@stat F·[0 1] S·982KiB[0B 982KiB] Sc·[0.00 0.01] +06:44:58.731582 db@open opening +06:44:58.731608 journal@recovery F·1 +06:44:58.733590 journal@recovery recovering @94 +06:44:58.741468 memdb@flush created L0@97 N·864 S·142KiB "BH:..91c,v6735":"blo..ore,v6246" +06:44:58.741742 version@stat F·[1 1] S·1MiB[142KiB 982KiB] Sc·[0.25 0.01] +06:44:58.757548 db@janitor F·5 G·1 +06:44:58.757554 db@janitor removing table-93 +06:44:58.757586 db@open done T·26.001494ms +06:45:36.822661 db@close closing +06:45:36.822719 db@close done T·57.27µs +=============== Oct 17, 2024 (UTC) =============== +06:45:46.666531 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.666665 version@stat F·[1 1] S·1MiB[142KiB 982KiB] Sc·[0.25 0.01] +06:45:46.666678 db@open opening +06:45:46.666711 journal@recovery F·1 +06:45:46.668662 journal@recovery recovering @98 +06:45:46.669474 memdb@flush created L0@100 N·42 S·10KiB "BH:..b99,v7144":"blo..ore,v7111" +06:45:46.669607 version@stat F·[2 1] S·1MiB[152KiB 982KiB] Sc·[0.50 0.01] +06:45:46.672153 db@janitor F·5 G·0 +06:45:46.672180 db@open done T·5.497737ms +06:46:16.625229 table@compaction L0·2 -> L1·1 S·1MiB Q·7178 +06:46:16.632031 table@build created L1@103 N·5936 S·1MiB "BH:..c5c,v363":"blo..ore,v7147" +06:46:16.632059 version@stat F·[0 1] S·1MiB[0B 1MiB] Sc·[0.00 0.01] +06:46:16.632639 table@compaction committed F-2 S+122B Ke·0 D·151 T·7.390783ms +06:46:16.632728 table@remove removed @97 +06:46:16.632918 table@remove removed @96 +06:47:06.119564 db@close closing +06:47:06.119632 db@close done T·68.141µs +=============== Oct 17, 2024 (UTC) =============== +06:47:26.289119 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.289213 version@stat F·[0 1] S·1MiB[0B 1MiB] Sc·[0.00 0.01] +06:47:26.289224 db@open opening +06:47:26.289255 journal@recovery F·1 +06:47:26.291626 journal@recovery recovering @101 +06:47:26.293004 memdb@flush created L0@104 N·90 S·14KiB "BH:..022,v7211":"blo..ore,v7154" +06:47:26.293131 version@stat F·[1 1] S·1MiB[14KiB 1MiB] Sc·[0.25 0.01] +06:47:26.295912 db@janitor F·5 G·1 +06:47:26.295920 db@janitor removing table-100 +06:47:26.295955 db@open done T·6.727058ms +06:47:51.340007 table@compaction L0·1 -> L1·1 S·1MiB Q·7263 +06:47:51.346786 table@build created L1@107 N·6011 S·1MiB "BH:..c5c,v363":"blo..ore,v7238" +06:47:51.346809 version@stat F·[0 1] S·1MiB[0B 1MiB] Sc·[0.00 0.01] +06:47:51.348343 table@compaction committed F-1 S-97B Ke·0 D·15 T·8.315581ms +06:47:51.348639 table@remove removed @103 +06:47:53.050412 db@close closing +06:47:53.050476 db@close done T·62.691µs diff --git a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/MANIFEST-000091 b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/MANIFEST-000091 deleted file mode 100644 index 849bb8a758..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/MANIFEST-000091 and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/blockstore.db/MANIFEST-000106 b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/MANIFEST-000106 new file mode 100644 index 0000000000..3959a2ff27 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/blockstore.db/MANIFEST-000106 differ diff --git a/.docker/container-state/nucleus-testnet-data/data/cs.wal/wal b/.docker/container-state/nucleus-testnet-data/data/cs.wal/wal index 1e135caef0..a0de7456d1 100644 Binary files a/.docker/container-state/nucleus-testnet-data/data/cs.wal/wal and b/.docker/container-state/nucleus-testnet-data/data/cs.wal/wal differ diff --git a/.docker/container-state/nucleus-testnet-data/data/evidence.db/000062.log b/.docker/container-state/nucleus-testnet-data/data/evidence.db/000070.log similarity index 100% rename from .docker/container-state/nucleus-testnet-data/data/evidence.db/000062.log rename to .docker/container-state/nucleus-testnet-data/data/evidence.db/000070.log diff --git a/.docker/container-state/nucleus-testnet-data/data/evidence.db/CURRENT b/.docker/container-state/nucleus-testnet-data/data/evidence.db/CURRENT index e8c02667ae..be93edb695 100644 --- a/.docker/container-state/nucleus-testnet-data/data/evidence.db/CURRENT +++ b/.docker/container-state/nucleus-testnet-data/data/evidence.db/CURRENT @@ -1 +1 @@ -MANIFEST-000063 +MANIFEST-000071 diff --git a/.docker/container-state/nucleus-testnet-data/data/evidence.db/CURRENT.bak b/.docker/container-state/nucleus-testnet-data/data/evidence.db/CURRENT.bak index ebafc63b8e..5893b8f83b 100644 --- a/.docker/container-state/nucleus-testnet-data/data/evidence.db/CURRENT.bak +++ b/.docker/container-state/nucleus-testnet-data/data/evidence.db/CURRENT.bak @@ -1 +1 @@ -MANIFEST-000061 +MANIFEST-000069 diff --git a/.docker/container-state/nucleus-testnet-data/data/evidence.db/LOG b/.docker/container-state/nucleus-testnet-data/data/evidence.db/LOG index bba04859e3..603afd6c50 100644 --- a/.docker/container-state/nucleus-testnet-data/data/evidence.db/LOG +++ b/.docker/container-state/nucleus-testnet-data/data/evidence.db/LOG @@ -283,3 +283,39 @@ 17:59:01.713504 version@stat F·[] S·0B[] Sc·[] 17:59:01.716861 db@janitor F·2 G·0 17:59:01.716876 db@open done T·3.62282ms +=============== Oct 17, 2024 (UTC) =============== +05:10:40.957492 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.957616 version@stat F·[] S·0B[] Sc·[] +05:10:40.957629 db@open opening +05:10:40.957658 journal@recovery F·1 +05:10:40.957894 journal@recovery recovering @62 +05:10:40.958388 version@stat F·[] S·0B[] Sc·[] +05:10:40.961122 db@janitor F·2 G·0 +05:10:40.961141 db@open done T·3.507417ms +=============== Oct 17, 2024 (UTC) =============== +06:44:58.803399 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.803504 version@stat F·[] S·0B[] Sc·[] +06:44:58.803519 db@open opening +06:44:58.803556 journal@recovery F·1 +06:44:58.803645 journal@recovery recovering @64 +06:44:58.803912 version@stat F·[] S·0B[] Sc·[] +06:44:58.813046 db@janitor F·2 G·0 +06:44:58.813055 db@open done T·9.533813ms +=============== Oct 17, 2024 (UTC) =============== +06:45:46.688140 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.688207 version@stat F·[] S·0B[] Sc·[] +06:45:46.688217 db@open opening +06:45:46.688244 journal@recovery F·1 +06:45:46.688329 journal@recovery recovering @66 +06:45:46.688475 version@stat F·[] S·0B[] Sc·[] +06:45:46.691028 db@janitor F·2 G·0 +06:45:46.691045 db@open done T·2.823914ms +=============== Oct 17, 2024 (UTC) =============== +06:47:26.306936 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.307069 version@stat F·[] S·0B[] Sc·[] +06:47:26.307085 db@open opening +06:47:26.307124 journal@recovery F·1 +06:47:26.307921 journal@recovery recovering @68 +06:47:26.308103 version@stat F·[] S·0B[] Sc·[] +06:47:26.310856 db@janitor F·2 G·0 +06:47:26.310866 db@open done T·3.776282ms diff --git a/.docker/container-state/nucleus-testnet-data/data/evidence.db/MANIFEST-000063 b/.docker/container-state/nucleus-testnet-data/data/evidence.db/MANIFEST-000063 deleted file mode 100644 index f5b9b4efb8..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/evidence.db/MANIFEST-000063 and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/evidence.db/MANIFEST-000071 b/.docker/container-state/nucleus-testnet-data/data/evidence.db/MANIFEST-000071 new file mode 100644 index 0000000000..b4aa9a9181 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/evidence.db/MANIFEST-000071 differ diff --git a/.docker/container-state/nucleus-testnet-data/data/priv_validator_state.json b/.docker/container-state/nucleus-testnet-data/data/priv_validator_state.json index 8bf82338a8..70797c4328 100644 --- a/.docker/container-state/nucleus-testnet-data/data/priv_validator_state.json +++ b/.docker/container-state/nucleus-testnet-data/data/priv_validator_state.json @@ -1,7 +1,7 @@ { - "height": "1036", + "height": "1207", "round": 0, "step": 3, - "signature": "v27Rs6us+6Q7cj+aLxVHFzzu3VuuAnLMflQ/rhuAh4CgOngnQ5pvY25d3yFM32fBXneJEm0bRYy/30RDgYtEDA==", - "signbytes": "740802110C0400000000000022480A20893D8ECF312013CEA3102503BF9E39C5C78EB6AEAFA0DE202529F944D899793B122408011220D72D5FD098990DF6F3EC52E4602F660D611936C04D5DD307585531367D52F82C2A0C08FEA296B4061082BDE6E502320F6E75636C6575732D746573746E6574" + "signature": "icgswVCjm79XUsbei+n25O+rpUOvze+sTf2mK8ldJjFTDKocmcTEENgkYLa9qUoOTtAw8QgluaDJgzmiiBjwDg==", + "signbytes": "74080211B70400000000000022480A20284E4194EEBC89AFFBE87A98751F5A51DA90E72D8D6345FA1D2BBFEB89343EFC12240801122015262FE05A2AC0DA993FDF279A954CB68A15673F1521A48D39E7D65768595D802A0C0897E6C2B806108C99CBA301320F6E75636C6575732D746573746E6574" } \ No newline at end of file diff --git a/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/000062.log b/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/000070.log similarity index 100% rename from .docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/000062.log rename to .docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/000070.log diff --git a/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/CURRENT b/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/CURRENT index e8c02667ae..be93edb695 100644 --- a/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/CURRENT +++ b/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/CURRENT @@ -1 +1 @@ -MANIFEST-000063 +MANIFEST-000071 diff --git a/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/CURRENT.bak b/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/CURRENT.bak index ebafc63b8e..5893b8f83b 100644 --- a/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/CURRENT.bak +++ b/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/CURRENT.bak @@ -1 +1 @@ -MANIFEST-000061 +MANIFEST-000069 diff --git a/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/LOG b/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/LOG index f1dec582ce..1210bffa25 100644 --- a/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/LOG +++ b/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/LOG @@ -283,3 +283,39 @@ 17:59:01.671281 version@stat F·[] S·0B[] Sc·[] 17:59:01.674069 db@janitor F·2 G·0 17:59:01.674079 db@open done T·3.076606ms +=============== Oct 17, 2024 (UTC) =============== +05:10:40.917433 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.917549 version@stat F·[] S·0B[] Sc·[] +05:10:40.917564 db@open opening +05:10:40.917602 journal@recovery F·1 +05:10:40.917711 journal@recovery recovering @62 +05:10:40.918030 version@stat F·[] S·0B[] Sc·[] +05:10:40.922003 db@janitor F·2 G·0 +05:10:40.922017 db@open done T·4.448515ms +=============== Oct 17, 2024 (UTC) =============== +06:44:58.694325 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.694411 version@stat F·[] S·0B[] Sc·[] +06:44:58.694425 db@open opening +06:44:58.694464 journal@recovery F·1 +06:44:58.694541 journal@recovery recovering @64 +06:44:58.694784 version@stat F·[] S·0B[] Sc·[] +06:44:58.713362 db@janitor F·2 G·0 +06:44:58.713379 db@open done T·18.950723ms +=============== Oct 17, 2024 (UTC) =============== +06:45:46.641800 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.641874 version@stat F·[] S·0B[] Sc·[] +06:45:46.641887 db@open opening +06:45:46.641917 journal@recovery F·1 +06:45:46.642008 journal@recovery recovering @66 +06:45:46.642134 version@stat F·[] S·0B[] Sc·[] +06:45:46.646348 db@janitor F·2 G·0 +06:45:46.646361 db@open done T·4.469118ms +=============== Oct 17, 2024 (UTC) =============== +06:47:26.263677 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.263742 version@stat F·[] S·0B[] Sc·[] +06:47:26.263752 db@open opening +06:47:26.263776 journal@recovery F·1 +06:47:26.263857 journal@recovery recovering @68 +06:47:26.264177 version@stat F·[] S·0B[] Sc·[] +06:47:26.269116 db@janitor F·2 G·0 +06:47:26.269129 db@open done T·5.373646ms diff --git a/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/MANIFEST-000063 b/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/MANIFEST-000063 deleted file mode 100644 index f5b9b4efb8..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/MANIFEST-000063 and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/MANIFEST-000071 b/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/MANIFEST-000071 new file mode 100644 index 0000000000..b4aa9a9181 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/snapshots/metadata.db/MANIFEST-000071 differ diff --git a/.docker/container-state/nucleus-testnet-data/data/state.db/000091.ldb b/.docker/container-state/nucleus-testnet-data/data/state.db/000091.ldb deleted file mode 100644 index ce41e60f12..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/state.db/000091.ldb and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/state.db/000106.ldb b/.docker/container-state/nucleus-testnet-data/data/state.db/000106.ldb new file mode 100644 index 0000000000..41709b5d5a Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/state.db/000106.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/state.db/000092.log b/.docker/container-state/nucleus-testnet-data/data/state.db/000107.log similarity index 83% rename from .docker/container-state/nucleus-testnet-data/data/state.db/000092.log rename to .docker/container-state/nucleus-testnet-data/data/state.db/000107.log index 7aa8691b72..57c76a9bb0 100644 Binary files a/.docker/container-state/nucleus-testnet-data/data/state.db/000092.log and b/.docker/container-state/nucleus-testnet-data/data/state.db/000107.log differ diff --git a/.docker/container-state/nucleus-testnet-data/data/state.db/000088.ldb b/.docker/container-state/nucleus-testnet-data/data/state.db/000109.ldb similarity index 70% rename from .docker/container-state/nucleus-testnet-data/data/state.db/000088.ldb rename to .docker/container-state/nucleus-testnet-data/data/state.db/000109.ldb index 72caa9fb6f..c3d631b9d3 100644 Binary files a/.docker/container-state/nucleus-testnet-data/data/state.db/000088.ldb and b/.docker/container-state/nucleus-testnet-data/data/state.db/000109.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/state.db/CURRENT b/.docker/container-state/nucleus-testnet-data/data/state.db/CURRENT index f60e23b00c..db6fa61f96 100644 --- a/.docker/container-state/nucleus-testnet-data/data/state.db/CURRENT +++ b/.docker/container-state/nucleus-testnet-data/data/state.db/CURRENT @@ -1 +1 @@ -MANIFEST-000093 +MANIFEST-000108 diff --git a/.docker/container-state/nucleus-testnet-data/data/state.db/CURRENT.bak b/.docker/container-state/nucleus-testnet-data/data/state.db/CURRENT.bak index 2f2c868af7..c8e9be65ea 100644 --- a/.docker/container-state/nucleus-testnet-data/data/state.db/CURRENT.bak +++ b/.docker/container-state/nucleus-testnet-data/data/state.db/CURRENT.bak @@ -1 +1 @@ -MANIFEST-000090 +MANIFEST-000104 diff --git a/.docker/container-state/nucleus-testnet-data/data/state.db/LOG b/.docker/container-state/nucleus-testnet-data/data/state.db/LOG index c811eadd21..ceaaced552 100644 --- a/.docker/container-state/nucleus-testnet-data/data/state.db/LOG +++ b/.docker/container-state/nucleus-testnet-data/data/state.db/LOG @@ -394,3 +394,75 @@ 17:59:01.705289 db@open done T·5.934209ms 17:59:27.006738 db@close closing 17:59:27.006773 db@close done T·35.621µs +=============== Oct 17, 2024 (UTC) =============== +05:10:40.948045 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.948116 version@stat F·[1 1] S·480KiB[87KiB 392KiB] Sc·[0.25 0.00] +05:10:40.948126 db@open opening +05:10:40.948156 journal@recovery F·1 +05:10:40.948359 journal@recovery recovering @92 +05:10:40.949167 memdb@flush created L0@94 N·25 S·5KiB "abc..032,v5184":"val..038,v5206" +05:10:40.949303 version@stat F·[2 1] S·485KiB[93KiB 392KiB] Sc·[0.50 0.00] +05:10:40.952715 db@janitor F·5 G·0 +05:10:40.952726 db@open done T·4.596875ms +05:10:52.975901 table@compaction L0·2 -> L1·1 S·485KiB Q·5219 +05:10:52.980319 table@build created L1@97 N·3114 S·432KiB "abc..y:1,v6":"val..999,v5010" +05:10:52.980342 version@stat F·[0 1] S·432KiB[0B 432KiB] Sc·[0.00 0.00] +05:10:52.980914 table@compaction committed F-2 S-53KiB Ke·0 D·148 T·4.995718ms +05:10:52.980988 table@remove removed @91 +05:10:52.981079 table@remove removed @88 +05:22:45.688861 db@close closing +05:22:45.688894 db@close done T·32.78µs +=============== Oct 17, 2024 (UTC) =============== +06:44:58.757687 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.757762 version@stat F·[0 1] S·432KiB[0B 432KiB] Sc·[0.00 0.00] +06:44:58.757772 db@open opening +06:44:58.757803 journal@recovery F·1 +06:44:58.758114 journal@recovery recovering @95 +06:44:58.766158 memdb@flush created L0@98 N·720 S·160KiB "abc..037,v5210":"val..182,v5927" +06:44:58.768181 version@stat F·[1 1] S·593KiB[160KiB 432KiB] Sc·[0.25 0.00] +06:44:58.782781 db@janitor F·5 G·1 +06:44:58.782787 db@janitor removing table-94 +06:44:58.782815 db@open done T·25.040516ms +06:45:31.737199 table@compaction L0·1 -> L1·1 S·593KiB Q·5960 +06:45:31.742113 table@build created L1@101 N·3546 S·502KiB "abc..y:1,v6":"val..999,v5010" +06:45:31.742138 version@stat F·[0 1] S·502KiB[0B 502KiB] Sc·[0.00 0.00] +06:45:31.742713 table@compaction committed F-1 S-90KiB Ke·0 D·288 T·5.489518ms +06:45:31.742852 table@remove removed @97 +06:45:36.822729 db@close closing +06:45:36.822759 db@close done T·30.251µs +=============== Oct 17, 2024 (UTC) =============== +06:45:46.672283 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.672368 version@stat F·[0 1] S·502KiB[0B 502KiB] Sc·[0.00 0.00] +06:45:46.672378 db@open opening +06:45:46.672405 journal@recovery F·1 +06:45:46.672600 journal@recovery recovering @99 +06:45:46.673679 memdb@flush created L0@102 N·35 S·14KiB "abc..181,v5931":"val..189,v5963" +06:45:46.674067 version@stat F·[1 1] S·517KiB[14KiB 502KiB] Sc·[0.25 0.00] +06:45:46.676665 db@janitor F·5 G·1 +06:45:46.676674 db@janitor removing table-98 +06:45:46.676808 db@open done T·4.425948ms +06:46:17.600950 table@compaction L0·1 -> L1·1 S·517KiB Q·5996 +06:46:17.606418 table@build created L1@105 N·3567 S·509KiB "abc..y:1,v6":"val..999,v5010" +06:46:17.606444 version@stat F·[0 1] S·509KiB[0B 509KiB] Sc·[0.00 0.00] +06:46:17.607473 table@compaction committed F-1 S-8KiB Ke·0 D·14 T·6.501365ms +06:46:17.607615 table@remove removed @101 +06:47:06.119645 db@close closing +06:47:06.119690 db@close done T·44.54µs +=============== Oct 17, 2024 (UTC) =============== +06:47:26.296053 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.296123 version@stat F·[0 1] S·509KiB[0B 509KiB] Sc·[0.00 0.00] +06:47:26.296133 db@open opening +06:47:26.296160 journal@recovery F·1 +06:47:26.296240 journal@recovery recovering @103 +06:47:26.297208 memdb@flush created L0@106 N·75 S·14KiB "abc..188,v5967":"val..204,v6039" +06:47:26.297326 version@stat F·[1 1] S·523KiB[14KiB 509KiB] Sc·[0.25 0.00] +06:47:26.300025 db@janitor F·5 G·1 +06:47:26.300032 db@janitor removing table-102 +06:47:26.300068 db@open done T·3.931804ms +06:47:51.340428 table@compaction L0·1 -> L1·1 S·523KiB Q·6062 +06:47:51.345463 table@build created L1@109 N·3612 S·515KiB "abc..y:1,v6":"val..999,v5010" +06:47:51.345490 version@stat F·[0 1] S·515KiB[0B 515KiB] Sc·[0.00 0.01] +06:47:51.347253 table@compaction committed F-1 S-8KiB Ke·0 D·30 T·6.790988ms +06:47:51.347409 table@remove removed @105 +06:47:53.050487 db@close closing +06:47:53.050515 db@close done T·27.49µs diff --git a/.docker/container-state/nucleus-testnet-data/data/state.db/MANIFEST-000093 b/.docker/container-state/nucleus-testnet-data/data/state.db/MANIFEST-000093 deleted file mode 100644 index f044b3f1c5..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/state.db/MANIFEST-000093 and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/state.db/MANIFEST-000108 b/.docker/container-state/nucleus-testnet-data/data/state.db/MANIFEST-000108 new file mode 100644 index 0000000000..3e0e6991cd Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/state.db/MANIFEST-000108 differ diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000078.ldb b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000078.ldb deleted file mode 100644 index 3c915bd371..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000078.ldb and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000079.ldb b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000079.ldb deleted file mode 100644 index d228e9d6d5..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000079.ldb and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000084.ldb b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000084.ldb deleted file mode 100644 index dbe9644aa0..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000084.ldb and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000089.ldb b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000089.ldb deleted file mode 100644 index c1388b6fb7..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000089.ldb and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000090.log b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000090.log deleted file mode 100644 index 0897ff3654..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000090.log and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000095.ldb b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000095.ldb new file mode 100644 index 0000000000..d60494d2f4 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000095.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000096.ldb b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000096.ldb new file mode 100644 index 0000000000..ced510724b Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000096.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000099.ldb b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000099.ldb new file mode 100644 index 0000000000..835a70959c Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000099.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000102.ldb b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000102.ldb new file mode 100644 index 0000000000..b1a9417bb5 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000102.ldb differ diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000103.log b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000103.log new file mode 100644 index 0000000000..83c9380d98 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/000103.log differ diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/CURRENT b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/CURRENT index 00f4669871..c8e9be65ea 100644 --- a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/CURRENT +++ b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/CURRENT @@ -1 +1 @@ -MANIFEST-000091 +MANIFEST-000104 diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/CURRENT.bak b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/CURRENT.bak index 948a0b647f..ed5ac889a6 100644 --- a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/CURRENT.bak +++ b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/CURRENT.bak @@ -1 +1 @@ -MANIFEST-000088 +MANIFEST-000101 diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/LOG b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/LOG index 6f949c5d7c..d472949dbb 100644 --- a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/LOG +++ b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/LOG @@ -350,3 +350,52 @@ 17:59:01.708419 version@stat F·[3 1] S·550KiB[60KiB 490KiB] Sc·[0.75 0.00] 17:59:01.712571 db@janitor F·6 G·0 17:59:01.712584 db@open done T·6.619955ms +=============== Oct 17, 2024 (UTC) =============== +05:10:40.953178 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +05:10:40.953252 version@stat F·[3 1] S·550KiB[60KiB 490KiB] Sc·[0.75 0.00] +05:10:40.953263 db@open opening +05:10:40.953304 journal@recovery F·1 +05:10:40.953387 journal@recovery recovering @90 +05:10:40.954217 memdb@flush created L0@92 N·175 S·2KiB "blo..\x01\xc4\b,v36695":"blo..k\x00\x01,v36845" +05:10:40.954329 version@stat F·[4 1] S·553KiB[63KiB 490KiB] Sc·[1.00 0.00] +05:10:40.956867 db@janitor F·7 G·0 +05:10:40.956878 db@open done T·3.611318ms +05:10:40.956914 table@compaction L0·4 -> L1·1 S·553KiB Q·36870 +05:10:40.970187 table@build created L1@95 N·30671 S·553KiB "\tr\xf8..pSV,v26111":"\xf2~\xe4..\x05\xa4\xc4,v21827" +05:10:40.970217 version@stat F·[0 1] S·553KiB[0B 553KiB] Sc·[0.00 0.01] +05:10:40.970807 table@compaction committed F-4 S-550B Ke·0 D·447 T·13.871697ms +05:10:40.970885 table@remove removed @89 +05:10:40.970918 table@remove removed @84 +05:10:40.970943 table@remove removed @79 +05:10:40.971049 table@remove removed @78 +=============== Oct 17, 2024 (UTC) =============== +06:44:58.783278 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:44:58.783368 version@stat F·[0 1] S·553KiB[0B 553KiB] Sc·[0.00 0.01] +06:44:58.783380 db@open opening +06:44:58.783411 journal@recovery F·1 +06:44:58.783501 journal@recovery recovering @93 +06:44:58.792589 memdb@flush created L0@96 N·5227 S·104KiB "\t\xf8|..\"=\xa9,v39810":"\x95\xbat..\xc7FU,v40268" +06:44:58.792706 version@stat F·[1 1] S·657KiB[104KiB 553KiB] Sc·[0.25 0.01] +06:44:58.802828 db@janitor F·5 G·1 +06:44:58.802834 db@janitor removing table-92 +06:44:58.802858 db@open done T·19.474428ms +=============== Oct 17, 2024 (UTC) =============== +06:45:46.677216 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:45:46.677292 version@stat F·[1 1] S·657KiB[104KiB 553KiB] Sc·[0.25 0.01] +06:45:46.677310 db@open opening +06:45:46.677340 journal@recovery F·1 +06:45:46.677425 journal@recovery recovering @97 +06:45:46.678375 memdb@flush created L0@99 N·302 S·14KiB "\\\x7f\xf1..AP\b,v42182":"\xbf\x87\xea..]\x96\xf3,v42260" +06:45:46.678538 version@stat F·[2 1] S·671KiB[118KiB 553KiB] Sc·[0.50 0.01] +06:45:46.687729 db@janitor F·5 G·0 +06:45:46.687742 db@open done T·10.428559ms +=============== Oct 17, 2024 (UTC) =============== +06:47:26.300462 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +06:47:26.300551 version@stat F·[2 1] S·671KiB[118KiB 553KiB] Sc·[0.50 0.01] +06:47:26.300565 db@open opening +06:47:26.300606 journal@recovery F·1 +06:47:26.300688 journal@recovery recovering @100 +06:47:26.301782 memdb@flush created L0@102 N·525 S·7KiB "blo..\x01Ĥ,v42402":"blo..k\x00\x01,v42902" +06:47:26.301919 version@stat F·[3 1] S·678KiB[125KiB 553KiB] Sc·[0.75 0.01] +06:47:26.306059 db@janitor F·6 G·0 +06:47:26.306075 db@open done T·5.504547ms diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/MANIFEST-000091 b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/MANIFEST-000091 deleted file mode 100644 index d75cffd4ec..0000000000 Binary files a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/MANIFEST-000091 and /dev/null differ diff --git a/.docker/container-state/nucleus-testnet-data/data/tx_index.db/MANIFEST-000104 b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/MANIFEST-000104 new file mode 100644 index 0000000000..57f27cbda8 Binary files /dev/null and b/.docker/container-state/nucleus-testnet-data/data/tx_index.db/MANIFEST-000104 differ diff --git a/.github/actions/deps-install/action.yml b/.github/actions/deps-install/action.yml index 25ed15bf50..02b0ffbec4 100644 --- a/.github/actions/deps-install/action.yml +++ b/.github/actions/deps-install/action.yml @@ -12,33 +12,54 @@ inputs: runs: using: 'composite' steps: + - name: Download protoc (Linux) + if: runner.os == 'Linux' && contains(inputs.deps, 'protoc') + uses: ./.github/actions/download-and-verify + with: + url: "https://github.com/protocolbuffers/protobuf/releases/download/v25.3/protoc-25.3-linux-x86_64.zip" + output_file: "protoc-25.3-linux-x86_64.zip" + checksum: "f853e691868d0557425ea290bf7ba6384eef2fa9b04c323afab49a770ba9da80" + - name: Install protoc (Linux) env: TMP: ${{ inputs.temp || runner.temp }} if: runner.os == 'Linux' && contains(inputs.deps, 'protoc') shell: bash run: | - wget https://github.com/protocolbuffers/protobuf/releases/download/v25.3/protoc-25.3-linux-x86_64.zip - unzip protoc-25.3-linux-x86_64 -d "$TMP/protobuf" + unzip protoc-25.3-linux-x86_64.zip -d "$TMP/protobuf" echo "$TMP/protobuf/bin" >> $GITHUB_PATH + - name: Download protoc (MacOS) + if: runner.os == 'macOS' && contains(inputs.deps, 'protoc') + uses: ./.github/actions/download-and-verify + with: + url: "https://github.com/protocolbuffers/protobuf/releases/download/v25.3/protoc-25.3-osx-x86_64.zip" + output_file: "protoc-25.3-osx-x86_64.zip" + checksum: "247e003b8e115405172eacc50bd19825209d85940728e766f0848eee7c80e2a1" + - name: Install protoc (MacOS) env: TMP: ${{ inputs.temp || runner.temp }} if: runner.os == 'macOS' && contains(inputs.deps, 'protoc') shell: bash run: | - wget https://github.com/protocolbuffers/protobuf/releases/download/v25.3/protoc-25.3-osx-x86_64.zip unzip protoc-25.3-osx-x86_64.zip -d "$TMP/protobuf" echo "$TMP/protobuf/bin" >> $GITHUB_PATH + + - name: Download protoc (Windows) + uses: ./.github/actions/download-and-verify + with: + url: "https://github.com/protocolbuffers/protobuf/releases/download/v25.3/protoc-25.3-win64.zip" + output_file: "protoc-25.3-win64.zip" + checksum: "d6b336b852726364313330631656b7f395dde5b1141b169f5c4b8d43cdf01482" + - name: Install protoc (Windows) env: TMP: ${{ inputs.temp || runner.temp }} if: runner.os == 'Windows' && contains(inputs.deps, 'protoc') shell: powershell run: | - Invoke-WebRequest -Uri https://github.com/protocolbuffers/protobuf/releases/download/v25.3/protoc-25.3-win64.zip -OutFile protoc-25.3-win64.zip 7z x protoc-25.3-win64.zip -o"$TMP\protobuf" echo "$TMP\protobuf\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append diff --git a/.github/actions/download-and-verify/action.yml b/.github/actions/download-and-verify/action.yml new file mode 100644 index 0000000000..198601f486 --- /dev/null +++ b/.github/actions/download-and-verify/action.yml @@ -0,0 +1,46 @@ +name: "Download and verify remote files" + +runs: + using: "composite" + steps: + - name: Download (Unix) + if: runner.os != 'Windows' + shell: bash + run: curl -L -o ${{ inputs.output_file }} ${{ inputs.url }} + + - name: Download (Windows) + if: runner.os == 'Windows' + shell: powershell + run: Invoke-WebRequest -Uri ${{ inputs.url }} -OutFile ${{ inputs.output_file }} + + - name: Verify (Unix) + if: runner.os != 'Windows' + shell: bash + run: | + if [[ "$RUNNER_OS" == "macOS" ]]; then + echo "${{ inputs.checksum }} *${{ inputs.output_file }}" | shasum -a 256 -c + else + echo "${{ inputs.checksum }} ${{ inputs.output_file }}" | sha256sum -c + fi + + - name: Verify (Windows) + if: runner.os == 'Windows' + shell: powershell + run: | + $expectedChecksum = "${{ inputs.checksum }}" + $actualChecksum = (Get-FileHash -Path "${{ inputs.output_file }}" -Algorithm SHA256).Hash + if ($expectedChecksum -ne $actualChecksum) { + Write-Output "Checksum did not match! Expected: $expectedChecksum, Found: $actualChecksum" + exit 1 + } + +inputs: + url: + description: "URL of the remote file." + required: true + output_file: + description: "Output path." + required: true + checksum: + description: "Expected checksum of the downloaded file." + required: true diff --git a/.github/workflows/adex-cli.yml b/.github/workflows/adex-cli.yml deleted file mode 100644 index 32b62f04ed..0000000000 --- a/.github/workflows/adex-cli.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Adex CLI -on: [push] - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -env: - BRANCH_NAME: ${{ github.head_ref || github.ref_name }} - -jobs: - code-check: - name: Code Checks - timeout-minutes: 60 - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - steps: - - uses: actions/checkout@v3 - - - name: Cargo cache - uses: ./.github/actions/cargo-cache - - - name: Start checking code format and lint - continue-on-error: true - run: | - cargo fmt --manifest-path ./mm2src/adex_cli/Cargo.toml --all -- --check - cargo clippy --manifest-path ./mm2src/adex_cli/Cargo.toml --all-targets --all-features -- --D warnings - - - name: Start building - run: | - cargo build --manifest-path ./mm2src/adex_cli/Cargo.toml - - - name: Start testing - run: | - cargo test --manifest-path ./mm2src/adex_cli/Cargo.toml --no-fail-fast diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 19cb9c5d67..12a60bbc3c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -122,7 +122,7 @@ jobs: - name: Test run: | - wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash + wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/0adeeabdd484ef40539d1275c6a765f5c530ea79/zcutil/fetch-params-alt.sh | bash cargo test --test 'mm2_tests_main' --no-fail-fast mac-x86-64-kdf-integration: @@ -154,7 +154,7 @@ jobs: - name: Test run: | - wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash + wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/0adeeabdd484ef40539d1275c6a765f5c530ea79/zcutil/fetch-params-alt.sh | bash cargo test --test 'mm2_tests_main' --no-fail-fast win-x86-64-kdf-integration: @@ -181,10 +181,16 @@ jobs: - name: Cargo cache uses: ./.github/actions/cargo-cache + - name: Download wget64 + uses: ./.github/actions/download-and-verify + with: + url: "https://github.com/KomodoPlatform/komodo/raw/d456be35acd1f8584e1e4f971aea27bd0644d5c5/zcutil/wget64.exe" + output_file: "/wget64.exe" + checksum: "d80719431dc22b0e4a070f61fab982b113a4ed9a6d4cf25e64b5be390dcadb94" + - name: Test run: | - Invoke-WebRequest -Uri https://github.com/KomodoPlatform/komodo/raw/d456be35acd1f8584e1e4f971aea27bd0644d5c5/zcutil/wget64.exe -OutFile \wget64.exe - Invoke-WebRequest -Uri https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.bat -OutFile \cmd.bat && \cmd.bat + Invoke-WebRequest -Uri https://raw.githubusercontent.com/KomodoPlatform/komodo/0adeeabdd484ef40539d1275c6a765f5c530ea79/zcutil/fetch-params-alt.bat -OutFile \cmd.bat && \cmd.bat cargo test --test 'mm2_tests_main' --no-fail-fast docker-tests: @@ -213,7 +219,7 @@ jobs: - name: Test run: | - wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/master/zcutil/fetch-params-alt.sh | bash + wget -O - https://raw.githubusercontent.com/KomodoPlatform/komodo/v0.8.1//zcutil/fetch-params-alt.sh | bash cargo test --test 'docker_tests_main' --features run-docker-tests --no-fail-fast wasm: @@ -241,11 +247,17 @@ jobs: - name: Install wasm-pack run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + - name: Download geckodriver + uses: ./.github/actions/download-and-verify + with: + url: "https://github.com/mozilla/geckodriver/releases/download/v0.32.2/geckodriver-v0.32.2-linux64.tar.gz" + output_file: "geckodriver-v0.32.2-linux64.tar.gz" + checksum: "1eab226bf009599f5aa1d77d9ed4c374e10a03fd848b500be1b32cefd2cbec64" + - name: Install firefox and geckodriver run: | sudo apt-get update -y sudo apt-get install -y firefox - wget https://github.com/mozilla/geckodriver/releases/download/v0.32.2/geckodriver-v0.32.2-linux64.tar.gz sudo tar -xzvf geckodriver-v0.32.2-linux64.tar.gz -C /bin sudo chmod +x /bin/geckodriver diff --git a/CHANGELOG.md b/CHANGELOG.md index 20a2424397..f3d8816316 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,92 @@ +## v2.2.0-beta - 2024-11-22 + +**Features:** +- Connection Healthcheck + - Connection healthcheck implementation for peers was introduced. [#2194](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2194) +- Custom Tokens Activation + - Support for enabling custom EVM (ERC20, PLG20, etc..) tokens without requiring them to be in the coins config was added. [#2141](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2141) + - This allows users to interact with any ERC20 token by providing the contract address. + +**Enhancements/Fixes:** +- Trading Protocol Upgrade [#1895](https://github.com/KomodoPlatform/atomicDEX-API/issues/1895) + - EVM TPU taker methods were implemented and enhancements were made to ETH docker tests. [#2169](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2169) + - EVM TPU maker methods were implemented. [#2211](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2211) +- NFT integration [#900](https://github.com/KomodoPlatform/atomicDEX-API/issues/900) + - Refund methods for NFT swaps were completed. [#2129](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2129) + - `token_id` field was added to the tx history primary key. [#2209](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2209) +- Graceful Shutdown + - CTRL-C signal handling with graceful shutdown was implemented. [#2213](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2213) +- Seed Management [#1939](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1939) + - A new `get_wallet_names` RPC was added to retrieve information about all wallet names and the currently active one. [#2202](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2202) +- Cosmos Integration [#1432](https://github.com/KomodoPlatform/atomicDEX-API/issues/1432) + - Cosmos tx broadcasting error was fixed by upgrading cosmrs to version 15. [#2238](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2238) + - Cosmos transaction history implementation was incorrectly parsing addresses (using the relayer address instead of the cross-chain address) from IBC transactions. The address parsing logic was fixed in [#2245](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2245) +- Order Management + - Cancel order race condition was addressed using time-based cache. [#2232](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2232) +- Swap Improvements + - A legacy swap issue was resolved where taker spent maker payment transactions were sometimes incorrectly marked as successful when they were actually reverted or not confirmed, particularly in EVM-based swaps. [#2199](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2199) + - Two new events were added: "MakerPaymentSpendConfirmed" and "MakerPaymentSpendConfirmFailed" + - A fix was introduced where Takers don't need to confirm their own payment as they can wait for the spending of it straight away. [#2249](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2249) + - This invalidates this fix [#1442](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1442), a better solution will be introduced where taker rebroadcasts their transaction if it's not on the chain. + - A fix was introduced for recover funds for takers when the swap was marked as unsuccessful due to the maker payment spend transaction not being confirmed. [#2242](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2242) + - The required confirmations from coin config for taker/maker payment spend are now used instead of using 1 confirmation max. This is because some chains require more than 1 confirmation for finality, e.g. Polygon. +- Swap watchers [#1431](https://github.com/KomodoPlatform/atomicDEX-API/issues/1431) + - Taker fee validation retries now work the same way as for makers. [#2263](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2263) +- Electrum Client + - Electrum client was refactored to add min/max connection controls, with server priority based on list order. [#1966](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1966) + - Electrum client can now operate in single-server mode (1,1) to reduce resource usage (especially beneficial for mobile) or multi-server (legacy) mode for reliability. + - Higher priority servers automatically replace lower priority ones when reconnecting during periodic retries or when connection count drops below minimum. +- Coins Activation + - EVM addresses are now displayed in full in iguana v2 activation response. [#2254](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2254) +- HD Wallet [#1838](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1838) + - Balance is now returned as `CoinBalanceMap` for both UTXOs and QTUM. [#2259](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2259) + - This is to return the same type/json across all coins for GUIs since EVM uses `CoinBalanceMap`. + - EVM addresses are displayed in full in `get_new_address` response after [#2264](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2264) +- RPC Service + - A fix was introduced to run rpc request futures till completion in [#1966](https://github.com/KomodoPlatform/komodo-defi-framework/pull/1966) + - This ensures RPC request futures complete fully even if clients disconnect, preventing partial state updates and maintaining data consistency. +- Security Enhancements + - Message lifetime overflows were added to prevent creating messages for proxy with too long lifetimes. [#2233](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2233) + - Remote files are now handled in a safer way in CI. [#2217](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2217) +- Build Process + - `wasm-opt` overriding was removed. [#2200](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2200) +- Escaped response body in native RPC was removed. [#2219](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2219) +- Creation of the all-zeroes dir on KDF start was stopped. [#2218](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2218) +- OPTIONS requests to KDF server were added. [#2191](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2191) + +**Removals:** +- Solana Support [#1085](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1085) + - Solana implementation was removed until it can be redone using the latest Solana SDK. [#2239](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2239) +- Adex-CLI [#1682](https://github.com/KomodoPlatform/atomicDEX-API/issues/1682) + - adex-cli was deprecated pending work on a simpler, more maintainable implementation. [#2234](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2234) + +**Other Changes:** +- Documentation + - Issue link in README was updated. [#2227](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2227) + - Commit badges were updated to use dev branch in README. [#2193](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2193) + - Leftover subcommands were removed from help message. [#2235](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2235) [#2270](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2270) +- Code Structure + - lib.rs was replaced by mm2.rs as the root lib for mm2_main. [#2178](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2178) +- Code Improvements + - P2P feature was added to mm2_net dependency to allow the coins crate to be compiled and tested independently. [#2210](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2210) + - Coins mod clippy warnings in WASM were fixed. [#2224](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2224) + - Nonsense CLI arguments were removed. [#2216](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2216) +- Tests + - Tendermint IBC tests were fixed by preparing IBC channels inside the container. [#2246](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2246) + - `.wait()` usage was replaced with `block_on` in tests to ensure consistent runtime usage, fixing issues with tokio TCP streams in non-tokio runtimes. [#2220](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2220) + - Debug assertions for tests were enabled. [#2204](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2204) + - More Sepolia test endpoints were added in [#2262](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2262) + +**NB - Backwards compatibility breaking changes:** +- RPC Renaming + - `get_peers_info` RPC was renamed to `get_directly_connected_peers`. [#2195](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2195) +- Cosmos Integration [#1432](https://github.com/KomodoPlatform/atomicDEX-API/issues/1432) + - Updates to Tendermint activation payloads: + - 'rpc_urls' field (previously a list of plain string values) is replaced with 'nodes' (a list of JSON objects). [#2173](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2173) +- Komodo DeFi Proxy + - All RPC methods fields controlling komodo-defi-proxy are renamed to 'komodo_proxy', affecting various activations, including ETH/EVM. [#2173](https://github.com/KomodoPlatform/komodo-defi-framework/pull/2173) + + ## v2.1.0-beta - 2024-07-31 **Features:** diff --git a/Cargo.lock b/Cargo.lock index 755ad68862..2bc1c3d543 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,12 +119,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "aliasable" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -134,15 +128,6 @@ dependencies = [ "libc", ] -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi", -] - [[package]] name = "anyhow" version = "1.0.42" @@ -186,12 +171,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247" -[[package]] -name = "assert_matches" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" - [[package]] name = "async-io" version = "1.13.0" @@ -261,7 +240,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -278,7 +257,7 @@ version = "0.1.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "531b97fb4cd3dfdce92c35dedbfdc1f0b9d8091c8ca943d6dae340ef5012d514" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", ] @@ -391,12 +370,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base32" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" - [[package]] name = "base58" version = "0.2.0" @@ -409,12 +382,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - [[package]] name = "base64" version = "0.13.0" @@ -471,15 +438,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bip32" version = "0.2.2" @@ -610,20 +568,6 @@ dependencies = [ "constant_time_eq", ] -[[package]] -name = "blake3" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" -dependencies = [ - "arrayref", - "arrayvec 0.7.1", - "cc", - "cfg-if 1.0.0", - "constant_time_eq", - "digest 0.10.7", -] - [[package]] name = "block-buffer" version = "0.9.0" @@ -695,51 +639,6 @@ dependencies = [ "serde_with", ] -[[package]] -name = "borsh" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" -dependencies = [ - "borsh-derive", - "hashbrown 0.11.2", -] - -[[package]] -name = "borsh-derive" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" -dependencies = [ - "borsh-derive-internal", - "borsh-schema-derive-internal", - "proc-macro-crate 0.1.5", - "proc-macro2 1.0.69", - "syn 1.0.95", -] - -[[package]] -name = "borsh-derive-internal" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" -dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 1.0.95", -] - -[[package]] -name = "borsh-schema-derive-internal" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" -dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 1.0.95", -] - [[package]] name = "bs58" version = "0.4.0" @@ -764,42 +663,12 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" -[[package]] -name = "bv" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" -dependencies = [ - "feature-probe", - "serde", -] - [[package]] name = "byte-slice-cast" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65c1bf4a04a88c54f589125563643d773f3254b5c38571395e2b591c693bbc81" -[[package]] -name = "bytemuck" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e851ca7c24871e7336801608a4797d7376545b6928a10d32d75685687141ead" -dependencies = [ - "bytemuck_derive", -] - -[[package]] -name = "bytemuck_derive" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e215f8c2f9f79cb53c8335e687ffd07d5bfcb6fe5fc80723762d0be46e7cc54" -dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 1.0.95", -] - [[package]] name = "byteorder" version = "1.4.3" @@ -825,44 +694,12 @@ dependencies = [ "serde", ] -[[package]] -name = "bzip2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" -dependencies = [ - "bzip2-sys", - "libc", -] - -[[package]] -name = "bzip2-sys" -version = "0.1.11+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - [[package]] name = "cache-padded" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24508e28c677875c380c20f4d28124fab6f8ed4ef929a1397d7b1a31e92f1005" -[[package]] -name = "caps" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61bf7211aad104ce2769ec05efcdfabf85ee84ac92461d142f22cf8badd0e54c" -dependencies = [ - "errno 0.2.8", - "libc", - "thiserror", -] - [[package]] name = "cbc" version = "0.1.2" @@ -877,9 +714,6 @@ name = "cc" version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" -dependencies = [ - "jobserver", -] [[package]] name = "cfg-if" @@ -965,21 +799,6 @@ dependencies = [ "inout", ] -[[package]] -name = "clap" -version = "2.33.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" -dependencies = [ - "ansi_term", - "atty", - "bitflags", - "strsim 0.8.0", - "textwrap", - "unicode-width", - "vec_map", -] - [[package]] name = "cloudabi" version = "0.0.3" @@ -989,15 +808,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "cloudabi" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" -dependencies = [ - "bitflags", -] - [[package]] name = "codespan-reporting" version = "0.11.1" @@ -1016,7 +826,6 @@ dependencies = [ "async-trait", "base58", "base64 0.21.7", - "bincode", "bip32", "bitcoin", "bitcoin_hashes", @@ -1026,6 +835,7 @@ dependencies = [ "bytes 0.4.12", "cfg-if 1.0.0", "chain", + "chrono", "common", "cosmrs", "crossbeam 0.8.2", @@ -1034,7 +844,6 @@ dependencies = [ "derive_more", "dirs", "ed25519-dalek", - "ed25519-dalek-bip32 0.2.0", "enum_derives", "ethabi", "ethcore-transaction", @@ -1073,16 +882,19 @@ dependencies = [ "mm2_metrics", "mm2_net", "mm2_number", + "mm2_p2p", "mm2_rpc", "mm2_state_machine", "mm2_test_helpers", "mocktopus", + "nom", "num-traits", - "parking_lot 0.12.0", + "parking_lot", "primitives", "prost", "prost-build", "protobuf", + "proxy_signature", "rand 0.7.3", "regex", "reqwest", @@ -1092,7 +904,6 @@ dependencies = [ "rpc_task", "rust-ini", "rustls 0.21.10", - "satomic-swap", "script", "secp256k1 0.20.3", "secp256k1 0.24.3", @@ -1101,15 +912,12 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "serde_with", "serialization", "serialization_derive", "sha2 0.10.7", "sha3 0.9.1", - "solana-client", - "solana-sdk", - "solana-transaction-status", - "spl-associated-token-account", - "spl-token", + "sia-rust", "spv_validation", "tendermint-rpc", "time 0.3.20", @@ -1121,7 +929,7 @@ dependencies = [ "tower-service", "url", "utxo_signer", - "uuid 1.2.2", + "uuid", "wagyu-zcash-parameters", "wasm-bindgen", "wasm-bindgen-futures", @@ -1159,7 +967,7 @@ dependencies = [ "mm2_metamask", "mm2_metrics", "mm2_number", - "parking_lot 0.12.0", + "parking_lot", "rpc", "rpc_task", "ser_error", @@ -1203,7 +1011,7 @@ dependencies = [ "libc", "lightning", "log", - "parking_lot 0.12.0", + "parking_lot", "parking_lot_core 0.6.2", "primitive-types", "rand 0.7.3", @@ -1219,7 +1027,7 @@ dependencies = [ "sha2 0.10.7", "shared_ref_counter", "tokio", - "uuid 1.2.2", + "uuid", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", @@ -1245,21 +1053,6 @@ dependencies = [ "crossbeam-utils 0.8.16", ] -[[package]] -name = "console" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" -dependencies = [ - "encode_unicode", - "libc", - "once_cell", - "regex", - "terminal_size", - "unicode-width", - "winapi", -] - [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -1270,16 +1063,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "console_log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501a375961cef1a0d44767200e66e4a559283097e91d0730b1d75dfb2f8a1494" -dependencies = [ - "log", - "web-sys", -] - [[package]] name = "const-oid" version = "0.9.6" @@ -1319,9 +1102,9 @@ dependencies = [ [[package]] name = "cosmos-sdk-proto" -version = "0.19.0" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73c9d2043a9e617b0d602fbc0a0ecd621568edbf3a9774890a6d562389bd8e1c" +checksum = "82e23f6ab56d5f031cde05b8b82a5fefd3a1a223595c79e32317a97189e612bc" dependencies = [ "prost", "prost-types", @@ -1330,18 +1113,18 @@ dependencies = [ [[package]] name = "cosmrs" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af13955d6f356272e6def9ff5e2450a7650df536d8934f47052a20c76513d2f6" +checksum = "5d184abb7b0039cc64f282dfa5b34165e4c5a7410ab46804636d53f4d09aee44" dependencies = [ "cosmos-sdk-proto", "ecdsa", "eyre", - "getrandom 0.2.9", "k256", "rand_core 0.6.4", "serde", "serde_json", + "signature 2.2.0", "subtle-encoding", "tendermint", "thiserror", @@ -1551,7 +1334,7 @@ dependencies = [ "mm2_eth", "mm2_metamask", "num-traits", - "parking_lot 0.12.0", + "parking_lot", "primitives", "rpc", "rpc_task", @@ -1602,16 +1385,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "crypto-mac" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bcd97a54c7ca5ce2f6eb16f6bede5b0ab5f0055fedc17d2f0b4466e21671ca" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "crypto-mac" version = "0.11.1" @@ -1718,7 +1491,7 @@ dependencies = [ "cc", "codespan-reporting", "lazy_static", - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "scratch", "syn 1.0.95", @@ -1736,7 +1509,7 @@ version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -1759,9 +1532,9 @@ checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", - "strsim 0.10.0", + "strsim", "syn 1.0.95", ] @@ -1776,17 +1549,6 @@ dependencies = [ "syn 1.0.95", ] -[[package]] -name = "dashmap" -version = "4.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" -dependencies = [ - "cfg-if 1.0.0", - "num_cpus", - "rayon", -] - [[package]] name = "data-encoding" version = "2.4.0" @@ -1825,7 +1587,7 @@ dependencies = [ "rusqlite", "sql-builder", "tokio", - "uuid 1.2.2", + "uuid", ] [[package]] @@ -1838,55 +1600,17 @@ dependencies = [ "zeroize", ] -[[package]] -name = "derivation-path" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193388a8c8c75a490b604ff61775e236541b8975e98e5ca1f6ea97d122b7e2db" -dependencies = [ - "failure", -] - -[[package]] -name = "derivation-path" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 1.0.95", -] - [[package]] name = "derive_more" version = "0.99.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] -[[package]] -name = "dialoguer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61579ada4ec0c6031cfac3f86fdba0d195a7ebeb5e36693bd53cb5999a25beeb" -dependencies = [ - "console", - "lazy_static", - "tempfile", - "zeroize", -] - [[package]] name = "digest" version = "0.9.0" @@ -1908,15 +1632,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "dir-diff" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2860407d7d7e2e004bb2128510ad9e8d669e76fa005ccf567977b5d71b8b4a0b" -dependencies = [ - "walkdir", -] - [[package]] name = "directories" version = "3.0.2" @@ -1937,16 +1652,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if 1.0.0", - "dirs-sys-next", -] - [[package]] name = "dirs-sys" version = "0.3.6" @@ -1958,40 +1663,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users 0.4.0", - "winapi", -] - -[[package]] -name = "dlopen" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e80ad39f814a9abe68583cd50a2d45c8a67561c3361ab8da240587dda80937" -dependencies = [ - "dlopen_derive", - "lazy_static", - "libc", - "winapi", -] - -[[package]] -name = "dlopen_derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f236d9e1b1fbd81cea0f9cbdc8dcc7e8ebcd80e6659cd7cb2ad5f6c05946c581" -dependencies = [ - "libc", - "quote 0.6.13", - "syn 0.15.44", -] - [[package]] name = "dtoa" version = "1.0.2" @@ -2018,6 +1689,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ + "serde", "signature 1.4.0", ] @@ -2054,44 +1726,20 @@ dependencies = [ "ed25519 1.5.2", "rand 0.7.3", "serde", + "serde_bytes", "sha2 0.9.9", "zeroize", ] [[package]] -name = "ed25519-dalek-bip32" -version = "0.1.1" +name = "edit-distance" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057f328f31294b5ab432e6c39642f54afd1531677d6d4ba2905932844cc242f3" -dependencies = [ - "derivation-path 0.1.3", - "ed25519-dalek", - "failure", - "hmac 0.9.0", - "sha2 0.9.9", -] +checksum = "bbbaaaf38131deb9ca518a274a45bfdb8771f139517b073b16c2d3d32ae5037b" [[package]] -name = "ed25519-dalek-bip32" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d2be62a4061b872c8c0873ee4fc6f101ce7b889d039f019c5fa2af471a59908" -dependencies = [ - "derivation-path 0.2.0", - "ed25519-dalek", - "hmac 0.12.1", - "sha2 0.10.7", -] - -[[package]] -name = "edit-distance" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbaaaf38131deb9ca518a274a45bfdb8771f139517b073b16c2d3d32ae5037b" - -[[package]] -name = "either" -version = "1.8.1" +name = "either" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" @@ -2114,12 +1762,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "encode_unicode" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" - [[package]] name = "encoding_rs" version = "0.8.28" @@ -2142,7 +1784,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" dependencies = [ "heck", - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -2163,7 +1805,7 @@ name = "enum_derives" version = "0.1.0" dependencies = [ "itertools", - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -2319,28 +1961,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "failure" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" -dependencies = [ - "backtrace", - "failure_derive", -] - -[[package]] -name = "failure_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" -dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 1.0.95", - "synstructure", -] - [[package]] name = "fallible-iterator" version = "0.2.0" @@ -2362,12 +1982,6 @@ dependencies = [ "instant", ] -[[package]] -name = "feature-probe" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" - [[package]] name = "ff" version = "0.8.0" @@ -2395,18 +2009,6 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" -[[package]] -name = "filetime" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "redox_syscall 0.2.10", - "winapi", -] - [[package]] name = "findshlibs" version = "0.5.0" @@ -2453,7 +2055,6 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" dependencies = [ - "eyre", "paste", ] @@ -2465,11 +2066,10 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "matches", "percent-encoding", ] @@ -2597,7 +2197,7 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", ] @@ -2681,22 +2281,11 @@ version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "serde", "typenum", "version_check", "zeroize", ] -[[package]] -name = "gethostname" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e692e296bfac1d2533ef168d0b60ff5897b8b70a4009276834014dd8924cc028" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "getrandom" version = "0.1.14" @@ -2922,17 +2511,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" -[[package]] -name = "hidapi" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b1717343691998deb81766bfcd1dce6df0d5d6c37070b5a3de2bb6d39f7822" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - [[package]] name = "hkd32" version = "0.6.0" @@ -2955,16 +2533,6 @@ dependencies = [ "digest 0.9.0", ] -[[package]] -name = "hmac" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deae6d9dbb35ec2c502d62b8f7b1c000a0822c3b0794ba36b3149c0a1c840dff" -dependencies = [ - "crypto-mac 0.9.1", - "digest 0.9.0", -] - [[package]] name = "hmac" version = "0.11.0" @@ -3195,6 +2763,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "if-addrs" version = "0.7.0" @@ -3257,7 +2835,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5dacb10c5b3bb92d46ba347505a9041e676bb20ad220101326bffb0c93031ee" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -3268,12 +2846,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" -[[package]] -name = "index_list" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9d968042a4902e08810946fc7cd5851eb75e80301342305af755ca06cb82ce" - [[package]] name = "indexmap" version = "1.9.3" @@ -3294,18 +2866,6 @@ dependencies = [ "hashbrown 0.14.3", ] -[[package]] -name = "indicatif" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d207dc617c7a380ab07ff572a6e52fa202a2a8f355860ac9c38e23f8196be1b" -dependencies = [ - "console", - "lazy_static", - "number_prefix", - "regex", -] - [[package]] name = "inout" version = "0.1.3" @@ -3407,15 +2967,6 @@ dependencies = [ "libc", ] -[[package]] -name = "jobserver" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" version = "0.3.64" @@ -3463,9 +3014,7 @@ dependencies = [ "cfg-if 1.0.0", "ecdsa", "elliptic-curve", - "once_cell", "sha2 0.10.7", - "signature 2.2.0", ] [[package]] @@ -3535,16 +3084,6 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" -[[package]] -name = "libloading" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" -dependencies = [ - "cfg-if 1.0.0", - "winapi", -] - [[package]] name = "libm" version = "0.1.4" @@ -3627,7 +3166,7 @@ dependencies = [ "multihash", "multistream-select", "once_cell", - "parking_lot 0.12.0", + "parking_lot", "pin-project", "quick-protobuf", "rand 0.8.4", @@ -3647,7 +3186,7 @@ dependencies = [ "libp2p-core", "libp2p-identity", "log", - "parking_lot 0.12.0", + "parking_lot", "smallvec 1.6.1", "trust-dns-resolver", ] @@ -3733,7 +3272,7 @@ dependencies = [ "asn1_der", "bs58 0.5.0", "ed25519-dalek", - "libsecp256k1 0.7.0", + "libsecp256k1", "log", "multihash", "quick-protobuf", @@ -3866,7 +3405,7 @@ source = "git+https://github.com/KomodoPlatform/rust-libp2p.git?tag=k-0.52.4#6fc dependencies = [ "heck", "proc-macro-warning", - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", ] @@ -3911,7 +3450,7 @@ dependencies = [ "libp2p-core", "libp2p-identity", "log", - "parking_lot 0.12.0", + "parking_lot", "quicksink", "rw-stream-sink", "soketto", @@ -3933,25 +3472,6 @@ dependencies = [ "yamux 0.13.1", ] -[[package]] -name = "libsecp256k1" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9d220bc1feda2ac231cb78c3d26f27676b8cf82c96971f7aeef3d0cf2797c73" -dependencies = [ - "arrayref", - "base64 0.12.3", - "digest 0.9.0", - "hmac-drbg", - "libsecp256k1-core 0.2.2", - "libsecp256k1-gen-ecmult 0.2.1", - "libsecp256k1-gen-genmult 0.2.1", - "rand 0.7.3", - "serde", - "sha2 0.9.9", - "typenum", -] - [[package]] name = "libsecp256k1" version = "0.7.0" @@ -3962,26 +3482,15 @@ dependencies = [ "base64 0.13.0", "digest 0.9.0", "hmac-drbg", - "libsecp256k1-core 0.3.0", - "libsecp256k1-gen-ecmult 0.3.0", - "libsecp256k1-gen-genmult 0.3.0", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", "rand 0.8.4", "serde", "sha2 0.9.9", "typenum", ] -[[package]] -name = "libsecp256k1-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f6ab710cec28cef759c5f18671a27dae2a5f952cdaaee1d8e2908cb2478a80" -dependencies = [ - "crunchy", - "digest 0.9.0", - "subtle", -] - [[package]] name = "libsecp256k1-core" version = "0.3.0" @@ -3993,31 +3502,13 @@ dependencies = [ "subtle", ] -[[package]] -name = "libsecp256k1-gen-ecmult" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3" -dependencies = [ - "libsecp256k1-core 0.2.2", -] - [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" dependencies = [ - "libsecp256k1-core 0.3.0", -] - -[[package]] -name = "libsecp256k1-gen-genmult" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d" -dependencies = [ - "libsecp256k1-core 0.2.2", + "libsecp256k1-core", ] [[package]] @@ -4026,7 +3517,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" dependencies = [ - "libsecp256k1-core 0.3.0", + "libsecp256k1-core", ] [[package]] @@ -4223,15 +3714,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -[[package]] -name = "memmap2" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" -dependencies = [ - "libc", -] - [[package]] name = "memoffset" version = "0.5.6" @@ -4296,7 +3778,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddece26afd34c31585c74a4db0630c376df271c285d682d1e55012197830b6df" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", ] @@ -4358,7 +3840,7 @@ dependencies = [ [[package]] name = "mm2_bin_lib" -version = "2.1.0-beta" +version = "2.2.0-beta" dependencies = [ "chrono", "common", @@ -4394,6 +3876,7 @@ dependencies = [ "hex", "instant", "lazy_static", + "libp2p", "mm2_err_handle", "mm2_event_stream", "mm2_metrics", @@ -4407,7 +3890,7 @@ dependencies = [ "serde_json", "shared_ref_counter", "tokio", - "uuid 1.2.2", + "uuid", "wasm-bindgen-test", ] @@ -4477,7 +3960,7 @@ dependencies = [ "cfg-if 1.0.0", "common", "futures 0.3.28", - "parking_lot 0.12.0", + "parking_lot", "serde", "tokio", "wasm-bindgen-test", @@ -4592,7 +4075,7 @@ dependencies = [ "mocktopus", "num-traits", "parity-util-mem", - "parking_lot 0.12.0", + "parking_lot", "primitives", "prost", "prost-build", @@ -4616,6 +4099,7 @@ dependencies = [ "serde_json", "serialization", "serialization_derive", + "sia-rust", "sp-runtime-interface", "sp-trie", "spv_validation", @@ -4623,7 +4107,8 @@ dependencies = [ "tokio", "trie-db", "trie-root 0.16.0", - "uuid 1.2.2", + "url", + "uuid", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", @@ -4646,7 +4131,7 @@ dependencies = [ "lazy_static", "mm2_err_handle", "mm2_eth", - "parking_lot 0.12.0", + "parking_lot", "serde", "serde_derive", "serde_json", @@ -4698,10 +4183,8 @@ dependencies = [ "lazy_static", "mm2_core", "mm2_err_handle", - "mm2_event_stream", - "mm2_p2p", + "mm2_number", "mm2_state_machine", - "parking_lot 0.12.0", "pin-project", "prost", "rand 0.7.3", @@ -4750,12 +4233,17 @@ dependencies = [ "lazy_static", "libp2p", "log", + "mm2_core", + "mm2_event_stream", + "mm2_number", + "parking_lot", "rand 0.7.3", "regex", "rmp-serde", "secp256k1 0.20.3", "serde", "serde_bytes", + "serde_json", "sha2 0.10.7", "smallvec 1.6.1", "syn 2.0.38", @@ -4779,7 +4267,7 @@ dependencies = [ "ser_error_derive", "serde", "serde_json", - "uuid 1.2.2", + "uuid", ] [[package]] @@ -4817,7 +4305,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "uuid 1.2.2", + "uuid", ] [[package]] @@ -4835,7 +4323,7 @@ version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3048ef3680533a27f9f8e7d6a0bce44dc61e4895ea0f42709337fa1c8616fefe" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -4974,19 +4462,6 @@ dependencies = [ "smallvec 1.6.1", ] -[[package]] -name = "nix" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" -dependencies = [ - "bitflags", - "cc", - "cfg-if 1.0.0", - "libc", - "memoffset 0.6.4", -] - [[package]] name = "nix" version = "0.24.3" @@ -5031,13 +4506,13 @@ dependencies = [ [[package]] name = "num-derive" -version = "0.3.3" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", - "syn 1.0.95", + "syn 2.0.38", ] [[package]] @@ -5082,34 +4557,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num_enum" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9bd055fb730c4f8f4f57d45d35cd6b3f0980535b056dc7ff119cee6a66ed6f" -dependencies = [ - "derivative", - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9" -dependencies = [ - "proc-macro-crate 1.1.3", - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 1.0.95", -] - -[[package]] -name = "number_prefix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" - [[package]] name = "object" version = "0.29.0" @@ -5146,30 +4593,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "ouroboros" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f357ef82d1b4db66fbed0b8d542cbd3c22d0bf5b393b3c257b9ba4568e70c9c3" -dependencies = [ - "aliasable", - "ouroboros_macro", - "stable_deref_trait", -] - -[[package]] -name = "ouroboros_macro" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44a0b52c2cbaef7dffa5fec1a43274afe8bd2a644fa9fc50a9ef4ff0269b1257" -dependencies = [ - "Inflector", - "proc-macro-error", - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 1.0.95", -] - [[package]] name = "packed_simd_2" version = "0.3.8" @@ -5210,8 +4633,8 @@ version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c45ed1f39709f5a89338fab50e59816b2e8815f5bb58276e7ddf9afd495f73f8" dependencies = [ - "proc-macro-crate 1.1.3", - "proc-macro2 1.0.69", + "proc-macro-crate", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -5228,7 +4651,7 @@ dependencies = [ "impl-trait-for-tuples", "lru 0.7.5", "parity-util-mem-derive", - "parking_lot 0.12.0", + "parking_lot", "primitive-types", "smallvec 1.6.1", "winapi", @@ -5240,7 +4663,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "syn 1.0.95", "synstructure", ] @@ -5257,17 +4680,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" -[[package]] -name = "parking_lot" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.0", -] - [[package]] name = "parking_lot" version = "0.12.0" @@ -5285,7 +4697,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" dependencies = [ "cfg-if 0.1.10", - "cloudabi 0.0.3", + "cloudabi", "libc", "redox_syscall 0.1.56", "rustc_version 0.2.3", @@ -5293,21 +4705,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "parking_lot_core" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" -dependencies = [ - "cfg-if 0.1.10", - "cloudabi 0.1.0", - "instant", - "libc", - "redox_syscall 0.1.56", - "smallvec 1.6.1", - "winapi", -] - [[package]] name = "parking_lot_core" version = "0.9.1" @@ -5339,49 +4736,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" [[package]] -name = "pbkdf2" -version = "0.4.0" +name = "peg" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +checksum = "295283b02df346d1ef66052a757869b2876ac29a6bb0ac3f5f7cd44aebe40e8f" dependencies = [ - "crypto-mac 0.8.0", + "peg-macros", + "peg-runtime", ] [[package]] -name = "pbkdf2" -version = "0.9.0" +name = "peg-macros" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" +checksum = "bdad6a1d9cf116a059582ce415d5f5566aabcd4008646779dab7fdc2a9a9d426" dependencies = [ - "crypto-mac 0.11.1", + "peg-runtime", + "proc-macro2", + "quote 1.0.33", ] [[package]] -name = "peg" -version = "0.7.0" +name = "peg-runtime" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a" -dependencies = [ - "peg-macros", - "peg-runtime", -] - -[[package]] -name = "peg-macros" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c" -dependencies = [ - "peg-runtime", - "proc-macro2 1.0.69", - "quote 1.0.33", -] - -[[package]] -name = "peg-runtime" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" +checksum = "e3aeb8f54c078314c2065ee649a7241f46b9d8e418e1a9581ba0546657d7aa3a" [[package]] name = "pem" @@ -5394,9 +4773,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "petgraph" @@ -5423,7 +4802,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", ] @@ -5521,12 +4900,12 @@ checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" [[package]] name = "prettyplease" -version = "0.1.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28f53e8b192565862cf99343194579a022eb9c7dd3a8d03134734803c7b3125" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ - "proc-macro2 1.0.69", - "syn 1.0.95", + "proc-macro2", + "syn 2.0.38", ] [[package]] @@ -5553,15 +4932,6 @@ dependencies = [ "uint", ] -[[package]] -name = "proc-macro-crate" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" -dependencies = [ - "toml", -] - [[package]] name = "proc-macro-crate" version = "1.1.3" @@ -5569,31 +4939,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ "thiserror", - "toml", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2 1.0.69", - "quote 1.0.33", - "syn 1.0.95", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "version_check", + "toml 0.5.7", ] [[package]] @@ -5602,20 +4948,11 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70550716265d1ec349c41f70dd4f964b4fd88394efe4405f0c1da679c4799a07" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", ] -[[package]] -name = "proc-macro2" -version = "0.4.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" -dependencies = [ - "unicode-xid 0.1.0", -] - [[package]] name = "proc-macro2" version = "1.0.69" @@ -5633,7 +4970,7 @@ checksum = "78c2f43e8969d51935d2a7284878ae053ba30034cd563f673cde37ba5205685e" dependencies = [ "dtoa", "itoa 1.0.10", - "parking_lot 0.12.0", + "parking_lot", "prometheus-client-derive-encode", ] @@ -5643,16 +4980,16 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b6a5217beb0ad503ee7fa752d451c905113d70721b937126158f3106a48cc1" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] [[package]] name = "prost" -version = "0.11.9" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes 1.4.0", "prost-derive", @@ -5660,44 +4997,43 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.11.9" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes 1.4.0", "heck", "itertools", - "lazy_static", "log", "multimap", + "once_cell", "petgraph", "prettyplease", "prost", "prost-types", "regex", - "syn 1.0.95", + "syn 2.0.38", "tempfile", - "which", ] [[package]] name = "prost-derive" -version = "0.11.9" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", "itertools", - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", - "syn 1.0.95", + "syn 2.0.38", ] [[package]] name = "prost-types" -version = "0.11.9" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ "prost", ] @@ -5728,12 +5064,15 @@ dependencies = [ ] [[package]] -name = "qstring" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" +name = "proxy_signature" +version = "0.1.0" dependencies = [ - "percent-encoding", + "chrono", + "http 0.2.12", + "libp2p", + "rand 0.7.3", + "serde", + "serde_json", ] [[package]] @@ -5796,22 +5135,13 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" -[[package]] -name = "quote" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" -dependencies = [ - "proc-macro2 0.4.30", -] - [[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", ] [[package]] @@ -6003,7 +5333,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" dependencies = [ - "cloudabi 0.0.3", + "cloudabi", "fuchsia-cprng", "libc", "rand_core 0.4.2", @@ -6049,31 +5379,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "rayon" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" -dependencies = [ - "autocfg 1.1.0", - "crossbeam-deque 0.8.1", - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" -dependencies = [ - "crossbeam-channel 0.5.1", - "crossbeam-deque 0.8.1", - "crossbeam-utils 0.8.16", - "lazy_static", - "num_cpus", -] - [[package]] name = "rcgen" version = "0.10.0" @@ -6146,7 +5451,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c523ccaed8ac4b0288948849a350b37d3035827413c458b6a40ddb614bb4f72" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -6307,16 +5612,6 @@ dependencies = [ "serde", ] -[[package]] -name = "rpassword" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc936cf8a7ea60c58f030fd36a612a48f440610214dc54bc36431f9ea0c3efb" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "rpc" version = "0.1.0" @@ -6359,7 +5654,7 @@ dependencies = [ "log", "netlink-packet-route", "netlink-proto", - "nix 0.24.3", + "nix", "thiserror", "tokio", ] @@ -6576,14 +5871,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "satomic-swap" -version = "0.1.0" -source = "git+https://github.com/KomodoPlatform/satomic-swap.git?rev=413e472#413e4725a97f2c4d5d34101b3d2c49009c95cb28" -dependencies = [ - "solana-program", -] - [[package]] name = "scale-info" version = "2.1.2" @@ -6602,8 +5889,8 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50e334bb10a245e28e5fd755cabcafd96cfcd167c99ae63a46924ca8d8703a3c" dependencies = [ - "proc-macro-crate 1.1.3", - "proc-macro2 1.0.69", + "proc-macro-crate", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -6787,7 +6074,7 @@ dependencies = [ name = "ser_error_derive" version = "0.1.0" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "ser_error", "syn 1.0.95", @@ -6828,7 +6115,7 @@ version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", ] @@ -6851,11 +6138,20 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dc6b7951b17b051f3210b063f12cc17320e2fe30ae05b0fe2a3abb068551c76" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -6885,23 +6181,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" dependencies = [ "darling", - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] -[[package]] -name = "serde_yaml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a521f2940385c165a24ee286aa8599633d162077a54bdcae2a6fd5a7bfa7a0" -dependencies = [ - "indexmap 1.9.3", - "ryu", - "serde", - "yaml-rust", -] - [[package]] name = "serialization" version = "0.1.0" @@ -6998,6 +6282,26 @@ dependencies = [ "log", ] +[[package]] +name = "sia-rust" +version = "0.1.0" +source = "git+https://github.com/KomodoPlatform/sia-rust?rev=9f188b80b3213bcb604e7619275251ce08fae808#9f188b80b3213bcb604e7619275251ce08fae808" +dependencies = [ + "base64 0.21.7", + "blake2b_simd", + "chrono", + "derive_more", + "ed25519-dalek", + "hex", + "nom", + "reqwest", + "rustc-hex", + "serde", + "serde_json", + "serde_with", + "url", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -7073,687 +6377,74 @@ dependencies = [ "futures-util", "libc", "once_cell", - "scoped-tls", - "slab", - "socket2 0.3.19", - "wepoll-sys-stjepang", - "winapi", -] - -[[package]] -name = "snow" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ccba027ba85743e09d15c03296797cad56395089b832b48b5a5217880f57733" -dependencies = [ - "aes-gcm", - "blake2", - "chacha20poly1305", - "curve25519-dalek 4.0.0-rc.1", - "rand_core 0.6.4", - "ring 0.16.20", - "rustc_version 0.4.0", - "sha2 0.10.7", - "subtle", -] - -[[package]] -name = "socket2" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "winapi", -] - -[[package]] -name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "socket2" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" -dependencies = [ - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "soketto" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "083624472e8817d44d02c0e55df043737ff11f279af924abdf93845717c2b75c" -dependencies = [ - "base64 0.13.0", - "bytes 1.4.0", - "futures 0.3.28", - "httparse", - "log", - "rand 0.8.4", - "sha-1", -] - -[[package]] -name = "solana-account-decoder" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea8c1862fc46c6ab40d83d15ced24a7afb1f3422da5824f1e9260f5ac10141f" -dependencies = [ - "Inflector", - "base64 0.12.3", - "bincode", - "bs58 0.4.0", - "bv", - "lazy_static", - "serde", - "serde_derive", - "serde_json", - "solana-config-program", - "solana-sdk", - "solana-vote-program", - "spl-token", - "thiserror", - "zstd", -] - -[[package]] -name = "solana-address-lookup-table-program" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c60728aec35d772e6614319558cdccbe3f845102699b65ba5ac7497da0b626a" -dependencies = [ - "bincode", - "bytemuck", - "log", - "num-derive", - "num-traits", - "rustc_version 0.4.0", - "serde", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-program-runtime", - "solana-sdk", - "thiserror", -] - -[[package]] -name = "solana-bloom" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddcd7c6adb802bc812a5a80c8de06ba0f0e8df0cca296a8b4e67cd04c16218f" -dependencies = [ - "bv", - "fnv", - "log", - "rand 0.7.3", - "rayon", - "rustc_version 0.4.0", - "serde", - "serde_derive", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-sdk", -] - -[[package]] -name = "solana-bucket-map" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3435b145894971a58a08a7b6be997ec782239fdecd5edd9846cd1d6aa5986" -dependencies = [ - "fs_extra", - "log", - "memmap2", - "rand 0.7.3", - "rayon", - "solana-logger", - "solana-measure", - "solana-sdk", - "tempfile", -] - -[[package]] -name = "solana-clap-utils" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8417a89c377728dbfbf1966b6493544f6e5e168ebc5bb444f3526481fae94e31" -dependencies = [ - "chrono", - "clap", - "rpassword", - "solana-perf", - "solana-remote-wallet", - "solana-sdk", - "thiserror", - "tiny-bip39", - "uriparse", - "url", -] - -[[package]] -name = "solana-cli-config" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e8b011d36369ef2bc3dff63fee078bf2916a4fd21f3aa702ee731c7ddf83d28" -dependencies = [ - "dirs-next", - "lazy_static", - "serde", - "serde_derive", - "serde_yaml", - "url", -] - -[[package]] -name = "solana-client" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e20f4df8cee4a1819f1c5b0d3d85d50c30f27133b2ae68c2fd92655e4aede34a" -dependencies = [ - "base64 0.13.0", - "bincode", - "bs58 0.4.0", - "clap", - "indicatif", - "jsonrpc-core", - "log", - "rayon", - "reqwest", - "semver 1.0.6", - "serde", - "serde_derive", - "serde_json", - "solana-account-decoder", - "solana-clap-utils", - "solana-faucet", - "solana-measure", - "solana-net-utils", - "solana-sdk", - "solana-transaction-status", - "solana-version", - "solana-vote-program", - "thiserror", - "tokio", - "tungstenite", - "url", -] - -[[package]] -name = "solana-compute-budget-program" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685567c221f6bb5b64387f7b45d03036ad112b2ecbcd0f94b11204efab9f891e" -dependencies = [ - "solana-program-runtime", - "solana-sdk", -] - -[[package]] -name = "solana-config-program" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f4b04403ff77f09eba5cf94078c1178161e26d346245b06180866ab5286fe6b" -dependencies = [ - "bincode", - "chrono", - "serde", - "serde_derive", - "solana-program-runtime", - "solana-sdk", -] - -[[package]] -name = "solana-faucet" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a11e1b6d5ce435bb3df95f2a970cd80500a8abf94ea87558c35fe0cce8456ab" -dependencies = [ - "bincode", - "byteorder", - "clap", - "log", - "serde", - "serde_derive", - "solana-clap-utils", - "solana-cli-config", - "solana-logger", - "solana-metrics", - "solana-sdk", - "solana-version", - "spl-memo", - "thiserror", - "tokio", -] - -[[package]] -name = "solana-frozen-abi" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5f69a79200f5ba439eb8b790c5e00beab4d1fae4da69ce023c69c6ac1b55bf1" -dependencies = [ - "bs58 0.4.0", - "bv", - "generic-array", - "log", - "memmap2", - "rustc_version 0.4.0", - "serde", - "serde_derive", - "sha2 0.9.9", - "solana-frozen-abi-macro", - "solana-logger", - "thiserror", -] - -[[package]] -name = "solana-frozen-abi-macro" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402fffb54bf5d335e6df26fc1719feecfbd7a22fafdf6649fe78380de3c47384" -dependencies = [ - "proc-macro2 1.0.69", - "quote 1.0.33", - "rustc_version 0.4.0", - "syn 1.0.95", -] - -[[package]] -name = "solana-logger" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942dc59fc9da66d178362051b658646b65dc11cea0bc804e4ecd2528d3c1279f" -dependencies = [ - "env_logger", - "lazy_static", - "log", -] - -[[package]] -name = "solana-measure" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ccd5b1278b115249d6ca5a203fd852f7d856e048488c24442222ee86e682bd9" -dependencies = [ - "log", - "solana-sdk", -] - -[[package]] -name = "solana-metrics" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9774cd8309f599797b1612731fbc56df6c612879ab4923a3dc7234400eea419" -dependencies = [ - "env_logger", - "gethostname", - "lazy_static", - "log", - "reqwest", - "solana-sdk", -] - -[[package]] -name = "solana-net-utils" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cb530af085d8aab563530ed39703096aa526249d350082823882fdd59cdf839" -dependencies = [ - "bincode", - "clap", - "log", - "nix 0.23.1", - "rand 0.7.3", - "serde", - "serde_derive", - "socket2 0.4.9", - "solana-logger", - "solana-sdk", - "solana-version", - "tokio", - "url", -] - -[[package]] -name = "solana-perf" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4117c0cf7753bc18f3a09f4973175c3f2c7c5d8e3c9bc15cab09060b06f3434f" -dependencies = [ - "ahash 0.7.6", - "bincode", - "bv", - "caps", - "curve25519-dalek 3.2.0", - "dlopen", - "dlopen_derive", - "fnv", - "lazy_static", - "libc", - "log", - "nix 0.23.1", - "rand 0.7.3", - "rayon", - "serde", - "solana-bloom", - "solana-logger", - "solana-metrics", - "solana-rayon-threadlimit", - "solana-sdk", - "solana-vote-program", -] - -[[package]] -name = "solana-program" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a463f546a2f5842d35974bd4691ae5ceded6785ec24db440f773723f6ce4e11" -dependencies = [ - "base64 0.13.0", - "bincode", - "bitflags", - "blake3", - "borsh", - "borsh-derive", - "bs58 0.4.0", - "bv", - "bytemuck", - "console_error_panic_hook", - "console_log", - "curve25519-dalek 3.2.0", - "getrandom 0.1.14", - "itertools", - "js-sys", - "lazy_static", - "libsecp256k1 0.6.0", - "log", - "num-derive", - "num-traits", - "parking_lot 0.11.1", - "rand 0.7.3", - "rustc_version 0.4.0", - "rustversion", - "serde", - "serde_bytes", - "serde_derive", - "sha2 0.9.9", - "sha3 0.9.1", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-logger", - "solana-sdk-macro", - "thiserror", - "wasm-bindgen", -] - -[[package]] -name = "solana-program-runtime" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09841673334eab958d5bedab9c9d75ed2ff7a7ef70e7dfd6b239c6838a3d79ec" -dependencies = [ - "base64 0.13.0", - "bincode", - "itertools", - "libc", - "libloading", - "log", - "num-derive", - "num-traits", - "rustc_version 0.4.0", - "serde", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-logger", - "solana-measure", - "solana-sdk", - "thiserror", -] - -[[package]] -name = "solana-rayon-threadlimit" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92893e3129dfabb703cd045e1367f3ced91202a2d0b6179a3dcd62ad6bead3b" -dependencies = [ - "lazy_static", - "num_cpus", -] - -[[package]] -name = "solana-remote-wallet" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "315534baecaae3f804548ccc4738d73ae01bf6523219787ebe55ee66d8db9a85" -dependencies = [ - "base32", - "console", - "dialoguer", - "hidapi", - "log", - "num-derive", - "num-traits", - "parking_lot 0.11.1", - "qstring", - "semver 1.0.6", - "solana-sdk", - "thiserror", - "uriparse", -] - -[[package]] -name = "solana-runtime" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bd06e905260433f7e8d18bccb2e2eb2aa5cc53379d104d331ddeb12e13230a0" -dependencies = [ - "arrayref", - "bincode", - "blake3", - "bv", - "bytemuck", - "byteorder", - "bzip2", - "crossbeam-channel 0.5.1", - "dashmap", - "dir-diff", - "flate2", - "fnv", - "index_list", - "itertools", - "lazy_static", - "log", - "memmap2", - "num-derive", - "num-traits", - "num_cpus", - "ouroboros", - "rand 0.7.3", - "rayon", - "regex", - "rustc_version 0.4.0", - "serde", - "serde_derive", - "solana-address-lookup-table-program", - "solana-bloom", - "solana-bucket-map", - "solana-compute-budget-program", - "solana-config-program", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-logger", - "solana-measure", - "solana-metrics", - "solana-program-runtime", - "solana-rayon-threadlimit", - "solana-sdk", - "solana-stake-program", - "solana-vote-program", - "symlink", - "tar", - "tempfile", - "thiserror", - "zstd", -] - -[[package]] -name = "solana-sdk" -version = "1.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6560e605c68fa1e3e66a9d3c8529d097d402e1183f80dd06a2c870d0ecb795c2" -dependencies = [ - "assert_matches", - "base64 0.13.0", - "bincode", - "bitflags", - "borsh", - "bs58 0.4.0", - "bytemuck", - "byteorder", - "chrono", - "derivation-path 0.1.3", - "digest 0.9.0", - "ed25519-dalek", - "ed25519-dalek-bip32 0.1.1", - "generic-array", - "hmac 0.11.0", - "itertools", - "js-sys", - "lazy_static", - "libsecp256k1 0.6.0", - "log", - "memmap2", - "num-derive", - "num-traits", - "pbkdf2 0.9.0", - "qstring", - "rand 0.7.3", - "rand_chacha 0.2.2", - "rustc_version 0.4.0", - "rustversion", - "serde", - "serde_bytes", - "serde_derive", - "serde_json", - "sha2 0.9.9", - "sha3 0.9.1", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-logger", - "solana-program", - "solana-sdk-macro", - "thiserror", - "uriparse", - "wasm-bindgen", + "scoped-tls", + "slab", + "socket2 0.3.19", + "wepoll-sys-stjepang", + "winapi", ] [[package]] -name = "solana-sdk-macro" -version = "1.9.20" +name = "snow" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c834b4e02ac911b13c13aed08b3f847e722f6be79d31b1c660c1dbd2dee83cdb" +checksum = "5ccba027ba85743e09d15c03296797cad56395089b832b48b5a5217880f57733" dependencies = [ - "bs58 0.4.0", - "proc-macro2 1.0.69", - "quote 1.0.33", - "rustversion", - "syn 1.0.95", + "aes-gcm", + "blake2", + "chacha20poly1305", + "curve25519-dalek 4.0.0-rc.1", + "rand_core 0.6.4", + "ring 0.16.20", + "rustc_version 0.4.0", + "sha2 0.10.7", + "subtle", ] [[package]] -name = "solana-stake-program" -version = "1.9.20" +name = "socket2" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92597c0ed16d167d5ee48e5b13e92dfaed9c55b23a13ec261440136cd418649" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" dependencies = [ - "bincode", - "log", - "num-derive", - "num-traits", - "rustc_version 0.4.0", - "serde", - "serde_derive", - "solana-config-program", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-metrics", - "solana-program-runtime", - "solana-sdk", - "solana-vote-program", - "thiserror", + "cfg-if 1.0.0", + "libc", + "winapi", ] [[package]] -name = "solana-transaction-status" -version = "1.9.20" +name = "socket2" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612a51efa19380992e81fc64a2fb55d42aed32c67d795848d980cbe1f9693250" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ - "Inflector", - "base64 0.12.3", - "bincode", - "bs58 0.4.0", - "lazy_static", - "log", - "serde", - "serde_derive", - "serde_json", - "solana-account-decoder", - "solana-measure", - "solana-metrics", - "solana-runtime", - "solana-sdk", - "solana-vote-program", - "spl-associated-token-account", - "spl-memo", - "spl-token", - "thiserror", + "libc", + "winapi", ] [[package]] -name = "solana-version" -version = "1.9.20" +name = "socket2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222e2c91640d45cd9617dfc07121555a9bdac10e6e105f6931b758f46db6faaa" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" dependencies = [ - "log", - "rustc_version 0.4.0", - "serde", - "serde_derive", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-sdk", + "libc", + "windows-sys 0.48.0", ] [[package]] -name = "solana-vote-program" -version = "1.9.20" +name = "soketto" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4cc64945010e9e76d368493ad091aa5cf43ee16f69296290ebb5c815e433232" +checksum = "083624472e8817d44d02c0e55df043737ff11f279af924abdf93845717c2b75c" dependencies = [ - "bincode", + "base64 0.13.0", + "bytes 1.4.0", + "futures 0.3.28", + "httparse", "log", - "num-derive", - "num-traits", - "rustc_version 0.4.0", - "serde", - "serde_derive", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-logger", - "solana-metrics", - "solana-program-runtime", - "solana-sdk", - "thiserror", + "rand 0.8.4", + "sha-1", ] [[package]] @@ -7787,7 +6478,7 @@ version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d676664972e22a0796176e81e7bec41df461d1edf52090955cdab55f2c956ff2" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -7816,8 +6507,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ecb916b9664ed9f90abef0ff5a3e61454c1efea5861b2997e03f39b59b955f" dependencies = [ "Inflector", - "proc-macro-crate 1.1.3", - "proc-macro2 1.0.69", + "proc-macro-crate", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", ] @@ -7901,39 +6592,6 @@ dependencies = [ "der", ] -[[package]] -name = "spl-associated-token-account" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "393e2240d521c3dd770806bff25c2c00d761ac962be106e14e22dd912007f428" -dependencies = [ - "solana-program", - "spl-token", -] - -[[package]] -name = "spl-memo" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0dc6f70db6bacea7ff25870b016a65ba1d1b6013536f08e4fd79a8f9005325" -dependencies = [ - "solana-program", -] - -[[package]] -name = "spl-token" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93bfdd5bd7c869cb565c7d7635c4fafe189b988a0bdef81063cd9585c6b8dc01" -dependencies = [ - "arrayref", - "num-derive", - "num-traits", - "num_enum", - "solana-program", - "thiserror", -] - [[package]] name = "spv_validation" version = "0.1.0" @@ -7971,31 +6629,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f9799e6d412271cb2414597581128b03f3285f260ea49f5363d07df6a332b3e" dependencies = [ "Inflector", - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "serde", "serde_json", "unicode-xid 0.2.0", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - [[package]] name = "strsim" version = "0.10.0" @@ -8023,12 +6669,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" -[[package]] -name = "symlink" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" - [[package]] name = "syn" version = "0.11.11" @@ -8040,24 +6680,13 @@ dependencies = [ "unicode-xid 0.0.4", ] -[[package]] -name = "syn" -version = "0.15.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "unicode-xid 0.1.0", -] - [[package]] name = "syn" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "unicode-ident", ] @@ -8068,7 +6697,7 @@ version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "unicode-ident", ] @@ -8094,7 +6723,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", "unicode-xid 0.2.0", @@ -8127,17 +6756,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "tar" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" -dependencies = [ - "filetime", - "libc", - "xattr", -] - [[package]] name = "tempfile" version = "3.4.0" @@ -8153,9 +6771,9 @@ dependencies = [ [[package]] name = "tendermint" -version = "0.32.2" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f0a7d05cf78524782337f8edd55cbc578d159a16ad4affe2135c92f7dbac7f0" +checksum = "43f8a10105d0a7c4af0a242e23ed5a12519afe5cc0e68419da441bb5981a6802" dependencies = [ "bytes 1.4.0", "digest 0.10.7", @@ -8184,23 +6802,23 @@ dependencies = [ [[package]] name = "tendermint-config" -version = "0.32.2" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71a72dbbea6dde12045d261f2c70c0de039125675e8a026c8d5ad34522756372" +checksum = "ac6bf36c613bb113737c333e3c1d6dfd3c99f8ac679e84feb58dd6456d77fb2e" dependencies = [ "flex-error", "serde", "serde_json", "tendermint", - "toml", + "toml 0.8.19", "url", ] [[package]] name = "tendermint-proto" -version = "0.32.2" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cec054567d16d85e8c3f6a3139963d1a66d9d3051ed545d31562550e9bcc3d" +checksum = "ff525d5540a9fc535c38dc0d92a98da3ee36fcdfbda99cecb9f3cce5cd4d41d7" dependencies = [ "bytes 1.4.0", "flex-error", @@ -8216,9 +6834,9 @@ dependencies = [ [[package]] name = "tendermint-rpc" -version = "0.32.2" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d119d83a130537fc4a98c3c9eb6899ebe857fea4860400a61675bfb5f0b35129" +checksum = "2d8fe61b1772cd50038bdeeadf53773bb37a09e639dd8e6d996668fd220ddb29" dependencies = [ "async-trait", "bytes 1.4.0", @@ -8226,6 +6844,7 @@ dependencies = [ "getrandom 0.2.9", "peg", "pin-project", + "rand 0.8.4", "semver 1.0.6", "serde", "serde_bytes", @@ -8238,7 +6857,7 @@ dependencies = [ "thiserror", "time 0.3.20", "url", - "uuid 0.8.2", + "uuid", "walkdir", ] @@ -8251,16 +6870,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "terminal_size" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "test_helpers" version = "0.1.0" @@ -8285,15 +6894,6 @@ dependencies = [ "sha2 0.10.7", ] -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - [[package]] name = "thiserror" version = "1.0.40" @@ -8309,7 +6909,7 @@ version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", ] @@ -8352,25 +6952,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tiny-bip39" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" -dependencies = [ - "anyhow", - "hmac 0.8.1", - "once_cell", - "pbkdf2 0.4.0", - "rand 0.7.3", - "rustc-hash", - "sha2 0.9.9", - "thiserror", - "unicode-normalization", - "wasm-bindgen", - "zeroize", -] - [[package]] name = "tiny-keccak" version = "1.4.4" @@ -8415,7 +6996,6 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot 0.12.0", "pin-project-lite 0.2.9", "signal-hook-registry", "socket2 0.4.9", @@ -8449,7 +7029,7 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", ] @@ -8542,11 +7122,45 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +dependencies = [ + "indexmap 2.2.3", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tonic" -version = "0.9.2" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" +checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" dependencies = [ "async-stream", "async-trait", @@ -8554,8 +7168,6 @@ dependencies = [ "base64 0.21.7", "bytes 1.4.0", "flate2", - "futures-core", - "futures-util", "h2", "http 0.2.12", "http-body 0.4.5", @@ -8564,6 +7176,7 @@ dependencies = [ "percent-encoding", "pin-project", "prost", + "rustls 0.21.10", "rustls-pemfile 1.0.2", "tokio", "tokio-rustls 0.24.1", @@ -8572,20 +7185,20 @@ dependencies = [ "tower-layer", "tower-service", "tracing", - "webpki-roots 0.23.1", + "webpki-roots 0.25.4", ] [[package]] name = "tonic-build" -version = "0.9.2" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6fdaae4c2c638bb70fe42803a26fbd6fc6ac8c72f5c59f67ecc2a2dcabf4b07" +checksum = "9d021fc044c18582b9a2408cd0dd05b1596e3ecdb5c4df822bb0183545683889" dependencies = [ "prettyplease", - "proc-macro2 1.0.69", + "proc-macro2", "prost-build", "quote 1.0.33", - "syn 1.0.95", + "syn 2.0.38", ] [[package]] @@ -8638,7 +7251,7 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", ] @@ -8724,7 +7337,7 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "idna", + "idna 0.2.3", "ipnet", "lazy_static", "rand 0.8.4", @@ -8748,7 +7361,7 @@ dependencies = [ "ipconfig", "lazy_static", "lru-cache", - "parking_lot 0.12.0", + "parking_lot", "resolv-conf", "smallvec 1.6.1", "thiserror", @@ -8782,7 +7395,6 @@ dependencies = [ "url", "utf-8", "webpki", - "webpki-roots 0.22.3", ] [[package]] @@ -8810,12 +7422,9 @@ source = "git+https://github.com/KomodoPlatform/mm2-parity-ethereum.git?rev=mm2- [[package]] name = "unicode-bidi" -version = "0.3.4" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -dependencies = [ - "matches", -] +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" @@ -8825,9 +7434,9 @@ checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] @@ -8844,12 +7453,6 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" -[[package]] -name = "unicode-xid" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" - [[package]] name = "unicode-xid" version = "0.2.0" @@ -8888,25 +7491,14 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "uriparse" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e515b1ada404168e145ac55afba3c42f04cf972201a8552d42e2abb17c1b7221" -dependencies = [ - "fnv", - "lazy_static", -] - [[package]] name = "url" -version = "2.2.2" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", - "idna", - "matches", + "idna 0.5.0", "percent-encoding", "serde", ] @@ -8937,15 +7529,9 @@ dependencies = [ [[package]] name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" - -[[package]] -name = "uuid" -version = "1.2.2" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom 0.2.9", "rand 0.8.4", @@ -8964,12 +7550,6 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" version = "0.9.4" @@ -9090,7 +7670,7 @@ dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", "wasm-bindgen-shared", @@ -9124,7 +7704,7 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 2.0.38", "wasm-bindgen-backend", @@ -9157,7 +7737,7 @@ version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c2e18093f11c19ca4e188c177fecc7c372304c311189f12c2f9bea5b7324ac7" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", ] @@ -9187,11 +7767,11 @@ dependencies = [ "getrandom 0.2.9", "headers", "hex", - "idna", + "idna 0.2.3", "js-sys", "jsonrpc-core", "log", - "parking_lot 0.12.0", + "parking_lot", "pin-project", "rand 0.8.4", "reqwest", @@ -9248,16 +7828,6 @@ dependencies = [ "cc", ] -[[package]] -name = "which" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55551e42cbdf2ce2bedd2203d0cc08dba002c27510f86dab6d0ce304cba3dfe" -dependencies = [ - "either", - "libc", -] - [[package]] name = "widestring" version = "0.5.1" @@ -9528,6 +8098,15 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.7.0" @@ -9563,24 +8142,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "xattr" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" -dependencies = [ - "libc", -] - -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "yamux" version = "0.12.1" @@ -9590,7 +8151,7 @@ dependencies = [ "futures 0.3.28", "log", "nohash-hasher", - "parking_lot 0.12.0", + "parking_lot", "pin-project", "rand 0.8.4", "static_assertions", @@ -9606,7 +8167,7 @@ dependencies = [ "instant", "log", "nohash-hasher", - "parking_lot 0.12.0", + "parking_lot", "pin-project", "rand 0.8.4", "static_assertions", @@ -9767,37 +8328,8 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ - "proc-macro2 1.0.69", + "proc-macro2", "quote 1.0.33", "syn 1.0.95", "synstructure", ] - -[[package]] -name = "zstd" -version = "0.9.2+zstd.1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2390ea1bf6c038c39674f22d95f0564725fc06034a47129179810b2fc58caa54" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "4.1.3+zstd.1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e99d81b99fb3c2c2c794e3fe56c305c63d5173a16a46b5850b07c935ffc7db79" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "1.6.2+zstd.1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f" -dependencies = [ - "cc", - "libc", -] diff --git a/Cargo.toml b/Cargo.toml index 4de3f093e9..ab18c83da1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,15 +36,13 @@ members = [ "mm2src/mm2_p2p", "mm2src/mm2_rpc", "mm2src/mm2_state_machine", + "mm2src/proxy_signature", "mm2src/rpc_task", "mm2src/trezor", ] exclude = [ "mm2src/adex_cli", - "mm2src/floodsub", - "mm2src/gossipsub", - "mm2src/mm2_libp2p", "mm2src/mm2_test_helpers", ] @@ -58,11 +56,7 @@ opt-level = 3 strip = true codegen-units = 1 # lto = true -panic = "abort" - -[profile.test] -# required to avoid a long running process of librustcash additional chain validation that is enabled with debug assertions -debug-assertions = false +panic = 'unwind' [profile.dev] opt-level = 0 @@ -73,4 +67,4 @@ incremental = true codegen-units = 256 [profile.release.package.mocktopus] -opt-level = 1 # TODO: MIR fails on optimizing this dependency, remove that.. \ No newline at end of file +opt-level = 1 # TODO: MIR fails on optimizing this dependency, remove that.. diff --git a/README.md b/README.md index ff05f9d501..441c23ba8c 100755 --- a/README.md +++ b/README.md @@ -12,10 +12,10 @@ downloads - last commit + last commit - +
@@ -48,7 +48,7 @@ ## What is the Komodo DeFi Framework? -The Komodo DeFi Framework is open-source [atomic-swap](https://komodoplatform.com/en/academy/atomic-swaps/) software for seamless, decentralised, peer to peer trading between almost every blockchain asset in existence. This software works with propagation of orderbooks and swap states through the [libp2p](https://libp2p.io/) protocol and uses [Hash Time Lock Contracts (HTLCs)](https://en.bitcoinwiki.org/wiki/Hashed_Timelock_Contracts) for ensuring that the two parties in a swap either mutually complete a trade, or funds return to thier original owner. +The Komodo DeFi Framework is open-source [atomic-swap](https://komodoplatform.com/en/academy/atomic-swaps/) software for seamless, decentralized, peer to peer trading between almost every blockchain asset in existence. This software works with propagation of orderbooks and swap states through the [libp2p](https://libp2p.io/) protocol and uses [Hash Time Lock Contracts (HTLCs)](https://en.bitcoinwiki.org/wiki/Hashed_Timelock_Contracts) for ensuring that the two parties in a swap either mutually complete a trade, or funds return to thier original owner. There is no 3rd party intermediary, no proxy tokens, and at all times users remain in sole possession of their private keys. @@ -172,7 +172,7 @@ Refer to the [Komodo Developer Docs](https://developers.komodoplatform.com/basic ## Project structure -[mm2src](mm2src) - Rust code, contains some parts ported from C `as is` (e.g. `lp_ordermatch`) to reach the most essential/error prone code. Some other modules/crates are reimplemented from scratch. +[mm2src](mm2src) - Rust code, contains some parts ported from C `as is` (e.g. `lp_ordermatch`) to reach the most essential/error-prone code. Some other modules/crates are reimplemented from scratch. ## Additional docs for developers @@ -185,12 +185,13 @@ Refer to the [Komodo Developer Docs](https://developers.komodoplatform.com/basic ## Disclaimer -This repository contains the `work in progress` code of the brand new Komodo DeFi Framework (kdf) built mainly on Rust. -The current state can be considered as a alpha version. +This repository contains the `work in progress` code of the brand-new Komodo DeFi Framework (kdf) built mainly on Rust. +The current state can be considered as an alpha version. **WARNING: Use with test coins only or with assets which value does not exceed an amount you are willing to lose. This is alpha stage software! ** ## Help and troubleshooting -If you have any question/want to report a bug/suggest an improvement feel free to [open an issue](https://github.com/artemii235/SuperNET/issues/new) or join the [Komodo Platform Discord](https://discord.gg/PGxVm2y) `dev-marketmaker` channel. +If you have any question/want to report a bug/suggest an improvement feel free to [open an issue](https://github.com/KomodoPlatform/komodo-defi-framework/issues/new/choose) or join the [Komodo Platform Discord](https://discord.gg/PGxVm2y) `dev-marketmaker` channel. + diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000000..068d7e886b --- /dev/null +++ b/clippy.toml @@ -0,0 +1,4 @@ +[[disallowed-methods]] +path = "futures::future::Future::wait" +replacement = "common::block_on_f01" +reason = "Use the default KDF async executor." \ No newline at end of file diff --git a/mm2src/adex_cli/Cargo.lock b/mm2src/adex_cli/Cargo.lock index 4de83cbd97..5d5eb5abeb 100644 --- a/mm2src/adex_cli/Cargo.lock +++ b/mm2src/adex_cli/Cargo.lock @@ -21,7 +21,7 @@ dependencies = [ "common", "derive_more", "directories", - "env_logger 0.7.1", + "env_logger", "gstuff", "http 0.2.9", "hyper", @@ -29,6 +29,7 @@ dependencies = [ "inquire", "itertools", "log", + "mm2_core", "mm2_net", "mm2_number", "mm2_rpc", @@ -285,7 +286,7 @@ checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -626,7 +627,7 @@ dependencies = [ "chrono", "crossbeam", "derive_more", - "env_logger 0.9.3", + "env_logger", "findshlibs", "fnv", "futures 0.1.31", @@ -857,7 +858,7 @@ dependencies = [ "proc-macro2", "quote 1.0.27", "scratch", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -874,7 +875,7 @@ checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -900,7 +901,7 @@ checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -967,19 +968,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" -[[package]] -name = "env_logger" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" -dependencies = [ - "atty", - "humantime 1.3.0", - "log", - "regex", - "termcolor", -] - [[package]] name = "env_logger" version = "0.9.3" @@ -987,7 +975,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", - "humantime 2.1.0", + "humantime", "log", "regex", "termcolor", @@ -1488,15 +1476,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" -[[package]] -name = "humantime" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -dependencies = [ - "quick-error", -] - [[package]] name = "humantime" version = "2.1.0" @@ -1603,7 +1582,7 @@ checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -2244,7 +2223,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote 1.0.27", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -2466,7 +2445,7 @@ dependencies = [ "itertools", "proc-macro2", "quote 1.0.27", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -2485,12 +2464,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quote" version = "0.3.15" @@ -3135,7 +3108,7 @@ dependencies = [ "proc-macro2", "quote 1.0.27", "ser_error", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -3189,7 +3162,7 @@ checksum = "2dc6b7951b17b051f3210b063f12cc17320e2fe30ae05b0fe2a3abb068551c76" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -3392,9 +3365,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.95" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote 1.0.27", @@ -3475,7 +3448,7 @@ checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] @@ -3576,7 +3549,7 @@ checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ "proc-macro2", "quote 1.0.27", - "syn 1.0.95", + "syn 1.0.109", ] [[package]] diff --git a/mm2src/adex_cli/Cargo.toml b/mm2src/adex_cli/Cargo.toml index d2b38a4cba..cb477cacb0 100644 --- a/mm2src/adex_cli/Cargo.toml +++ b/mm2src/adex_cli/Cargo.toml @@ -7,23 +7,24 @@ description = "Provides a CLI interface and facilitates interoperating to komodo # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -anyhow = { version = "=1.0.42", features = ["std"] } -async-trait = "=0.1.52" +anyhow = { version = "1.0", features = ["std"] } +async-trait = "0.1" clap = { version = "4.2", features = ["derive"] } common = { path = "../common" } derive_more = "0.99" directories = "5.0" -env_logger = "0.7.1" +env_logger = "0.9.3" http = "0.2" hyper = { version = "0.14.26", features = ["client", "http2", "tcp"] } -hyper-rustls = "0.24.0" -gstuff = { version = "=0.7.4" , features = [ "nightly" ]} +hyper-rustls = "0.24" +gstuff = { version = "0.7" , features = [ "nightly" ]} inquire = "0.6" itertools = "0.10" log = "0.4.21" mm2_net = { path = "../mm2_net" } mm2_number = { path = "../mm2_number" } mm2_rpc = { path = "../mm2_rpc"} +mm2_core = { path = "../mm2_core" } passwords = "3.1" rpc = { path = "../mm2_bitcoin/rpc" } rustls = { version = "0.21", features = [ "dangerous_configuration" ] } @@ -31,8 +32,8 @@ serde = "1.0" serde_json = { version = "1", features = ["preserve_order", "raw_value"] } sysinfo = "0.28" tiny-bip39 = "0.8.0" -tokio = { version = "=1.25.0", features = [ "macros" ] } -uuid = { version = "=1.2.2", features = ["fast-rng", "serde", "v4"] } +tokio = { version = "1.20.0", features = [ "macros" ] } +uuid = { version = "1.2.2", features = ["fast-rng", "serde", "v4"] } [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.3", features = ["processthreadsapi", "winnt"] } diff --git a/mm2src/adex_cli/README.md b/mm2src/adex_cli/README.md new file mode 100644 index 0000000000..6066b2c0bc --- /dev/null +++ b/mm2src/adex_cli/README.md @@ -0,0 +1,3 @@ +## ⚠️ Deprecated + +**The `adex-cli` tool is no longer maintained and has been deprecated.** \ No newline at end of file diff --git a/mm2src/adex_cli/src/adex_proc/adex_proc_impl.rs b/mm2src/adex_cli/src/adex_proc/adex_proc_impl.rs index 33d4fbfb62..24c108c6da 100644 --- a/mm2src/adex_cli/src/adex_proc/adex_proc_impl.rs +++ b/mm2src/adex_cli/src/adex_proc/adex_proc_impl.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, bail, Result}; -use log::{error, info, warn}; +use log::{debug, error, info, warn}; use mm2_rpc::data::legacy::{BalanceResponse, CoinInitResponse, GetEnabledResponse, Mm2RpcResult, MmVersionResponse, OrderbookRequest, OrderbookResponse, SellBuyRequest, SellBuyResponse, Status}; use serde_json::{json, Value as Json}; @@ -38,7 +38,7 @@ impl AdexProc<'_, '_, let activation_scheme = get_activation_scheme()?; let activation_method = activation_scheme.get_activation_method(asset)?; - + debug!("Got activation scheme for the coin: {}, {:?}", asset, activation_method); let enable = Command::builder() .flatten_data(activation_method) .userpass(self.get_rpc_password()?) diff --git a/mm2src/adex_cli/src/rpc_data.rs b/mm2src/adex_cli/src/rpc_data.rs index a3146cbe47..2c634759ef 100644 --- a/mm2src/adex_cli/src/rpc_data.rs +++ b/mm2src/adex_cli/src/rpc_data.rs @@ -40,6 +40,10 @@ pub(crate) struct ElectrumRequest { #[serde(skip_serializing_if = "Vec::is_empty")] pub(super) servers: Vec, #[serde(skip_serializing_if = "Option::is_none")] + min_connected: Option, + #[serde(skip_serializing_if = "Option::is_none")] + max_connected: Option, + #[serde(skip_serializing_if = "Option::is_none")] mm2: Option, #[serde(default)] tx_history: bool, @@ -62,4 +66,5 @@ pub(super) struct Server { protocol: ElectrumProtocol, #[serde(default)] disable_cert_verification: bool, + pub timeout_sec: Option, } diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index 1c1fe1637d..290a0dd7f5 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -5,27 +5,16 @@ edition = "2018" [features] zhtlc-native-tests = [] -# TODO -enable-solana = [ - "dep:bincode", - "dep:ed25519-dalek-bip32", - "dep:solana-client", - "dep:solana-sdk", - "dep:solana-transaction-status", - "dep:spl-token", - "dep:spl-associated-token-account", - "dep:satomic-swap" -] enable-sia = [ "dep:reqwest", - "blake2b_simd" + "dep:blake2b_simd", + "dep:sia-rust" ] default = [] run-docker-tests = [] for-tests = [] [lib] -name = "coins" path = "lp_coins.rs" doctest = false @@ -42,13 +31,14 @@ byteorder = "1.3" bytes = "0.4" cfg-if = "1.0" chain = { path = "../mm2_bitcoin/chain" } +chrono = { version = "0.4.23", "features" = ["serde"] } common = { path = "../common" } -cosmrs = { version = "0.14.0", default-features = false } +cosmrs = { version = "0.16", default-features = false } crossbeam = "0.8" crypto = { path = "../crypto" } db_common = { path = "../db_common" } derive_more = "0.99" -ed25519-dalek = "1.0.1" +ed25519-dalek = { version = "1.0.1", features = ["serde"] } enum_derives = { path = "../derives/enum_derives" } ethabi = { version = "17.0.0" } ethcore-transaction = { git = "https://github.com/KomodoPlatform/mm2-parity-ethereum.git", rev = "mm2-v2.1.1" } @@ -70,6 +60,7 @@ jsonrpc-core = "18.0.0" keys = { path = "../mm2_bitcoin/keys" } lazy_static = "1.4" libc = "0.2" +nom = "6.1.2" mm2_core = { path = "../mm2_core" } mm2_err_handle = { path = "../mm2_err_handle" } mm2_event_stream = { path = "../mm2_event_stream" } @@ -78,14 +69,16 @@ mm2_io = { path = "../mm2_io" } mm2_metrics = { path = "../mm2_metrics" } mm2_net = { path = "../mm2_net" } mm2_number = { path = "../mm2_number"} +mm2_p2p = { path = "../mm2_p2p" } mm2_rpc = { path = "../mm2_rpc" } mm2_state_machine = { path = "../mm2_state_machine" } mocktopus = "0.8.0" num-traits = "0.2" parking_lot = { version = "0.12.0", features = ["nightly"] } primitives = { path = "../mm2_bitcoin/primitives" } -prost = "0.11" +prost = "0.12" protobuf = "2.20" +proxy_signature = { path = "../proxy_signature" } rand = { version = "0.7", features = ["std", "small_rng"] } regex = "1" reqwest = { version = "0.11.9", default-features = false, features = ["json"], optional = true } @@ -93,7 +86,6 @@ rlp = { version = "0.5" } rmp-serde = "0.14.3" rpc = { path = "../mm2_bitcoin/rpc" } rpc_task = { path = "../rpc_task" } -satomic-swap = { git = "https://github.com/KomodoPlatform/satomic-swap.git", rev = "413e472", optional = true } script = { path = "../mm2_bitcoin/script" } secp256k1 = { version = "0.20" } ser_error = { path = "../derives/ser_error" } @@ -101,14 +93,16 @@ ser_error_derive = { path = "../derives/ser_error_derive" } serde = "1.0" serde_derive = "1.0" serde_json = { version = "1", features = ["preserve_order", "raw_value"] } +serde_with = "1.14.0" serialization = { path = "../mm2_bitcoin/serialization" } serialization_derive = { path = "../mm2_bitcoin/serialization_derive" } +sia-rust = { git = "https://github.com/KomodoPlatform/sia-rust", rev = "9f188b80b3213bcb604e7619275251ce08fae808", optional = true } spv_validation = { path = "../mm2_bitcoin/spv_validation" } sha2 = "0.10" sha3 = "0.9" utxo_signer = { path = "utxo_signer" } # using the same version as cosmrs -tendermint-rpc = { version = "0.32.0", default-features = false } +tendermint-rpc = { version = "0.35", default-features = false } tokio-tungstenite-wasm = { git = "https://github.com/KomodoPlatform/tokio-tungstenite-wasm", rev = "d20abdb", features = ["rustls-tls-native-roots"]} url = { version = "2.2.2", features = ["serde"] } uuid = { version = "1.2.2", features = ["fast-rng", "serde", "v4"] } @@ -120,15 +114,6 @@ zcash_client_backend = { git = "https://github.com/KomodoPlatform/librustzcash.g zcash_extras = { git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.4.1" } zcash_primitives = {features = ["transparent-inputs"], git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.4.1" } -[target.'cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))'.dependencies] -bincode = { version = "1.3.3", default-features = false, optional = true } -ed25519-dalek-bip32 = { version = "0.2.0", default-features = false, optional = true } -solana-client = { version = "1", default-features = false, optional = true } -solana-sdk = { version = "1", default-features = false, optional = true } -solana-transaction-status = { version = "1", optional = true } -spl-token = { version = "3", optional = true } -spl-associated-token-account = { version = "1", optional = true } - [target.'cfg(target_arch = "wasm32")'.dependencies] blake2b_simd = "0.5" ff = "0.8" @@ -140,7 +125,7 @@ mm2_db = { path = "../mm2_db" } mm2_metamask = { path = "../mm2_metamask" } mm2_test_helpers = { path = "../mm2_test_helpers" } time = { version = "0.3.20", features = ["wasm-bindgen"] } -tonic = { version = "0.9", default-features = false, features = ["prost", "codegen", "gzip"] } +tonic = { version = "0.10", default-features = false, features = ["prost", "codegen", "gzip"] } tower-service = "0.3" wasm-bindgen = "0.2.86" wasm-bindgen-futures = { version = "0.4.1" } @@ -165,7 +150,7 @@ rustls = { version = "0.21", features = ["dangerous_configuration"] } secp256k1v24 = { version = "0.24", package = "secp256k1" } tokio = { version = "1.20" } tokio-rustls = { version = "0.24" } -tonic = { version = "0.9", features = ["tls", "tls-webpki-roots", "gzip"] } +tonic = { version = "0.10", features = ["tls", "tls-webpki-roots", "gzip"] } webpki-roots = { version = "0.25" } zcash_client_sqlite = { git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.4.1" } zcash_proofs = { git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.4.1", default-features = false, features = ["local-prover", "multicore"] } @@ -180,5 +165,5 @@ mm2_test_helpers = { path = "../mm2_test_helpers" } wagyu-zcash-parameters = { version = "0.2" } [build-dependencies] -prost-build = { version = "0.11", default-features = false } -tonic-build = { version = "0.9", default-features = false, features = ["prost"] } +prost-build = { version = "0.12", default-features = false } +tonic-build = { version = "0.10", default-features = false, features = ["prost"] } diff --git a/mm2src/coins/coin_errors.rs b/mm2src/coins/coin_errors.rs index 34b0c46486..6075317bfc 100644 --- a/mm2src/coins/coin_errors.rs +++ b/mm2src/coins/coin_errors.rs @@ -1,11 +1,12 @@ -use crate::eth::nft_swap_v2::errors::{Erc721FunctionError, HtlcParamsError, PaymentStatusErr, PrepareTxDataError}; +use crate::eth::eth_swap_v2::{PaymentStatusErr, PrepareTxDataError, ValidatePaymentV2Err}; +use crate::eth::nft_swap_v2::errors::{Erc721FunctionError, HtlcParamsError}; use crate::eth::{EthAssocTypesError, EthNftAssocTypesError, Web3RpcError}; use crate::{utxo::rpc_clients::UtxoRpcError, NumConversError, UnexpectedDerivationMethod}; use enum_derives::EnumFromStringify; use futures01::Future; use mm2_err_handle::prelude::MmError; use spv_validation::helpers_validation::SPVError; -use std::num::TryFromIntError; +use std::{array::TryFromSliceError, num::TryFromIntError}; /// Helper type used as result for swap payment validation function(s) pub type ValidatePaymentFut = Box> + Send>; @@ -23,7 +24,9 @@ pub enum ValidatePaymentError { "NumConversError", "UnexpectedDerivationMethod", "keys::Error", - "PrepareTxDataError" + "PrepareTxDataError", + "ethabi::Error", + "TryFromSliceError" )] InternalError(String), /// Problem with deserializing the transaction, or one of the transaction parts is invalid. @@ -48,8 +51,8 @@ pub enum ValidatePaymentError { WatcherRewardError(String), /// Input payment timelock overflows the type used by specific coin. TimelockOverflow(TryFromIntError), - #[display(fmt = "Nft Protocol is not supported yet!")] - NftProtocolNotSupported, + ProtocolNotSupported(String), + InvalidData(String), } impl From for ValidatePaymentError { @@ -75,7 +78,9 @@ impl From for ValidatePaymentError { | Web3RpcError::Timeout(internal) | Web3RpcError::NumConversError(internal) | Web3RpcError::InvalidGasApiConfig(internal) => ValidatePaymentError::InternalError(internal), - Web3RpcError::NftProtocolNotSupported => ValidatePaymentError::NftProtocolNotSupported, + Web3RpcError::NftProtocolNotSupported => { + ValidatePaymentError::ProtocolNotSupported("Nft protocol is not supported".to_string()) + }, } } } @@ -84,9 +89,8 @@ impl From for ValidatePaymentError { fn from(err: PaymentStatusErr) -> Self { match err { PaymentStatusErr::Transport(e) => Self::Transport(e), - PaymentStatusErr::AbiError(e) - | PaymentStatusErr::Internal(e) - | PaymentStatusErr::TxDeserializationError(e) => Self::InternalError(e), + PaymentStatusErr::ABIError(e) | PaymentStatusErr::Internal(e) => Self::InternalError(e), + PaymentStatusErr::InvalidData(e) => Self::InvalidData(e), } } } @@ -95,7 +99,16 @@ impl From for ValidatePaymentError { fn from(err: HtlcParamsError) -> Self { match err { HtlcParamsError::WrongPaymentTx(e) => ValidatePaymentError::WrongPaymentTx(e), - HtlcParamsError::TxDeserializationError(e) => ValidatePaymentError::TxDeserializationError(e), + HtlcParamsError::ABIError(e) | HtlcParamsError::InvalidData(e) => ValidatePaymentError::InvalidData(e), + } + } +} + +impl From for ValidatePaymentError { + fn from(err: ValidatePaymentV2Err) -> Self { + match err { + ValidatePaymentV2Err::UnexpectedPaymentState(e) => ValidatePaymentError::UnexpectedPaymentState(e), + ValidatePaymentV2Err::WrongPaymentTx(e) => ValidatePaymentError::WrongPaymentTx(e), } } } diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index b5f5b0ecde..7934bdf485 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -48,8 +48,8 @@ use crate::rpc_command::{account_balance, get_new_address, init_account_balance, init_scan_for_new_addresses}; use crate::{coin_balance, scan_for_new_addresses_impl, BalanceResult, CoinWithDerivationMethod, DerivationMethod, DexFee, Eip1559Ops, MakerNftSwapOpsV2, ParseCoinAssocTypes, ParseNftAssocTypes, PayForGasParams, - PrivKeyPolicy, RefundMakerPaymentArgs, RpcCommonOps, SendNftMakerPaymentArgs, SpendNftMakerPaymentArgs, - ToBytes, ValidateNftMakerPaymentArgs, ValidateWatcherSpendInput, WatcherSpendType}; + PrivKeyPolicy, RpcCommonOps, SendNftMakerPaymentArgs, SpendNftMakerPaymentArgs, ToBytes, + ValidateNftMakerPaymentArgs, ValidateWatcherSpendInput, WatcherSpendType}; use async_trait::async_trait; use bitcrypto::{dhash160, keccak256, ripemd160, sha256}; use common::custom_futures::repeatable::{Ready, Retry, RetryOnError}; @@ -58,7 +58,7 @@ use common::executor::{abortable_queue::AbortableQueue, AbortOnDropHandle, Abort AbortedError, SpawnAbortable, Timer}; use common::log::{debug, error, info, warn}; use common::number_type_casting::SafeTypeCastingNumbers; -use common::{get_utc_timestamp, now_sec, small_rng, DEX_FEE_ADDR_RAW_PUBKEY}; +use common::{now_sec, small_rng, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::privkey::key_pair_from_secret; use crypto::{Bip44Chain, CryptoCtx, CryptoCtxError, GlobalHDAccountArc, KeyPairPolicy}; use derive_more::Display; @@ -76,10 +76,8 @@ use futures::future::{join, join_all, select_ok, try_join_all, Either, FutureExt use futures01::Future; use http::Uri; use instant::Instant; -use keys::Public as HtlcPubKey; use mm2_core::mm_ctx::{MmArc, MmWeak}; use mm2_event_stream::behaviour::{EventBehaviour, EventInitStatus}; -use mm2_net::transport::{KomodefiProxyAuthValidation, ProxyAuthValidationGenerator}; use mm2_number::bigdecimal_custom::CheckedDivision; use mm2_number::{BigDecimal, BigUint, MmNumber}; #[cfg(test)] use mocktopus::macros::*; @@ -160,6 +158,12 @@ pub(crate) use eip1559_gas_fee::FeePerGasEstimated; use eip1559_gas_fee::{BlocknativeGasApiCaller, FeePerGasSimpleEstimator, GasApiConfig, GasApiProvider, InfuraGasApiCaller}; +pub mod erc20; +use erc20::get_token_decimals; + +pub(crate) mod eth_swap_v2; +use eth_swap_v2::{EthPaymentType, PaymentMethod}; + /// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol /// Dev chain (195.201.137.5:8565) contract address: 0x83965C539899cC0F918552e5A26915de40ee8852 /// Ropsten: https://ropsten.etherscan.io/address/0x7bc1bbdd6a0a722fc9bffc49c921b685ecb84b94 @@ -173,6 +177,8 @@ const ERC721_ABI: &str = include_str!("eth/erc721_abi.json"); const ERC1155_ABI: &str = include_str!("eth/erc1155_abi.json"); const NFT_SWAP_CONTRACT_ABI: &str = include_str!("eth/nft_swap_contract_abi.json"); const NFT_MAKER_SWAP_V2_ABI: &str = include_str!("eth/nft_maker_swap_v2_abi.json"); +const MAKER_SWAP_V2_ABI: &str = include_str!("eth/maker_swap_v2_abi.json"); +const TAKER_SWAP_V2_ABI: &str = include_str!("eth/taker_swap_v2_abi.json"); /// Payment states from etomic swap smart contract: https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol#L5 pub enum PaymentState { @@ -244,12 +250,51 @@ pub mod gas_limit { pub const ERC20_RECEIVER_SPEND: u64 = 150_000; /// Gas limit for swap refund tx with coins pub const ETH_SENDER_REFUND: u64 = 100_000; - /// Gas limit for swap refund tx with with ERC20 tokens + /// Gas limit for swap refund tx with ERC20 tokens pub const ERC20_SENDER_REFUND: u64 = 150_000; /// Gas limit for other operations pub const ETH_MAX_TRADE_GAS: u64 = 150_000; } +/// Default gas limits for EthGasLimitV2 +pub mod gas_limit_v2 { + /// Gas limits for maker operations in EtomicSwapMakerV2 contract + pub mod maker { + pub const ETH_PAYMENT: u64 = 65_000; + pub const ERC20_PAYMENT: u64 = 150_000; + pub const ETH_TAKER_SPEND: u64 = 100_000; + pub const ERC20_TAKER_SPEND: u64 = 150_000; + pub const ETH_MAKER_REFUND_TIMELOCK: u64 = 90_000; + pub const ERC20_MAKER_REFUND_TIMELOCK: u64 = 100_000; + pub const ETH_MAKER_REFUND_SECRET: u64 = 90_000; + pub const ERC20_MAKER_REFUND_SECRET: u64 = 100_000; + } + + /// Gas limits for taker operations in EtomicSwapTakerV2 contract + pub mod taker { + pub const ETH_PAYMENT: u64 = 65_000; + pub const ERC20_PAYMENT: u64 = 150_000; + pub const ETH_MAKER_SPEND: u64 = 100_000; + pub const ERC20_MAKER_SPEND: u64 = 115_000; + pub const ETH_TAKER_REFUND_TIMELOCK: u64 = 90_000; + pub const ERC20_TAKER_REFUND_TIMELOCK: u64 = 100_000; + pub const ETH_TAKER_REFUND_SECRET: u64 = 90_000; + pub const ERC20_TAKER_REFUND_SECRET: u64 = 100_000; + pub const APPROVE_PAYMENT: u64 = 50_000; + } + + pub mod nft_maker { + pub const ERC721_PAYMENT: u64 = 130_000; + pub const ERC1155_PAYMENT: u64 = 130_000; + pub const ERC721_TAKER_SPEND: u64 = 100_000; + pub const ERC1155_TAKER_SPEND: u64 = 100_000; + pub const ERC721_MAKER_REFUND_TIMELOCK: u64 = 100_000; + pub const ERC1155_MAKER_REFUND_TIMELOCK: u64 = 100_000; + pub const ERC721_MAKER_REFUND_SECRET: u64 = 100_000; + pub const ERC1155_MAKER_REFUND_SECRET: u64 = 100_000; + } +} + /// Coin conf param to override default gas limits #[derive(Deserialize)] #[serde(default)] @@ -268,7 +313,7 @@ pub struct EthGasLimit { pub erc20_receiver_spend: u64, /// Gas limit for swap refund tx with coins pub eth_sender_refund: u64, - /// Gas limit for swap refund tx with with ERC20 tokens + /// Gas limit for swap refund tx with ERC20 tokens pub erc20_sender_refund: u64, /// Gas limit for other operations pub eth_max_trade_gas: u64, @@ -290,14 +335,183 @@ impl Default for EthGasLimit { } } -/// Lifetime of generated signed message for proxy-auth requests -const PROXY_AUTH_SIGNED_MESSAGE_LIFETIME_SEC: i64 = 90; +#[derive(Default, Deserialize)] +#[serde(default)] +pub struct EthGasLimitV2 { + pub maker: MakerGasLimitV2, + pub taker: TakerGasLimitV2, + pub nft_maker: NftMakerGasLimitV2, +} + +#[derive(Deserialize)] +#[serde(default)] +pub struct MakerGasLimitV2 { + pub eth_payment: u64, + pub erc20_payment: u64, + pub eth_taker_spend: u64, + pub erc20_taker_spend: u64, + pub eth_maker_refund_timelock: u64, + pub erc20_maker_refund_timelock: u64, + pub eth_maker_refund_secret: u64, + pub erc20_maker_refund_secret: u64, +} + +#[derive(Deserialize)] +#[serde(default)] +pub struct TakerGasLimitV2 { + pub eth_payment: u64, + pub erc20_payment: u64, + pub eth_maker_spend: u64, + pub erc20_maker_spend: u64, + pub eth_taker_refund_timelock: u64, + pub erc20_taker_refund_timelock: u64, + pub eth_taker_refund_secret: u64, + pub erc20_taker_refund_secret: u64, + pub approve_payment: u64, +} + +#[derive(Deserialize)] +#[serde(default)] +pub struct NftMakerGasLimitV2 { + pub erc721_payment: u64, + pub erc1155_payment: u64, + pub erc721_taker_spend: u64, + pub erc1155_taker_spend: u64, + pub erc721_maker_refund_timelock: u64, + pub erc1155_maker_refund_timelock: u64, + pub erc721_maker_refund_secret: u64, + pub erc1155_maker_refund_secret: u64, +} + +impl EthGasLimitV2 { + fn gas_limit( + &self, + coin_type: &EthCoinType, + payment_type: EthPaymentType, + method: PaymentMethod, + ) -> Result { + match coin_type { + EthCoinType::Eth => { + let gas_limit = match payment_type { + EthPaymentType::MakerPayments => match method { + PaymentMethod::Send => self.maker.eth_payment, + PaymentMethod::Spend => self.maker.eth_taker_spend, + PaymentMethod::RefundTimelock => self.maker.eth_maker_refund_timelock, + PaymentMethod::RefundSecret => self.maker.eth_maker_refund_secret, + }, + EthPaymentType::TakerPayments => match method { + PaymentMethod::Send => self.taker.eth_payment, + PaymentMethod::Spend => self.taker.eth_maker_spend, + PaymentMethod::RefundTimelock => self.taker.eth_taker_refund_timelock, + PaymentMethod::RefundSecret => self.taker.eth_taker_refund_secret, + }, + }; + Ok(gas_limit) + }, + EthCoinType::Erc20 { .. } => { + let gas_limit = match payment_type { + EthPaymentType::MakerPayments => match method { + PaymentMethod::Send => self.maker.erc20_payment, + PaymentMethod::Spend => self.maker.erc20_taker_spend, + PaymentMethod::RefundTimelock => self.maker.erc20_maker_refund_timelock, + PaymentMethod::RefundSecret => self.maker.erc20_maker_refund_secret, + }, + EthPaymentType::TakerPayments => match method { + PaymentMethod::Send => self.taker.erc20_payment, + PaymentMethod::Spend => self.taker.erc20_maker_spend, + PaymentMethod::RefundTimelock => self.taker.erc20_taker_refund_timelock, + PaymentMethod::RefundSecret => self.taker.erc20_taker_refund_secret, + }, + }; + Ok(gas_limit) + }, + EthCoinType::Nft { .. } => Err("NFT protocol is not supported for ETH and ERC20 Swaps".to_string()), + } + } + + fn nft_gas_limit(&self, contract_type: &ContractType, method: PaymentMethod) -> u64 { + match contract_type { + ContractType::Erc1155 => match method { + PaymentMethod::Send => self.nft_maker.erc1155_payment, + PaymentMethod::Spend => self.nft_maker.erc1155_taker_spend, + PaymentMethod::RefundTimelock => self.nft_maker.erc1155_maker_refund_timelock, + PaymentMethod::RefundSecret => self.nft_maker.erc1155_maker_refund_secret, + }, + ContractType::Erc721 => match method { + PaymentMethod::Send => self.nft_maker.erc721_payment, + PaymentMethod::Spend => self.nft_maker.erc721_taker_spend, + PaymentMethod::RefundTimelock => self.nft_maker.erc721_maker_refund_timelock, + PaymentMethod::RefundSecret => self.nft_maker.erc721_maker_refund_secret, + }, + } + } +} + +impl Default for MakerGasLimitV2 { + fn default() -> Self { + MakerGasLimitV2 { + eth_payment: gas_limit_v2::maker::ETH_PAYMENT, + erc20_payment: gas_limit_v2::maker::ERC20_PAYMENT, + eth_taker_spend: gas_limit_v2::maker::ETH_TAKER_SPEND, + erc20_taker_spend: gas_limit_v2::maker::ERC20_TAKER_SPEND, + eth_maker_refund_timelock: gas_limit_v2::maker::ETH_MAKER_REFUND_TIMELOCK, + erc20_maker_refund_timelock: gas_limit_v2::maker::ERC20_MAKER_REFUND_TIMELOCK, + eth_maker_refund_secret: gas_limit_v2::maker::ETH_MAKER_REFUND_SECRET, + erc20_maker_refund_secret: gas_limit_v2::maker::ERC20_MAKER_REFUND_SECRET, + } + } +} + +impl Default for TakerGasLimitV2 { + fn default() -> Self { + TakerGasLimitV2 { + eth_payment: gas_limit_v2::taker::ETH_PAYMENT, + erc20_payment: gas_limit_v2::taker::ERC20_PAYMENT, + eth_maker_spend: gas_limit_v2::taker::ETH_MAKER_SPEND, + erc20_maker_spend: gas_limit_v2::taker::ERC20_MAKER_SPEND, + eth_taker_refund_timelock: gas_limit_v2::taker::ETH_TAKER_REFUND_TIMELOCK, + erc20_taker_refund_timelock: gas_limit_v2::taker::ERC20_TAKER_REFUND_TIMELOCK, + eth_taker_refund_secret: gas_limit_v2::taker::ETH_TAKER_REFUND_SECRET, + erc20_taker_refund_secret: gas_limit_v2::taker::ERC20_TAKER_REFUND_SECRET, + approve_payment: gas_limit_v2::taker::APPROVE_PAYMENT, + } + } +} + +impl Default for NftMakerGasLimitV2 { + fn default() -> Self { + NftMakerGasLimitV2 { + erc721_payment: gas_limit_v2::nft_maker::ERC721_PAYMENT, + erc1155_payment: gas_limit_v2::nft_maker::ERC1155_PAYMENT, + erc721_taker_spend: gas_limit_v2::nft_maker::ERC721_TAKER_SPEND, + erc1155_taker_spend: gas_limit_v2::nft_maker::ERC1155_TAKER_SPEND, + erc721_maker_refund_timelock: gas_limit_v2::nft_maker::ERC721_MAKER_REFUND_TIMELOCK, + erc1155_maker_refund_timelock: gas_limit_v2::nft_maker::ERC1155_MAKER_REFUND_TIMELOCK, + erc721_maker_refund_secret: gas_limit_v2::nft_maker::ERC721_MAKER_REFUND_SECRET, + erc1155_maker_refund_secret: gas_limit_v2::nft_maker::ERC1155_MAKER_REFUND_SECRET, + } + } +} + +trait ExtractGasLimit: Default + for<'de> Deserialize<'de> { + fn key() -> &'static str; +} + +impl ExtractGasLimit for EthGasLimit { + fn key() -> &'static str { "gas_limit" } +} + +impl ExtractGasLimit for EthGasLimitV2 { + fn key() -> &'static str { "gas_limit_v2" } +} /// Max transaction type according to EIP-2718 const ETH_MAX_TX_TYPE: u64 = 0x7f; lazy_static! { pub static ref SWAP_CONTRACT: Contract = Contract::load(SWAP_CONTRACT_ABI.as_bytes()).unwrap(); + pub static ref MAKER_SWAP_V2: Contract = Contract::load(MAKER_SWAP_V2_ABI.as_bytes()).unwrap(); + pub static ref TAKER_SWAP_V2: Contract = Contract::load(TAKER_SWAP_V2_ABI.as_bytes()).unwrap(); pub static ref ERC20_CONTRACT: Contract = Contract::load(ERC20_ABI.as_bytes()).unwrap(); pub static ref ERC721_CONTRACT: Contract = Contract::load(ERC721_ABI.as_bytes()).unwrap(); pub static ref ERC1155_CONTRACT: Contract = Contract::load(ERC1155_ABI.as_bytes()).unwrap(); @@ -384,11 +598,11 @@ type GasDetails = (U256, PayForGasOption); pub enum Web3RpcError { #[display(fmt = "Transport: {}", _0)] Transport(String), - #[from_stringify("serde_json::Error")] #[display(fmt = "Invalid response: {}", _0)] InvalidResponse(String), #[display(fmt = "Timeout: {}", _0)] Timeout(String), + #[from_stringify("serde_json::Error")] #[display(fmt = "Internal: {}", _0)] Internal(String), #[display(fmt = "Invalid gas api provider config: {}", _0)] @@ -649,6 +863,7 @@ pub struct EthCoinImpl { derivation_method: Arc, sign_message_prefix: Option, swap_contract_address: Address, + swap_v2_contracts: Option, fallback_swap_contract: Option
, contract_supports_watchers: bool, web3_instances: AsyncMutex>, @@ -671,7 +886,7 @@ pub struct EthCoinImpl { /// and unlocked once the transaction is confirmed. This prevents nonce conflicts when multiple transactions /// are initiated concurrently from the same address. address_nonce_locks: Arc>>>>, - erc20_tokens_infos: Arc>>, + erc20_tokens_infos: Arc>>, /// Stores information about NFTs owned by the user. Each entry in the HashMap is uniquely identified by a composite key /// consisting of the token address and token ID, separated by a comma. This field is essential for tracking the NFT assets /// information (chain & contract type, amount etc.), where ownership and amount, in ERC1155 case, might change over time. @@ -680,6 +895,8 @@ pub struct EthCoinImpl { pub(crate) platform_fee_estimator_state: Arc, /// Config provided gas limits for swap and send transactions pub(crate) gas_limit: EthGasLimit, + /// Config provided gas limits v2 for swap v2 transactions + pub(crate) gas_limit_v2: EthGasLimitV2, /// This spawner is used to spawn coin's related futures that should be aborted on coin deactivation /// and on [`MmArc::stop`]. pub abortable_system: AbortableQueue, @@ -693,7 +910,7 @@ pub struct Web3Instance { /// Information about a token that follows the ERC20 protocol on an EVM-based network. #[derive(Clone, Debug)] -pub struct Erc20TokenInfo { +pub struct Erc20TokenDetails { /// The contract address of the token on the EVM-based network. pub token_address: Address, /// The number of decimal places the token uses. @@ -701,6 +918,13 @@ pub struct Erc20TokenInfo { pub decimals: u8, } +#[derive(Copy, Clone, Deserialize)] +pub struct SwapV2Contracts { + pub maker_swap_v2_contract: Address, + pub taker_swap_v2_contract: Address, + pub nft_maker_swap_v2_contract: Address, +} + #[derive(Deserialize, Serialize)] #[serde(tag = "format")] pub enum EthAddressFormat { @@ -819,9 +1043,18 @@ impl EthCoinImpl { /// The id used to differentiate payments on Etomic swap smart contract pub(crate) fn etomic_swap_id(&self, time_lock: u32, secret_hash: &[u8]) -> Vec { let timelock_bytes = time_lock.to_le_bytes(); + self.generate_etomic_swap_id(&timelock_bytes, secret_hash) + } - let mut input = Vec::with_capacity(timelock_bytes.len() + secret_hash.len()); - input.extend_from_slice(&timelock_bytes); + /// The id used to differentiate payments on Etomic swap v2 smart contracts + pub(crate) fn etomic_swap_id_v2(&self, time_lock: u64, secret_hash: &[u8]) -> Vec { + let timelock_bytes = time_lock.to_le_bytes(); + self.generate_etomic_swap_id(&timelock_bytes, secret_hash) + } + + fn generate_etomic_swap_id(&self, time_lock_bytes: &[u8], secret_hash: &[u8]) -> Vec { + let mut input = Vec::with_capacity(time_lock_bytes.len() + secret_hash.len()); + input.extend_from_slice(time_lock_bytes); input.extend_from_slice(secret_hash); sha256(&input).to_vec() } @@ -838,14 +1071,14 @@ impl EthCoinImpl { } } - pub fn add_erc_token_info(&self, ticker: String, info: Erc20TokenInfo) { + pub fn add_erc_token_info(&self, ticker: String, info: Erc20TokenDetails) { self.erc20_tokens_infos.lock().unwrap().insert(ticker, info); } /// # Warning /// Be very careful using this function since it returns dereferenced clone /// of value behind the MutexGuard and makes it non-thread-safe. - pub fn get_erc_tokens_infos(&self) -> HashMap { + pub fn get_erc_tokens_infos(&self) -> HashMap { let guard = self.erc20_tokens_infos.lock().unwrap(); (*guard).clone() } @@ -895,20 +1128,20 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit get_valid_nft_addr_to_withdraw(coin, &withdraw_type.to, &withdraw_type.token_address)?; let token_id_str = &withdraw_type.token_id.to_string(); - let wallet_amount = eth_coin.erc1155_balance(token_addr, token_id_str).await?; + let wallet_erc1155_amount = eth_coin.erc1155_balance(token_addr, token_id_str).await?; - let amount_dec = if withdraw_type.max { - wallet_amount.clone() + let amount_uint = if withdraw_type.max { + wallet_erc1155_amount.clone() } else { - withdraw_type.amount.unwrap_or_else(|| 1.into()) + withdraw_type.amount.unwrap_or_else(|| BigUint::from(1u32)) }; - if amount_dec > wallet_amount { + if amount_uint > wallet_erc1155_amount { return MmError::err(WithdrawError::NotEnoughNftsAmount { token_address: withdraw_type.token_address, token_id: withdraw_type.token_id.to_string(), - available: wallet_amount, - required: amount_dec, + available: wallet_erc1155_amount, + required: amount_uint, }); } @@ -919,7 +1152,7 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit let token_id_u256 = U256::from_dec_str(token_id_str).map_to_mm(|e| NumConversError::new(format!("{:?}", e)))?; let amount_u256 = - U256::from_dec_str(&amount_dec.to_string()).map_to_mm(|e| NumConversError::new(format!("{:?}", e)))?; + U256::from_dec_str(&amount_uint.to_string()).map_to_mm(|e| NumConversError::new(format!("{:?}", e)))?; let data = function.encode_input(&[ Token::Address(my_address), Token::Address(to_addr), @@ -978,7 +1211,7 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit contract_type: ContractType::Erc1155, token_address: withdraw_type.token_address, token_id: withdraw_type.token_id, - amount: amount_dec, + amount: amount_uint, fee_details: Some(fee_details.into()), coin: eth_coin.ticker.clone(), block_height: 0, @@ -1069,7 +1302,7 @@ pub async fn withdraw_erc721(ctx: MmArc, withdraw_type: WithdrawErc721) -> Withd contract_type: ContractType::Erc721, token_address: withdraw_type.token_address, token_id: withdraw_type.token_id, - amount: 1.into(), + amount: BigUint::from(1u8), fee_details: Some(fee_details.into()), coin: eth_coin.ticker.clone(), block_height: 0, @@ -1088,30 +1321,35 @@ impl Deref for EthCoin { #[async_trait] impl SwapOps for EthCoin { - fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut { - let address = try_tx_fus!(addr_from_raw_pubkey(fee_addr)); - - Box::new( - self.send_to_address( - address, - try_tx_fus!(wei_from_big_decimal(&dex_fee.fee_amount().into(), self.decimals)), - ) - .map(TransactionEnum::from), + async fn send_taker_fee( + &self, + fee_addr: &[u8], + dex_fee: DexFee, + _uuid: &[u8], + _expire_at: u64, + ) -> TransactionResult { + let address = try_tx_s!(addr_from_raw_pubkey(fee_addr)); + self.send_to_address( + address, + try_tx_s!(wei_from_big_decimal(&dex_fee.fee_amount().into(), self.decimals)), ) + .map(TransactionEnum::from) + .compat() + .await } - fn send_maker_payment(&self, maker_payment: SendPaymentArgs) -> TransactionFut { - Box::new( - self.send_hash_time_locked_payment(maker_payment) - .map(TransactionEnum::from), - ) + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + self.send_hash_time_locked_payment(maker_payment_args) + .compat() + .await + .map(TransactionEnum::from) } - fn send_taker_payment(&self, taker_payment: SendPaymentArgs) -> TransactionFut { - Box::new( - self.send_hash_time_locked_payment(taker_payment) - .map(TransactionEnum::from), - ) + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + self.send_hash_time_locked_payment(taker_payment_args) + .map(TransactionEnum::from) + .compat() + .await } async fn send_maker_spends_taker_payment( @@ -1144,10 +1382,15 @@ impl SwapOps for EthCoin { .map(TransactionEnum::from) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { let tx = match validate_fee_args.fee_tx { TransactionEnum::SignedEthTx(t) => t.clone(), - _ => panic!(), + fee_tx => { + return MmError::err(ValidatePaymentError::InternalError(format!( + "Invalid fee tx type. fee tx: {:?}", + fee_tx + ))) + }, }; validate_fee_impl(self.clone(), EthValidateFeeArgs { fee_tx_hash: &tx.tx_hash(), @@ -1157,6 +1400,8 @@ impl SwapOps for EthCoin { min_block_number: validate_fee_args.min_block_number, uuid: validate_fee_args.uuid, }) + .compat() + .await } #[inline] @@ -1169,70 +1414,62 @@ impl SwapOps for EthCoin { self.validate_payment(input).compat().await } - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, - if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { - let id = self.etomic_swap_id( - try_fus!(if_my_payment_sent_args.time_lock.try_into()), - if_my_payment_sent_args.secret_hash, - ); - let swap_contract_address = try_fus!(if_my_payment_sent_args.swap_contract_address.try_to_address()); - let selfi = self.clone(); + if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, + ) -> Result, String> { + let time_lock = if_my_payment_sent_args + .time_lock + .try_into() + .map_err(|e: TryFromIntError| e.to_string())?; + let id = self.etomic_swap_id(time_lock, if_my_payment_sent_args.secret_hash); + let swap_contract_address = if_my_payment_sent_args.swap_contract_address.try_to_address()?; let from_block = if_my_payment_sent_args.search_from_block; - let fut = async move { - let status = try_s!( - selfi - .payment_status(swap_contract_address, Token::FixedBytes(id.clone())) - .compat() - .await - ); + let status = self + .payment_status(swap_contract_address, Token::FixedBytes(id.clone())) + .compat() + .await?; - if status == U256::from(PaymentState::Uninitialized as u8) { - return Ok(None); - }; + if status == U256::from(PaymentState::Uninitialized as u8) { + return Ok(None); + }; - let mut current_block = try_s!(selfi.current_block().compat().await); - if current_block < from_block { - current_block = from_block; - } + let mut current_block = self.current_block().compat().await?; + if current_block < from_block { + current_block = from_block; + } - let mut from_block = from_block; + let mut from_block = from_block; - loop { - let to_block = current_block.min(from_block + selfi.logs_block_range); + loop { + let to_block = current_block.min(from_block + self.logs_block_range); - let events = try_s!( - selfi - .payment_sent_events(swap_contract_address, from_block, to_block) - .compat() - .await - ); + let events = self + .payment_sent_events(swap_contract_address, from_block, to_block) + .compat() + .await?; - let found = events.iter().find(|event| &event.data.0[..32] == id.as_slice()); + let found = events.iter().find(|event| &event.data.0[..32] == id.as_slice()); - match found { - Some(event) => { - let transaction = try_s!( - selfi - .transaction(TransactionId::Hash(event.transaction_hash.unwrap())) - .await - ); - match transaction { - Some(t) => break Ok(Some(try_s!(signed_tx_from_web3_tx(t)).into())), - None => break Ok(None), - } - }, - None => { - if to_block >= current_block { - break Ok(None); - } - from_block = to_block; - }, - } + match found { + Some(event) => { + let transaction = try_s!( + self.transaction(TransactionId::Hash(event.transaction_hash.unwrap())) + .await + ); + match transaction { + Some(t) => break Ok(Some(try_s!(signed_tx_from_web3_tx(t)).into())), + None => break Ok(None), + } + }, + None => { + if to_block >= current_block { + break Ok(None); + } + from_block = to_block; + }, } - }; - Box::new(fut.boxed().compat()) + } } async fn search_for_swap_tx_spend_my( @@ -1742,7 +1979,11 @@ impl WatcherOps for EthCoin { ))); } }, - EthCoinType::Nft { .. } => return MmError::err(ValidatePaymentError::NftProtocolNotSupported), + EthCoinType::Nft { .. } => { + return MmError::err(ValidatePaymentError::ProtocolNotSupported( + "Nft protocol is not supported by watchers yet".to_string(), + )) + }, } Ok(()) @@ -1983,7 +2224,11 @@ impl WatcherOps for EthCoin { ))); } }, - EthCoinType::Nft { .. } => return MmError::err(ValidatePaymentError::NftProtocolNotSupported), + EthCoinType::Nft { .. } => { + return MmError::err(ValidatePaymentError::ProtocolNotSupported( + "Nft protocol is not supported by watchers yet".to_string(), + )) + }, } Ok(()) @@ -2327,18 +2572,18 @@ impl MarketCoinOps for EthCoin { Box::new(fut.boxed().compat()) } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { - let unverified: UnverifiedTransactionWrapper = try_tx_fus!(rlp::decode(args.tx_bytes)); - let tx = try_tx_fus!(SignedEthTx::new(unverified)); + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { + let unverified: UnverifiedTransactionWrapper = try_tx_s!(rlp::decode(args.tx_bytes)); + let tx = try_tx_s!(SignedEthTx::new(unverified)); let swap_contract_address = match args.swap_contract_address { - Some(addr) => try_tx_fus!(addr.try_to_address()), + Some(addr) => try_tx_s!(addr.try_to_address()), None => match tx.unsigned().action() { Call(address) => *address, Create => { - return Box::new(futures01::future::err(TransactionErr::Plain(ERRL!( + return Err(TransactionErr::Plain(ERRL!( "Invalid payment action: the payment action cannot be create" - )))) + ))) }, }, }; @@ -2347,85 +2592,79 @@ impl MarketCoinOps for EthCoin { EthCoinType::Eth => get_function_name("ethPayment", args.watcher_reward), EthCoinType::Erc20 { .. } => get_function_name("erc20Payment", args.watcher_reward), EthCoinType::Nft { .. } => { - return Box::new(futures01::future::err(TransactionErr::ProtocolNotSupported(ERRL!( + return Err(TransactionErr::ProtocolNotSupported(ERRL!( "Nft Protocol is not supported yet!" - )))) + ))) }, }; - let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&func_name)); - let decoded = try_tx_fus!(decode_contract_call(payment_func, tx.unsigned().data())); + let payment_func = try_tx_s!(SWAP_CONTRACT.function(&func_name)); + let decoded = try_tx_s!(decode_contract_call(payment_func, tx.unsigned().data())); let id = match decoded.first() { Some(Token::FixedBytes(bytes)) => bytes.clone(), invalid_token => { - return Box::new(futures01::future::err(TransactionErr::Plain(ERRL!( + return Err(TransactionErr::Plain(ERRL!( "Expected Token::FixedBytes, got {:?}", invalid_token - )))) + ))) }, }; - let selfi = self.clone(); - let from_block = args.from_block; - let wait_until = args.wait_until; - let check_every = args.check_every; - let fut = async move { - loop { - if now_sec() > wait_until { - return TX_PLAIN_ERR!( - "Waited too long until {} for transaction {:?} to be spent ", - wait_until, - tx, - ); - } - let current_block = match selfi.current_block().compat().await { - Ok(b) => b, - Err(e) => { - error!("Error getting block number: {}", e); - Timer::sleep(5.).await; - continue; - }, - }; + loop { + if now_sec() > args.wait_until { + return TX_PLAIN_ERR!( + "Waited too long until {} for transaction {:?} to be spent ", + args.wait_until, + tx, + ); + } - let events = match selfi - .spend_events(swap_contract_address, from_block, current_block) - .compat() - .await - { - Ok(ev) => ev, - Err(e) => { - error!("Error getting spend events: {}", e); - Timer::sleep(5.).await; - continue; - }, - }; + let current_block = match self.current_block().compat().await { + Ok(b) => b, + Err(e) => { + error!("Error getting block number: {}", e); + Timer::sleep(5.).await; + continue; + }, + }; - let found = events.iter().find(|event| &event.data.0[..32] == id.as_slice()); + let events = match self + .spend_events(swap_contract_address, args.from_block, current_block) + .compat() + .await + { + Ok(ev) => ev, + Err(e) => { + error!("Error getting spend events: {}", e); + Timer::sleep(5.).await; + continue; + }, + }; - if let Some(event) = found { - if let Some(tx_hash) = event.transaction_hash { - let transaction = match selfi.transaction(TransactionId::Hash(tx_hash)).await { - Ok(Some(t)) => t, - Ok(None) => { - info!("Tx {} not found yet", tx_hash); - Timer::sleep(check_every).await; - continue; - }, - Err(e) => { - error!("Get tx {} error: {}", tx_hash, e); - Timer::sleep(check_every).await; - continue; - }, - }; + let found = events.iter().find(|event| &event.data.0[..32] == id.as_slice()); - return Ok(TransactionEnum::from(try_tx_s!(signed_tx_from_web3_tx(transaction)))); - } - } + if let Some(event) = found { + if let Some(tx_hash) = event.transaction_hash { + let transaction = match self.transaction(TransactionId::Hash(tx_hash)).await { + Ok(Some(t)) => t, + Ok(None) => { + info!("Tx {} not found yet", tx_hash); + Timer::sleep(args.check_every).await; + continue; + }, + Err(e) => { + error!("Get tx {} error: {}", tx_hash, e); + Timer::sleep(args.check_every).await; + continue; + }, + }; - Timer::sleep(5.).await; + return Ok(TransactionEnum::from(try_tx_s!(signed_tx_from_web3_tx(transaction)))); + } } - }; - Box::new(fut.boxed().compat()) + + Timer::sleep(5.).await; + } } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { @@ -4391,7 +4630,7 @@ impl EthCoin { self.get_token_balance_for_address(my_address, token_address).await } - async fn erc1155_balance(&self, token_addr: Address, token_id: &str) -> MmResult { + async fn erc1155_balance(&self, token_addr: Address, token_id: &str) -> MmResult { let wallet_amount_uint = match self.coin_type { EthCoinType::Eth | EthCoinType::Nft { .. } => { let function = ERC1155_CONTRACT.function("balanceOf")?; @@ -4417,7 +4656,8 @@ impl EthCoin { )) }, }; - let wallet_amount = u256_to_big_decimal(wallet_amount_uint, self.decimals)?; + // The "balanceOf" function in ERC1155 standard returns the exact count of tokens held by address without any decimals or scaling factors + let wallet_amount = wallet_amount_uint.to_string().parse::()?; Ok(wallet_amount) } @@ -4595,7 +4835,7 @@ impl EthCoin { EthCoinType::Erc20 { token_addr, .. } => token_addr, EthCoinType::Nft { .. } => { return Err(TransactionErr::ProtocolNotSupported(ERRL!( - "Nft Protocol is not supported yet!" + "Nft Protocol is not supported by 'approve'!" ))) }, }; @@ -4935,7 +5175,11 @@ impl EthCoin { ))); } }, - EthCoinType::Nft { .. } => return MmError::err(ValidatePaymentError::NftProtocolNotSupported), + EthCoinType::Nft { .. } => { + return MmError::err(ValidatePaymentError::ProtocolNotSupported( + "Nft protocol is not supported by legacy swap".to_string(), + )) + }, } Ok(()) @@ -5778,45 +6022,6 @@ impl TryToAddress for Option { } } -pub trait KomodoDefiAuthMessages { - fn proxy_auth_sign_message_hash(message: String) -> Option<[u8; 32]>; - fn generate_proxy_auth_signed_validation( - generator: ProxyAuthValidationGenerator, - ) -> SignatureResult; -} - -impl KomodoDefiAuthMessages for EthCoin { - fn proxy_auth_sign_message_hash(message: String) -> Option<[u8; 32]> { - let message_prefix = "atomicDEX Auth Ethereum Signed Message:\n"; - let prefix_len = CompactInteger::from(message_prefix.len()); - - let mut stream = Stream::new(); - prefix_len.serialize(&mut stream); - stream.append_slice(message_prefix.as_bytes()); - stream.append_slice(message.len().to_string().as_bytes()); - stream.append_slice(message.as_bytes()); - - Some(keccak256(&stream.out()).take()) - } - - fn generate_proxy_auth_signed_validation( - generator: ProxyAuthValidationGenerator, - ) -> SignatureResult { - let timestamp_message = get_utc_timestamp() + PROXY_AUTH_SIGNED_MESSAGE_LIFETIME_SEC; - - let message_hash = EthCoin::proxy_auth_sign_message_hash(timestamp_message.to_string()) - .ok_or(SignatureError::PrefixNotFound)?; - let signature = sign(&generator.secret, &H256::from(message_hash))?; - - Ok(KomodefiProxyAuthValidation { - coin_ticker: generator.coin_ticker, - address: generator.address, - timestamp_message, - signature: format!("0x{}", signature), - }) - } -} - fn validate_fee_impl(coin: EthCoin, validate_fee_args: EthValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { let fee_tx_hash = validate_fee_args.fee_tx_hash.to_owned(); let sender_addr = try_f!( @@ -5912,7 +6117,11 @@ fn validate_fee_impl(coin: EthCoin, validate_fee_args: EthValidateFeeArgs<'_>) - }, } }, - EthCoinType::Nft { .. } => return MmError::err(ValidatePaymentError::NftProtocolNotSupported), + EthCoinType::Nft { .. } => { + return MmError::err(ValidatePaymentError::ProtocolNotSupported( + "Nft protocol is not supported".to_string(), + )) + }, } Ok(()) @@ -6112,32 +6321,6 @@ fn signed_tx_from_web3_tx(transaction: Web3Transaction) -> Result, token_addr: Address) -> Result { - let function = try_s!(ERC20_CONTRACT.function("decimals")); - let data = try_s!(function.encode_input(&[])); - let request = CallRequest { - from: Some(Address::default()), - to: Some(token_addr), - gas: None, - gas_price: None, - value: Some(0.into()), - data: Some(data.into()), - ..CallRequest::default() - }; - - let res = web3 - .eth() - .call(request, Some(BlockId::Number(BlockNumber::Latest))) - .map_err(|e| ERRL!("{}", e)) - .await?; - let tokens = try_s!(function.decode_output(&res.0)); - let decimals = match tokens[0] { - Token::Uint(dec) => dec.as_u64(), - _ => return ERR!("Invalid decimals type {:?}", tokens), - }; - Ok(decimals as u8) -} - pub fn valid_addr_from_str(addr_str: &str) -> Result { let addr = try_s!(addr_from_str(addr_str)); if !is_valid_checksum_addr(addr_str) { @@ -6270,10 +6453,7 @@ pub async fn eth_coin_from_conf_and_request( Some("ws") | Some("wss") => { const TMP_SOCKET_CONNECTION: Duration = Duration::from_secs(20); - let node = WebsocketTransportNode { - uri: uri.clone(), - gui_auth: false, - }; + let node = WebsocketTransportNode { uri: uri.clone() }; let websocket_transport = WebsocketTransport::with_event_handlers(node, event_handlers.clone()); // Temporarily start the connection loop (we close the connection once we have the client version below). @@ -6288,7 +6468,10 @@ pub async fn eth_coin_from_conf_and_request( Web3Transport::Websocket(websocket_transport) }, Some("http") | Some("https") => { - let node = HttpTransportNode { uri, gui_auth: false }; + let node = HttpTransportNode { + uri, + komodo_proxy: false, + }; Web3Transport::new_http_with_event_handlers(node, event_handlers.clone()) }, @@ -6392,7 +6575,8 @@ pub async fn eth_coin_from_conf_and_request( let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(ctx, conf, &coin_type).await?; let max_eth_tx_type = get_max_eth_tx_type_conf(ctx, conf, &coin_type).await?; - let gas_limit = extract_gas_limit_from_conf(conf)?; + let gas_limit: EthGasLimit = extract_gas_limit_from_conf(conf)?; + let gas_limit_v2: EthGasLimitV2 = extract_gas_limit_from_conf(conf)?; let coin = EthCoinImpl { priv_key_policy: key_pair, @@ -6400,6 +6584,7 @@ pub async fn eth_coin_from_conf_and_request( coin_type, sign_message_prefix, swap_contract_address, + swap_v2_contracts: None, fallback_swap_contract, contract_supports_watchers, decimals, @@ -6418,6 +6603,7 @@ pub async fn eth_coin_from_conf_and_request( nfts_infos: Default::default(), platform_fee_estimator_state, gas_limit, + gas_limit_v2, abortable_system, }; @@ -6780,20 +6966,12 @@ impl ToBytes for SignedEthTx { } } -#[derive(Debug, Display)] +#[derive(Debug, Display, EnumFromStringify)] pub enum EthAssocTypesError { InvalidHexString(String), + #[from_stringify("DecoderError")] TxParseError(String), ParseSignatureError(String), - KeysError(keys::Error), -} - -impl From for EthAssocTypesError { - fn from(e: DecoderError) -> Self { EthAssocTypesError::TxParseError(e.to_string()) } -} - -impl From for EthAssocTypesError { - fn from(e: keys::Error) -> Self { EthAssocTypesError::KeysError(e) } } #[derive(Debug, Display)] @@ -6811,7 +6989,7 @@ impl From for EthNftAssocTypesError { impl ParseCoinAssocTypes for EthCoin { type Address = Address; type AddressParseError = MmError; - type Pubkey = HtlcPubKey; + type Pubkey = Public; type PubkeyParseError = MmError; type Tx = SignedEthTx; type TxParseError = MmError; @@ -6836,8 +7014,9 @@ impl ParseCoinAssocTypes for EthCoin { Address::from_str(address).map_to_mm(|e| EthAssocTypesError::InvalidHexString(e.to_string())) } + /// As derive_htlc_pubkey_v2 returns coin specific pubkey we can use [Public::from_slice] directly fn parse_pubkey(&self, pubkey: &[u8]) -> Result { - HtlcPubKey::from_slice(pubkey).map_to_mm(EthAssocTypesError::from) + Ok(Public::from_slice(pubkey)) } fn parse_tx(&self, tx: &[u8]) -> Result { @@ -6872,6 +7051,10 @@ impl ToBytes for ContractType { fn to_bytes(&self) -> Vec { self.to_string().into_bytes() } } +impl ToBytes for Public { + fn to_bytes(&self) -> Vec { self.0.to_vec() } +} + impl ParseNftAssocTypes for EthCoin { type ContractAddress = Address; type TokenId = BigUint; @@ -6922,16 +7105,16 @@ impl MakerNftSwapOpsV2 for EthCoin { async fn refund_nft_maker_payment_v2_timelock( &self, - args: RefundPaymentArgs<'_>, + args: RefundNftMakerPaymentArgs<'_, Self>, ) -> Result { self.refund_nft_maker_payment_v2_timelock_impl(args).await } async fn refund_nft_maker_payment_v2_secret( &self, - _args: RefundMakerPaymentArgs<'_, Self>, + args: RefundNftMakerPaymentArgs<'_, Self>, ) -> Result { - todo!() + self.refund_nft_maker_payment_v2_secret_impl(args).await } } @@ -7046,11 +7229,12 @@ pub fn pubkey_from_extended(extended_pubkey: &Secp256k1ExtendedPublicKey) -> Pub pubkey_uncompressed } -fn extract_gas_limit_from_conf(coin_conf: &Json) -> Result { - if coin_conf["gas_limit"].is_null() { +fn extract_gas_limit_from_conf(coin_conf: &Json) -> Result { + let key = T::key(); + if coin_conf[key].is_null() { Ok(Default::default()) } else { - json::from_value(coin_conf["gas_limit"].clone()).map_err(|e| e.to_string()) + json::from_value(coin_conf[key].clone()).map_err(|e| e.to_string()) } } @@ -7061,3 +7245,214 @@ impl Eip1559Ops for EthCoin { *self.swap_txfee_policy.lock().unwrap() = swap_txfee_policy } } + +#[async_trait] +impl TakerCoinSwapOpsV2 for EthCoin { + /// Wrapper for [EthCoin::send_taker_funding_impl] + async fn send_taker_funding(&self, args: SendTakerFundingArgs<'_>) -> Result { + self.send_taker_funding_impl(args).await + } + + /// Wrapper for [EthCoin::validate_taker_funding_impl] + async fn validate_taker_funding(&self, args: ValidateTakerFundingArgs<'_, Self>) -> ValidateSwapV2TxResult { + self.validate_taker_funding_impl(args).await + } + + async fn refund_taker_funding_timelock( + &self, + args: RefundTakerPaymentArgs<'_>, + ) -> Result { + self.refund_taker_payment_with_timelock_impl(args).await + } + + async fn refund_taker_funding_secret( + &self, + args: RefundFundingSecretArgs<'_, Self>, + ) -> Result { + self.refund_taker_funding_secret_impl(args).await + } + + /// Wrapper for [EthCoin::search_for_taker_funding_spend_impl] + async fn search_for_taker_funding_spend( + &self, + tx: &Self::Tx, + _from_block: u64, + _secret_hash: &[u8], + ) -> Result>, SearchForFundingSpendErr> { + self.search_for_taker_funding_spend_impl(tx).await + } + + /// Eth doesnt have preimages + async fn gen_taker_funding_spend_preimage( + &self, + args: &GenTakerFundingSpendArgs<'_, Self>, + _swap_unique_data: &[u8], + ) -> GenPreimageResult { + Ok(TxPreimageWithSig { + preimage: args.funding_tx.clone(), + signature: args.funding_tx.signature(), + }) + } + + /// Eth doesnt have preimages + async fn validate_taker_funding_spend_preimage( + &self, + _gen_args: &GenTakerFundingSpendArgs<'_, Self>, + _preimage: &TxPreimageWithSig, + ) -> ValidateTakerFundingSpendPreimageResult { + Ok(()) + } + + /// Wrapper for [EthCoin::taker_payment_approve] + async fn sign_and_send_taker_funding_spend( + &self, + _preimage: &TxPreimageWithSig, + args: &GenTakerFundingSpendArgs<'_, Self>, + _swap_unique_data: &[u8], + ) -> Result { + self.taker_payment_approve(args).await + } + + async fn refund_combined_taker_payment( + &self, + args: RefundTakerPaymentArgs<'_>, + ) -> Result { + self.refund_taker_payment_with_timelock_impl(args).await + } + + /// Eth doesnt have preimages + async fn gen_taker_payment_spend_preimage( + &self, + args: &GenTakerPaymentSpendArgs<'_, Self>, + _swap_unique_data: &[u8], + ) -> GenPreimageResult { + Ok(TxPreimageWithSig { + preimage: args.taker_tx.clone(), + signature: args.taker_tx.signature(), + }) + } + + /// Eth doesnt have preimages + async fn validate_taker_payment_spend_preimage( + &self, + _gen_args: &GenTakerPaymentSpendArgs<'_, Self>, + _preimage: &TxPreimageWithSig, + ) -> ValidateTakerPaymentSpendPreimageResult { + Ok(()) + } + + /// Wrapper for [EthCoin::sign_and_broadcast_taker_payment_spend_impl] + async fn sign_and_broadcast_taker_payment_spend( + &self, + _preimage: &TxPreimageWithSig, + gen_args: &GenTakerPaymentSpendArgs<'_, Self>, + secret: &[u8], + _swap_unique_data: &[u8], + ) -> Result { + self.sign_and_broadcast_taker_payment_spend_impl(gen_args, secret).await + } + + /// Wrapper for [EthCoin::wait_for_taker_payment_spend_impl] + async fn wait_for_taker_payment_spend( + &self, + taker_payment: &Self::Tx, + _from_block: u64, + wait_until: u64, + ) -> MmResult { + self.wait_for_taker_payment_spend_impl(taker_payment, wait_until).await + } +} + +impl CommonSwapOpsV2 for EthCoin { + #[inline(always)] + fn derive_htlc_pubkey_v2(&self, _swap_unique_data: &[u8]) -> Self::Pubkey { + match self.priv_key_policy { + EthPrivKeyPolicy::Iguana(ref key_pair) + | EthPrivKeyPolicy::HDWallet { + activated_key: ref key_pair, + .. + } => *key_pair.public(), + EthPrivKeyPolicy::Trezor => todo!(), + #[cfg(target_arch = "wasm32")] + EthPrivKeyPolicy::Metamask(ref metamask_policy) => { + // The metamask public key should be uncompressed + // Remove the first byte (0x04) from the uncompressed public key + let pubkey_bytes: [u8; 64] = metamask_policy.public_key_uncompressed[1..65] + .try_into() + .expect("slice with incorrect length"); + Public::from_slice(&pubkey_bytes) + }, + } + } + + #[inline(always)] + fn derive_htlc_pubkey_v2_bytes(&self, swap_unique_data: &[u8]) -> Vec { + self.derive_htlc_pubkey_v2(swap_unique_data).to_bytes() + } +} + +#[cfg(all(feature = "for-tests", not(target_arch = "wasm32")))] +impl EthCoin { + pub async fn set_coin_type(&self, new_coin_type: EthCoinType) -> EthCoin { + let coin = EthCoinImpl { + ticker: self.ticker.clone(), + coin_type: new_coin_type, + priv_key_policy: self.priv_key_policy.clone(), + derivation_method: Arc::clone(&self.derivation_method), + sign_message_prefix: self.sign_message_prefix.clone(), + swap_contract_address: self.swap_contract_address, + swap_v2_contracts: self.swap_v2_contracts, + fallback_swap_contract: self.fallback_swap_contract, + contract_supports_watchers: self.contract_supports_watchers, + web3_instances: AsyncMutex::new(self.web3_instances.lock().await.clone()), + decimals: self.decimals, + history_sync_state: Mutex::new(self.history_sync_state.lock().unwrap().clone()), + required_confirmations: AtomicU64::new( + self.required_confirmations.load(std::sync::atomic::Ordering::SeqCst), + ), + swap_txfee_policy: Mutex::new(self.swap_txfee_policy.lock().unwrap().clone()), + max_eth_tx_type: self.max_eth_tx_type, + ctx: self.ctx.clone(), + chain_id: self.chain_id, + trezor_coin: self.trezor_coin.clone(), + logs_block_range: self.logs_block_range, + address_nonce_locks: Arc::clone(&self.address_nonce_locks), + erc20_tokens_infos: Arc::clone(&self.erc20_tokens_infos), + nfts_infos: Arc::clone(&self.nfts_infos), + platform_fee_estimator_state: Arc::clone(&self.platform_fee_estimator_state), + gas_limit: EthGasLimit::default(), + gas_limit_v2: EthGasLimitV2::default(), + abortable_system: self.abortable_system.create_subsystem().unwrap(), + }; + EthCoin(Arc::new(coin)) + } +} + +#[async_trait] +impl MakerCoinSwapOpsV2 for EthCoin { + async fn send_maker_payment_v2(&self, args: SendMakerPaymentArgs<'_, Self>) -> Result { + self.send_maker_payment_v2_impl(args).await + } + + async fn validate_maker_payment_v2(&self, args: ValidateMakerPaymentArgs<'_, Self>) -> ValidatePaymentResult<()> { + self.validate_maker_payment_v2_impl(args).await + } + + async fn refund_maker_payment_v2_timelock( + &self, + args: RefundMakerPaymentTimelockArgs<'_>, + ) -> Result { + self.refund_maker_payment_v2_timelock_impl(args).await + } + + async fn refund_maker_payment_v2_secret( + &self, + args: RefundMakerPaymentSecretArgs<'_, Self>, + ) -> Result { + self.refund_maker_payment_v2_secret_impl(args).await + } + + async fn spend_maker_payment_v2(&self, args: SpendMakerPaymentArgs<'_, Self>) -> Result { + self.spend_maker_payment_v2_impl(args).await + } +} diff --git a/mm2src/coins/eth/erc20.rs b/mm2src/coins/eth/erc20.rs new file mode 100644 index 0000000000..75f7033fda --- /dev/null +++ b/mm2src/coins/eth/erc20.rs @@ -0,0 +1,107 @@ +use crate::eth::web3_transport::Web3Transport; +use crate::eth::{EthCoin, ERC20_CONTRACT}; +use crate::{CoinsContext, MmCoinEnum}; +use ethabi::Token; +use ethereum_types::Address; +use futures_util::TryFutureExt; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::mm_error::MmResult; +use web3::types::{BlockId, BlockNumber, CallRequest}; +use web3::{Transport, Web3}; + +async fn call_erc20_function( + web3: &Web3, + token_addr: Address, + function_name: &str, +) -> Result, String> { + let function = try_s!(ERC20_CONTRACT.function(function_name)); + let data = try_s!(function.encode_input(&[])); + let request = CallRequest { + from: Some(Address::default()), + to: Some(token_addr), + gas: None, + gas_price: None, + value: Some(0.into()), + data: Some(data.into()), + ..CallRequest::default() + }; + + let res = web3 + .eth() + .call(request, Some(BlockId::Number(BlockNumber::Latest))) + .map_err(|e| ERRL!("{}", e)) + .await?; + function.decode_output(&res.0).map_err(|e| ERRL!("{}", e)) +} + +pub(crate) async fn get_token_decimals(web3: &Web3, token_addr: Address) -> Result { + let tokens = call_erc20_function(web3, token_addr, "decimals").await?; + let Some(token) = tokens.into_iter().next() else { + return ERR!("No value returned from decimals() call"); + }; + let Token::Uint(dec) = token else { + return ERR!("Expected Uint token for decimals, got {:?}", token); + }; + Ok(dec.as_u64() as u8) +} + +async fn get_token_symbol(coin: &EthCoin, token_addr: Address) -> Result { + let web3 = try_s!(coin.web3().await); + let tokens = call_erc20_function(&web3, token_addr, "symbol").await?; + let Some(token) = tokens.into_iter().next() else { + return ERR!("No value returned from symbol() call"); + }; + let Token::String(symbol) = token else { + return ERR!("Expected String token for symbol, got {:?}", token); + }; + Ok(symbol) +} + +#[derive(Serialize)] +pub struct Erc20TokenInfo { + pub symbol: String, + pub decimals: u8, +} + +pub async fn get_erc20_token_info(coin: &EthCoin, token_addr: Address) -> Result { + let symbol = get_token_symbol(coin, token_addr).await?; + let web3 = try_s!(coin.web3().await); + let decimals = get_token_decimals(&web3, token_addr).await?; + Ok(Erc20TokenInfo { symbol, decimals }) +} + +/// Finds if an ERC20 token is in coins config by its contract address and returns its ticker. +pub fn get_erc20_ticker_by_contract_address(ctx: &MmArc, platform: &str, contract_address: &str) -> Option { + ctx.conf["coins"].as_array()?.iter().find_map(|coin| { + let protocol = coin.get("protocol")?; + let protocol_type = protocol.get("type")?.as_str()?; + if protocol_type != "ERC20" { + return None; + } + let protocol_data = protocol.get("protocol_data")?; + let coin_platform = protocol_data.get("platform")?.as_str()?; + let coin_contract_address = protocol_data.get("contract_address")?.as_str()?; + + if coin_platform == platform && coin_contract_address == contract_address { + coin.get("coin")?.as_str().map(|s| s.to_string()) + } else { + None + } + }) +} + +/// Finds an enabled ERC20 token by its contract address and returns it as `MmCoinEnum`. +pub async fn get_enabled_erc20_by_contract( + ctx: &MmArc, + contract_address: Address, +) -> MmResult, String> { + let cctx = CoinsContext::from_ctx(ctx)?; + let coins = cctx.coins.lock().await; + + Ok(coins.values().find_map(|coin| match &coin.inner { + MmCoinEnum::EthCoin(eth_coin) if eth_coin.erc20_token_address() == Some(contract_address) => { + Some(coin.inner.clone()) + }, + _ => None, + })) +} diff --git a/mm2src/coins/eth/eth_balance_events.rs b/mm2src/coins/eth/eth_balance_events.rs index 231aa68507..0cc798ad7e 100644 --- a/mm2src/coins/eth/eth_balance_events.rs +++ b/mm2src/coins/eth/eth_balance_events.rs @@ -14,7 +14,7 @@ use mm2_number::BigDecimal; use std::collections::{HashMap, HashSet}; use super::EthCoin; -use crate::{eth::{u256_to_big_decimal, Erc20TokenInfo}, +use crate::{eth::{u256_to_big_decimal, Erc20TokenDetails}, BalanceError, CoinWithDerivationMethod, MmCoin}; struct BalanceData { @@ -40,9 +40,9 @@ async fn get_all_balance_results_concurrently(coin: &EthCoin, addresses: HashSet // // Unlike tokens, the platform coin length is constant (=1). Instead of creating a generic // type and mapping the platform coin and the entire token list (which can grow at any time), we map - // the platform coin to Erc20TokenInfo so that we can use the token list right away without + // the platform coin to Erc20TokenDetails so that we can use the token list right away without // additional mapping. - tokens.insert(coin.ticker.clone(), Erc20TokenInfo { + tokens.insert(coin.ticker.clone(), Erc20TokenDetails { // This is a dummy value, since there is no token address for the platform coin. // In the fetch_balance function, we check if the token_ticker is equal to this // coin's ticker to avoid using token_address to fetch the balance @@ -72,7 +72,7 @@ async fn fetch_balance( coin: &EthCoin, address: Address, token_ticker: String, - info: &Erc20TokenInfo, + info: &Erc20TokenDetails, ) -> Result { let (balance_as_u256, decimals) = if token_ticker == coin.ticker { ( diff --git a/mm2src/coins/eth/eth_swap_v2/eth_maker_swap_v2.rs b/mm2src/coins/eth/eth_swap_v2/eth_maker_swap_v2.rs new file mode 100644 index 0000000000..3089604ede --- /dev/null +++ b/mm2src/coins/eth/eth_swap_v2/eth_maker_swap_v2.rs @@ -0,0 +1,486 @@ +use super::{validate_amount, validate_from_to_and_status, EthPaymentType, PaymentMethod, PrepareTxDataError, + ZERO_VALUE}; +use crate::coin_errors::{ValidatePaymentError, ValidatePaymentResult}; +use crate::eth::{decode_contract_call, get_function_input_data, wei_from_big_decimal, EthCoin, EthCoinType, + MakerPaymentStateV2, SignedEthTx, MAKER_SWAP_V2}; +use crate::{ParseCoinAssocTypes, RefundMakerPaymentSecretArgs, RefundMakerPaymentTimelockArgs, SendMakerPaymentArgs, + SpendMakerPaymentArgs, SwapTxTypeWithSecretHash, TransactionErr, ValidateMakerPaymentArgs}; +use ethabi::{Function, Token}; +use ethcore_transaction::Action; +use ethereum_types::{Address, Public, U256}; +use ethkey::public_to_address; +use futures::compat::Future01CompatExt; +use mm2_err_handle::mm_error::MmError; +use mm2_err_handle::prelude::MapToMmResult; +use std::convert::TryInto; +use web3::types::TransactionId; + +const ETH_MAKER_PAYMENT: &str = "ethMakerPayment"; +const ERC20_MAKER_PAYMENT: &str = "erc20MakerPayment"; + +/// state index for `MakerPayment` structure from `EtomicSwapMakerV2.sol` +/// +/// struct MakerPayment { +/// bytes20 paymentHash; +/// uint32 paymentLockTime; +/// MakerPaymentState state; +/// } +const MAKER_PAYMENT_STATE_INDEX: usize = 2; + +struct MakerPaymentArgs { + taker_address: Address, + taker_secret_hash: [u8; 32], + maker_secret_hash: [u8; 32], + payment_time_lock: u64, +} + +struct MakerValidationArgs<'a> { + swap_id: Vec, + amount: U256, + taker: Address, + taker_secret_hash: &'a [u8; 32], + maker_secret_hash: &'a [u8; 32], + payment_time_lock: u64, +} + +struct MakerRefundTimelockArgs { + payment_amount: U256, + taker_address: Address, + taker_secret_hash: [u8; 32], + maker_secret_hash: [u8; 32], + payment_time_lock: u64, + token_address: Address, +} + +struct MakerRefundSecretArgs { + payment_amount: U256, + taker_address: Address, + taker_secret: [u8; 32], + maker_secret_hash: [u8; 32], + payment_time_lock: u64, + token_address: Address, +} + +impl EthCoin { + pub(crate) async fn send_maker_payment_v2_impl( + &self, + args: SendMakerPaymentArgs<'_, Self>, + ) -> Result { + let maker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")))? + .maker_swap_v2_contract; + let payment_amount = try_tx_s!(wei_from_big_decimal(&args.amount, self.decimals)); + let payment_args = { + let taker_address = public_to_address(args.taker_pub); + MakerPaymentArgs { + taker_address, + taker_secret_hash: try_tx_s!(args.taker_secret_hash.try_into()), + maker_secret_hash: try_tx_s!(args.maker_secret_hash.try_into()), + payment_time_lock: args.time_lock, + } + }; + match &self.coin_type { + EthCoinType::Eth => { + let data = try_tx_s!(self.prepare_maker_eth_payment_data(&payment_args).await); + self.sign_and_send_transaction( + payment_amount, + Action::Call(maker_swap_v2_contract), + data, + U256::from(self.gas_limit_v2.maker.eth_payment), + ) + .compat() + .await + }, + EthCoinType::Erc20 { + platform: _, + token_addr, + } => { + let data = try_tx_s!( + self.prepare_maker_erc20_payment_data(&payment_args, payment_amount, *token_addr) + .await + ); + self.handle_allowance(maker_swap_v2_contract, payment_amount, args.time_lock) + .await?; + self.sign_and_send_transaction( + U256::from(ZERO_VALUE), + Action::Call(maker_swap_v2_contract), + data, + U256::from(self.gas_limit_v2.maker.erc20_payment), + ) + .compat() + .await + }, + EthCoinType::Nft { .. } => Err(TransactionErr::ProtocolNotSupported(ERRL!( + "NFT protocol is not supported for ETH and ERC20 Swaps" + ))), + } + } + + pub(crate) async fn validate_maker_payment_v2_impl( + &self, + args: ValidateMakerPaymentArgs<'_, Self>, + ) -> ValidatePaymentResult<()> { + if let EthCoinType::Nft { .. } = self.coin_type { + return MmError::err(ValidatePaymentError::ProtocolNotSupported( + "NFT protocol is not supported for ETH and ERC20 Swaps".to_string(), + )); + } + let maker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| { + ValidatePaymentError::InternalError("Expected swap_v2_contracts to be Some, but found None".to_string()) + })? + .maker_swap_v2_contract; + let taker_secret_hash = args.taker_secret_hash.try_into()?; + let maker_secret_hash = args.maker_secret_hash.try_into()?; + validate_amount(&args.amount).map_to_mm(ValidatePaymentError::InternalError)?; + let swap_id = self.etomic_swap_id_v2(args.time_lock, args.maker_secret_hash); + let maker_status = self + .payment_status_v2( + maker_swap_v2_contract, + Token::FixedBytes(swap_id.clone()), + &MAKER_SWAP_V2, + EthPaymentType::MakerPayments, + MAKER_PAYMENT_STATE_INDEX, + ) + .await?; + + let tx_from_rpc = self + .transaction(TransactionId::Hash(args.maker_payment_tx.tx_hash())) + .await?; + let tx_from_rpc = tx_from_rpc.as_ref().ok_or_else(|| { + ValidatePaymentError::TxDoesNotExist(format!( + "Didn't find provided tx {:?} on ETH node", + args.maker_payment_tx.tx_hash() + )) + })?; + let maker_address = public_to_address(args.maker_pub); + validate_from_to_and_status( + tx_from_rpc, + maker_address, + maker_swap_v2_contract, + maker_status, + MakerPaymentStateV2::PaymentSent as u8, + )?; + + let validation_args = { + let amount = wei_from_big_decimal(&args.amount, self.decimals)?; + MakerValidationArgs { + swap_id, + amount, + taker: self.my_addr().await, + taker_secret_hash, + maker_secret_hash, + payment_time_lock: args.time_lock, + } + }; + match self.coin_type { + EthCoinType::Eth => { + let function = MAKER_SWAP_V2.function(ETH_MAKER_PAYMENT)?; + let decoded = decode_contract_call(function, &tx_from_rpc.input.0)?; + validate_eth_maker_payment_data(&decoded, &validation_args, function, tx_from_rpc.value)?; + }, + EthCoinType::Erc20 { token_addr, .. } => { + let function = MAKER_SWAP_V2.function(ERC20_MAKER_PAYMENT)?; + let decoded = decode_contract_call(function, &tx_from_rpc.input.0)?; + validate_erc20_maker_payment_data(&decoded, &validation_args, function, token_addr)?; + }, + EthCoinType::Nft { .. } => { + return MmError::err(ValidatePaymentError::ProtocolNotSupported( + "NFT protocol is not supported for ETH and ERC20 Swaps".to_string(), + )); + }, + } + Ok(()) + } + + pub(crate) async fn refund_maker_payment_v2_timelock_impl( + &self, + args: RefundMakerPaymentTimelockArgs<'_>, + ) -> Result { + let token_address = self + .get_token_address() + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + let maker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")))? + .maker_swap_v2_contract; + let gas_limit = self + .gas_limit_v2 + .gas_limit( + &self.coin_type, + EthPaymentType::MakerPayments, + PaymentMethod::RefundTimelock, + ) + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + + let (maker_secret_hash, taker_secret_hash) = match args.tx_type_with_secret_hash { + SwapTxTypeWithSecretHash::MakerPaymentV2 { + maker_secret_hash, + taker_secret_hash, + } => (maker_secret_hash, taker_secret_hash), + _ => { + return Err(TransactionErr::Plain(ERRL!( + "Unsupported swap tx type for timelock refund" + ))) + }, + }; + let payment_amount = try_tx_s!(wei_from_big_decimal(&args.amount, self.decimals)); + + let args = { + let taker_address = public_to_address(&Public::from_slice(args.taker_pub)); + MakerRefundTimelockArgs { + payment_amount, + taker_address, + taker_secret_hash: try_tx_s!(taker_secret_hash.try_into()), + maker_secret_hash: try_tx_s!(maker_secret_hash.try_into()), + payment_time_lock: args.time_lock, + token_address, + } + }; + let data = try_tx_s!(self.prepare_refund_maker_payment_timelock_data(args).await); + + self.sign_and_send_transaction( + U256::from(ZERO_VALUE), + Action::Call(maker_swap_v2_contract), + data, + U256::from(gas_limit), + ) + .compat() + .await + } + + pub(crate) async fn refund_maker_payment_v2_secret_impl( + &self, + args: RefundMakerPaymentSecretArgs<'_, Self>, + ) -> Result { + let token_address = self + .get_token_address() + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + let maker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")))? + .maker_swap_v2_contract; + let gas_limit = self + .gas_limit_v2 + .gas_limit( + &self.coin_type, + EthPaymentType::MakerPayments, + PaymentMethod::RefundSecret, + ) + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + + let taker_secret = try_tx_s!(args.taker_secret.try_into()); + let maker_secret_hash = try_tx_s!(args.maker_secret_hash.try_into()); + let payment_amount = try_tx_s!(wei_from_big_decimal(&args.amount, self.decimals)); + let args = { + let taker_address = public_to_address(args.taker_pub); + MakerRefundSecretArgs { + payment_amount, + taker_address, + taker_secret, + maker_secret_hash, + payment_time_lock: args.time_lock, + token_address, + } + }; + let data = try_tx_s!(self.prepare_refund_maker_payment_secret_data(args).await); + + self.sign_and_send_transaction( + U256::from(ZERO_VALUE), + Action::Call(maker_swap_v2_contract), + data, + U256::from(gas_limit), + ) + .compat() + .await + } + + pub(crate) async fn spend_maker_payment_v2_impl( + &self, + args: SpendMakerPaymentArgs<'_, Self>, + ) -> Result { + let token_address = self + .get_token_address() + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + let maker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")))? + .maker_swap_v2_contract; + let gas_limit = self + .gas_limit_v2 + .gas_limit(&self.coin_type, EthPaymentType::MakerPayments, PaymentMethod::Spend) + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + + let data = try_tx_s!(self.prepare_spend_maker_payment_data(args, token_address).await); + + self.sign_and_send_transaction( + U256::from(ZERO_VALUE), + Action::Call(maker_swap_v2_contract), + data, + U256::from(gas_limit), + ) + .compat() + .await + } + + /// Prepares data for EtomicSwapMakerV2 contract [ethMakerPayment](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapMakerV2.sol#L30) method + async fn prepare_maker_eth_payment_data(&self, args: &MakerPaymentArgs) -> Result, PrepareTxDataError> { + let function = MAKER_SWAP_V2.function(ETH_MAKER_PAYMENT)?; + let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let data = function.encode_input(&[ + Token::FixedBytes(id), + Token::Address(args.taker_address), + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Uint(args.payment_time_lock.into()), + ])?; + Ok(data) + } + + /// Prepares data for EtomicSwapMakerV2 contract [erc20MakerPayment](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapMakerV2.sol#L64) method + async fn prepare_maker_erc20_payment_data( + &self, + args: &MakerPaymentArgs, + payment_amount: U256, + token_address: Address, + ) -> Result, PrepareTxDataError> { + let function = MAKER_SWAP_V2.function(ERC20_MAKER_PAYMENT)?; + let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let data = function.encode_input(&[ + Token::FixedBytes(id), + Token::Uint(payment_amount), + Token::Address(token_address), + Token::Address(args.taker_address), + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Uint(args.payment_time_lock.into()), + ])?; + Ok(data) + } + + /// Prepares data for EtomicSwapMakerV2 contract [refundMakerPaymentTimelock](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapMakerV2.sol#L144) method + async fn prepare_refund_maker_payment_timelock_data( + &self, + args: MakerRefundTimelockArgs, + ) -> Result, PrepareTxDataError> { + let function = MAKER_SWAP_V2.function("refundMakerPaymentTimelock")?; + let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let data = function.encode_input(&[ + Token::FixedBytes(id), + Token::Uint(args.payment_amount), + Token::Address(args.taker_address), + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Address(args.token_address), + ])?; + Ok(data) + } + + /// Prepares data for EtomicSwapMakerV2 contract [refundMakerPaymentSecret](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapMakerV2.sol#L190) method + async fn prepare_refund_maker_payment_secret_data( + &self, + args: MakerRefundSecretArgs, + ) -> Result, PrepareTxDataError> { + let function = MAKER_SWAP_V2.function("refundMakerPaymentSecret")?; + let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let data = function.encode_input(&[ + Token::FixedBytes(id), + Token::Uint(args.payment_amount), + Token::Address(args.taker_address), + Token::FixedBytes(args.taker_secret.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Address(args.token_address), + ])?; + Ok(data) + } + + /// Prepares data for EtomicSwapMakerV2 contract [spendMakerPayment](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapMakerV2.sol#L104) method + async fn prepare_spend_maker_payment_data( + &self, + args: SpendMakerPaymentArgs<'_, Self>, + token_address: Address, + ) -> Result, PrepareTxDataError> { + let function = MAKER_SWAP_V2.function("spendMakerPayment")?; + let id = self.etomic_swap_id_v2(args.time_lock, args.maker_secret_hash); + let maker_address = public_to_address(args.maker_pub); + let payment_amount = wei_from_big_decimal(&args.amount, self.decimals) + .map_err(|e| PrepareTxDataError::Internal(e.to_string()))?; + let data = function.encode_input(&[ + Token::FixedBytes(id), + Token::Uint(payment_amount), + Token::Address(maker_address), + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret.to_vec()), + Token::Address(token_address), + ])?; + Ok(data) + } +} + +/// Validation function for ETH maker payment data +fn validate_eth_maker_payment_data( + decoded: &[Token], + args: &MakerValidationArgs, + func: &Function, + tx_value: U256, +) -> Result<(), MmError> { + let checks = vec![ + (0, Token::FixedBytes(args.swap_id.clone()), "id"), + (1, Token::Address(args.taker), "taker"), + (2, Token::FixedBytes(args.taker_secret_hash.to_vec()), "takerSecretHash"), + (3, Token::FixedBytes(args.maker_secret_hash.to_vec()), "makerSecretHash"), + (4, Token::Uint(U256::from(args.payment_time_lock)), "paymentLockTime"), + ]; + + for (index, expected_token, field_name) in checks { + let token = get_function_input_data(decoded, func, index).map_to_mm(ValidatePaymentError::InternalError)?; + if token != expected_token { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "ETH Maker Payment `{}` {:?} is invalid, expected {:?}", + field_name, + decoded.get(index), + expected_token + ))); + } + } + if args.amount != tx_value { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "ETH Maker Payment amount, is invalid, expected {:?}, got {:?}", + args.amount, tx_value + ))); + } + Ok(()) +} + +/// Validation function for ERC20 maker payment data +fn validate_erc20_maker_payment_data( + decoded: &[Token], + args: &MakerValidationArgs, + func: &Function, + token_addr: Address, +) -> Result<(), MmError> { + let checks = vec![ + (0, Token::FixedBytes(args.swap_id.clone()), "id"), + (1, Token::Uint(args.amount), "amount"), + (2, Token::Address(token_addr), "tokenAddress"), + (3, Token::Address(args.taker), "taker"), + (4, Token::FixedBytes(args.taker_secret_hash.to_vec()), "takerSecretHash"), + (5, Token::FixedBytes(args.maker_secret_hash.to_vec()), "makerSecretHash"), + (6, Token::Uint(U256::from(args.payment_time_lock)), "paymentLockTime"), + ]; + + for (index, expected_token, field_name) in checks { + let token = get_function_input_data(decoded, func, index).map_to_mm(ValidatePaymentError::InternalError)?; + if token != expected_token { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "ERC20 Maker Payment `{}` {:?} is invalid, expected {:?}", + field_name, + decoded.get(index), + expected_token + ))); + } + } + Ok(()) +} diff --git a/mm2src/coins/eth/eth_swap_v2/eth_taker_swap_v2.rs b/mm2src/coins/eth/eth_swap_v2/eth_taker_swap_v2.rs new file mode 100644 index 0000000000..fea91b0408 --- /dev/null +++ b/mm2src/coins/eth/eth_swap_v2/eth_taker_swap_v2.rs @@ -0,0 +1,768 @@ +use super::{check_decoded_length, validate_amount, validate_from_to_and_status, validate_payment_state, + EthPaymentType, PaymentMethod, PrepareTxDataError, ZERO_VALUE}; +use crate::eth::{decode_contract_call, get_function_input_data, wei_from_big_decimal, EthCoin, EthCoinType, + ParseCoinAssocTypes, RefundFundingSecretArgs, RefundTakerPaymentArgs, SendTakerFundingArgs, + SignedEthTx, SwapTxTypeWithSecretHash, TakerPaymentStateV2, TransactionErr, ValidateSwapV2TxError, + ValidateSwapV2TxResult, ValidateTakerFundingArgs, TAKER_SWAP_V2}; +use crate::{FundingTxSpend, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, SearchForFundingSpendErr, + WaitForPaymentSpendError}; +use common::executor::Timer; +use common::now_sec; +use ethabi::{Function, Token}; +use ethcore_transaction::Action; +use ethereum_types::{Address, Public, U256}; +use ethkey::public_to_address; +use futures::compat::Future01CompatExt; +use mm2_err_handle::prelude::{MapToMmResult, MmError, MmResult}; +use std::convert::TryInto; +use web3::types::TransactionId; + +const ETH_TAKER_PAYMENT: &str = "ethTakerPayment"; +const ERC20_TAKER_PAYMENT: &str = "erc20TakerPayment"; +const TAKER_PAYMENT_APPROVE: &str = "takerPaymentApprove"; + +/// state index for `TakerPayment` structure from `EtomicSwapTakerV2.sol` +/// +/// struct TakerPayment { +/// bytes20 paymentHash; +/// uint32 preApproveLockTime; +/// uint32 paymentLockTime; +/// TakerPaymentState state; +/// } +const TAKER_PAYMENT_STATE_INDEX: usize = 3; + +struct TakerFundingArgs { + dex_fee: U256, + payment_amount: U256, + maker_address: Address, + taker_secret_hash: [u8; 32], + maker_secret_hash: [u8; 32], + funding_time_lock: u64, + payment_time_lock: u64, +} + +struct TakerRefundTimelockArgs { + dex_fee: U256, + payment_amount: U256, + maker_address: Address, + taker_secret_hash: [u8; 32], + maker_secret_hash: [u8; 32], + payment_time_lock: u64, + token_address: Address, +} + +struct TakerRefundSecretArgs { + dex_fee: U256, + payment_amount: U256, + maker_address: Address, + taker_secret: [u8; 32], + maker_secret_hash: [u8; 32], + payment_time_lock: u64, + token_address: Address, +} + +struct TakerValidationArgs<'a> { + swap_id: Vec, + amount: U256, + dex_fee: U256, + receiver: Address, + taker_secret_hash: &'a [u8; 32], + maker_secret_hash: &'a [u8; 32], + funding_time_lock: u64, + payment_time_lock: u64, +} + +impl EthCoin { + /// Calls `"ethTakerPayment"` or `"erc20TakerPayment"` swap contract methods. + /// Returns taker sent payment transaction. + pub(crate) async fn send_taker_funding_impl( + &self, + args: SendTakerFundingArgs<'_>, + ) -> Result { + let taker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")))? + .taker_swap_v2_contract; + // TODO add burnFee support + let dex_fee = try_tx_s!(wei_from_big_decimal(&args.dex_fee.fee_amount().into(), self.decimals)); + + let payment_amount = try_tx_s!(wei_from_big_decimal( + &(args.trading_amount.clone() + args.premium_amount.clone()), + self.decimals + )); + let funding_args = { + let maker_address = public_to_address(&Public::from_slice(args.maker_pub)); + TakerFundingArgs { + dex_fee, + payment_amount, + maker_address, + taker_secret_hash: try_tx_s!(args.taker_secret_hash.try_into()), + maker_secret_hash: try_tx_s!(args.maker_secret_hash.try_into()), + funding_time_lock: args.funding_time_lock, + payment_time_lock: args.payment_time_lock, + } + }; + match &self.coin_type { + EthCoinType::Eth => { + let data = try_tx_s!(self.prepare_taker_eth_funding_data(&funding_args).await); + let eth_total_payment = payment_amount.checked_add(dex_fee).ok_or_else(|| { + TransactionErr::Plain(ERRL!("Overflow occurred while calculating eth_total_payment")) + })?; + self.sign_and_send_transaction( + eth_total_payment, + Action::Call(taker_swap_v2_contract), + data, + U256::from(self.gas_limit_v2.taker.eth_payment), + ) + .compat() + .await + }, + EthCoinType::Erc20 { + platform: _, + token_addr, + } => { + let data = try_tx_s!(self.prepare_taker_erc20_funding_data(&funding_args, *token_addr).await); + self.handle_allowance(taker_swap_v2_contract, payment_amount, args.funding_time_lock) + .await?; + self.sign_and_send_transaction( + U256::from(ZERO_VALUE), + Action::Call(taker_swap_v2_contract), + data, + U256::from(self.gas_limit_v2.taker.erc20_payment), + ) + .compat() + .await + }, + EthCoinType::Nft { .. } => Err(TransactionErr::ProtocolNotSupported(ERRL!( + "NFT protocol is not supported for ETH and ERC20 Swaps" + ))), + } + } + + pub(crate) async fn validate_taker_funding_impl( + &self, + args: ValidateTakerFundingArgs<'_, Self>, + ) -> ValidateSwapV2TxResult { + if let EthCoinType::Nft { .. } = self.coin_type { + return MmError::err(ValidateSwapV2TxError::ProtocolNotSupported( + "NFT protocol is not supported for ETH and ERC20 Swaps".to_string(), + )); + } + let taker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| { + ValidateSwapV2TxError::Internal("Expected swap_v2_contracts to be Some, but found None".to_string()) + })? + .taker_swap_v2_contract; + let taker_secret_hash = args.taker_secret_hash.try_into()?; + let maker_secret_hash = args.maker_secret_hash.try_into()?; + validate_amount(&args.trading_amount).map_err(ValidateSwapV2TxError::Internal)?; + let swap_id = self.etomic_swap_id_v2(args.payment_time_lock, args.maker_secret_hash); + let taker_status = self + .payment_status_v2( + taker_swap_v2_contract, + Token::FixedBytes(swap_id.clone()), + &TAKER_SWAP_V2, + EthPaymentType::TakerPayments, + TAKER_PAYMENT_STATE_INDEX, + ) + .await?; + + let tx_from_rpc = self.transaction(TransactionId::Hash(args.funding_tx.tx_hash())).await?; + let tx_from_rpc = tx_from_rpc.as_ref().ok_or_else(|| { + ValidateSwapV2TxError::TxDoesNotExist(format!( + "Didn't find provided tx {:?} on ETH node", + args.funding_tx.tx_hash() + )) + })?; + let taker_address = public_to_address(args.taker_pub); + validate_from_to_and_status( + tx_from_rpc, + taker_address, + taker_swap_v2_contract, + taker_status, + TakerPaymentStateV2::PaymentSent as u8, + )?; + + let validation_args = { + let dex_fee = wei_from_big_decimal(&args.dex_fee.fee_amount().into(), self.decimals)?; + let payment_amount = wei_from_big_decimal(&(args.trading_amount + args.premium_amount), self.decimals)?; + TakerValidationArgs { + swap_id, + amount: payment_amount, + dex_fee, + receiver: self.my_addr().await, + taker_secret_hash, + maker_secret_hash, + funding_time_lock: args.funding_time_lock, + payment_time_lock: args.payment_time_lock, + } + }; + match self.coin_type { + EthCoinType::Eth => { + let function = TAKER_SWAP_V2.function(ETH_TAKER_PAYMENT)?; + let decoded = decode_contract_call(function, &tx_from_rpc.input.0)?; + validate_eth_taker_payment_data(&decoded, &validation_args, function, tx_from_rpc.value)?; + }, + EthCoinType::Erc20 { token_addr, .. } => { + let function = TAKER_SWAP_V2.function(ERC20_TAKER_PAYMENT)?; + let decoded = decode_contract_call(function, &tx_from_rpc.input.0)?; + validate_erc20_taker_payment_data(&decoded, &validation_args, function, token_addr)?; + }, + EthCoinType::Nft { .. } => { + return MmError::err(ValidateSwapV2TxError::ProtocolNotSupported( + "NFT protocol is not supported for ETH and ERC20 Swaps".to_string(), + )); + }, + } + Ok(()) + } + + /// Taker approves payment calling `takerPaymentApprove` for EVM based chains. + /// Function accepts taker payment transaction, returns taker approve payment transaction. + pub(crate) async fn taker_payment_approve( + &self, + args: &GenTakerFundingSpendArgs<'_, Self>, + ) -> Result { + let gas_limit = match self.coin_type { + EthCoinType::Eth | EthCoinType::Erc20 { .. } => U256::from(self.gas_limit_v2.taker.approve_payment), + EthCoinType::Nft { .. } => { + return Err(TransactionErr::ProtocolNotSupported(ERRL!( + "NFT protocol is not supported for ETH and ERC20 Swaps" + ))) + }, + }; + let (taker_swap_v2_contract, send_func, token_address) = self + .taker_swap_v2_details(ETH_TAKER_PAYMENT, ERC20_TAKER_PAYMENT) + .await?; + let decoded = try_tx_s!(decode_contract_call(send_func, args.funding_tx.unsigned().data())); + let taker_status = try_tx_s!( + self.payment_status_v2( + taker_swap_v2_contract, + decoded[0].clone(), + &TAKER_SWAP_V2, + EthPaymentType::TakerPayments, + TAKER_PAYMENT_STATE_INDEX, + ) + .await + ); + validate_payment_state(args.funding_tx, taker_status, TakerPaymentStateV2::PaymentSent as u8) + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + let data = try_tx_s!( + self.prepare_taker_payment_approve_data(args, decoded, token_address) + .await + ); + let approve_tx = self + .sign_and_send_transaction( + U256::from(ZERO_VALUE), + Action::Call(taker_swap_v2_contract), + data, + gas_limit, + ) + .compat() + .await?; + Ok(approve_tx) + } + + pub(crate) async fn refund_taker_payment_with_timelock_impl( + &self, + args: RefundTakerPaymentArgs<'_>, + ) -> Result { + let token_address = self + .get_token_address() + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + let taker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")))? + .taker_swap_v2_contract; + let gas_limit = self + .gas_limit_v2 + .gas_limit( + &self.coin_type, + EthPaymentType::TakerPayments, + PaymentMethod::RefundTimelock, + ) + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + + let (maker_secret_hash, taker_secret_hash) = match args.tx_type_with_secret_hash { + SwapTxTypeWithSecretHash::TakerPaymentV2 { + maker_secret_hash, + taker_secret_hash, + } => (maker_secret_hash, taker_secret_hash), + _ => { + return Err(TransactionErr::Plain(ERRL!( + "Unsupported swap tx type for timelock refund" + ))) + }, + }; + let dex_fee = try_tx_s!(wei_from_big_decimal( + &args.dex_fee.fee_amount().to_decimal(), + self.decimals + )); + let payment_amount = try_tx_s!(wei_from_big_decimal( + &(args.trading_amount + args.premium_amount), + self.decimals + )); + + let args = { + let maker_address = public_to_address(&Public::from_slice(args.maker_pub)); + TakerRefundTimelockArgs { + dex_fee, + payment_amount, + maker_address, + taker_secret_hash: try_tx_s!(taker_secret_hash.try_into()), + maker_secret_hash: try_tx_s!(maker_secret_hash.try_into()), + payment_time_lock: args.time_lock, + token_address, + } + }; + let data = try_tx_s!(self.prepare_taker_refund_payment_timelock_data(args).await); + + self.sign_and_send_transaction( + U256::from(ZERO_VALUE), + Action::Call(taker_swap_v2_contract), + data, + U256::from(gas_limit), + ) + .compat() + .await + } + + pub(crate) async fn refund_taker_funding_secret_impl( + &self, + args: RefundFundingSecretArgs<'_, Self>, + ) -> Result { + let token_address = self + .get_token_address() + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + let taker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")))? + .taker_swap_v2_contract; + let gas_limit = self + .gas_limit_v2 + .gas_limit( + &self.coin_type, + EthPaymentType::TakerPayments, + PaymentMethod::RefundSecret, + ) + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + + let taker_secret = try_tx_s!(args.taker_secret.try_into()); + let maker_secret_hash = try_tx_s!(args.maker_secret_hash.try_into()); + let dex_fee = try_tx_s!(wei_from_big_decimal( + &args.dex_fee.fee_amount().to_decimal(), + self.decimals + )); + let payment_amount = try_tx_s!(wei_from_big_decimal( + &(args.trading_amount + args.premium_amount), + self.decimals + )); + + let refund_args = { + let maker_address = public_to_address(args.maker_pubkey); + TakerRefundSecretArgs { + dex_fee, + payment_amount, + maker_address, + taker_secret, + maker_secret_hash, + payment_time_lock: args.payment_time_lock, + token_address, + } + }; + let data = try_tx_s!(self.prepare_taker_refund_payment_secret_data(&refund_args).await); + + self.sign_and_send_transaction( + U256::from(ZERO_VALUE), + Action::Call(taker_swap_v2_contract), + data, + U256::from(gas_limit), + ) + .compat() + .await + } + + /// Checks that taker payment state is `TakerApproved`. + /// Accepts a taker-approved payment transaction and returns it if the state is correct. + pub(crate) async fn search_for_taker_funding_spend_impl( + &self, + tx: &SignedEthTx, + ) -> Result>, SearchForFundingSpendErr> { + let (decoded, taker_swap_v2_contract) = self + .get_decoded_and_swap_contract(tx, TAKER_PAYMENT_APPROVE) + .await + .map_err(|e| SearchForFundingSpendErr::Internal(ERRL!("{}", e)))?; + let taker_status = self + .payment_status_v2( + taker_swap_v2_contract, + decoded[0].clone(), // id from takerPaymentApprove + &TAKER_SWAP_V2, + EthPaymentType::TakerPayments, + TAKER_PAYMENT_STATE_INDEX, + ) + .await + .map_err(|e| SearchForFundingSpendErr::Internal(ERRL!("{}", e)))?; + if taker_status == U256::from(TakerPaymentStateV2::TakerApproved as u8) { + return Ok(Some(FundingTxSpend::TransferredToTakerPayment(tx.clone()))); + } + Ok(None) + } + + /// Taker swap contract `spendTakerPayment` method is called for EVM based chains. + /// Returns maker spent payment transaction. + pub(crate) async fn sign_and_broadcast_taker_payment_spend_impl( + &self, + gen_args: &GenTakerPaymentSpendArgs<'_, Self>, + secret: &[u8], + ) -> Result { + let gas_limit = self + .gas_limit_v2 + .gas_limit(&self.coin_type, EthPaymentType::TakerPayments, PaymentMethod::Spend) + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + + let (taker_swap_v2_contract, approve_func, token_address) = self + .taker_swap_v2_details(TAKER_PAYMENT_APPROVE, TAKER_PAYMENT_APPROVE) + .await?; + let decoded = try_tx_s!(decode_contract_call(approve_func, gen_args.taker_tx.unsigned().data())); + let taker_status = try_tx_s!( + self.payment_status_v2( + taker_swap_v2_contract, + decoded[0].clone(), + &TAKER_SWAP_V2, + EthPaymentType::TakerPayments, + TAKER_PAYMENT_STATE_INDEX, + ) + .await + ); + validate_payment_state( + gen_args.taker_tx, + taker_status, + TakerPaymentStateV2::TakerApproved as u8, + ) + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + let data = try_tx_s!( + self.prepare_spend_taker_payment_data(gen_args, secret, decoded, token_address) + .await + ); + let spend_payment_tx = self + .sign_and_send_transaction( + U256::from(ZERO_VALUE), + Action::Call(taker_swap_v2_contract), + data, + U256::from(gas_limit), + ) + .compat() + .await?; + Ok(spend_payment_tx) + } + + /// Checks that taker payment state is `MakerSpent`. + /// Accepts maker spent payment transaction and returns it if payment status is correct. + pub(crate) async fn wait_for_taker_payment_spend_impl( + &self, + taker_payment: &SignedEthTx, + wait_until: u64, + ) -> MmResult { + let (decoded, taker_swap_v2_contract) = self + .get_decoded_and_swap_contract(taker_payment, "spendTakerPayment") + .await?; + loop { + let taker_status = self + .payment_status_v2( + taker_swap_v2_contract, + decoded[0].clone(), // id from spendTakerPayment + &TAKER_SWAP_V2, + EthPaymentType::TakerPayments, + TAKER_PAYMENT_STATE_INDEX, + ) + .await?; + if taker_status == U256::from(TakerPaymentStateV2::MakerSpent as u8) { + return Ok(taker_payment.clone()); + } + let now = now_sec(); + if now > wait_until { + return MmError::err(WaitForPaymentSpendError::Timeout { wait_until, now }); + } + Timer::sleep(10.).await; + } + } + + /// Prepares data for EtomicSwapTakerV2 contract [ethTakerPayment](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapTakerV2.sol#L44) method + async fn prepare_taker_eth_funding_data(&self, args: &TakerFundingArgs) -> Result, PrepareTxDataError> { + let function = TAKER_SWAP_V2.function(ETH_TAKER_PAYMENT)?; + let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let data = function.encode_input(&[ + Token::FixedBytes(id), + Token::Uint(args.dex_fee), + Token::Address(args.maker_address), + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Uint(args.funding_time_lock.into()), + Token::Uint(args.payment_time_lock.into()), + ])?; + Ok(data) + } + + /// Prepares data for EtomicSwapTakerV2 contract [erc20TakerPayment](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapTakerV2.sol#L83) method + async fn prepare_taker_erc20_funding_data( + &self, + args: &TakerFundingArgs, + token_address: Address, + ) -> Result, PrepareTxDataError> { + let function = TAKER_SWAP_V2.function(ERC20_TAKER_PAYMENT)?; + let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let data = function.encode_input(&[ + Token::FixedBytes(id), + Token::Uint(args.payment_amount), + Token::Uint(args.dex_fee), + Token::Address(token_address), + Token::Address(args.maker_address), + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Uint(args.funding_time_lock.into()), + Token::Uint(args.payment_time_lock.into()), + ])?; + Ok(data) + } + + /// Prepares data for EtomicSwapTakerV2 contract [refundTakerPaymentTimelock](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapTakerV2.sol#L208) method + async fn prepare_taker_refund_payment_timelock_data( + &self, + args: TakerRefundTimelockArgs, + ) -> Result, PrepareTxDataError> { + let function = TAKER_SWAP_V2.function("refundTakerPaymentTimelock")?; + let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let data = function.encode_input(&[ + Token::FixedBytes(id), + Token::Uint(args.payment_amount), + Token::Uint(args.dex_fee), + Token::Address(args.maker_address), + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Address(args.token_address), + ])?; + Ok(data) + } + + /// Prepares data for EtomicSwapTakerV2 contract [refundTakerPaymentSecret](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapTakerV2.sol#L267) method + async fn prepare_taker_refund_payment_secret_data( + &self, + args: &TakerRefundSecretArgs, + ) -> Result, PrepareTxDataError> { + let function = TAKER_SWAP_V2.function("refundTakerPaymentSecret")?; + let id = self.etomic_swap_id_v2(args.payment_time_lock, &args.maker_secret_hash); + let data = function.encode_input(&[ + Token::FixedBytes(id), + Token::Uint(args.payment_amount), + Token::Uint(args.dex_fee), + Token::Address(args.maker_address), + Token::FixedBytes(args.taker_secret.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Address(args.token_address), + ])?; + Ok(data) + } + + /// This function constructs the encoded transaction input data required to approve the taker payment ([takerPaymentApprove](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapTakerV2.sol#L128)). + /// The `decoded` parameter should contain the transaction input data from the `ethTakerPayment` or `erc20TakerPayment` function of the EtomicSwapTakerV2 contract. + async fn prepare_taker_payment_approve_data( + &self, + args: &GenTakerFundingSpendArgs<'_, Self>, + decoded: Vec, + token_address: Address, + ) -> Result, PrepareTxDataError> { + let function = TAKER_SWAP_V2.function(TAKER_PAYMENT_APPROVE)?; + let data = match self.coin_type { + EthCoinType::Eth => { + check_decoded_length(&decoded, 7)?; + let dex_fee = match &decoded[1] { + Token::Uint(value) => value, + _ => return Err(PrepareTxDataError::Internal("Invalid token type for dex fee".into())), + }; + let amount = args + .funding_tx + .unsigned() + .value() + .checked_sub(*dex_fee) + .ok_or_else(|| { + PrepareTxDataError::Internal("Underflow occurred while calculating amount".into()) + })?; + function.encode_input(&[ + decoded[0].clone(), // id from ethTakerPayment + Token::Uint(amount), // calculated payment amount (tx value - dexFee) + decoded[1].clone(), // dexFee from ethTakerPayment + decoded[2].clone(), // receiver from ethTakerPayment + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Address(token_address), // should be zero address Address::default() + ])? + }, + EthCoinType::Erc20 { .. } => { + check_decoded_length(&decoded, 9)?; + function.encode_input(&[ + decoded[0].clone(), // id from erc20TakerPayment + decoded[1].clone(), // amount from erc20TakerPayment + decoded[2].clone(), // dexFee from erc20TakerPayment + decoded[4].clone(), // receiver from erc20TakerPayment + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + Token::Address(token_address), // erc20 token address from EthCoinType::Erc20 + ])? + }, + EthCoinType::Nft { .. } => { + return Err(PrepareTxDataError::Internal("EthCoinType must be ETH or ERC20".into())) + }, + }; + Ok(data) + } + + /// Prepares data for EtomicSwapTakerV2 contract [spendTakerPayment](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapTakerV2.sol#L164) method + async fn prepare_spend_taker_payment_data( + &self, + args: &GenTakerPaymentSpendArgs<'_, Self>, + secret: &[u8], + decoded: Vec, + token_address: Address, + ) -> Result, PrepareTxDataError> { + check_decoded_length(&decoded, 7)?; + let function = TAKER_SWAP_V2.function("spendTakerPayment")?; + let taker_address = public_to_address(args.taker_pub); + let data = function.encode_input(&[ + decoded[0].clone(), // id from takerPaymentApprove + decoded[1].clone(), // amount from takerPaymentApprove + decoded[2].clone(), // dexFee from takerPaymentApprove + Token::Address(taker_address), // taker address + decoded[4].clone(), // takerSecretHash from ethTakerPayment + Token::FixedBytes(secret.to_vec()), // makerSecret + Token::Address(token_address), // tokenAddress + ])?; + Ok(data) + } + + /// Retrieves the taker smart contract address, the corresponding function, and the token address. + /// + /// Depending on the coin type (ETH or ERC20), it fetches the appropriate function name and token address. + /// Returns an error if the coin type is NFT or if the `swap_v2_contracts` is None. + async fn taker_swap_v2_details( + &self, + eth_func_name: &str, + erc20_func_name: &str, + ) -> Result<(Address, &Function, Address), TransactionErr> { + let (func, token_address) = match self.coin_type { + EthCoinType::Eth => (try_tx_s!(TAKER_SWAP_V2.function(eth_func_name)), Address::default()), + EthCoinType::Erc20 { token_addr, .. } => (try_tx_s!(TAKER_SWAP_V2.function(erc20_func_name)), token_addr), + EthCoinType::Nft { .. } => { + return Err(TransactionErr::ProtocolNotSupported(ERRL!( + "NFT protocol is not supported for ETH and ERC20 Swaps" + ))) + }, + }; + let taker_swap_v2_contract = self + .swap_v2_contracts + .as_ref() + .map(|contracts| contracts.taker_swap_v2_contract) + .ok_or_else(|| TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")))?; + Ok((taker_swap_v2_contract, func, token_address)) + } + + async fn get_decoded_and_swap_contract( + &self, + tx: &SignedEthTx, + function_name: &str, + ) -> Result<(Vec, Address), PrepareTxDataError> { + let decoded = { + let func = match self.coin_type { + EthCoinType::Eth | EthCoinType::Erc20 { .. } => TAKER_SWAP_V2.function(function_name)?, + EthCoinType::Nft { .. } => { + return Err(PrepareTxDataError::Internal( + "NFT protocol is not supported for ETH and ERC20 Swaps".to_string(), + )); + }, + }; + decode_contract_call(func, tx.unsigned().data())? + }; + let taker_swap_v2_contract = self + .swap_v2_contracts + .as_ref() + .map(|contracts| contracts.taker_swap_v2_contract) + .ok_or_else(|| { + PrepareTxDataError::Internal("Expected swap_v2_contracts to be Some, but found None".to_string()) + })?; + + Ok((decoded, taker_swap_v2_contract)) + } +} + +/// Validation function for ETH taker payment data +fn validate_eth_taker_payment_data( + decoded: &[Token], + args: &TakerValidationArgs, + func: &Function, + tx_value: U256, +) -> Result<(), MmError> { + let checks = vec![ + (0, Token::FixedBytes(args.swap_id.clone()), "id"), + (1, Token::Uint(args.dex_fee), "dexFee"), + (2, Token::Address(args.receiver), "receiver"), + (3, Token::FixedBytes(args.taker_secret_hash.to_vec()), "takerSecretHash"), + (4, Token::FixedBytes(args.maker_secret_hash.to_vec()), "makerSecretHash"), + (5, Token::Uint(U256::from(args.funding_time_lock)), "preApproveLockTime"), + (6, Token::Uint(U256::from(args.payment_time_lock)), "paymentLockTime"), + ]; + + for (index, expected_token, field_name) in checks { + let token = get_function_input_data(decoded, func, index).map_to_mm(ValidateSwapV2TxError::Internal)?; + if token != expected_token { + return MmError::err(ValidateSwapV2TxError::WrongPaymentTx(format!( + "ETH Taker Payment `{}` {:?} is invalid, expected {:?}", + field_name, + decoded.get(index), + expected_token + ))); + } + } + let total = args.amount.checked_add(args.dex_fee).ok_or_else(|| { + ValidateSwapV2TxError::Overflow("Overflow occurred while calculating total payment".to_string()) + })?; + if total != tx_value { + return MmError::err(ValidateSwapV2TxError::WrongPaymentTx(format!( + "ETH Taker Payment amount, is invalid, expected {:?}, got {:?}", + total, tx_value + ))); + } + Ok(()) +} + +/// Validation function for ERC20 taker payment data +fn validate_erc20_taker_payment_data( + decoded: &[Token], + args: &TakerValidationArgs, + func: &Function, + token_addr: Address, +) -> Result<(), MmError> { + let checks = vec![ + (0, Token::FixedBytes(args.swap_id.clone()), "id"), + (1, Token::Uint(args.amount), "amount"), + (2, Token::Uint(args.dex_fee), "dexFee"), + (3, Token::Address(token_addr), "tokenAddress"), + (4, Token::Address(args.receiver), "receiver"), + (5, Token::FixedBytes(args.taker_secret_hash.to_vec()), "takerSecretHash"), + (6, Token::FixedBytes(args.maker_secret_hash.to_vec()), "makerSecretHash"), + (7, Token::Uint(U256::from(args.funding_time_lock)), "preApproveLockTime"), + (8, Token::Uint(U256::from(args.payment_time_lock)), "paymentLockTime"), + ]; + + for (index, expected_token, field_name) in checks { + let token = get_function_input_data(decoded, func, index).map_to_mm(ValidateSwapV2TxError::Internal)?; + if token != expected_token { + return MmError::err(ValidateSwapV2TxError::WrongPaymentTx(format!( + "ERC20 Taker Payment `{}` {:?} is invalid, expected {:?}", + field_name, + decoded.get(index), + expected_token + ))); + } + } + Ok(()) +} diff --git a/mm2src/coins/eth/eth_swap_v2/mod.rs b/mm2src/coins/eth/eth_swap_v2/mod.rs new file mode 100644 index 0000000000..798a232d56 --- /dev/null +++ b/mm2src/coins/eth/eth_swap_v2/mod.rs @@ -0,0 +1,203 @@ +use crate::eth::{EthCoin, EthCoinType, ParseCoinAssocTypes, Transaction, TransactionErr}; +use enum_derives::EnumFromStringify; +use ethabi::{Contract, Token}; +use ethcore_transaction::SignedTransaction as SignedEthTx; +use ethereum_types::{Address, U256}; +use futures::compat::Future01CompatExt; +use mm2_err_handle::mm_error::MmError; +use mm2_number::BigDecimal; +use num_traits::Signed; +use web3::types::Transaction as Web3Tx; + +pub(crate) mod eth_maker_swap_v2; +pub(crate) mod eth_taker_swap_v2; + +/// ZERO_VALUE is used to represent a 0 amount in transactions where the value is encoded in the transaction input data. +/// This is typically used in function calls where the value is not directly transferred with the transaction, such as in +/// `spendTakerPayment` where the [amount](https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapTakerV2.sol#L166) +/// is provided as part of the input data rather than as an Ether value +pub(crate) const ZERO_VALUE: u32 = 0; + +pub enum EthPaymentType { + MakerPayments, + TakerPayments, +} + +impl EthPaymentType { + pub(crate) fn as_str(&self) -> &'static str { + match self { + EthPaymentType::MakerPayments => "makerPayments", + EthPaymentType::TakerPayments => "takerPayments", + } + } +} + +pub enum PaymentMethod { + Send, + Spend, + RefundTimelock, + RefundSecret, +} + +#[derive(Debug, Display)] +pub(crate) enum ValidatePaymentV2Err { + UnexpectedPaymentState(String), + WrongPaymentTx(String), +} + +#[derive(Debug, Display, EnumFromStringify)] +pub(crate) enum PaymentStatusErr { + #[from_stringify("ethabi::Error")] + #[display(fmt = "ABI error: {}", _0)] + ABIError(String), + #[from_stringify("web3::Error")] + #[display(fmt = "Transport error: {}", _0)] + Transport(String), + #[display(fmt = "Internal error: {}", _0)] + Internal(String), + #[display(fmt = "Invalid data error: {}", _0)] + InvalidData(String), +} + +#[derive(Debug, Display, EnumFromStringify)] +pub(crate) enum PrepareTxDataError { + #[from_stringify("ethabi::Error")] + #[display(fmt = "ABI error: {}", _0)] + ABIError(String), + #[display(fmt = "Internal error: {}", _0)] + Internal(String), +} + +impl EthCoin { + /// Retrieves the payment status from a given smart contract address based on the swap ID and state type. + pub(crate) async fn payment_status_v2( + &self, + swap_address: Address, + swap_id: Token, + contract_abi: &Contract, + payment_type: EthPaymentType, + state_index: usize, + ) -> Result { + let function_name = payment_type.as_str(); + let function = contract_abi.function(function_name)?; + let data = function.encode_input(&[swap_id])?; + let bytes = self + .call_request(self.my_addr().await, swap_address, None, Some(data.into())) + .await?; + let decoded_tokens = function.decode_output(&bytes.0)?; + + let state = decoded_tokens.get(state_index).ok_or_else(|| { + PaymentStatusErr::Internal(format!( + "Payment status must contain 'state' as the {} token", + state_index + )) + })?; + match state { + Token::Uint(state) => Ok(*state), + _ => Err(PaymentStatusErr::InvalidData(format!( + "Payment status must be Uint, got {:?}", + state + ))), + } + } + + pub(super) fn get_token_address(&self) -> Result { + match &self.coin_type { + EthCoinType::Eth => Ok(Address::default()), + EthCoinType::Erc20 { token_addr, .. } => Ok(*token_addr), + EthCoinType::Nft { .. } => Err("NFT protocol is not supported for ETH and ERC20 Swaps".to_string()), + } + } +} + +pub(crate) fn validate_payment_state( + tx: &SignedEthTx, + state: U256, + expected_state: u8, +) -> Result<(), PrepareTxDataError> { + if state != U256::from(expected_state) { + return Err(PrepareTxDataError::Internal(format!( + "Payment {:?} state is not `{}`, got `{}`", + tx, expected_state, state + ))); + } + Ok(()) +} + +pub(crate) fn validate_from_to_and_status( + tx_from_rpc: &Web3Tx, + expected_from: Address, + expected_to: Address, + status: U256, + expected_status: u8, +) -> Result<(), MmError> { + if status != U256::from(expected_status) { + return MmError::err(ValidatePaymentV2Err::UnexpectedPaymentState(format!( + "Payment state is not `PaymentSent`, got {}", + status + ))); + } + if tx_from_rpc.from != Some(expected_from) { + return MmError::err(ValidatePaymentV2Err::WrongPaymentTx(format!( + "Payment tx {:?} was sent from wrong address, expected {:?}", + tx_from_rpc, expected_from + ))); + } + // (in NFT case) as NFT owner calls "safeTransferFrom" directly, then in Transaction 'to' field we expect token_address + if tx_from_rpc.to != Some(expected_to) { + return MmError::err(ValidatePaymentV2Err::WrongPaymentTx(format!( + "Payment tx {:?} was sent to wrong address, expected {:?}", + tx_from_rpc, expected_to, + ))); + } + Ok(()) +} + +// TODO validate premium when add its support in swap_v2 +fn validate_amount(trading_amount: &BigDecimal) -> Result<(), String> { + if !trading_amount.is_positive() { + return Err("trading_amount must be a positive value".to_string()); + } + Ok(()) +} + +fn check_decoded_length(decoded: &Vec, expected_len: usize) -> Result<(), PrepareTxDataError> { + if decoded.len() != expected_len { + return Err(PrepareTxDataError::Internal(format!( + "Invalid number of tokens in decoded. Expected {}, found {}", + expected_len, + decoded.len() + ))); + } + Ok(()) +} + +impl EthCoin { + async fn handle_allowance( + &self, + swap_contract: Address, + payment_amount: U256, + time_lock: u64, + ) -> Result<(), TransactionErr> { + let allowed = self + .allowance(swap_contract) + .compat() + .await + .map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?; + + if allowed < payment_amount { + let approved_tx = self.approve(swap_contract, U256::max_value()).compat().await?; + self.wait_for_required_allowance(swap_contract, payment_amount, time_lock) + .compat() + .await + .map_err(|e| { + TransactionErr::Plain(ERRL!( + "Allowed value was not updated in time after sending approve transaction {:02x}: {}", + approved_tx.tx_hash_as_bytes(), + e + )) + })?; + } + Ok(()) + } +} diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 9332594931..19f45d10ae 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -1,27 +1,31 @@ use super::*; -use crate::eth::for_tests::{eth_coin_for_test, eth_coin_from_keypair}; -use crate::{DexFee, IguanaPrivKey}; -use common::{block_on, now_sec}; -#[cfg(not(target_arch = "wasm32"))] -use ethkey::{Generator, Random}; +use crate::IguanaPrivKey; +use common::{block_on, block_on_f01}; use mm2_core::mm_ctx::MmCtxBuilder; -use mm2_test_helpers::for_tests::{ETH_MAINNET_CHAIN_ID, ETH_MAINNET_NODE, ETH_SEPOLIA_CHAIN_ID, ETH_SEPOLIA_NODES, + +cfg_native!( + use crate::eth::for_tests::{eth_coin_for_test, eth_coin_from_keypair}; + use crate::DexFee; + + use common::now_sec; + use ethkey::{Generator, Random}; + use mm2_test_helpers::for_tests::{ETH_MAINNET_CHAIN_ID, ETH_MAINNET_NODE, ETH_SEPOLIA_CHAIN_ID, ETH_SEPOLIA_NODES, ETH_SEPOLIA_TOKEN_CONTRACT}; -use mocktopus::mocking::*; - -/// The gas price for the tests -const GAS_PRICE: u64 = 50_000_000_000; -// `GAS_PRICE` increased by 3% -const GAS_PRICE_APPROXIMATION_ON_START_SWAP: u64 = 51_500_000_000; -// `GAS_PRICE` increased by 5% -const GAS_PRICE_APPROXIMATION_ON_ORDER_ISSUE: u64 = 52_500_000_000; -// `GAS_PRICE` increased by 7% -const GAS_PRICE_APPROXIMATION_ON_TRADE_PREIMAGE: u64 = 53_500_000_000; + use mocktopus::mocking::*; + + /// The gas price for the tests + const GAS_PRICE: u64 = 50_000_000_000; + /// `GAS_PRICE` increased by 3% + const GAS_PRICE_APPROXIMATION_ON_START_SWAP: u64 = 51_500_000_000; + /// `GAS_PRICE` increased by 5% + const GAS_PRICE_APPROXIMATION_ON_ORDER_ISSUE: u64 = 52_500_000_000; + /// `GAS_PRICE` increased by 7% + const GAS_PRICE_APPROXIMATION_ON_TRADE_PREIMAGE: u64 = 53_500_000_000; +); + // old way to add some extra gas to the returned value from gas station (non-existent now), still used in tests const GAS_PRICE_PERCENT: u64 = 10; -const TAKER_PAYMENT_SPEND_SEARCH_INTERVAL: f64 = 1.; - fn check_sum(addr: &str, expected: &str) { let actual = checksum_address(addr); assert_eq!(expected, actual); @@ -154,8 +158,11 @@ fn test_wei_from_big_decimal() { assert_eq!(expected_wei, wei); } +#[cfg(not(target_arch = "wasm32"))] #[test] fn test_wait_for_payment_spend_timeout() { + const TAKER_PAYMENT_SPEND_SEARCH_INTERVAL: f64 = 1.; + EthCoin::spend_events.mock_safe(|_, _, _, _| MockResult::Return(Box::new(futures01::future::ok(vec![])))); EthCoin::current_block.mock_safe(|_| MockResult::Return(Box::new(futures01::future::ok(900)))); @@ -184,18 +191,16 @@ fn test_wait_for_payment_spend_timeout() { 184, 42, 106, ]; - assert!(coin - .wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { - tx_bytes: &tx_bytes, - secret_hash: &[], - wait_until, - from_block, - swap_contract_address: &coin.swap_contract_address(), - check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, - watcher_reward: false - }) - .wait() - .is_err()); + assert!(block_on(coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { + tx_bytes: &tx_bytes, + secret_hash: &[], + wait_until, + from_block, + swap_contract_address: &coin.swap_contract_address(), + check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, + watcher_reward: false + })) + .is_err()); } #[cfg(not(target_arch = "wasm32"))] @@ -222,7 +227,7 @@ fn test_withdraw_impl_manual_fee() { memo: None, ibc_source_channel: None, }; - coin.get_balance().wait().unwrap(); + block_on_f01(coin.get_balance()).unwrap(); let tx_details = block_on(withdraw_impl(coin, withdraw_req)).unwrap(); let expected = Some( @@ -271,7 +276,7 @@ fn test_withdraw_impl_fee_details() { memo: None, ibc_source_channel: None, }; - coin.get_balance().wait().unwrap(); + block_on_f01(coin.get_balance()).unwrap(); let tx_details = block_on(withdraw_impl(coin, withdraw_req)).unwrap(); let expected = Some( @@ -306,6 +311,7 @@ fn test_add_ten_pct_one_gwei() { assert_eq!(expected, actual); } +#[cfg(not(target_arch = "wasm32"))] #[test] fn get_sender_trade_preimage() { /// Trade fee for the ETH coin is `2 * 150_000 * gas_price` always. @@ -361,6 +367,7 @@ fn get_sender_trade_preimage() { assert_eq!(actual, expected); } +#[cfg(not(target_arch = "wasm32"))] #[test] fn get_erc20_sender_trade_preimage() { const APPROVE_GAS_LIMIT: u64 = 60_000; @@ -463,6 +470,7 @@ fn get_erc20_sender_trade_preimage() { ); } +#[cfg(not(target_arch = "wasm32"))] #[test] fn get_receiver_trade_preimage() { EthCoin::get_gas_price.mock_safe(|_| MockResult::Return(Box::pin(futures::future::ok(GAS_PRICE.into())))); @@ -476,13 +484,12 @@ fn get_receiver_trade_preimage() { paid_from_trading_vol: false, }; - let actual = coin - .get_receiver_trade_fee(FeeApproxStage::WithoutApprox) - .wait() - .expect("!get_sender_trade_fee"); + let actual = + block_on_f01(coin.get_receiver_trade_fee(FeeApproxStage::WithoutApprox)).expect("!get_sender_trade_fee"); assert_eq!(actual, expected_fee); } +#[cfg(not(target_arch = "wasm32"))] #[test] fn test_get_fee_to_send_taker_fee() { const DEX_FEE_AMOUNT: u64 = 100_000; @@ -533,6 +540,7 @@ fn test_get_fee_to_send_taker_fee() { /// /// Please note this test doesn't work correctly now, /// because as of now [`EthCoin::get_fee_to_send_taker_fee`] doesn't process the `Exception` web3 error correctly. +#[cfg(not(target_arch = "wasm32"))] #[test] #[ignore] fn test_get_fee_to_send_taker_fee_insufficient_balance() { @@ -562,6 +570,7 @@ fn test_get_fee_to_send_taker_fee_insufficient_balance() { ); } +#[cfg(not(target_arch = "wasm32"))] #[test] fn validate_dex_fee_invalid_sender_eth() { let (_ctx, coin) = eth_coin_for_test(EthCoinType::Eth, &[ETH_MAINNET_NODE], None, ETH_MAINNET_CHAIN_ID); @@ -582,13 +591,14 @@ fn validate_dex_fee_invalid_sender_eth() { min_block_number: 0, uuid: &[], }; - let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + let error = block_on(coin.validate_fee(validate_fee_args)).unwrap_err().into_inner(); match error { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("was sent from wrong address")), _ => panic!("Expected `WrongPaymentTx` wrong sender address, found {:?}", error), } } +#[cfg(not(target_arch = "wasm32"))] #[test] fn validate_dex_fee_invalid_sender_erc() { let (_ctx, coin) = eth_coin_for_test( @@ -617,13 +627,14 @@ fn validate_dex_fee_invalid_sender_erc() { min_block_number: 0, uuid: &[], }; - let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + let error = block_on(coin.validate_fee(validate_fee_args)).unwrap_err().into_inner(); match error { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("was sent from wrong address")), _ => panic!("Expected `WrongPaymentTx` wrong sender address, found {:?}", error), } } +#[cfg(not(target_arch = "wasm32"))] fn sender_compressed_pub(tx: &SignedEthTx) -> [u8; 33] { let tx_pubkey = tx.public.unwrap(); let mut raw_pubkey = [0; 65]; @@ -633,6 +644,7 @@ fn sender_compressed_pub(tx: &SignedEthTx) -> [u8; 33] { secp_public.serialize() } +#[cfg(not(target_arch = "wasm32"))] #[test] fn validate_dex_fee_eth_confirmed_before_min_block() { let (_ctx, coin) = eth_coin_for_test(EthCoinType::Eth, &[ETH_MAINNET_NODE], None, ETH_MAINNET_CHAIN_ID); @@ -655,13 +667,14 @@ fn validate_dex_fee_eth_confirmed_before_min_block() { min_block_number: 11784793, uuid: &[], }; - let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + let error = block_on(coin.validate_fee(validate_fee_args)).unwrap_err().into_inner(); match error { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("confirmed before min_block")), _ => panic!("Expected `WrongPaymentTx` early confirmation, found {:?}", error), } } +#[cfg(not(target_arch = "wasm32"))] #[test] fn validate_dex_fee_erc_confirmed_before_min_block() { let (_ctx, coin) = eth_coin_for_test( @@ -693,13 +706,14 @@ fn validate_dex_fee_erc_confirmed_before_min_block() { min_block_number: 11823975, uuid: &[], }; - let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + let error = block_on(coin.validate_fee(validate_fee_args)).unwrap_err().into_inner(); match error { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("confirmed before min_block")), _ => panic!("Expected `WrongPaymentTx` early confirmation, found {:?}", error), } } +#[cfg(not(target_arch = "wasm32"))] #[test] fn test_negotiate_swap_contract_addr_no_fallback() { let (_, coin) = eth_coin_for_test(EthCoinType::Eth, &[ETH_MAINNET_NODE], None, ETH_MAINNET_CHAIN_ID); @@ -727,6 +741,7 @@ fn test_negotiate_swap_contract_addr_no_fallback() { assert_eq!(Some(slice.to_vec().into()), result); } +#[cfg(not(target_arch = "wasm32"))] #[test] fn test_negotiate_swap_contract_addr_has_fallback() { let fallback = Address::from_str("0x8500AFc0bc5214728082163326C2FF0C73f4a871").unwrap(); @@ -815,15 +830,14 @@ fn polygon_check_if_my_payment_sent() { amount: &BigDecimal::default(), payment_instructions: &None, }; - let my_payment = coin - .check_if_my_payment_sent(if_my_payment_sent_args) - .wait() + let my_payment = block_on(coin.check_if_my_payment_sent(if_my_payment_sent_args)) .unwrap() .unwrap(); let expected_hash = BytesJson::from("69a20008cea0c15ee483b5bbdff942752634aa072dfd2ff715fe87eec302de11"); assert_eq!(expected_hash, my_payment.tx_hash_as_bytes()); } +#[cfg(not(target_arch = "wasm32"))] #[test] fn test_message_hash() { let key_pair = Random.generate().unwrap(); @@ -842,6 +856,7 @@ fn test_message_hash() { ); } +#[cfg(not(target_arch = "wasm32"))] #[test] fn test_sign_verify_message() { let key_pair = KeyPair::from_secret_slice( @@ -866,6 +881,7 @@ fn test_sign_verify_message() { assert!(is_valid); } +#[cfg(not(target_arch = "wasm32"))] #[test] fn test_eth_extract_secret() { let key_pair = Random.generate().unwrap(); diff --git a/mm2src/coins/eth/eth_wasm_tests.rs b/mm2src/coins/eth/eth_wasm_tests.rs index a36e0ac45a..287b3fb79a 100644 --- a/mm2src/coins/eth/eth_wasm_tests.rs +++ b/mm2src/coins/eth/eth_wasm_tests.rs @@ -40,7 +40,7 @@ async fn init_eth_coin_helper() -> Result<(MmArc, MmCoinEnum), String> { let req = json!({ "urls":ETH_SEPOLIA_NODES, - "swap_contract_address":ETH_SEPOLIA_SWAP_CONTRACT + "swap_contract_address":ETH_SEPOLIA_SWAP_CONTRACT, }); Ok((ctx.clone(), lp_coininit(&ctx, "ETH", &req).await?)) } diff --git a/mm2src/coins/eth/for_tests.rs b/mm2src/coins/eth/for_tests.rs index 72f3d080ad..cc6d5cd375 100644 --- a/mm2src/coins/eth/for_tests.rs +++ b/mm2src/coins/eth/for_tests.rs @@ -33,7 +33,7 @@ pub(crate) fn eth_coin_from_keypair( for url in urls.iter() { let node = HttpTransportNode { uri: url.parse().unwrap(), - gui_auth: false, + komodo_proxy: false, }; let transport = Web3Transport::new_http(node); let web3 = Web3::new(transport); @@ -50,7 +50,8 @@ pub(crate) fn eth_coin_from_keypair( }; let my_address = key_pair.address(); let coin_conf = coin_conf(&ctx, &ticker); - let gas_limit = extract_gas_limit_from_conf(&coin_conf).expect("expected valid gas_limit config"); + let gas_limit: EthGasLimit = extract_gas_limit_from_conf(&coin_conf).expect("expected valid gas_limit config"); + let gas_limit_v2: EthGasLimitV2 = extract_gas_limit_from_conf(&coin_conf).expect("expected valid gas_limit config"); let eth_coin = EthCoin(Arc::new(EthCoinImpl { coin_type, @@ -60,6 +61,7 @@ pub(crate) fn eth_coin_from_keypair( priv_key_policy: key_pair.into(), derivation_method: Arc::new(DerivationMethod::SingleAddress(my_address)), swap_contract_address: Address::from_str(ETH_SEPOLIA_SWAP_CONTRACT).unwrap(), + swap_v2_contracts: None, fallback_swap_contract, contract_supports_watchers: false, ticker, @@ -76,6 +78,7 @@ pub(crate) fn eth_coin_from_keypair( nfts_infos: Arc::new(Default::default()), platform_fee_estimator_state: Arc::new(FeeEstimatorState::CoinNotSupported), gas_limit, + gas_limit_v2, abortable_system: AbortableQueue::default(), })); (ctx, eth_coin) diff --git a/mm2src/coins/eth/maker_swap_v2_abi.json b/mm2src/coins/eth/maker_swap_v2_abi.json new file mode 100644 index 0000000000..74e5da3e80 --- /dev/null +++ b/mm2src/coins/eth/maker_swap_v2_abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"MakerPaymentRefundedSecret","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"MakerPaymentRefundedTimelock","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"MakerPaymentSent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"MakerPaymentSpent","type":"event"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"bytes32","name":"takerSecretHash","type":"bytes32"},{"internalType":"bytes32","name":"makerSecretHash","type":"bytes32"},{"internalType":"uint32","name":"paymentLockTime","type":"uint32"}],"name":"erc20MakerPayment","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"bytes32","name":"takerSecretHash","type":"bytes32"},{"internalType":"bytes32","name":"makerSecretHash","type":"bytes32"},{"internalType":"uint32","name":"paymentLockTime","type":"uint32"}],"name":"ethMakerPayment","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"makerPayments","outputs":[{"internalType":"bytes20","name":"paymentHash","type":"bytes20"},{"internalType":"uint32","name":"paymentLockTime","type":"uint32"},{"internalType":"enum EtomicSwapMakerV2.MakerPaymentState","name":"state","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"bytes32","name":"takerSecret","type":"bytes32"},{"internalType":"bytes32","name":"makerSecretHash","type":"bytes32"},{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"refundMakerPaymentSecret","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"bytes32","name":"takerSecretHash","type":"bytes32"},{"internalType":"bytes32","name":"makerSecretHash","type":"bytes32"},{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"refundMakerPaymentTimelock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"bytes32","name":"takerSecretHash","type":"bytes32"},{"internalType":"bytes32","name":"makerSecret","type":"bytes32"},{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"spendMakerPayment","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/mm2src/coins/eth/nft_swap_v2/errors.rs b/mm2src/coins/eth/nft_swap_v2/errors.rs index e66cd437d0..02cb3c7626 100644 --- a/mm2src/coins/eth/nft_swap_v2/errors.rs +++ b/mm2src/coins/eth/nft_swap_v2/errors.rs @@ -1,41 +1,27 @@ +pub(crate) use crate::eth::eth_swap_v2::PrepareTxDataError; use enum_derives::EnumFromStringify; #[derive(Debug, Display)] pub(crate) enum Erc721FunctionError { - AbiError(String), + #[display(fmt = "ABI error: {}", _0)] + ABIError(String), FunctionNotFound(String), } -#[derive(Debug, Display)] +#[derive(Debug, Display, EnumFromStringify)] pub(crate) enum HtlcParamsError { WrongPaymentTx(String), - TxDeserializationError(String), -} - -#[derive(Debug, Display, EnumFromStringify)] -pub(crate) enum PaymentStatusErr { - #[from_stringify("ethabi::Error")] - #[display(fmt = "Abi error: {}", _0)] - AbiError(String), - #[from_stringify("web3::Error")] - #[display(fmt = "Transport error: {}", _0)] - Transport(String), - #[display(fmt = "Internal error: {}", _0)] - Internal(String), - #[display(fmt = "Tx deserialization error: {}", _0)] - TxDeserializationError(String), -} - -#[derive(Debug, Display, EnumFromStringify)] -pub(crate) enum PrepareTxDataError { #[from_stringify("ethabi::Error")] - #[display(fmt = "Abi error: {}", _0)] - AbiError(String), - #[display(fmt = "Internal error: {}", _0)] - Internal(String), - Erc721FunctionError(Erc721FunctionError), + #[display(fmt = "ABI error: {}", _0)] + ABIError(String), + InvalidData(String), } impl From for PrepareTxDataError { - fn from(e: Erc721FunctionError) -> Self { Self::Erc721FunctionError(e) } + fn from(e: Erc721FunctionError) -> Self { + match e { + Erc721FunctionError::ABIError(e) => PrepareTxDataError::ABIError(e), + Erc721FunctionError::FunctionNotFound(e) => PrepareTxDataError::Internal(e), + } + } } diff --git a/mm2src/coins/eth/nft_swap_v2/mod.rs b/mm2src/coins/eth/nft_swap_v2/mod.rs index 19f5caca7f..f4c909bd32 100644 --- a/mm2src/coins/eth/nft_swap_v2/mod.rs +++ b/mm2src/coins/eth/nft_swap_v2/mod.rs @@ -1,52 +1,58 @@ -use crate::coin_errors::{ValidatePaymentError, ValidatePaymentResult}; use ethabi::{Contract, Token}; -use ethcore_transaction::{Action, UnverifiedTransactionWrapper}; +use ethcore_transaction::Action; use ethereum_types::{Address, U256}; +use ethkey::public_to_address; use futures::compat::Future01CompatExt; use mm2_err_handle::prelude::{MapToMmResult, MmError, MmResult}; use mm2_number::BigDecimal; -use std::convert::TryInto; -use web3::types::{Transaction as Web3Tx, TransactionId}; +use num_traits::Signed; +use web3::types::TransactionId; + +use super::ContractType; +use crate::coin_errors::{ValidatePaymentError, ValidatePaymentResult}; +use crate::eth::eth_swap_v2::{validate_from_to_and_status, validate_payment_state, EthPaymentType, PaymentMethod, + PaymentStatusErr, PrepareTxDataError, ZERO_VALUE}; +use crate::eth::{decode_contract_call, EthCoin, EthCoinType, MakerPaymentStateV2, SignedEthTx, ERC1155_CONTRACT, + ERC721_CONTRACT, NFT_MAKER_SWAP_V2}; +use crate::{ParseCoinAssocTypes, RefundNftMakerPaymentArgs, SendNftMakerPaymentArgs, SpendNftMakerPaymentArgs, + TransactionErr, ValidateNftMakerPaymentArgs}; pub(crate) mod errors; -use errors::{Erc721FunctionError, HtlcParamsError, PaymentStatusErr, PrepareTxDataError}; +use errors::{Erc721FunctionError, HtlcParamsError}; mod structs; -use structs::{ExpectedHtlcParams, PaymentType, ValidationParams}; - -use super::ContractType; -use crate::eth::{addr_from_raw_pubkey, decode_contract_call, EthCoin, EthCoinType, MakerPaymentStateV2, SignedEthTx, - TryToAddress, ERC1155_CONTRACT, ERC721_CONTRACT, NFT_MAKER_SWAP_V2}; -use crate::{ParseCoinAssocTypes, RefundPaymentArgs, SendNftMakerPaymentArgs, SpendNftMakerPaymentArgs, TransactionErr, - ValidateNftMakerPaymentArgs}; +use structs::{ExpectedHtlcParams, ValidationParams}; impl EthCoin { pub(crate) async fn send_nft_maker_payment_v2_impl( &self, args: SendNftMakerPaymentArgs<'_, Self>, ) -> Result { - try_tx_s!(validate_payment_args( - args.taker_secret_hash, - args.maker_secret_hash, - &args.amount, - args.nft_swap_info.contract_type - )); - let htlc_data = try_tx_s!(self.prepare_htlc_data(&args)); - match &self.coin_type { EthCoinType::Nft { .. } => { + try_tx_s!(validate_payment_args( + args.taker_secret_hash, + args.maker_secret_hash, + &args.amount, + args.nft_swap_info.contract_type + )); + let htlc_data = try_tx_s!(self.prepare_htlc_data(&args)); + let data = try_tx_s!(self.prepare_nft_maker_payment_v2_data(&args, htlc_data).await); + let gas_limit = self + .gas_limit_v2 + .nft_gas_limit(args.nft_swap_info.contract_type, PaymentMethod::Send); self.sign_and_send_transaction( - 0.into(), + ZERO_VALUE.into(), Action::Call(*args.nft_swap_info.token_address), data, - U256::from(self.gas_limit.eth_max_trade_gas), // TODO: fix to a more accurate const or estimated value + U256::from(gas_limit), ) .compat() .await }, - EthCoinType::Eth | EthCoinType::Erc20 { .. } => Err(TransactionErr::ProtocolNotSupported( - "ETH and ERC20 Protocols are not supported for NFT Swaps".to_string(), - )), + EthCoinType::Eth | EthCoinType::Erc20 { .. } => Err(TransactionErr::ProtocolNotSupported(ERRL!( + "ETH and ERC20 protocols are not supported for NFT swaps." + ))), } } @@ -54,43 +60,54 @@ impl EthCoin { &self, args: ValidateNftMakerPaymentArgs<'_, Self>, ) -> ValidatePaymentResult<()> { - let contract_type = args.nft_swap_info.contract_type; - validate_payment_args( - args.taker_secret_hash, - args.maker_secret_hash, - &args.amount, - contract_type, - ) - .map_err(ValidatePaymentError::InternalError)?; - let etomic_swap_contract = args.nft_swap_info.swap_contract_address; - let token_address = args.nft_swap_info.token_address; - let maker_address = addr_from_raw_pubkey(args.maker_pub).map_to_mm(ValidatePaymentError::InternalError)?; - let time_lock_u32 = args - .time_lock - .try_into() - .map_err(ValidatePaymentError::TimelockOverflow)?; - let swap_id = self.etomic_swap_id(time_lock_u32, args.maker_secret_hash); - let maker_status = self - .payment_status_v2( - *etomic_swap_contract, - Token::FixedBytes(swap_id.clone()), - &NFT_MAKER_SWAP_V2, - PaymentType::MakerPayments, - ) - .await?; - let tx_from_rpc = self - .transaction(TransactionId::Hash(args.maker_payment_tx.tx_hash())) - .await?; - let tx_from_rpc = tx_from_rpc.as_ref().ok_or_else(|| { - ValidatePaymentError::TxDoesNotExist(format!( - "Didn't find provided tx {:?} on ETH node", - args.maker_payment_tx.tx_hash() - )) - })?; - validate_from_to_and_maker_status(tx_from_rpc, maker_address, *token_address, maker_status).await?; match self.coin_type { EthCoinType::Nft { .. } => { - let (decoded, index_bytes) = get_decoded_tx_data_and_index_bytes(contract_type, &tx_from_rpc.input.0)?; + let nft_maker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| { + ValidatePaymentError::InternalError( + "Expected swap_v2_contracts to be Some, but found None".to_string(), + ) + })? + .nft_maker_swap_v2_contract; + let contract_type = args.nft_swap_info.contract_type; + validate_payment_args( + args.taker_secret_hash, + args.maker_secret_hash, + &args.amount, + contract_type, + ) + .map_err(ValidatePaymentError::InternalError)?; + let token_address = args.nft_swap_info.token_address; + let maker_address = public_to_address(args.maker_pub); + let swap_id = self.etomic_swap_id_v2(args.time_lock, args.maker_secret_hash); + let maker_status = self + .payment_status_v2( + nft_maker_swap_v2_contract, + Token::FixedBytes(swap_id.clone()), + &NFT_MAKER_SWAP_V2, + EthPaymentType::MakerPayments, + 2, + ) + .await?; + let tx_from_rpc = self + .transaction(TransactionId::Hash(args.maker_payment_tx.tx_hash())) + .await?; + let tx_from_rpc = tx_from_rpc.as_ref().ok_or_else(|| { + ValidatePaymentError::TxDoesNotExist(format!( + "Didn't find provided tx {:?} on ETH node", + args.maker_payment_tx.tx_hash() + )) + })?; + validate_from_to_and_status( + tx_from_rpc, + maker_address, + *token_address, + maker_status, + MakerPaymentStateV2::PaymentSent as u8, + )?; + + let (decoded, bytes_index) = get_decoded_tx_data_and_bytes_index(contract_type, &tx_from_rpc.input.0)?; let amount = if matches!(contract_type, &ContractType::Erc1155) { Some(args.amount.to_string()) @@ -100,14 +117,13 @@ impl EthCoin { let validation_params = ValidationParams { maker_address, - etomic_swap_contract: *etomic_swap_contract, + nft_maker_swap_v2_contract, token_id: args.nft_swap_info.token_id, amount, }; validate_decoded_data(&decoded, &validation_params)?; - let taker_address = - addr_from_raw_pubkey(args.taker_pub).map_to_mm(ValidatePaymentError::InternalError)?; + let taker_address = public_to_address(args.taker_pub); let htlc_params = ExpectedHtlcParams { swap_id, taker_address, @@ -116,7 +132,7 @@ impl EthCoin { maker_secret_hash: args.maker_secret_hash.to_vec(), time_lock: U256::from(args.time_lock), }; - decode_and_validate_htlc_params(decoded, index_bytes, htlc_params)?; + decode_and_validate_htlc_params(decoded, bytes_index, htlc_params)?; }, EthCoinType::Eth | EthCoinType::Erc20 { .. } => { return MmError::err(ValidatePaymentError::InternalError( @@ -131,52 +147,147 @@ impl EthCoin { &self, args: SpendNftMakerPaymentArgs<'_, Self>, ) -> Result { - let etomic_swap_contract = args.swap_contract_address; - if args.maker_secret.len() != 32 { - return Err(TransactionErr::Plain(ERRL!("maker_secret must be 32 bytes"))); - } - let contract_type = args.contract_type; - let (decoded, index_bytes) = try_tx_s!(get_decoded_tx_data_and_index_bytes( - contract_type, - args.maker_payment_tx.unsigned().data() - )); - - let (state, htlc_params) = try_tx_s!( - self.status_and_htlc_params_from_tx_data( - *etomic_swap_contract, - &NFT_MAKER_SWAP_V2, - &decoded, - index_bytes, - PaymentType::MakerPayments, - ) - .await - ); match self.coin_type { EthCoinType::Nft { .. } => { + let nft_maker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| { + TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")) + })? + .nft_maker_swap_v2_contract; + if args.maker_secret.len() != 32 { + return Err(TransactionErr::Plain(ERRL!("maker_secret must be 32 bytes"))); + } + let (decoded, bytes_index) = try_tx_s!(get_decoded_tx_data_and_bytes_index( + args.contract_type, + args.maker_payment_tx.unsigned().data() + )); + + let (state, htlc_params) = try_tx_s!( + self.status_and_htlc_params_from_tx_data( + nft_maker_swap_v2_contract, + &NFT_MAKER_SWAP_V2, + &decoded, + bytes_index, + EthPaymentType::MakerPayments, + 2 + ) + .await + ); let data = try_tx_s!(self.prepare_spend_nft_maker_v2_data(&args, decoded, htlc_params, state)); + let gas_limit = self + .gas_limit_v2 + .nft_gas_limit(args.contract_type, PaymentMethod::Spend); self.sign_and_send_transaction( - 0.into(), - Action::Call(*etomic_swap_contract), + ZERO_VALUE.into(), + Action::Call(nft_maker_swap_v2_contract), data, - U256::from(self.gas_limit.eth_max_trade_gas), // TODO: fix to a more accurate const or estimated value + U256::from(gas_limit), ) .compat() .await }, - EthCoinType::Eth | EthCoinType::Erc20 { .. } => Err(TransactionErr::ProtocolNotSupported( - "ETH and ERC20 Protocols are not supported for NFT Swaps".to_string(), - )), + EthCoinType::Eth | EthCoinType::Erc20 { .. } => Err(TransactionErr::ProtocolNotSupported(ERRL!( + "ETH and ERC20 protocols are not supported for NFT swaps." + ))), } } pub(crate) async fn refund_nft_maker_payment_v2_timelock_impl( &self, - args: RefundPaymentArgs<'_>, + args: RefundNftMakerPaymentArgs<'_, Self>, ) -> Result { - let _etomic_swap_contract = try_tx_s!(args.swap_contract_address.try_to_address()); - let tx: UnverifiedTransactionWrapper = try_tx_s!(rlp::decode(args.payment_tx)); - let _payment = try_tx_s!(SignedEthTx::new(tx)); - todo!() + match self.coin_type { + EthCoinType::Nft { .. } => { + let nft_maker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| { + TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")) + })? + .nft_maker_swap_v2_contract; + let (decoded, bytes_index) = try_tx_s!(get_decoded_tx_data_and_bytes_index( + args.contract_type, + args.maker_payment_tx.unsigned().data() + )); + + let (state, htlc_params) = try_tx_s!( + self.status_and_htlc_params_from_tx_data( + nft_maker_swap_v2_contract, + &NFT_MAKER_SWAP_V2, + &decoded, + bytes_index, + EthPaymentType::MakerPayments, + 2 + ) + .await + ); + let data = + try_tx_s!(self.prepare_refund_nft_maker_payment_v2_timelock(&args, decoded, htlc_params, state)); + let gas_limit = self + .gas_limit_v2 + .nft_gas_limit(args.contract_type, PaymentMethod::RefundTimelock); + self.sign_and_send_transaction( + ZERO_VALUE.into(), + Action::Call(nft_maker_swap_v2_contract), + data, + U256::from(gas_limit), + ) + .compat() + .await + }, + EthCoinType::Eth | EthCoinType::Erc20 { .. } => Err(TransactionErr::ProtocolNotSupported(ERRL!( + "ETH and ERC20 protocols are not supported for NFT swaps." + ))), + } + } + + pub(crate) async fn refund_nft_maker_payment_v2_secret_impl( + &self, + args: RefundNftMakerPaymentArgs<'_, Self>, + ) -> Result { + match self.coin_type { + EthCoinType::Nft { .. } => { + let nft_maker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| { + TransactionErr::Plain(ERRL!("Expected swap_v2_contracts to be Some, but found None")) + })? + .nft_maker_swap_v2_contract; + let (decoded, bytes_index) = try_tx_s!(get_decoded_tx_data_and_bytes_index( + args.contract_type, + args.maker_payment_tx.unsigned().data() + )); + + let (state, htlc_params) = try_tx_s!( + self.status_and_htlc_params_from_tx_data( + nft_maker_swap_v2_contract, + &NFT_MAKER_SWAP_V2, + &decoded, + bytes_index, + EthPaymentType::MakerPayments, + 2 + ) + .await + ); + + let data = + try_tx_s!(self.prepare_refund_nft_maker_payment_v2_secret(&args, decoded, htlc_params, state)); + let gas_limit = self + .gas_limit_v2 + .nft_gas_limit(args.contract_type, PaymentMethod::RefundSecret); + self.sign_and_send_transaction( + ZERO_VALUE.into(), + Action::Call(nft_maker_swap_v2_contract), + data, + U256::from(gas_limit), + ) + .compat() + .await + }, + EthCoinType::Eth | EthCoinType::Erc20 { .. } => Err(TransactionErr::ProtocolNotSupported(ERRL!( + "ETH and ERC20 protocols are not supported for NFT swaps." + ))), + } } async fn prepare_nft_maker_payment_v2_data( @@ -184,6 +295,12 @@ impl EthCoin { args: &SendNftMakerPaymentArgs<'_, Self>, htlc_data: Vec, ) -> Result, PrepareTxDataError> { + let nft_maker_swap_v2_contract = self + .swap_v2_contracts + .ok_or_else(|| { + PrepareTxDataError::Internal("Expected swap_v2_contracts to be Some, but found None".to_string()) + })? + .nft_maker_swap_v2_contract; match args.nft_swap_info.contract_type { ContractType::Erc1155 => { let function = ERC1155_CONTRACT.function("safeTransferFrom")?; @@ -191,7 +308,7 @@ impl EthCoin { .map_err(|e| PrepareTxDataError::Internal(e.to_string()))?; let data = function.encode_input(&[ Token::Address(self.my_addr().await), - Token::Address(*args.nft_swap_info.swap_contract_address), + Token::Address(nft_maker_swap_v2_contract), Token::Uint(U256::from(args.nft_swap_info.token_id)), Token::Uint(amount_u256), Token::Bytes(htlc_data), @@ -202,7 +319,7 @@ impl EthCoin { let function = erc721_transfer_with_data()?; let data = function.encode_input(&[ Token::Address(self.my_addr().await), - Token::Address(*args.nft_swap_info.swap_contract_address), + Token::Address(nft_maker_swap_v2_contract), Token::Uint(U256::from(args.nft_swap_info.token_id)), Token::Bytes(htlc_data), ])?; @@ -212,51 +329,19 @@ impl EthCoin { } fn prepare_htlc_data(&self, args: &SendNftMakerPaymentArgs<'_, Self>) -> Result, PrepareTxDataError> { - let taker_address = - addr_from_raw_pubkey(args.taker_pub).map_err(|e| PrepareTxDataError::Internal(ERRL!("{}", e)))?; - let time_lock_u32 = args - .time_lock - .try_into() - .map_err(|e| PrepareTxDataError::Internal(ERRL!("{}", e)))?; - let id = self.etomic_swap_id(time_lock_u32, args.maker_secret_hash); + let taker_address = public_to_address(args.taker_pub); + let id = self.etomic_swap_id_v2(args.time_lock, args.maker_secret_hash); let encoded = ethabi::encode(&[ Token::FixedBytes(id), Token::Address(taker_address), Token::Address(*args.nft_swap_info.token_address), Token::FixedBytes(args.taker_secret_hash.to_vec()), Token::FixedBytes(args.maker_secret_hash.to_vec()), - Token::Uint(U256::from(time_lock_u32)), + Token::Uint(U256::from(args.time_lock)), ]); Ok(encoded) } - /// Retrieves the payment status from a given smart contract address based on the swap ID and state type. - async fn payment_status_v2( - &self, - swap_address: Address, - swap_id: Token, - contract_abi: &Contract, - state_type: PaymentType, - ) -> Result { - let function_name = state_type.as_str(); - let function = contract_abi.function(function_name)?; - let data = function.encode_input(&[swap_id])?; - let bytes = self - .call_request(self.my_addr().await, swap_address, None, Some(data.into())) - .await?; - let decoded_tokens = function.decode_output(&bytes.0)?; - let state = decoded_tokens - .get(2) - .ok_or_else(|| PaymentStatusErr::Internal(ERRL!("Payment status must contain 'state' as the 2nd token")))?; - match state { - Token::Uint(state) => Ok(*state), - _ => Err(PaymentStatusErr::Internal(ERRL!( - "Payment status must be Uint, got {:?}", - state - ))), - } - } - /// Prepares the encoded transaction data for spending a maker's NFT payment on the blockchain. /// /// This function selects the appropriate contract function based on the NFT's contract type (ERC1155 or ERC721) @@ -268,40 +353,88 @@ impl EthCoin { htlc_params: Vec, state: U256, ) -> Result, PrepareTxDataError> { + validate_payment_state(args.maker_payment_tx, state, MakerPaymentStateV2::PaymentSent as u8)?; + let spend_func = match args.contract_type { ContractType::Erc1155 => NFT_MAKER_SWAP_V2.function("spendErc1155MakerPayment")?, ContractType::Erc721 => NFT_MAKER_SWAP_V2.function("spendErc721MakerPayment")?, }; - - if state != U256::from(MakerPaymentStateV2::PaymentSent as u8) { - return Err(PrepareTxDataError::Internal(ERRL!( - "Payment {:?} state is not PAYMENT_STATE_SENT, got {}", - args.maker_payment_tx, - state - ))); + // Initialize tokens with common elements + let mut input_tokens = vec![ + htlc_params[0].clone(), // swapId + Token::Address(args.maker_payment_tx.sender()), + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret.to_vec()), + htlc_params[2].clone(), // tokenAddress + decoded[2].clone(), // tokenId + ]; + // Add specific elements based on contract type + if let ContractType::Erc1155 = args.contract_type { + input_tokens.push(decoded[3].clone()); // amount } - let input_tokens = match args.contract_type { - ContractType::Erc1155 => vec![ - htlc_params[0].clone(), // swap_id - Token::Address(args.maker_payment_tx.sender()), - Token::FixedBytes(args.taker_secret_hash.to_vec()), - Token::FixedBytes(args.maker_secret.to_vec()), - htlc_params[2].clone(), // tokenAddress - decoded[2].clone(), // tokenId - decoded[3].clone(), // amount - ], - ContractType::Erc721 => vec![ - htlc_params[0].clone(), // swap_id - Token::Address(args.maker_payment_tx.sender()), - Token::FixedBytes(args.taker_secret_hash.to_vec()), - Token::FixedBytes(args.maker_secret.to_vec()), - htlc_params[2].clone(), // tokenAddress - decoded[2].clone(), // tokenId - ], + let data = spend_func.encode_input(&input_tokens)?; + Ok(data) + } + + fn prepare_refund_nft_maker_payment_v2_timelock( + &self, + args: &RefundNftMakerPaymentArgs<'_, Self>, + decoded: Vec, + htlc_params: Vec, + state: U256, + ) -> Result, PrepareTxDataError> { + validate_payment_state(args.maker_payment_tx, state, MakerPaymentStateV2::PaymentSent as u8)?; + + let refund_func = match args.contract_type { + ContractType::Erc1155 => NFT_MAKER_SWAP_V2.function("refundErc1155MakerPaymentTimelock")?, + ContractType::Erc721 => NFT_MAKER_SWAP_V2.function("refundErc721MakerPaymentTimelock")?, }; + // Initialize tokens with common elements + let mut input_tokens = vec![ + htlc_params[0].clone(), // swapId + htlc_params[1].clone(), // takerAddress + Token::FixedBytes(args.taker_secret_hash.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + htlc_params[2].clone(), // tokenAddress + decoded[2].clone(), // tokenId + ]; + // Add specific elements based on contract type + if let ContractType::Erc1155 = args.contract_type { + input_tokens.push(decoded[3].clone()); // amount + } - let data = spend_func.encode_input(&input_tokens)?; + let data = refund_func.encode_input(&input_tokens)?; + Ok(data) + } + + fn prepare_refund_nft_maker_payment_v2_secret( + &self, + args: &RefundNftMakerPaymentArgs<'_, Self>, + decoded: Vec, + htlc_params: Vec, + state: U256, + ) -> Result, PrepareTxDataError> { + validate_payment_state(args.maker_payment_tx, state, MakerPaymentStateV2::PaymentSent as u8)?; + + let refund_func = match args.contract_type { + ContractType::Erc1155 => NFT_MAKER_SWAP_V2.function("refundErc1155MakerPaymentSecret")?, + ContractType::Erc721 => NFT_MAKER_SWAP_V2.function("refundErc721MakerPaymentSecret")?, + }; + // Initialize tokens with common elements + let mut input_tokens = vec![ + htlc_params[0].clone(), // swapId + htlc_params[1].clone(), // takerAddress + Token::FixedBytes(args.taker_secret.to_vec()), + Token::FixedBytes(args.maker_secret_hash.to_vec()), + htlc_params[2].clone(), // tokenAddress + decoded[2].clone(), // tokenId + ]; + // Add specific elements based on contract type + if let ContractType::Erc1155 = args.contract_type { + input_tokens.push(decoded[3].clone()); // amount + } + let data = refund_func.encode_input(&input_tokens)?; Ok(data) } @@ -311,28 +444,30 @@ impl EthCoin { contract_abi: &Contract, decoded_data: &[Token], index: usize, - state_type: PaymentType, + payment_type: EthPaymentType, + state_index: usize, ) -> Result<(U256, Vec), PaymentStatusErr> { let data_bytes = match decoded_data.get(index) { Some(Token::Bytes(data_bytes)) => data_bytes, _ => { - return Err(PaymentStatusErr::TxDeserializationError(ERRL!( + return Err(PaymentStatusErr::InvalidData(ERRL!( "Failed to decode HTLCParams from data_bytes" ))) }, }; - let htlc_params = match ethabi::decode(htlc_params(), data_bytes) { - Ok(htlc_params) => htlc_params, - Err(_) => { - return Err(PaymentStatusErr::TxDeserializationError(ERRL!( - "Failed to decode HTLCParams from data_bytes" - ))) - }, - }; + let htlc_params = + ethabi::decode(htlc_params(), data_bytes).map_err(|e| PaymentStatusErr::ABIError(ERRL!("{}", e)))?; let state = self - .payment_status_v2(swap_address, htlc_params[0].clone(), contract_abi, state_type) + .payment_status_v2( + swap_address, + // swap_id has 0 index + htlc_params[0].clone(), + contract_abi, + payment_type, + state_index, + ) .await?; Ok((state, htlc_params)) @@ -343,7 +478,11 @@ impl EthCoin { fn validate_decoded_data(decoded: &[Token], params: &ValidationParams) -> Result<(), MmError> { let checks = vec![ (0, Token::Address(params.maker_address), "maker_address"), - (1, Token::Address(params.etomic_swap_contract), "etomic_swap_contract"), + ( + 1, + Token::Address(params.nft_maker_swap_v2_contract), + "nft_maker_swap_v2_contract", + ), (2, Token::Uint(U256::from(params.token_id)), "token_id"), ]; @@ -378,20 +517,13 @@ fn decode_and_validate_htlc_params( let data_bytes = match decoded.get(index) { Some(Token::Bytes(bytes)) => bytes, _ => { - return MmError::err(HtlcParamsError::TxDeserializationError( + return MmError::err(HtlcParamsError::InvalidData( "Expected Bytes for HTLCParams data".to_string(), )) }, }; - let decoded_params = match ethabi::decode(htlc_params(), data_bytes) { - Ok(params) => params, - Err(_) => { - return MmError::err(HtlcParamsError::TxDeserializationError( - "Failed to decode HTLCParams from data_bytes".to_string(), - )) - }, - }; + let decoded_params = ethabi::decode(htlc_params(), data_bytes)?; let expected_taker_secret_hash = Token::FixedBytes(expected_params.taker_secret_hash.clone()); let expected_maker_secret_hash = Token::FixedBytes(expected_params.maker_secret_hash.clone()); @@ -440,7 +572,7 @@ fn htlc_params() -> &'static [ethabi::ParamType] { /// function to check if BigDecimal is a positive integer #[inline(always)] -fn is_positive_integer(amount: &BigDecimal) -> bool { amount == &amount.with_scale(0) && amount > &BigDecimal::from(0) } +fn is_positive_integer(amount: &BigDecimal) -> bool { amount == &amount.with_scale(0) && amount.is_positive() } fn validate_payment_args<'a>( taker_secret_hash: &'a [u8], @@ -470,47 +602,19 @@ fn validate_payment_args<'a>( Ok(()) } -async fn validate_from_to_and_maker_status( - tx_from_rpc: &Web3Tx, - expected_from: Address, - expected_to: Address, - maker_status: U256, -) -> ValidatePaymentResult<()> { - if maker_status != U256::from(MakerPaymentStateV2::PaymentSent as u8) { - return MmError::err(ValidatePaymentError::UnexpectedPaymentState(format!( - "NFT Maker Payment state is not PAYMENT_STATE_SENT, got {}", - maker_status - ))); - } - if tx_from_rpc.from != Some(expected_from) { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "NFT Maker Payment tx {:?} was sent from wrong address, expected {:?}", - tx_from_rpc, expected_from - ))); - } - // As NFT owner calls "safeTransferFrom" directly, then in Transaction 'to' field we expect token_address - if tx_from_rpc.to != Some(expected_to) { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "NFT Maker Payment tx {:?} was sent to wrong address, expected {:?}", - tx_from_rpc, expected_to, - ))); - } - Ok(()) -} - -/// Identifies the correct "safeTransferFrom" function based on the contract type (either ERC1155 or ERC721) +/// Identifies the correct `"safeTransferFrom"` function based on the contract type (either ERC1155 or ERC721) /// and decodes the provided contract call bytes using the ABI of the identified function. Additionally, it returns /// the index position of the "bytes" field within the function's parameters. -pub(crate) fn get_decoded_tx_data_and_index_bytes( +pub(crate) fn get_decoded_tx_data_and_bytes_index( contract_type: &ContractType, contract_call_bytes: &[u8], ) -> Result<(Vec, usize), PrepareTxDataError> { - let (send_func, index_bytes) = match contract_type { + let (send_func, bytes_index) = match contract_type { ContractType::Erc1155 => (ERC1155_CONTRACT.function("safeTransferFrom")?, 4), ContractType::Erc721 => (erc721_transfer_with_data()?, 3), }; let decoded = decode_contract_call(send_func, contract_call_bytes)?; - Ok((decoded, index_bytes)) + Ok((decoded, bytes_index)) } /// ERC721 contract has overloaded versions of the `safeTransferFrom` function, @@ -520,7 +624,7 @@ pub(crate) fn get_decoded_tx_data_and_index_bytes( fn erc721_transfer_with_data<'a>() -> Result<&'a ethabi::Function, Erc721FunctionError> { let functions = ERC721_CONTRACT .functions_by_name("safeTransferFrom") - .map_err(|e| Erc721FunctionError::AbiError(ERRL!("{}", e)))?; + .map_err(|e| Erc721FunctionError::ABIError(ERRL!("{}", e)))?; // Find the correct function variant by inspecting the input parameters. let function = functions diff --git a/mm2src/coins/eth/nft_swap_v2/structs.rs b/mm2src/coins/eth/nft_swap_v2/structs.rs index a0c129bf4b..7bd4130d9f 100644 --- a/mm2src/coins/eth/nft_swap_v2/structs.rs +++ b/mm2src/coins/eth/nft_swap_v2/structs.rs @@ -11,23 +11,8 @@ pub(crate) struct ExpectedHtlcParams { pub(crate) struct ValidationParams<'a> { pub(crate) maker_address: Address, - pub(crate) etomic_swap_contract: Address, + pub(crate) nft_maker_swap_v2_contract: Address, pub(crate) token_id: &'a [u8], // Optional, as it's not needed for ERC721 pub(crate) amount: Option, } - -#[allow(dead_code)] -pub(crate) enum PaymentType { - MakerPayments, - TakerPayments, -} - -impl PaymentType { - pub(crate) fn as_str(&self) -> &'static str { - match self { - PaymentType::MakerPayments => "makerPayments", - PaymentType::TakerPayments => "takerPayments", - } - } -} diff --git a/mm2src/coins/eth/taker_swap_v2_abi.json b/mm2src/coins/eth/taker_swap_v2_abi.json new file mode 100644 index 0000000000..4f2c8b782c --- /dev/null +++ b/mm2src/coins/eth/taker_swap_v2_abi.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"feeAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"TakerPaymentApproved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"secret","type":"bytes32"}],"name":"TakerPaymentRefundedSecret","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"TakerPaymentRefundedTimelock","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"TakerPaymentSent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"id","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"secret","type":"bytes32"}],"name":"TakerPaymentSpent","type":"event"},{"inputs":[],"name":"dexFeeAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"dexFee","type":"uint256"},{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"bytes32","name":"takerSecretHash","type":"bytes32"},{"internalType":"bytes32","name":"makerSecretHash","type":"bytes32"},{"internalType":"uint32","name":"preApproveLockTime","type":"uint32"},{"internalType":"uint32","name":"paymentLockTime","type":"uint32"}],"name":"erc20TakerPayment","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"dexFee","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"bytes32","name":"takerSecretHash","type":"bytes32"},{"internalType":"bytes32","name":"makerSecretHash","type":"bytes32"},{"internalType":"uint32","name":"preApproveLockTime","type":"uint32"},{"internalType":"uint32","name":"paymentLockTime","type":"uint32"}],"name":"ethTakerPayment","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"dexFee","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"bytes32","name":"takerSecret","type":"bytes32"},{"internalType":"bytes32","name":"makerSecretHash","type":"bytes32"},{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"refundTakerPaymentSecret","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"dexFee","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"bytes32","name":"takerSecretHash","type":"bytes32"},{"internalType":"bytes32","name":"makerSecretHash","type":"bytes32"},{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"refundTakerPaymentTimelock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"dexFee","type":"uint256"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"bytes32","name":"takerSecretHash","type":"bytes32"},{"internalType":"bytes32","name":"makerSecret","type":"bytes32"},{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"spendTakerPayment","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"dexFee","type":"uint256"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"bytes32","name":"takerSecretHash","type":"bytes32"},{"internalType":"bytes32","name":"makerSecretHash","type":"bytes32"},{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"takerPaymentApprove","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"takerPayments","outputs":[{"internalType":"bytes20","name":"paymentHash","type":"bytes20"},{"internalType":"uint32","name":"preApproveLockTime","type":"uint32"},{"internalType":"uint32","name":"paymentLockTime","type":"uint32"},{"internalType":"enum EtomicSwapTakerV2.TakerPaymentState","name":"state","type":"uint8"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index e8bb6cebeb..cee2313ba2 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -1,4 +1,6 @@ use super::*; +use crate::eth::erc20::{get_enabled_erc20_by_contract, get_token_decimals}; +use crate::eth::web3_transport::http_transport::HttpTransport; use crate::hd_wallet::{load_hd_accounts_from_storage, HDAccountsMutex, HDPathAccountToAddressId, HDWalletCoinStorage, HDWalletStorageError, DEFAULT_GAP_LIMIT}; use crate::nft::get_nfts_for_activation; @@ -12,6 +14,8 @@ use instant::Instant; use mm2_err_handle::common_errors::WithInternal; #[cfg(target_arch = "wasm32")] use mm2_metamask::{from_metamask_error, MetamaskError, MetamaskRpcError, WithMetamaskRpcError}; +use mm2_p2p::p2p_ctx::P2PContext; +use proxy_signature::RawMessage; use rpc_task::RpcTaskError; use std::sync::atomic::Ordering; use url::Url; @@ -59,6 +63,8 @@ pub enum EthActivationV2Error { HwError(HwRpcError), #[display(fmt = "Hardware wallet must be called within rpc task framework")] InvalidHardwareWalletCall, + #[display(fmt = "Custom token error: {}", _0)] + CustomTokenError(CustomTokenError), } impl From for EthActivationV2Error { @@ -90,6 +96,7 @@ impl From for EthActivationV2Error { EthActivationV2Error::UnexpectedDerivationMethod(err) }, EthTokenActivationError::PrivKeyPolicyNotAllowed(e) => EthActivationV2Error::PrivKeyPolicyNotAllowed(e), + EthTokenActivationError::CustomTokenError(e) => EthActivationV2Error::CustomTokenError(e), } } } @@ -175,6 +182,8 @@ pub struct EthActivationV2Request { #[serde(default)] pub rpc_mode: EthRpcMode, pub swap_contract_address: Address, + #[serde(default)] + pub swap_v2_contracts: Option, pub fallback_swap_contract: Option
, #[serde(default)] pub contract_supports_watchers: bool, @@ -193,7 +202,7 @@ pub struct EthActivationV2Request { pub struct EthNode { pub url: String, #[serde(default)] - pub gui_auth: bool, + pub komodo_proxy: bool, } #[derive(Display, Serialize, SerializeErrorType)] @@ -206,6 +215,7 @@ pub enum EthTokenActivationError { Transport(String), UnexpectedDerivationMethod(UnexpectedDerivationMethod), PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), + CustomTokenError(CustomTokenError), } impl From for EthTokenActivationError { @@ -335,7 +345,7 @@ pub enum NftProviderEnum { Moralis { url: Url, #[serde(default)] - proxy_auth: bool, + komodo_proxy: bool, }, } @@ -371,9 +381,11 @@ pub struct NftProtocol { impl EthCoin { pub async fn initialize_erc20_token( &self, + ticker: String, activation_params: Erc20TokenActivationRequest, + token_conf: Json, protocol: Erc20Protocol, - ticker: String, + is_custom: bool, ) -> MmResult { // TODO // Check if ctx is required. @@ -382,9 +394,24 @@ impl EthCoin { .ok_or_else(|| String::from("No context")) .map_err(EthTokenActivationError::InternalError)?; - let conf = coin_conf(&ctx, &ticker); + // Todo: when custom token config storage is added, this might not be needed + // `is_custom` was added to avoid this unnecessary check for non-custom tokens + if is_custom { + match get_enabled_erc20_by_contract(&ctx, protocol.token_addr).await { + Ok(Some(token)) => { + return MmError::err(EthTokenActivationError::CustomTokenError( + CustomTokenError::TokenWithSameContractAlreadyActivated { + ticker: token.ticker().to_string(), + contract_address: display_eth_address(&protocol.token_addr), + }, + )); + }, + Ok(None) => {}, + Err(e) => return MmError::err(EthTokenActivationError::InternalError(e.to_string())), + } + } - let decimals = match conf["decimals"].as_u64() { + let decimals = match token_conf["decimals"].as_u64() { None | Some(0) => get_token_decimals( &self .web3() @@ -397,27 +424,13 @@ impl EthCoin { Some(d) => d as u8, }; - let web3_instances: Vec = self - .web3_instances - .lock() - .await - .iter() - .map(|node| { - let mut transport = node.web3.transport().clone(); - if let Some(auth) = transport.proxy_auth_validation_generator_as_mut() { - auth.coin_ticker = ticker.clone(); - } - let web3 = Web3::new(transport); - Web3Instance { - web3, - is_parity: node.is_parity, - } - }) - .collect(); - let required_confirmations = activation_params .required_confirmations - .unwrap_or_else(|| conf["required_confirmations"].as_u64().unwrap_or(1)) + .unwrap_or_else(|| { + token_conf["required_confirmations"] + .as_u64() + .unwrap_or(self.required_confirmations()) + }) .into(); // Create an abortable system linked to the `MmCtx` so if the app is stopped on `MmArc::stop`, @@ -428,9 +441,11 @@ impl EthCoin { platform: protocol.platform, token_addr: protocol.token_addr, }; - let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?; - let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type).await?; - let gas_limit = extract_gas_limit_from_conf(&conf) + let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &token_conf, &coin_type).await?; + let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &token_conf, &coin_type).await?; + let gas_limit: EthGasLimit = extract_gas_limit_from_conf(&token_conf) + .map_to_mm(|e| EthTokenActivationError::InternalError(format!("invalid gas_limit config {}", e)))?; + let gas_limit_v2: EthGasLimitV2 = extract_gas_limit_from_conf(&token_conf) .map_to_mm(|e| EthTokenActivationError::InternalError(format!("invalid gas_limit config {}", e)))?; let token = EthCoinImpl { @@ -442,11 +457,12 @@ impl EthCoin { coin_type, sign_message_prefix: self.sign_message_prefix.clone(), swap_contract_address: self.swap_contract_address, + swap_v2_contracts: self.swap_v2_contracts, fallback_swap_contract: self.fallback_swap_contract, contract_supports_watchers: self.contract_supports_watchers, decimals, ticker, - web3_instances: AsyncMutex::new(web3_instances), + web3_instances: AsyncMutex::new(self.web3_instances.lock().await.clone()), history_sync_state: Mutex::new(self.history_sync_state.lock().unwrap().clone()), swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), max_eth_tx_type, @@ -460,6 +476,7 @@ impl EthCoin { nfts_infos: Default::default(), platform_fee_estimator_state, gas_limit, + gas_limit_v2, abortable_system, }; @@ -474,10 +491,10 @@ impl EthCoin { /// It fetches NFT details from a given URL to populate the `nfts_infos` field, which stores information about the user's NFTs. /// /// This setup allows the Global NFT to function like a coin, supporting swap operations and providing easy access to NFT details via `nfts_infos`. - pub async fn global_nft_from_platform_coin( + pub async fn initialize_global_nft( &self, original_url: &Url, - proxy_auth: &bool, + komodo_proxy: bool, ) -> MmResult { let chain = Chain::from_ticker(self.ticker())?; let ticker = chain.to_nft_ticker().to_string(); @@ -485,9 +502,16 @@ impl EthCoin { let ctx = MmArc::from_weak(&self.ctx) .ok_or_else(|| String::from("No context")) .map_err(EthTokenActivationError::InternalError)?; + let p2p_ctx = P2PContext::fetch_from_mm_arc(&ctx); let conf = coin_conf(&ctx, &ticker); + let required_confirmations = AtomicU64::new( + conf["required_confirmations"] + .as_u64() + .unwrap_or_else(|| self.required_confirmations.load(Ordering::Relaxed)), + ); + // Create an abortable system linked to the `platform_coin` (which is self) so if the platform coin is disabled, // all spawned futures related to global Non-Fungible Token will be aborted as well. let abortable_system = self.abortable_system.create_subsystem()?; @@ -495,17 +519,25 @@ impl EthCoin { // Todo: support HD wallet for NFTs, currently we get nfts for enabled address only and there might be some issues when activating NFTs while ETH is activated with HD wallet let my_address = self.derivation_method.single_addr_or_err().await?; - let my_address_str = display_eth_address(&my_address); - let signed_message = - generate_signed_message(*proxy_auth, &chain, my_address_str, self.priv_key_policy()).await?; + let proxy_sign = if komodo_proxy { + let uri = Uri::from_str(original_url.as_ref()) + .map_err(|e| EthTokenActivationError::InternalError(e.to_string()))?; + let proxy_sign = RawMessage::sign(p2p_ctx.keypair(), &uri, 0, common::PROXY_REQUEST_EXPIRATION_SEC) + .map_err(|e| EthTokenActivationError::InternalError(e.to_string()))?; + Some(proxy_sign) + } else { + None + }; - let nft_infos = get_nfts_for_activation(&chain, &my_address, original_url, signed_message.as_ref()).await?; + let nft_infos = get_nfts_for_activation(&chain, &my_address, original_url, proxy_sign).await?; let coin_type = EthCoinType::Nft { platform: self.ticker.clone(), }; let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(&ctx, &conf, &coin_type).await?; let max_eth_tx_type = get_max_eth_tx_type_conf(&ctx, &conf, &coin_type).await?; - let gas_limit = extract_gas_limit_from_conf(&conf) + let gas_limit: EthGasLimit = extract_gas_limit_from_conf(&conf) + .map_to_mm(|e| EthTokenActivationError::InternalError(format!("invalid gas_limit config {}", e)))?; + let gas_limit_v2: EthGasLimitV2 = extract_gas_limit_from_conf(&conf) .map_to_mm(|e| EthTokenActivationError::InternalError(format!("invalid gas_limit config {}", e)))?; let global_nft = EthCoinImpl { @@ -515,14 +547,15 @@ impl EthCoin { derivation_method: self.derivation_method.clone(), sign_message_prefix: self.sign_message_prefix.clone(), swap_contract_address: self.swap_contract_address, + swap_v2_contracts: self.swap_v2_contracts, fallback_swap_contract: self.fallback_swap_contract, contract_supports_watchers: self.contract_supports_watchers, - web3_instances: self.web3_instances.lock().await.clone().into(), + web3_instances: AsyncMutex::new(self.web3_instances.lock().await.clone()), decimals: self.decimals, history_sync_state: Mutex::new(self.history_sync_state.lock().unwrap().clone()), swap_txfee_policy: Mutex::new(SwapTxFeePolicy::Internal), max_eth_tx_type, - required_confirmations: AtomicU64::new(self.required_confirmations.load(Ordering::Relaxed)), + required_confirmations, ctx: self.ctx.clone(), chain_id: self.chain_id, trezor_coin: self.trezor_coin.clone(), @@ -532,36 +565,15 @@ impl EthCoin { nfts_infos: Arc::new(AsyncMutex::new(nft_infos)), platform_fee_estimator_state, gas_limit, + gas_limit_v2, abortable_system, }; Ok(EthCoin(Arc::new(global_nft))) } } -pub(crate) async fn generate_signed_message( - proxy_auth: bool, - chain: &Chain, - my_address: String, - priv_key_policy: &EthPrivKeyPolicy, -) -> MmResult, GenerateSignedMessageError> { - if !proxy_auth { - return Ok(None); - } - - let secret = priv_key_policy.activated_key_or_err()?.secret().clone(); - let validation_generator = ProxyAuthValidationGenerator { - coin_ticker: chain.to_nft_ticker().to_string(), - secret, - address: my_address, - }; - - let signed_message = EthCoin::generate_proxy_auth_signed_validation(validation_generator)?; - - Ok(Some(signed_message)) -} - /// Activate eth coin from coin config and private key build policy, -/// version 2 of the activation function, with no intrinsic tokens creation +/// version 2 of the activation function, with no intrinsic tokens creation. pub async fn eth_coin_from_conf_and_request_v2( ctx: &MmArc, ticker: &str, @@ -576,6 +588,23 @@ pub async fn eth_coin_from_conf_and_request_v2( .into()); } + if ctx.use_trading_proto_v2() { + let contracts = req.swap_v2_contracts.as_ref().ok_or_else(|| { + EthActivationV2Error::InvalidPayload( + "swap_v2_contracts must be provided when using trading protocol v2".to_string(), + ) + })?; + if contracts.maker_swap_v2_contract == Address::default() + || contracts.taker_swap_v2_contract == Address::default() + || contracts.nft_maker_swap_v2_contract == Address::default() + { + return Err(EthActivationV2Error::InvalidSwapContractAddr( + "All swap_v2_contracts addresses must be non-zero".to_string(), + ) + .into()); + } + } + if let Some(fallback) = req.fallback_swap_contract { if fallback == Address::default() { return Err(EthActivationV2Error::InvalidFallbackSwapContract( @@ -597,33 +626,9 @@ pub async fn eth_coin_from_conf_and_request_v2( let chain_id = conf["chain_id"].as_u64().ok_or(EthActivationV2Error::ChainIdNotSet)?; let web3_instances = match (req.rpc_mode, &priv_key_policy) { - ( - EthRpcMode::Default, - EthPrivKeyPolicy::Iguana(key_pair) - | EthPrivKeyPolicy::HDWallet { - activated_key: key_pair, - .. - }, - ) => { - let auth_address = key_pair.address(); - let auth_address_str = display_eth_address(&auth_address); - build_web3_instances(ctx, ticker.to_string(), auth_address_str, key_pair, req.nodes.clone()).await? - }, - (EthRpcMode::Default, EthPrivKeyPolicy::Trezor) => { - let crypto_ctx = CryptoCtx::from_ctx(ctx)?; - let secp256k1_key_pair = crypto_ctx.mm2_internal_key_pair(); - let auth_key_pair = KeyPair::from_secret_slice(secp256k1_key_pair.private_ref()) - .map_to_mm(|_| EthActivationV2Error::InternalError("could not get internal keypair".to_string()))?; - let auth_address = auth_key_pair.address(); - let auth_address_str = display_eth_address(&auth_address); - build_web3_instances( - ctx, - ticker.to_string(), - auth_address_str, - &auth_key_pair, - req.nodes.clone(), - ) - .await? + (EthRpcMode::Default, EthPrivKeyPolicy::Iguana(_) | EthPrivKeyPolicy::HDWallet { .. }) + | (EthRpcMode::Default, EthPrivKeyPolicy::Trezor) => { + build_web3_instances(ctx, ticker.to_string(), req.nodes.clone()).await? }, #[cfg(target_arch = "wasm32")] (EthRpcMode::Metamask, EthPrivKeyPolicy::Metamask(_)) => { @@ -666,7 +671,9 @@ pub async fn eth_coin_from_conf_and_request_v2( let coin_type = EthCoinType::Eth; let platform_fee_estimator_state = FeeEstimatorState::init_fee_estimator(ctx, conf, &coin_type).await?; let max_eth_tx_type = get_max_eth_tx_type_conf(ctx, conf, &coin_type).await?; - let gas_limit = extract_gas_limit_from_conf(conf) + let gas_limit: EthGasLimit = extract_gas_limit_from_conf(conf) + .map_to_mm(|e| EthActivationV2Error::InternalError(format!("invalid gas_limit config {}", e)))?; + let gas_limit_v2: EthGasLimitV2 = extract_gas_limit_from_conf(conf) .map_to_mm(|e| EthActivationV2Error::InternalError(format!("invalid gas_limit config {}", e)))?; let coin = EthCoinImpl { @@ -675,6 +682,7 @@ pub async fn eth_coin_from_conf_and_request_v2( coin_type, sign_message_prefix, swap_contract_address: req.swap_contract_address, + swap_v2_contracts: req.swap_v2_contracts, fallback_swap_contract: req.fallback_swap_contract, contract_supports_watchers: req.contract_supports_watchers, decimals: ETH_DECIMALS, @@ -693,6 +701,7 @@ pub async fn eth_coin_from_conf_and_request_v2( nfts_infos: Default::default(), platform_fee_estimator_state, gas_limit, + gas_limit_v2, abortable_system, }; @@ -810,8 +819,6 @@ pub(crate) async fn build_address_and_priv_key_policy( async fn build_web3_instances( ctx: &MmArc, coin_ticker: String, - address: String, - key_pair: &KeyPair, mut eth_nodes: Vec, ) -> MmResult, EthActivationV2Error> { if eth_nodes.is_empty() { @@ -831,57 +838,7 @@ async fn build_web3_instances( .parse() .map_err(|_| EthActivationV2Error::InvalidPayload(format!("{} could not be parsed.", eth_node.url)))?; - let transport = match uri.scheme_str() { - Some("ws") | Some("wss") => { - const TMP_SOCKET_CONNECTION: Duration = Duration::from_secs(20); - - let node = WebsocketTransportNode { - uri: uri.clone(), - gui_auth: eth_node.gui_auth, - }; - - let mut websocket_transport = WebsocketTransport::with_event_handlers(node, event_handlers.clone()); - - if eth_node.gui_auth { - websocket_transport.proxy_auth_validation_generator = Some(ProxyAuthValidationGenerator { - coin_ticker: coin_ticker.clone(), - secret: key_pair.secret().clone(), - address: address.clone(), - }); - } - - // Temporarily start the connection loop (we close the connection once we have the client version below). - // Ideally, it would be much better to not do this workaround, which requires a lot of refactoring or - // dropping websocket support on parity nodes. - let fut = websocket_transport - .clone() - .start_connection_loop(Some(Instant::now() + TMP_SOCKET_CONNECTION)); - let settings = AbortSettings::info_on_abort(format!("connection loop stopped for {:?}", uri)); - ctx.spawner().spawn_with_settings(fut, settings); - - Web3Transport::Websocket(websocket_transport) - }, - Some("http") | Some("https") => { - let node = HttpTransportNode { - uri, - gui_auth: eth_node.gui_auth, - }; - - build_http_transport( - coin_ticker.clone(), - address.clone(), - key_pair, - node, - event_handlers.clone(), - ) - }, - _ => { - return MmError::err(EthActivationV2Error::InvalidPayload(format!( - "Invalid node address '{uri}'. Only http(s) and ws(s) nodes are supported" - ))); - }, - }; - + let transport = create_transport(ctx, &uri, ð_node, &event_handlers)?; let web3 = Web3::new(transport); let version = match web3.web3().client_version().await { Ok(v) => v, @@ -906,25 +863,67 @@ async fn build_web3_instances( Ok(web3_instances) } -fn build_http_transport( - coin_ticker: String, - address: String, - key_pair: &KeyPair, - node: HttpTransportNode, - event_handlers: Vec, +fn create_transport( + ctx: &MmArc, + uri: &Uri, + eth_node: &EthNode, + event_handlers: &[RpcTransportEventHandlerShared], +) -> MmResult { + match uri.scheme_str() { + Some("ws") | Some("wss") => Ok(create_websocket_transport(ctx, uri, eth_node, event_handlers)), + Some("http") | Some("https") => Ok(create_http_transport(ctx, uri, eth_node, event_handlers)), + _ => MmError::err(EthActivationV2Error::InvalidPayload(format!( + "Invalid node address '{uri}'. Only http(s) and ws(s) nodes are supported" + ))), + } +} + +fn create_websocket_transport( + ctx: &MmArc, + uri: &Uri, + eth_node: &EthNode, + event_handlers: &[RpcTransportEventHandlerShared], ) -> Web3Transport { - use crate::eth::web3_transport::http_transport::HttpTransport; + const TMP_SOCKET_CONNECTION: Duration = Duration::from_secs(20); - let gui_auth = node.gui_auth; - let mut http_transport = HttpTransport::with_event_handlers(node, event_handlers); + let node = WebsocketTransportNode { uri: uri.clone() }; - if gui_auth { - http_transport.proxy_auth_validation_generator = Some(ProxyAuthValidationGenerator { - coin_ticker, - secret: key_pair.secret().clone(), - address, - }); + let mut websocket_transport = WebsocketTransport::with_event_handlers(node, event_handlers.to_owned()); + + if eth_node.komodo_proxy { + websocket_transport.proxy_sign_keypair = Some(P2PContext::fetch_from_mm_arc(ctx).keypair().clone()); + } + + // Temporarily start the connection loop (we close the connection once we have the client version below). + // Ideally, it would be much better to not do this workaround, which requires a lot of refactoring or + // dropping websocket support on parity nodes. + let fut = websocket_transport + .clone() + .start_connection_loop(Some(Instant::now() + TMP_SOCKET_CONNECTION)); + let settings = AbortSettings::info_on_abort(format!("connection loop stopped for {:?}", uri)); + ctx.spawner().spawn_with_settings(fut, settings); + + Web3Transport::Websocket(websocket_transport) +} + +fn create_http_transport( + ctx: &MmArc, + uri: &Uri, + eth_node: &EthNode, + event_handlers: &[RpcTransportEventHandlerShared], +) -> Web3Transport { + let node = HttpTransportNode { + uri: uri.clone(), + komodo_proxy: eth_node.komodo_proxy, + }; + + let komodo_proxy = node.komodo_proxy; + let mut http_transport = HttpTransport::with_event_handlers(node, event_handlers.to_owned()); + + if komodo_proxy { + http_transport.proxy_sign_keypair = Some(P2PContext::fetch_from_mm_arc(ctx).keypair().clone()); } + Web3Transport::from(http_transport) } diff --git a/mm2src/coins/eth/web3_transport/http_transport.rs b/mm2src/coins/eth/web3_transport/http_transport.rs index 463e2455fa..378e384df2 100644 --- a/mm2src/coins/eth/web3_transport/http_transport.rs +++ b/mm2src/coins/eth/web3_transport/http_transport.rs @@ -1,9 +1,10 @@ -use crate::eth::web3_transport::handle_quicknode_payload; use crate::eth::{web3_transport::Web3SendOut, RpcTransportEventHandler, RpcTransportEventHandlerShared, Web3RpcError}; use common::APPLICATION_JSON; +use common::X_AUTH_PAYLOAD; use http::header::CONTENT_TYPE; use jsonrpc_core::{Call, Response}; -use mm2_net::transport::{KomodefiProxyAuthValidation, ProxyAuthValidationGenerator}; +use mm2_p2p::Keypair; +use proxy_signature::RawMessage; use serde_json::Value as Json; use std::ops::Deref; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; @@ -12,13 +13,6 @@ use web3::error::{Error, TransportError}; use web3::helpers::{build_request, to_result_from_output, to_string}; use web3::{RequestId, Transport}; -#[derive(Clone, Serialize)] -pub struct QuicknodePayload<'a> { - #[serde(flatten)] - pub request: &'a Call, - pub signed_message: KomodefiProxyAuthValidation, -} - /// Deserialize bytes RPC response into `Result`. /// Implementation copied from Web3 HTTP transport pub(crate) fn de_rpc_response(response: T, rpc_url: &str) -> Result @@ -46,13 +40,13 @@ pub struct HttpTransport { pub(crate) last_request_failed: Arc, node: HttpTransportNode, event_handlers: Vec, - pub(crate) proxy_auth_validation_generator: Option, + pub(crate) proxy_sign_keypair: Option, } #[derive(Clone, Debug)] pub struct HttpTransportNode { pub(crate) uri: http::Uri, - pub(crate) gui_auth: bool, + pub(crate) komodo_proxy: bool, } impl HttpTransport { @@ -63,7 +57,7 @@ impl HttpTransport { id: Arc::new(AtomicUsize::new(0)), node, event_handlers: Default::default(), - proxy_auth_validation_generator: None, + proxy_sign_keypair: None, last_request_failed: Arc::new(AtomicBool::new(false)), } } @@ -74,7 +68,7 @@ impl HttpTransport { id: Arc::new(AtomicUsize::new(0)), node, event_handlers, - proxy_auth_validation_generator: None, + proxy_sign_keypair: None, last_request_failed: Arc::new(AtomicBool::new(false)), } } @@ -108,26 +102,33 @@ async fn send_request(request: Call, transport: HttpTransport) -> Result serialized_request = r, - Err(e) => { - return Err(request_failed_error(request, e)); - }, - }; - } + let serialized_request = to_string(&request); + let request_bytes = serialized_request.as_bytes(); - transport - .event_handlers - .on_outgoing_request(serialized_request.as_bytes()); + transport.event_handlers.on_outgoing_request(request_bytes); - let mut req = http::Request::new(serialized_request.into_bytes()); + let mut req = http::Request::new(request_bytes.to_owned()); *req.method_mut() = http::Method::POST; *req.uri_mut() = transport.node.uri.clone(); req.headers_mut() .insert(CONTENT_TYPE, HeaderValue::from_static(APPLICATION_JSON)); + + if let Some(proxy_sign_keypair) = &transport.proxy_sign_keypair { + let proxy_sign = RawMessage::sign( + proxy_sign_keypair, + &transport.node.uri, + request_bytes.len(), + common::PROXY_REQUEST_EXPIRATION_SEC, + ) + .map_err(|e| request_failed_error(&request, Web3RpcError::Internal(e.to_string())))?; + + let proxy_sign_serialized = serde_json::to_string(&proxy_sign) + .map_err(|e| request_failed_error(&request, Web3RpcError::Internal(e.to_string())))?; + + req.headers_mut() + .insert(X_AUTH_PAYLOAD, proxy_sign_serialized.parse().unwrap()); + } + let timeout = Timer::sleep(REQUEST_TIMEOUT_S); let req = Box::pin(slurp_req(req)); let rc = select(req, timeout).await; @@ -144,14 +145,14 @@ async fn send_request(request: Call, transport: HttpTransport) -> Result r, Err(err) => { - return Err(request_failed_error(request, Web3RpcError::Transport(err.to_string()))); + return Err(request_failed_error(&request, Web3RpcError::Transport(err.to_string()))); }, }; @@ -159,7 +160,7 @@ async fn send_request(request: Call, transport: HttpTransport) -> Result Result r, Err(err) => { return Err(request_failed_error( - request, + &request, Web3RpcError::InvalidResponse(format!("Server: '{}', error: {}", transport.node.uri, err)), )); }, @@ -184,28 +185,41 @@ async fn send_request(request: Call, transport: HttpTransport) -> Result Result { - let mut serialized_request = to_string(&request); - - if transport.node.gui_auth { - match handle_quicknode_payload(&transport.proxy_auth_validation_generator, &request) { - Ok(r) => serialized_request = r, - Err(e) => { - return Err(request_failed_error( - request, - Web3RpcError::Transport(format!("Server: '{}', error: {}", transport.node.uri, e)), - )); - }, - }; - } + let serialized_request = to_string(&request); + let request_bytes = serialized_request.as_bytes(); + + let proxy_sign_header = if let Some(proxy_sign_keypair) = &transport.proxy_sign_keypair { + let proxy_sign = RawMessage::sign( + proxy_sign_keypair, + &transport.node.uri, + request_bytes.len(), + common::PROXY_REQUEST_EXPIRATION_SEC, + ) + .map_err(|e| request_failed_error(&request, Web3RpcError::Internal(e.to_string())))?; + + let proxy_sign_serialized = serde_json::to_string(&proxy_sign) + .map_err(|e| request_failed_error(&request, Web3RpcError::Internal(e.to_string())))?; + + Some(proxy_sign_serialized) + } else { + None + }; - match send_request_once(serialized_request, &transport.node.uri, &transport.event_handlers).await { + match send_request_once( + serialized_request, + &transport.node.uri, + &transport.event_handlers, + proxy_sign_header, + ) + .await + { Ok(response_json) => Ok(response_json), Err(Error::Transport(e)) => Err(request_failed_error( - request, + &request, Web3RpcError::Transport(format!("Server: '{}', error: {}", transport.node.uri, e)), )), Err(e) => Err(request_failed_error( - request, + &request, Web3RpcError::InvalidResponse(format!("Server: '{}', error: {}", transport.node.uri, e)), )), } @@ -216,6 +230,7 @@ async fn send_request_once( request_payload: String, uri: &http::Uri, event_handlers: &Vec, + proxy_sign_header: Option, ) -> Result { use http::header::ACCEPT; use mm2_net::wasm::http::FetchRequest; @@ -223,11 +238,19 @@ async fn send_request_once( // account for outgoing traffic event_handlers.on_outgoing_request(request_payload.as_bytes()); - let (status_code, response_str) = FetchRequest::post(&uri.to_string()) + let mut request = FetchRequest::post(&uri.to_string()); + + request = request .cors() .body_utf8(request_payload) .header(ACCEPT.as_str(), APPLICATION_JSON) - .header(CONTENT_TYPE.as_str(), APPLICATION_JSON) + .header(CONTENT_TYPE.as_str(), APPLICATION_JSON); + + if let Some(proxy_sign_header) = proxy_sign_header { + request = request.header(X_AUTH_PAYLOAD, &proxy_sign_header); + } + + let (status_code, response_str) = request .request_str() .await .map_err(|e| Error::Transport(TransportError::Message(ERRL!("{:?}", e))))?; @@ -252,7 +275,7 @@ async fn send_request_once( } } -fn request_failed_error(request: Call, error: Web3RpcError) -> Error { +fn request_failed_error(request: &Call, error: Web3RpcError) -> Error { let error = format!("request {:?} failed: {}", request, error); Error::Transport(TransportError::Message(error)) } diff --git a/mm2src/coins/eth/web3_transport/mod.rs b/mm2src/coins/eth/web3_transport/mod.rs index 421c9349a8..e064e6025a 100644 --- a/mm2src/coins/eth/web3_transport/mod.rs +++ b/mm2src/coins/eth/web3_transport/mod.rs @@ -2,15 +2,11 @@ use ethereum_types::U256; use futures::future::BoxFuture; use jsonrpc_core::Call; #[cfg(target_arch = "wasm32")] use mm2_metamask::MetamaskResult; -use mm2_net::transport::ProxyAuthValidationGenerator; use serde_json::Value as Json; use serde_json::Value; use std::sync::atomic::Ordering; -use web3::helpers::to_string; use web3::{Error, RequestId, Transport}; -use self::http_transport::QuicknodePayload; -use super::{EthCoin, KomodoDefiAuthMessages, Web3RpcError}; use crate::RpcTransportEventHandlerShared; pub(crate) mod http_transport; @@ -66,15 +62,6 @@ impl Web3Transport { pub fn new_http(node: http_transport::HttpTransportNode) -> Web3Transport { http_transport::HttpTransport::new(node).into() } - - pub fn proxy_auth_validation_generator_as_mut(&mut self) -> Option<&mut ProxyAuthValidationGenerator> { - match self { - Web3Transport::Http(http) => http.proxy_auth_validation_generator.as_mut(), - Web3Transport::Websocket(websocket) => websocket.proxy_auth_validation_generator.as_mut(), - #[cfg(target_arch = "wasm32")] - Web3Transport::Metamask(_) => None, - } - } } impl Transport for Web3Transport { @@ -133,27 +120,3 @@ pub struct FeeHistoryResult { #[serde(rename = "reward")] pub priority_rewards: Option>>, } - -/// Generates a signed message and inserts it into the request payload. -pub(super) fn handle_quicknode_payload( - proxy_auth_validation_generator: &Option, - request: &Call, -) -> Result { - let generator = proxy_auth_validation_generator - .clone() - .ok_or_else(|| Web3RpcError::Internal("ProxyAuthValidationGenerator is not provided for".to_string()))?; - - let signed_message = EthCoin::generate_proxy_auth_signed_validation(generator).map_err(|e| { - Web3RpcError::Internal(format!( - "KomodefiProxyAuthValidation signed message generation failed. Error: {:?}", - e - )) - })?; - - let auth_request = QuicknodePayload { - request, - signed_message, - }; - - Ok(to_string(&auth_request)) -} diff --git a/mm2src/coins/eth/web3_transport/websocket_transport.rs b/mm2src/coins/eth/web3_transport/websocket_transport.rs index 951ed4d2c6..6d19573781 100644 --- a/mm2src/coins/eth/web3_transport/websocket_transport.rs +++ b/mm2src/coins/eth/web3_transport/websocket_transport.rs @@ -5,7 +5,6 @@ //! less bandwidth. This efficiency is achieved by avoiding the handling of TCP handshakes (connection reusability) //! for each request. -use super::handle_quicknode_payload; use super::http_transport::de_rpc_response; use crate::eth::eth_rpc::ETH_RPC_REQUEST_TIMEOUT; use crate::eth::web3_transport::Web3SendOut; @@ -21,7 +20,8 @@ use futures_ticker::Ticker; use futures_util::{FutureExt, SinkExt, StreamExt}; use instant::{Duration, Instant}; use jsonrpc_core::Call; -use mm2_net::transport::ProxyAuthValidationGenerator; +use mm2_p2p::Keypair; +use proxy_signature::{ProxySign, RawMessage}; use std::sync::atomic::AtomicBool; use std::sync::{atomic::{AtomicUsize, Ordering}, Arc}; @@ -37,7 +37,6 @@ const KEEPALIVE_DURATION: Duration = Duration::from_secs(10); #[derive(Clone, Debug)] pub(crate) struct WebsocketTransportNode { pub(crate) uri: http::Uri, - pub(crate) gui_auth: bool, } #[derive(Clone, Debug)] @@ -46,7 +45,7 @@ pub struct WebsocketTransport { pub(crate) last_request_failed: Arc, node: WebsocketTransportNode, event_handlers: Vec, - pub(crate) proxy_auth_validation_generator: Option, + pub(crate) proxy_sign_keypair: Option, controller_channel: Arc, connection_guard: Arc>, } @@ -93,17 +92,12 @@ impl WebsocketTransport { } .into(), connection_guard: Arc::new(AsyncMutex::new(())), - proxy_auth_validation_generator: None, + proxy_sign_keypair: None, last_request_failed: Arc::new(AtomicBool::new(false)), } } - async fn handle_keepalive( - &self, - wsocket: &mut WebSocketStream, - response_notifiers: &mut ExpirableMap>>, - expires_at: Option, - ) -> OuterAction { + async fn handle_keepalive(&self, wsocket: &mut WebSocketStream, expires_at: Option) -> OuterAction { const SIMPLE_REQUEST: &str = r#"{"jsonrpc":"2.0","method":"net_version","params":[],"id": 0 }"#; if let Some(expires_at) = expires_at { @@ -113,10 +107,7 @@ impl WebsocketTransport { } } - // Drop expired response notifier channels - response_notifiers.clear_expired_entries(); - - let mut should_continue = Default::default(); + let mut should_continue = false; for _ in 0..MAX_ATTEMPTS { match wsocket .send(tokio_tungstenite_wasm::Message::Text(SIMPLE_REQUEST.to_string())) @@ -207,9 +198,6 @@ impl WebsocketTransport { } if let Some(id) = inc_event.get("id") { - // just to ensure we don't have outdated entries - response_notifiers.clear_expired_entries(); - let request_id = id.as_u64().unwrap_or_default() as usize; if let Some(notifier) = response_notifiers.remove(&request_id) { @@ -280,7 +268,7 @@ impl WebsocketTransport { loop { futures_util::select! { _ = keepalive_interval.next().fuse() => { - match self.handle_keepalive(&mut wsocket, &mut response_notifiers, expires_at).await { + match self.handle_keepalive(&mut wsocket, expires_at).await { OuterAction::None => {}, OuterAction::Continue => continue, OuterAction::Break => break, @@ -340,25 +328,40 @@ async fn send_request( request_id: RequestId, event_handlers: Vec, ) -> Result { - let mut serialized_request = to_string(&request); + /// komodo-defi-proxy expects proxy signatures in the socket messages as they + /// cannot be provided through headers. + #[derive(Serialize)] + struct ProxyWrapper<'a> { + #[serde(flatten)] + pub request: &'a Call, + pub proxy_sign: ProxySign, + } - if transport.node.gui_auth { - match handle_quicknode_payload(&transport.proxy_auth_validation_generator, &request) { - Ok(r) => serialized_request = r, - Err(e) => { - return Err(Error::Transport(TransportError::Message(format!( - "Couldn't generate signed message payload for {:?}. Error: {e}", - request - )))); - }, + let mut serialized_request = to_string(&request); + let request_bytes = serialized_request.as_bytes().to_owned(); + + if let Some(proxy_sign_keypair) = &transport.proxy_sign_keypair { + let proxy_sign = RawMessage::sign( + proxy_sign_keypair, + &transport.node.uri, + request_bytes.len(), + common::PROXY_REQUEST_EXPIRATION_SEC, + ) + .map_err(|e| Error::Transport(TransportError::Message(e.to_string())))?; + + let wrapper = ProxyWrapper { + request: &request, + proxy_sign, }; + + serialized_request = serde_json::to_string(&wrapper)?; } let mut tx = transport.controller_channel.tx.lock().await; - let (notification_sender, notification_receiver) = futures::channel::oneshot::channel::>(); + let (notification_sender, notification_receiver) = oneshot::channel::>(); - event_handlers.on_outgoing_request(serialized_request.as_bytes()); + event_handlers.on_outgoing_request(&request_bytes); tx.send(ControllerMessage::Request(WsRequest { request_id, diff --git a/mm2src/coins/hd_wallet/pubkey.rs b/mm2src/coins/hd_wallet/pubkey.rs index 7732819295..7babb12bd5 100644 --- a/mm2src/coins/hd_wallet/pubkey.rs +++ b/mm2src/coins/hd_wallet/pubkey.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "enable-sia")] +use crate::siacoin::sia_hd_wallet::Ed25519ExtendedPublicKey; use crate::CoinProtocol; use super::*; @@ -30,6 +32,13 @@ impl ExtendedPublicKeyOps for Secp256k1ExtendedPublicKey { fn to_string(&self, prefix: Prefix) -> String { self.to_string(prefix) } } +#[cfg(feature = "enable-sia")] +impl ExtendedPublicKeyOps for Ed25519ExtendedPublicKey { + fn derive_child(&self, child_number: ChildNumber) -> Result { self.derive_child(child_number) } + + fn to_string(&self, prefix: Prefix) -> String { self.to_string(prefix) } +} + /// This trait should be implemented for coins /// to support extracting extended public keys from any depth. /// The extraction can be from either an internal or external wallet. diff --git a/mm2src/coins/lightning.rs b/mm2src/coins/lightning.rs index 2feb7ef014..67b27ba8f4 100644 --- a/mm2src/coins/lightning.rs +++ b/mm2src/coins/lightning.rs @@ -610,43 +610,40 @@ impl LightningCoin { #[async_trait] impl SwapOps for LightningCoin { // Todo: This uses dummy data for now for the sake of swap P.O.C., this should be implemented probably after agreeing on how fees will work for lightning - fn send_taker_fee(&self, _fee_addr: &[u8], _dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut { - let fut = async move { Ok(TransactionEnum::LightningPayment(PaymentHash([1; 32]))) }; - Box::new(fut.boxed().compat()) + async fn send_taker_fee( + &self, + _fee_addr: &[u8], + _dex_fee: DexFee, + _uuid: &[u8], + _expire_at: u64, + ) -> TransactionResult { + Ok(TransactionEnum::LightningPayment(PaymentHash([1; 32]))) } - fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionFut { + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { let invoice = match maker_payment_args.payment_instructions.clone() { Some(PaymentInstructions::Lightning(invoice)) => invoice, - _ => try_tx_fus!(ERR!("Invalid instructions, ligntning invoice is expected")), + _ => try_tx_s!(ERR!("Invalid instructions, ligntning invoice is expected")), }; - let coin = self.clone(); - let fut = async move { - // No need for max_total_cltv_expiry_delta for lightning maker payment since the maker is the side that reveals the secret/preimage - let payment = try_tx_s!(coin.pay_invoice(invoice, None).await); - Ok(payment.payment_hash.into()) - }; - Box::new(fut.boxed().compat()) + // No need for max_total_cltv_expiry_delta for lightning maker payment since the maker is the side that reveals the secret/preimage + let payment = try_tx_s!(self.pay_invoice(invoice, None).await); + Ok(payment.payment_hash.into()) } - fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionFut { + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { let invoice = match taker_payment_args.payment_instructions.clone() { Some(PaymentInstructions::Lightning(invoice)) => invoice, - _ => try_tx_fus!(ERR!("Invalid instructions, ligntning invoice is expected")), + _ => try_tx_s!(ERR!("Invalid instructions, ligntning invoice is expected")), }; let max_total_cltv_expiry_delta = self .estimate_blocks_from_duration(taker_payment_args.time_lock_duration) .try_into() .expect("max_total_cltv_expiry_delta shouldn't exceed u32::MAX"); - let coin = self.clone(); - let fut = async move { - // Todo: The path/s used is already logged when PaymentPathSuccessful/PaymentPathFailed events are fired, it might be better to save it to the DB and retrieve it with the payment info. - let payment = try_tx_s!(coin.pay_invoice(invoice, Some(max_total_cltv_expiry_delta)).await); - Ok(payment.payment_hash.into()) - }; - Box::new(fut.boxed().compat()) + // Todo: The path/s used is already logged when PaymentPathSuccessful/PaymentPathFailed events are fired, it might be better to save it to the DB and retrieve it with the payment info. + let payment = try_tx_s!(self.pay_invoice(invoice, Some(max_total_cltv_expiry_delta)).await); + Ok(payment.payment_hash.into()) } #[inline] @@ -684,9 +681,7 @@ impl SwapOps for LightningCoin { } // Todo: This validates the dummy fee for now for the sake of swap P.O.C., this should be implemented probably after agreeing on how fees will work for lightning - fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { - Box::new(futures01::future::ok(())) - } + async fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { Ok(()) } #[inline] async fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentResult<()> { @@ -698,29 +693,26 @@ impl SwapOps for LightningCoin { self.validate_swap_payment(input).compat().await } - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, - ) -> Box, Error = String> + Send> { + ) -> Result, String> { let invoice = match if_my_payment_sent_args.payment_instructions.clone() { Some(PaymentInstructions::Lightning(invoice)) => invoice, - _ => try_f!(ERR!("Invalid instructions, ligntning invoice is expected")), + _ => return ERR!("Invalid instructions, ligntning invoice is expected"), }; let payment_hash = PaymentHash((invoice.payment_hash()).into_inner()); let payment_hex = hex::encode(payment_hash.0); - let coin = self.clone(); - let fut = async move { - match coin.db.get_payment_from_db(payment_hash).await { - Ok(maybe_payment) => Ok(maybe_payment.map(|p| p.payment_hash.into())), - Err(e) => ERR!( - "Unable to check if payment {} is in db or not error: {}", - payment_hex, - e - ), - } - }; - Box::new(fut.boxed().compat()) + + match self.db.get_payment_from_db(payment_hash).await { + Ok(maybe_payment) => Ok(maybe_payment.map(|p| p.payment_hash.into())), + Err(e) => ERR!( + "Unable to check if payment {} is in db or not error: {}", + payment_hex, + e + ), + } } // Todo: need to also check on-chain spending @@ -1172,58 +1164,53 @@ impl MarketCoinOps for LightningCoin { Box::new(fut.boxed().compat()) } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { - let payment_hash = try_tx_fus!(payment_hash_from_slice(args.tx_bytes)); + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { + let payment_hash = try_tx_s!(payment_hash_from_slice(args.tx_bytes)); let payment_hex = hex::encode(payment_hash.0); - let coin = self.clone(); - let wait_until = args.wait_until; - let fut = async move { - loop { - if now_sec() > wait_until { - return Err(TransactionErr::Plain(ERRL!( - "Waited too long until {} for payment {} to be spent", - wait_until, - payment_hex - ))); - } + loop { + if now_sec() > args.wait_until { + return Err(TransactionErr::Plain(ERRL!( + "Waited too long until {} for payment {} to be spent", + args.wait_until, + payment_hex + ))); + } - match coin.db.get_payment_from_db(payment_hash).await { - Ok(Some(payment)) => match payment.status { - HTLCStatus::Pending => (), - HTLCStatus::Claimable => { - return Err(TransactionErr::Plain(ERRL!( - "Payment {} has an invalid status of {} in the db", - payment_hex, - payment.status - ))) - }, - HTLCStatus::Succeeded => return Ok(TransactionEnum::LightningPayment(payment_hash)), - HTLCStatus::Failed => { - return Err(TransactionErr::Plain(ERRL!( - "Lightning swap payment {} failed", - payment_hex - ))) - }, - }, - Ok(None) => return Err(TransactionErr::Plain(ERRL!("Payment {} not found in DB", payment_hex))), - Err(e) => { + match self.db.get_payment_from_db(payment_hash).await { + Ok(Some(payment)) => match payment.status { + HTLCStatus::Pending => (), + HTLCStatus::Claimable => { return Err(TransactionErr::Plain(ERRL!( - "Error getting payment {} from db: {}", + "Payment {} has an invalid status of {} in the db", payment_hex, - e + payment.status ))) }, - } - - // note: When sleeping for only 1 second the test_send_payment_and_swaps unit test took 20 seconds to complete instead of 37 seconds when sleeping for 10 seconds - // Todo: In next sprints, should add a mutex for lightning swap payments to avoid overloading the shared db connection with requests when the sleep time is reduced and multiple swaps are ran together. - // Todo: The aim is to make lightning swap payments as fast as possible, more sleep time can be allowed for maker payment since it waits for the secret to be revealed on another chain first. - // Todo: Running swap payments statuses should be loaded from db on restarts in this case. - Timer::sleep(10.).await; + HTLCStatus::Succeeded => return Ok(TransactionEnum::LightningPayment(payment_hash)), + HTLCStatus::Failed => { + return Err(TransactionErr::Plain(ERRL!( + "Lightning swap payment {} failed", + payment_hex + ))) + }, + }, + Ok(None) => return Err(TransactionErr::Plain(ERRL!("Payment {} not found in DB", payment_hex))), + Err(e) => { + return Err(TransactionErr::Plain(ERRL!( + "Error getting payment {} from db: {}", + payment_hex, + e + ))) + }, } - }; - Box::new(fut.boxed().compat()) + + // note: When sleeping for only 1 second the test_send_payment_and_swaps unit test took 20 seconds to complete instead of 37 seconds when sleeping for 10 seconds + // Todo: In next sprints, should add a mutex for lightning swap payments to avoid overloading the shared db connection with requests when the sleep time is reduced and multiple swaps are ran together. + // Todo: The aim is to make lightning swap payments as fast as possible, more sleep time can be allowed for maker payment since it waits for the secret to be revealed on another chain first. + // Todo: Running swap payments statuses should be loaded from db on restarts in this case. + Timer::sleep(10.).await; + } } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { diff --git a/mm2src/coins/lightning/ln_platform.rs b/mm2src/coins/lightning/ln_platform.rs index 4d0573af95..a3a4c22776 100644 --- a/mm2src/coins/lightning/ln_platform.rs +++ b/mm2src/coins/lightning/ln_platform.rs @@ -1,8 +1,8 @@ use super::*; use crate::lightning::ln_errors::{SaveChannelClosingError, SaveChannelClosingResult}; -use crate::utxo::rpc_clients::{BestBlock as RpcBestBlock, BlockHashOrHeight, ConfirmedTransactionInfo, - ElectrumBlockHeader, ElectrumClient, ElectrumNonce, EstimateFeeMethod, - UtxoRpcClientEnum, UtxoRpcResult}; +use crate::lightning::ln_utils::RpcBestBlock; +use crate::utxo::rpc_clients::{BlockHashOrHeight, ConfirmedTransactionInfo, ElectrumBlockHeader, ElectrumClient, + ElectrumNonce, EstimateFeeMethod, UtxoRpcClientEnum, UtxoRpcResult}; use crate::utxo::spv::SimplePaymentVerification; use crate::utxo::utxo_standard::UtxoStandardCoin; use crate::utxo::GetConfirmedTxError; @@ -15,7 +15,7 @@ use bitcoin::hash_types::{BlockHash, TxMerkleNode, Txid}; use bitcoin_hashes::{sha256d, Hash}; use common::executor::{abortable_queue::AbortableQueue, AbortableSystem, SpawnFuture, Timer}; use common::log::{debug, error, info}; -use common::wait_until_sec; +use common::{block_on_f01, wait_until_sec}; use futures::compat::Future01CompatExt; use futures::future::join_all; use keys::hash::H256; @@ -542,7 +542,6 @@ impl Platform { check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, watcher_reward: false, }) - .compat() .await .map_to_mm(|e| SaveChannelClosingError::WaitForFundingTxSpendError(e.get_plain_text_format()))?; @@ -570,17 +569,15 @@ impl FeeEstimator for Platform { ConfirmationTarget::HighPriority => self.confirmations_targets.high_priority, }; let fee_per_kb = tokio::task::block_in_place(move || { - self.rpc_client() - .estimate_fee_sat( - platform_coin.decimals(), - // Todo: when implementing Native client detect_fee_method should be used for Native and - // EstimateFeeMethod::Standard for Electrum - &EstimateFeeMethod::Standard, - &conf.estimate_fee_mode, - n_blocks, - ) - .wait() - .unwrap_or(latest_fees) + block_on_f01(self.rpc_client().estimate_fee_sat( + platform_coin.decimals(), + // Todo: when implementing Native client detect_fee_method should be used for Native and + // EstimateFeeMethod::Standard for Electrum + &EstimateFeeMethod::Standard, + &conf.estimate_fee_mode, + n_blocks, + )) + .unwrap_or(latest_fees) }); // Set default fee to last known fee for the corresponding confirmation target diff --git a/mm2src/coins/lightning/ln_utils.rs b/mm2src/coins/lightning/ln_utils.rs index 693b7c3a4f..5b4ac5698d 100644 --- a/mm2src/coins/lightning/ln_utils.rs +++ b/mm2src/coins/lightning/ln_utils.rs @@ -3,7 +3,7 @@ use crate::lightning::ln_db::LightningDB; use crate::lightning::ln_platform::{get_best_header, ln_best_block_update_loop, update_best_block}; use crate::lightning::ln_sql::SqliteLightningDB; use crate::lightning::ln_storage::{LightningStorage, NodesAddressesMap}; -use crate::utxo::rpc_clients::BestBlock as RpcBestBlock; +use crate::utxo::rpc_clients::ElectrumBlockHeader; use bitcoin::hash_types::BlockHash; use bitcoin_hashes::{sha256d, Hash}; use common::executor::SpawnFuture; @@ -38,6 +38,21 @@ pub type ChainMonitor = chainmonitor::ChainMonitor< pub type ChannelManager = SimpleArcChannelManager; pub type Router = DefaultRouter, Arc, Arc>; +#[derive(Debug, PartialEq)] +pub struct RpcBestBlock { + pub height: u64, + pub hash: H256Json, +} + +impl From for RpcBestBlock { + fn from(block_header: ElectrumBlockHeader) -> Self { + RpcBestBlock { + height: block_header.block_height(), + hash: block_header.block_hash(), + } + } +} + #[inline] fn ln_data_dir(ctx: &MmArc, ticker: &str) -> PathBuf { ctx.dbdir().join("LIGHTNING").join(ticker) } diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 653237585b..5e40eb9221 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -54,7 +54,7 @@ use crypto::{derive_secp256k1_secret, Bip32Error, Bip44Chain, CryptoCtx, CryptoC Secp256k1ExtendedPublicKey, Secp256k1Secret, WithHwRpcError}; use derive_more::Display; use enum_derives::{EnumFromStringify, EnumFromTrait}; -use ethereum_types::H256; +use ethereum_types::{H256, U256}; use futures::compat::Future01CompatExt; use futures::lock::{Mutex as AsyncMutex, MutexGuard as AsyncMutexGuard}; use futures::{FutureExt, TryFutureExt}; @@ -66,12 +66,13 @@ use mm2_core::mm_ctx::{from_ctx, MmArc}; use mm2_err_handle::prelude::*; use mm2_metrics::MetricsWeak; use mm2_number::{bigdecimal::{BigDecimal, ParseBigDecimalError, Zero}, - MmNumber}; + BigUint, MmNumber, ParseBigIntError}; use mm2_rpc::data::legacy::{EnabledCoin, GetEnabledResponse, Mm2RpcResult}; use parking_lot::Mutex as PaMutex; use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::{self as json, Value as Json}; +use std::array::TryFromSliceError; use std::cmp::Ordering; use std::collections::hash_map::{HashMap, RawEntryMut}; use std::collections::HashSet; @@ -124,29 +125,6 @@ macro_rules! try_f { }; } -#[cfg(feature = "enable-solana")] -macro_rules! try_tx_fus_err { - ($err: expr) => { - return Box::new(futures01::future::err(crate::TransactionErr::Plain(ERRL!( - "{:?}", $err - )))) - }; -} - -#[cfg(feature = "enable-solana")] -macro_rules! try_tx_fus_opt { - ($e: expr, $err: expr) => { - match $e { - Some(ok) => ok, - None => { - return Box::new(futures01::future::err(crate::TransactionErr::Plain(ERRL!( - "{:?}", $err - )))) - }, - } - }; -} - /// `TransactionErr` compatible `try_fus` macro. macro_rules! try_tx_fus { ($e: expr) => { @@ -240,10 +218,11 @@ use coin_errors::{MyAddressError, ValidatePaymentError, ValidatePaymentFut, Vali pub mod coins_tests; pub mod eth; +use eth::erc20::get_erc20_ticker_by_contract_address; +use eth::eth_swap_v2::{PaymentStatusErr, PrepareTxDataError, ValidatePaymentV2Err}; use eth::GetValidEthWithdrawAddError; use eth::{eth_coin_from_conf_and_request, get_eth_address, EthCoin, EthGasDetailsErr, EthTxFeeDetails, GetEthAddressError, SignedEthTx}; -use ethereum_types::U256; pub mod hd_wallet; use hd_wallet::{AccountUpdatingError, AddressDerivingError, HDAccountOps, HDAddressId, HDAddressOps, HDCoinAddress, @@ -276,29 +255,8 @@ pub use test_coin::TestCoin; pub mod tx_history_storage; -#[doc(hidden)] -#[allow(unused_variables)] -#[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") -))] -pub mod solana; -#[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") -))] -pub use solana::spl::SplToken; -#[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") -))] -pub use solana::{SolTransaction, SolanaActivationParams, SolanaCoin, SolanaFeeDetails}; +#[cfg(feature = "enable-sia")] pub mod siacoin; +#[cfg(feature = "enable-sia")] use siacoin::SiaCoin; pub mod utxo; use utxo::bch::{bch_coin_with_policy, BchActivationRequest, BchCoin}; @@ -318,8 +276,6 @@ use script::Script; pub mod z_coin; use crate::coin_balance::{BalanceObjectOps, HDWalletBalanceObject}; use z_coin::{ZCoin, ZcoinProtocolInfo}; -#[cfg(feature = "enable-sia")] pub mod sia; -#[cfg(feature = "enable-sia")] use sia::SiaCoin; pub type TransactionFut = Box + Send>; pub type TransactionResult = Result; @@ -629,8 +585,6 @@ pub trait Transaction: fmt::Debug + 'static { pub enum TransactionEnum { UtxoTx(UtxoTx), SignedEthTx(SignedEthTx), - #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] - SolTransaction(SolTransaction), ZTransaction(ZTransaction), CosmosTransaction(CosmosTransaction), #[cfg(not(target_arch = "wasm32"))] @@ -639,8 +593,6 @@ pub enum TransactionEnum { ifrom!(TransactionEnum, UtxoTx); ifrom!(TransactionEnum, SignedEthTx); -#[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] -ifrom!(TransactionEnum, SolTransaction); ifrom!(TransactionEnum, ZTransaction); #[cfg(not(target_arch = "wasm32"))] ifrom!(TransactionEnum, LightningPayment); @@ -664,8 +616,6 @@ impl Deref for TransactionEnum { TransactionEnum::CosmosTransaction(ref t) => t, #[cfg(not(target_arch = "wasm32"))] TransactionEnum::LightningPayment(ref p) => p, - #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] - TransactionEnum::SolTransaction(ref s) => s, } } } @@ -885,7 +835,10 @@ pub enum SwapTxTypeWithSecretHash<'a> { taker_secret_hash: &'a [u8], }, /// Taker payment v2 - TakerPaymentV2 { maker_secret_hash: &'a [u8] }, + TakerPaymentV2 { + maker_secret_hash: &'a [u8], + taker_secret_hash: &'a [u8], + }, } impl<'a> SwapTxTypeWithSecretHash<'a> { @@ -907,7 +860,7 @@ impl<'a> SwapTxTypeWithSecretHash<'a> { my_public, other_public, ), - SwapTxTypeWithSecretHash::TakerPaymentV2 { maker_secret_hash } => { + SwapTxTypeWithSecretHash::TakerPaymentV2 { maker_secret_hash, .. } => { swap_proto_v2_scripts::taker_payment_script(time_lock, maker_secret_hash, my_public, other_public) }, } @@ -921,7 +874,7 @@ impl<'a> SwapTxTypeWithSecretHash<'a> { maker_secret_hash, taker_secret_hash, } => [*maker_secret_hash, *taker_secret_hash].concat(), - SwapTxTypeWithSecretHash::TakerPaymentV2 { maker_secret_hash } => maker_secret_hash.to_vec(), + SwapTxTypeWithSecretHash::TakerPaymentV2 { maker_secret_hash, .. } => maker_secret_hash.to_vec(), } } } @@ -985,6 +938,32 @@ pub struct RefundPaymentArgs<'a> { pub watcher_reward: bool, } +#[derive(Debug)] +pub struct RefundMakerPaymentTimelockArgs<'a> { + pub payment_tx: &'a [u8], + pub time_lock: u64, + pub taker_pub: &'a [u8], + pub tx_type_with_secret_hash: SwapTxTypeWithSecretHash<'a>, + pub swap_unique_data: &'a [u8], + pub watcher_reward: bool, + pub amount: BigDecimal, +} + +#[derive(Debug)] +pub struct RefundTakerPaymentArgs<'a> { + pub payment_tx: &'a [u8], + pub time_lock: u64, + pub maker_pub: &'a [u8], + pub tx_type_with_secret_hash: SwapTxTypeWithSecretHash<'a>, + pub swap_unique_data: &'a [u8], + pub watcher_reward: bool, + pub dex_fee: &'a DexFee, + /// Additional reward for maker (premium) + pub premium_amount: BigDecimal, + /// Actual volume of taker's payment + pub trading_amount: BigDecimal, +} + /// Helper struct wrapping arguments for [SwapOps::check_if_my_payment_sent]. #[derive(Clone, Debug)] pub struct CheckIfMyPaymentSentArgs<'a> { @@ -1091,11 +1070,11 @@ pub enum WatcherRewardError { /// Swap operations (mostly based on the Hash/Time locked transactions implemented by coin wallets). #[async_trait] pub trait SwapOps { - fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8], expire_at: u64) -> TransactionFut; + async fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8], expire_at: u64) -> TransactionResult; - fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionFut; + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult; - fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionFut; + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult; async fn send_maker_spends_taker_payment( &self, @@ -1111,16 +1090,16 @@ pub trait SwapOps { async fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult; - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()>; + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()>; async fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentResult<()>; async fn validate_taker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentResult<()>; - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, - ) -> Box, Error = String> + Send>; + ) -> Result, String>; async fn search_for_swap_tx_spend_my( &self, @@ -1144,14 +1123,13 @@ pub trait SwapOps { /// Whether the refund transaction can be sent now /// For example: there are no additional conditions for ETH, but for some UTXO coins we should wait for /// locktime < MTP - fn can_refund_htlc(&self, locktime: u64) -> Box + Send + '_> { + async fn can_refund_htlc(&self, locktime: u64) -> Result { let now = now_sec(); - let result = if now > locktime { - CanRefundHtlc::CanRefundNow + if now > locktime { + Ok(CanRefundHtlc::CanRefundNow) } else { - CanRefundHtlc::HaveToWait(locktime - now + 1) - }; - Box::new(futures01::future::ok(result)) + Ok(CanRefundHtlc::HaveToWait(locktime - now + 1)) + } } /// Whether the swap payment is refunded automatically or not when the locktime expires, or the other side fails the HTLC. @@ -1279,10 +1257,16 @@ pub trait WatcherOps { /// Helper struct wrapping arguments for [TakerCoinSwapOpsV2::send_taker_funding] pub struct SendTakerFundingArgs<'a> { - /// Taker will be able to refund the payment after this timestamp - pub time_lock: u64, - /// The hash of the secret generated by taker, this needs to be revealed for immediate refund + /// For UTXO-based coins, the taker can refund the funding after this timestamp if the maker hasn't claimed it. + /// For smart contracts, the taker can refund the payment after this timestamp if the maker hasn't pre-approved the transaction. + /// This field is additionally used to wait for confirmations of ERC20 approval transaction. + pub funding_time_lock: u64, + /// For smart contracts, the taker can refund the payment after this timestamp if the maker hasn't claimed it by revealing their secret. + pub payment_time_lock: u64, + /// The hash of the secret generated by the taker, needed for immediate refund pub taker_secret_hash: &'a [u8], + /// The hash of the secret generated by the maker, needed to spend the payment + pub maker_secret_hash: &'a [u8], /// Maker's pubkey pub maker_pub: &'a [u8], /// DEX fee @@ -1298,11 +1282,17 @@ pub struct SendTakerFundingArgs<'a> { /// Helper struct wrapping arguments for [TakerCoinSwapOpsV2::refund_taker_funding_secret] pub struct RefundFundingSecretArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { pub funding_tx: &'a Coin::Tx, - pub time_lock: u64, + pub funding_time_lock: u64, + pub payment_time_lock: u64, pub maker_pubkey: &'a Coin::Pubkey, pub taker_secret: &'a [u8], pub taker_secret_hash: &'a [u8], - pub swap_contract_address: &'a Option, + pub maker_secret_hash: &'a [u8], + pub dex_fee: &'a DexFee, + /// Additional reward for maker (premium) + pub premium_amount: BigDecimal, + /// Actual volume of taker's payment + pub trading_amount: BigDecimal, pub swap_unique_data: &'a [u8], pub watcher_reward: bool, } @@ -1329,12 +1319,18 @@ pub struct GenTakerFundingSpendArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { pub struct ValidateTakerFundingArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { /// Taker funding transaction pub funding_tx: &'a Coin::Tx, - /// Taker will be able to refund the payment after this timestamp - pub time_lock: u64, + /// In EVM case: The timestamp after which the taker can refund the funding transaction if the taker hasn't pre-approved the transaction + /// In UTXO case: Taker will be able to refund the payment after this timestamp + pub funding_time_lock: u64, + /// In EVM case: The timestamp after which the taker can refund the payment transaction if the maker hasn't claimed it by revealing their secret. + /// UTXO doesn't use it + pub payment_time_lock: u64, /// The hash of the secret generated by taker pub taker_secret_hash: &'a [u8], + /// The hash of the secret generated by maker + pub maker_secret_hash: &'a [u8], /// Taker's pubkey - pub other_pub: &'a Coin::Pubkey, + pub taker_pub: &'a Coin::Pubkey, /// DEX fee amount pub dex_fee: &'a DexFee, /// Additional reward for maker (premium) @@ -1415,23 +1411,36 @@ impl From for TxGenError { } /// Enum covering error cases that can happen during swap v2 transaction validation. -#[derive(Debug, Display)] +#[derive(Debug, Display, EnumFromStringify)] pub enum ValidateSwapV2TxError { /// Payment sent to wrong address or has invalid amount. InvalidDestinationOrAmount(String), /// Error during conversion of BigDecimal amount to coin's specific monetary units (satoshis, wei, etc.). NumConversion(String), /// RPC error. + #[from_stringify("web3::Error")] Rpc(String), /// Serialized tx bytes don't match ones received from coin's RPC. #[display(fmt = "Tx bytes {:02x} don't match ones received from rpc {:02x}", actual, from_rpc)] - TxBytesMismatch { from_rpc: BytesJson, actual: BytesJson }, + TxBytesMismatch { + from_rpc: BytesJson, + actual: BytesJson, + }, /// Provided transaction doesn't have output with specific index TxLacksOfOutputs, - /// Input payment timelock overflows the type used by specific coin. - LocktimeOverflow(String), + /// Indicates that overflow occurred, either while calculating a total payment or converting the timelock. + Overflow(String), /// Internal error + #[from_stringify("ethabi::Error", "TryFromSliceError")] Internal(String), + /// Payment transaction is in unexpected state. E.g., `Uninitialized` instead of `PaymentSent` for ETH payment. + UnexpectedPaymentState(String), + /// Payment transaction doesn't exist on-chain. + TxDoesNotExist(String), + /// Transaction has wrong properties, for example, it has been sent to a wrong address. + WrongPaymentTx(String), + ProtocolNotSupported(String), + InvalidData(String), } impl From for ValidateSwapV2TxError { @@ -1442,6 +1451,33 @@ impl From for ValidateSwapV2TxError { fn from(err: UtxoRpcError) -> Self { ValidateSwapV2TxError::Rpc(err.to_string()) } } +impl From for ValidateSwapV2TxError { + fn from(err: PaymentStatusErr) -> Self { + match err { + PaymentStatusErr::Internal(e) => ValidateSwapV2TxError::Internal(e), + PaymentStatusErr::Transport(e) => ValidateSwapV2TxError::Rpc(e), + PaymentStatusErr::ABIError(e) | PaymentStatusErr::InvalidData(e) => ValidateSwapV2TxError::InvalidData(e), + } + } +} + +impl From for ValidateSwapV2TxError { + fn from(err: ValidatePaymentV2Err) -> Self { + match err { + ValidatePaymentV2Err::UnexpectedPaymentState(e) => ValidateSwapV2TxError::UnexpectedPaymentState(e), + ValidatePaymentV2Err::WrongPaymentTx(e) => ValidateSwapV2TxError::WrongPaymentTx(e), + } + } +} + +impl From for ValidateSwapV2TxError { + fn from(err: PrepareTxDataError) -> Self { + match err { + PrepareTxDataError::ABIError(e) | PrepareTxDataError::Internal(e) => ValidateSwapV2TxError::Internal(e), + } + } +} + /// Enum covering error cases that can happen during taker funding spend preimage validation. #[derive(Debug, Display, EnumFromStringify)] pub enum ValidateTakerFundingSpendPreimageError { @@ -1561,8 +1597,6 @@ pub struct NftSwapInfo<'a, Coin: ParseNftAssocTypes + ?Sized> { pub token_id: &'a [u8], /// The type of smart contract that governs this NFT pub contract_type: &'a Coin::ContractType, - /// Etomic swap contract address - pub swap_contract_address: &'a Coin::ContractAddress, } pub struct SendNftMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ParseNftAssocTypes + ?Sized> { @@ -1620,7 +1654,7 @@ pub struct ValidateNftMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ParseNftA pub nft_swap_info: &'a NftSwapInfo<'a, Coin>, } -pub struct RefundMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { +pub struct RefundMakerPaymentSecretArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { /// Maker payment tx pub maker_payment_tx: &'a Coin::Tx, /// Maker will be able to refund the payment after this timestamp @@ -1635,6 +1669,24 @@ pub struct RefundMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { pub taker_pub: &'a Coin::Pubkey, /// Unique data of specific swap pub swap_unique_data: &'a [u8], + pub amount: BigDecimal, +} + +/// Common refund NFT Maker Payment structure for [MakerNftSwapOpsV2::refund_nft_maker_payment_v2_timelock] and +/// [MakerNftSwapOpsV2::refund_nft_maker_payment_v2_secret] methods +pub struct RefundNftMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ParseNftAssocTypes + ?Sized> { + /// Maker payment tx + pub maker_payment_tx: &'a Coin::Tx, + /// The hash of the secret generated by taker, this is used for immediate refund + pub taker_secret_hash: &'a [u8], + /// The hash of the secret generated by maker, taker needs it to spend the payment + pub maker_secret_hash: &'a [u8], + /// Taker's secret + pub taker_secret: &'a [u8], + /// Unique data of specific swap + pub swap_unique_data: &'a [u8], + /// The type of smart contract that governs this NFT + pub contract_type: &'a Coin::ContractType, } pub struct SpendMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { @@ -1652,13 +1704,12 @@ pub struct SpendMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { pub maker_pub: &'a Coin::Pubkey, /// Unique data of specific swap pub swap_unique_data: &'a [u8], + pub amount: BigDecimal, } pub struct SpendNftMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ParseNftAssocTypes + ?Sized> { /// Maker payment tx pub maker_payment_tx: &'a Coin::Tx, - /// Maker will be able to refund the payment after this timestamp - pub time_lock: u64, /// The hash of the secret generated by taker, this is used for immediate refund pub taker_secret_hash: &'a [u8], /// The hash of the secret generated by maker, taker needs it to spend the payment @@ -1671,13 +1722,11 @@ pub struct SpendNftMakerPaymentArgs<'a, Coin: ParseCoinAssocTypes + ParseNftAsso pub swap_unique_data: &'a [u8], /// The type of smart contract that governs this NFT pub contract_type: &'a Coin::ContractType, - /// Etomic swap contract address - pub swap_contract_address: &'a Coin::ContractAddress, } /// Operations specific to maker coin in [Trading Protocol Upgrade implementation](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1895) #[async_trait] -pub trait MakerCoinSwapOpsV2: ParseCoinAssocTypes + Send + Sync + 'static { +pub trait MakerCoinSwapOpsV2: ParseCoinAssocTypes + CommonSwapOpsV2 + Send + Sync + 'static { /// Generate and broadcast maker payment transaction async fn send_maker_payment_v2(&self, args: SendMakerPaymentArgs<'_, Self>) -> Result; @@ -1685,12 +1734,15 @@ pub trait MakerCoinSwapOpsV2: ParseCoinAssocTypes + Send + Sync + 'static { async fn validate_maker_payment_v2(&self, args: ValidateMakerPaymentArgs<'_, Self>) -> ValidatePaymentResult<()>; /// Refund maker payment transaction using timelock path - async fn refund_maker_payment_v2_timelock(&self, args: RefundPaymentArgs<'_>) -> Result; + async fn refund_maker_payment_v2_timelock( + &self, + args: RefundMakerPaymentTimelockArgs<'_>, + ) -> Result; /// Refund maker payment transaction using immediate refund path async fn refund_maker_payment_v2_secret( &self, - args: RefundMakerPaymentArgs<'_, Self>, + args: RefundMakerPaymentSecretArgs<'_, Self>, ) -> Result; /// Spend maker payment transaction @@ -1719,19 +1771,19 @@ pub trait MakerNftSwapOpsV2: ParseCoinAssocTypes + ParseNftAssocTypes + Send + S /// Refund NFT maker payment transaction using timelock path async fn refund_nft_maker_payment_v2_timelock( &self, - args: RefundPaymentArgs<'_>, + args: RefundNftMakerPaymentArgs<'_, Self>, ) -> Result; /// Refund NFT maker payment transaction using immediate refund path async fn refund_nft_maker_payment_v2_secret( &self, - args: RefundMakerPaymentArgs<'_, Self>, + args: RefundNftMakerPaymentArgs<'_, Self>, ) -> Result; } /// Enum representing errors that can occur while waiting for taker payment spend. -#[derive(Display)] -pub enum WaitForTakerPaymentSpendError { +#[derive(Display, Debug, EnumFromStringify)] +pub enum WaitForPaymentSpendError { /// Timeout error variant, indicating that the wait for taker payment spend has timed out. #[display( fmt = "Timed out waiting for taker payment spend, wait_until {}, now {}", @@ -1744,24 +1796,47 @@ pub enum WaitForTakerPaymentSpendError { /// The current timestamp when the timeout occurred. now: u64, }, - /// Invalid input transaction error variant, containing additional information about the error. InvalidInputTx(String), + Internal(String), + #[from_stringify("ethabi::Error")] + #[display(fmt = "ABI error: {}", _0)] + ABIError(String), + InvalidData(String), + Transport(String), } -impl From for WaitForTakerPaymentSpendError { +impl From for WaitForPaymentSpendError { fn from(err: WaitForOutputSpendErr) -> Self { match err { - WaitForOutputSpendErr::Timeout { wait_until, now } => { - WaitForTakerPaymentSpendError::Timeout { wait_until, now } - }, + WaitForOutputSpendErr::Timeout { wait_until, now } => WaitForPaymentSpendError::Timeout { wait_until, now }, WaitForOutputSpendErr::NoOutputWithIndex(index) => { - WaitForTakerPaymentSpendError::InvalidInputTx(format!("Tx doesn't have output with index {}", index)) + WaitForPaymentSpendError::InvalidInputTx(format!("Tx doesn't have output with index {}", index)) }, } } } +impl From for WaitForPaymentSpendError { + fn from(e: PaymentStatusErr) -> Self { + match e { + PaymentStatusErr::ABIError(e) => Self::ABIError(e), + PaymentStatusErr::Transport(e) => Self::Transport(e), + PaymentStatusErr::Internal(e) => Self::Internal(e), + PaymentStatusErr::InvalidData(e) => Self::InvalidData(e), + } + } +} + +impl From for WaitForPaymentSpendError { + fn from(e: PrepareTxDataError) -> Self { + match e { + PrepareTxDataError::ABIError(e) => Self::ABIError(e), + PrepareTxDataError::Internal(e) => Self::Internal(e), + } + } +} + /// Enum representing different ways a funding transaction can be spent. /// /// This enum is generic over types that implement the `ParseCoinAssocTypes` trait. @@ -1807,11 +1882,12 @@ pub enum SearchForFundingSpendErr { Rpc(String), /// Variant indicating an error during conversion of the `from_block` argument with associated `TryFromIntError`. FromBlockConversionErr(TryFromIntError), + Internal(String), } /// Operations specific to taker coin in [Trading Protocol Upgrade implementation](https://github.com/KomodoPlatform/komodo-defi-framework/issues/1895) #[async_trait] -pub trait TakerCoinSwapOpsV2: ParseCoinAssocTypes + Send + Sync + 'static { +pub trait TakerCoinSwapOpsV2: ParseCoinAssocTypes + CommonSwapOpsV2 + Send + Sync + 'static { /// Generate and broadcast taker funding transaction that includes dex fee, maker premium and actual trading volume. /// Funding tx can be reclaimed immediately if maker back-outs (doesn't send maker payment) async fn send_taker_funding(&self, args: SendTakerFundingArgs<'_>) -> Result; @@ -1820,7 +1896,8 @@ pub trait TakerCoinSwapOpsV2: ParseCoinAssocTypes + Send + Sync + 'static { async fn validate_taker_funding(&self, args: ValidateTakerFundingArgs<'_, Self>) -> ValidateSwapV2TxResult; /// Refunds taker funding transaction using time-locked path without secret reveal. - async fn refund_taker_funding_timelock(&self, args: RefundPaymentArgs<'_>) -> Result; + async fn refund_taker_funding_timelock(&self, args: RefundTakerPaymentArgs<'_>) + -> Result; /// Reclaims taker funding transaction using immediate refund path with secret reveal. async fn refund_taker_funding_secret( @@ -1850,7 +1927,7 @@ pub trait TakerCoinSwapOpsV2: ParseCoinAssocTypes + Send + Sync + 'static { preimage: &TxPreimageWithSig, ) -> ValidateTakerFundingSpendPreimageResult; - /// Generates and signs a preimage spending funding tx to the combined taker payment + /// Sign and send a spending funding tx to the combined taker payment async fn sign_and_send_taker_funding_spend( &self, preimage: &TxPreimageWithSig, @@ -1859,7 +1936,8 @@ pub trait TakerCoinSwapOpsV2: ParseCoinAssocTypes + Send + Sync + 'static { ) -> Result; /// Refunds taker payment transaction. - async fn refund_combined_taker_payment(&self, args: RefundPaymentArgs<'_>) -> Result; + async fn refund_combined_taker_payment(&self, args: RefundTakerPaymentArgs<'_>) + -> Result; /// Generates and signs taker payment spend preimage. The preimage and signature should be /// shared with maker to proceed with protocol execution. @@ -1891,10 +1969,15 @@ pub trait TakerCoinSwapOpsV2: ParseCoinAssocTypes + Send + Sync + 'static { taker_payment: &Self::Tx, from_block: u64, wait_until: u64, - ) -> MmResult; + ) -> MmResult; +} +#[async_trait] +pub trait CommonSwapOpsV2: ParseCoinAssocTypes + Send + Sync + 'static { /// Derives an HTLC key-pair and returns a public key corresponding to that key. fn derive_htlc_pubkey_v2(&self, swap_unique_data: &[u8]) -> Self::Pubkey; + + fn derive_htlc_pubkey_v2_bytes(&self, swap_unique_data: &[u8]) -> Vec; } /// Operations that coins have independently from the MarketMaker. @@ -1945,7 +2028,13 @@ pub trait MarketCoinOps { fn wait_for_confirmations(&self, input: ConfirmPaymentInput) -> Box + Send>; - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut; + /// Waits for spending/unlocking of funds locked in a HTLC construction specific to the coin's + /// chain. Implementation should monitor locked funds (UTXO/contract/etc.) until funds are + /// spent/unlocked or timeout is reached. + /// + /// Returns spending tx/event from mempool/pending state to allow prompt extraction of preimage + /// secret. + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult; fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result>; @@ -2129,13 +2218,6 @@ pub enum TxFeeDetails { Qrc20(Qrc20FeeDetails), Slp(SlpFeeDetails), Tendermint(TendermintFeeDetails), - #[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") - ))] - Solana(SolanaFeeDetails), } /// Deserialize the TxFeeDetails as an untagged enum. @@ -2150,13 +2232,6 @@ impl<'de> Deserialize<'de> for TxFeeDetails { Utxo(UtxoFeeDetails), Eth(EthTxFeeDetails), Qrc20(Qrc20FeeDetails), - #[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") - ))] - Solana(SolanaFeeDetails), Tendermint(TendermintFeeDetails), } @@ -2164,13 +2239,6 @@ impl<'de> Deserialize<'de> for TxFeeDetails { TxFeeDetailsUnTagged::Utxo(f) => Ok(TxFeeDetails::Utxo(f)), TxFeeDetailsUnTagged::Eth(f) => Ok(TxFeeDetails::Eth(f)), TxFeeDetailsUnTagged::Qrc20(f) => Ok(TxFeeDetails::Qrc20(f)), - #[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") - ))] - TxFeeDetailsUnTagged::Solana(f) => Ok(TxFeeDetails::Solana(f)), TxFeeDetailsUnTagged::Tendermint(f) => Ok(TxFeeDetails::Tendermint(f)), } } @@ -2188,16 +2256,6 @@ impl From for TxFeeDetails { fn from(qrc20_details: Qrc20FeeDetails) -> Self { TxFeeDetails::Qrc20(qrc20_details) } } -#[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") -))] -impl From for TxFeeDetails { - fn from(solana_details: SolanaFeeDetails) -> Self { TxFeeDetails::Solana(solana_details) } -} - impl From for TxFeeDetails { fn from(tendermint_details: TendermintFeeDetails) -> Self { TxFeeDetails::Tendermint(tendermint_details) } } @@ -2585,7 +2643,7 @@ pub enum BalanceError { UnexpectedDerivationMethod(UnexpectedDerivationMethod), #[display(fmt = "Wallet storage error: {}", _0)] WalletStorageError(String), - #[from_stringify("Bip32Error", "NumConversError")] + #[from_stringify("Bip32Error", "NumConversError", "ParseBigIntError")] #[display(fmt = "Internal: {}", _0)] Internal(String), } @@ -2937,8 +2995,8 @@ pub enum WithdrawError { NotEnoughNftsAmount { token_address: String, token_id: String, - available: BigDecimal, - required: BigDecimal, + available: BigUint, + required: BigUint, }, #[display(fmt = "DB error {}", _0)] DbError(String), @@ -3219,6 +3277,10 @@ pub trait MmCoin: /// The coin can be initialized, but it cannot participate in the swaps. fn wallet_only(&self, ctx: &MmArc) -> bool { let coin_conf = coin_conf(ctx, self.ticker()); + // If coin is not in config, it means that it was added manually (a custom token) and should be treated as wallet only + if coin_conf.is_null() { + return true; + } coin_conf["wallet_only"].as_bool().unwrap_or(false) } @@ -3402,20 +3464,6 @@ pub enum MmCoinEnum { SlpToken(SlpToken), Tendermint(TendermintCoin), TendermintToken(TendermintToken), - #[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") - ))] - SolanaCoin(SolanaCoin), - #[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") - ))] - SplToken(SplToken), #[cfg(not(target_arch = "wasm32"))] LightningCoin(LightningCoin), #[cfg(feature = "enable-sia")] @@ -3435,26 +3483,6 @@ impl From for MmCoinEnum { fn from(c: TestCoin) -> MmCoinEnum { MmCoinEnum::Test(c) } } -#[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") -))] -impl From for MmCoinEnum { - fn from(c: SolanaCoin) -> MmCoinEnum { MmCoinEnum::SolanaCoin(c) } -} - -#[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") -))] -impl From for MmCoinEnum { - fn from(c: SplToken) -> MmCoinEnum { MmCoinEnum::SplToken(c) } -} - impl From for MmCoinEnum { fn from(coin: QtumCoin) -> Self { MmCoinEnum::QtumCoin(coin) } } @@ -3512,20 +3540,6 @@ impl Deref for MmCoinEnum { #[cfg(feature = "enable-sia")] MmCoinEnum::SiaCoin(ref c) => c, MmCoinEnum::Test(ref c) => c, - #[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") - ))] - MmCoinEnum::SolanaCoin(ref c) => c, - #[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") - ))] - MmCoinEnum::SplToken(ref c) => c, } } } @@ -4235,14 +4249,6 @@ pub enum CoinProtocol { network: BlockchainNetwork, confirmation_targets: PlatformCoinConfirmationTargets, }, - #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] - SOLANA, - #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] - SPLTOKEN { - platform: String, - token_contract_address: String, - decimals: u8, - }, ZHTLC(ZcoinProtocolInfo), #[cfg(feature = "enable-sia")] SIA, @@ -4251,9 +4257,101 @@ pub enum CoinProtocol { }, } -pub type RpcTransportEventHandlerShared = Arc; +#[derive(Clone, Debug, Deserialize, Display, PartialEq, Serialize)] +pub enum CustomTokenError { + #[display( + fmt = "Token with the same ticker already exists in coins configs, ticker in config: {}", + ticker_in_config + )] + DuplicateTickerInConfig { ticker_in_config: String }, + #[display( + fmt = "Token with the same contract address already exists in coins configs, ticker in config: {}", + ticker_in_config + )] + DuplicateContractInConfig { ticker_in_config: String }, + #[display( + fmt = "Token is already activated, ticker: {}, contract address: {}", + ticker, + contract_address + )] + TokenWithSameContractAlreadyActivated { ticker: String, contract_address: String }, +} + +impl CoinProtocol { + /// Returns the platform coin associated with the coin protocol, if any. + pub fn platform(&self) -> Option<&str> { + match self { + CoinProtocol::QRC20 { platform, .. } + | CoinProtocol::ERC20 { platform, .. } + | CoinProtocol::SLPTOKEN { platform, .. } + | CoinProtocol::NFT { platform, .. } => Some(platform), + CoinProtocol::TENDERMINTTOKEN(info) => Some(&info.platform), + #[cfg(not(target_arch = "wasm32"))] + CoinProtocol::LIGHTNING { platform, .. } => Some(platform), + CoinProtocol::UTXO + | CoinProtocol::QTUM + | CoinProtocol::ETH + | CoinProtocol::BCH { .. } + | CoinProtocol::TENDERMINT(_) + | CoinProtocol::ZHTLC(_) => None, + #[cfg(feature = "enable-sia")] + CoinProtocol::SIA => None, + } + } + + /// Returns the contract address associated with the coin, if any. + pub fn contract_address(&self) -> Option<&str> { + match self { + CoinProtocol::QRC20 { contract_address, .. } | CoinProtocol::ERC20 { contract_address, .. } => { + Some(contract_address) + }, + CoinProtocol::SLPTOKEN { .. } + | CoinProtocol::UTXO + | CoinProtocol::QTUM + | CoinProtocol::ETH + | CoinProtocol::BCH { .. } + | CoinProtocol::TENDERMINT(_) + | CoinProtocol::TENDERMINTTOKEN(_) + | CoinProtocol::ZHTLC(_) + | CoinProtocol::NFT { .. } => None, + #[cfg(not(target_arch = "wasm32"))] + CoinProtocol::LIGHTNING { .. } => None, + #[cfg(feature = "enable-sia")] + CoinProtocol::SIA => None, + } + } + + /// Several checks to be preformed when a custom token is being activated to check uniqueness among other things. + #[allow(clippy::result_large_err)] + pub fn custom_token_validations(&self, ctx: &MmArc) -> MmResult<(), CustomTokenError> { + let CoinProtocol::ERC20 { + platform, + contract_address, + } = self + else { + return Ok(()); + }; + + // Check if there is a token with the same contract address in the config. + // If there is, return an error as the user should use this token instead of activating a custom one. + // This is necessary as we will create an orderbook for this custom token using the contract address, + // if it is duplicated in config, we will have two orderbooks one using the ticker and one using the contract address. + // Todo: We should use the contract address for orderbook topics instead of the ticker once we make custom tokens non-wallet only. + // If a coin is added to the config later, users who added it as a custom token and did not update will not see the orderbook. + if let Some(existing_ticker) = get_erc20_ticker_by_contract_address(ctx, platform, contract_address) { + return Err(MmError::new(CustomTokenError::DuplicateContractInConfig { + ticker_in_config: existing_ticker, + })); + } + + Ok(()) + } +} -/// Common methods to measure the outgoing requests and incoming responses statistics. +/// Common methods to handle the connection events. +/// +/// Note that the handler methods are sync and shouldn't take long time executing, otherwise it will hurt the performance. +/// If a handler needs to do some heavy work, it should be spawned/done in a separate thread. pub trait RpcTransportEventHandler { fn debug_info(&self) -> String; @@ -4261,12 +4359,15 @@ pub trait RpcTransportEventHandler { fn on_incoming_response(&self, data: &[u8]); - fn on_connected(&self, address: String) -> Result<(), String>; + fn on_connected(&self, address: &str) -> Result<(), String>; - fn on_disconnected(&self, address: String) -> Result<(), String>; + fn on_disconnected(&self, address: &str) -> Result<(), String>; } -impl fmt::Debug for dyn RpcTransportEventHandler + Send + Sync { +pub type SharableRpcTransportEventHandler = dyn RpcTransportEventHandler + Send + Sync; +pub type RpcTransportEventHandlerShared = Arc; + +impl fmt::Debug for SharableRpcTransportEventHandler { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.debug_info()) } } @@ -4277,9 +4378,21 @@ impl RpcTransportEventHandler for RpcTransportEventHandlerShared { 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: &str) -> Result<(), String> { self.as_ref().on_connected(address) } + + fn on_disconnected(&self, address: &str) -> Result<(), String> { self.as_ref().on_disconnected(address) } +} + +impl RpcTransportEventHandler for Box { + fn debug_info(&self) -> String { self.as_ref().debug_info() } + + 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_connected(&self, address: &str) -> Result<(), String> { self.as_ref().on_connected(address) } - fn on_disconnected(&self, address: String) -> Result<(), String> { self.as_ref().on_disconnected(address) } + fn on_disconnected(&self, address: &str) -> Result<(), String> { self.as_ref().on_disconnected(address) } } impl RpcTransportEventHandler for Vec { @@ -4300,16 +4413,28 @@ impl RpcTransportEventHandler for Vec { } } - fn on_connected(&self, address: String) -> Result<(), String> { + fn on_connected(&self, address: &str) -> Result<(), String> { + let mut errors = vec![]; for handler in self { - try_s!(handler.on_connected(address.clone())) + if let Err(e) = handler.on_connected(address) { + errors.push((handler.debug_info(), e)) + } + } + if !errors.is_empty() { + return Err(format!("Errors: {:?}", errors)); } Ok(()) } - fn on_disconnected(&self, address: String) -> Result<(), String> { + fn on_disconnected(&self, address: &str) -> Result<(), String> { + let mut errors = vec![]; for handler in self { - try_s!(handler.on_disconnected(address.clone())) + if let Err(e) = handler.on_disconnected(address) { + errors.push((handler.debug_info(), e)) + } + } + if !errors.is_empty() { + return Err(format!("Errors: {:?}", errors)); } Ok(()) } @@ -4370,17 +4495,9 @@ impl RpcTransportEventHandler for CoinTransportMetrics { "coin" => self.ticker.to_owned(), "client" => self.client.to_owned()); } - fn on_connected(&self, _address: String) -> Result<(), String> { - // Handle a new connected endpoint if necessary. - // Now just return the Ok - Ok(()) - } + fn on_connected(&self, _address: &str) -> Result<(), String> { Ok(()) } - fn on_disconnected(&self, _address: String) -> Result<(), String> { - // Handle disconnected endpoint if necessary. - // Now just return the Ok - Ok(()) - } + fn on_disconnected(&self, _address: &str) -> Result<(), String> { Ok(()) } } #[async_trait] @@ -4403,10 +4520,20 @@ pub fn coin_conf(ctx: &MmArc, ticker: &str) -> Json { } } -pub fn is_wallet_only_conf(conf: &Json) -> bool { conf["wallet_only"].as_bool().unwrap_or(false) } +pub fn is_wallet_only_conf(conf: &Json) -> bool { + // If coin is not in config, it means that it was added manually (a custom token) and should be treated as wallet only + if conf.is_null() { + return true; + } + conf["wallet_only"].as_bool().unwrap_or(false) +} pub fn is_wallet_only_ticker(ctx: &MmArc, ticker: &str) -> bool { let coin_conf = coin_conf(ctx, ticker); + // If coin is not in config, it means that it was added manually (a custom token) and should be treated as wallet only + if coin_conf.is_null() { + return true; + } coin_conf["wallet_only"].as_bool().unwrap_or(false) } @@ -4504,14 +4631,6 @@ pub async fn lp_coininit(ctx: &MmArc, ticker: &str, req: &Json) -> Result return ERR!("NFT protocol is not supported by lp_coininit"), #[cfg(not(target_arch = "wasm32"))] CoinProtocol::LIGHTNING { .. } => return ERR!("Lightning protocol is not supported by lp_coininit"), - #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] - CoinProtocol::SOLANA => { - return ERR!("Solana protocol is not supported by lp_coininit - use enable_solana_with_tokens instead") - }, - #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] - CoinProtocol::SPLTOKEN { .. } => { - return ERR!("SplToken protocol is not supported by lp_coininit - use enable_spl instead") - }, #[cfg(feature = "enable-sia")] CoinProtocol::SIA { .. } => { return ERR!("SIA protocol is not supported by lp_coininit. Use task::enable_sia::init"); @@ -5096,10 +5215,6 @@ pub fn address_by_coin_conf_and_pubkey_str( CoinProtocol::LIGHTNING { .. } => { ERR!("address_by_coin_conf_and_pubkey_str is not implemented for lightning protocol yet!") }, - #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] - CoinProtocol::SOLANA | CoinProtocol::SPLTOKEN { .. } => { - ERR!("Solana pubkey is the public address - you do not need to use this rpc call.") - }, CoinProtocol::ZHTLC { .. } => ERR!("address_by_coin_conf_and_pubkey_str is not supported for ZHTLC protocol!"), #[cfg(feature = "enable-sia")] CoinProtocol::SIA { .. } => ERR!("address_by_coin_conf_and_pubkey_str is not supported for SIA protocol!"), // TODO Alright diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index 4d432235a9..afc5f260a9 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -1,5 +1,8 @@ +use http::Uri; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::{MmError, MmResult}; +use mm2_p2p::p2p_ctx::P2PContext; +use proxy_signature::{ProxySign, RawMessage}; use url::Url; pub(crate) mod nft_errors; @@ -22,6 +25,8 @@ use crate::nft::nft_errors::{ClearNftDbError, MetaFromUrlError, ProtectFromSpamE use crate::nft::nft_structs::{build_nft_with_empty_meta, BuildNftFields, ClearNftDbReq, NftCommon, NftCtx, NftInfo, NftTransferCommon, PhishingDomainReq, PhishingDomainRes, RefreshMetadataReq, SpamContractReq, SpamContractRes, TransferMeta, TransferStatus, UriMeta}; +#[cfg(not(target_arch = "wasm32"))] +use crate::nft::storage::NftMigrationOps; use crate::nft::storage::{NftListStorageOps, NftTransferHistoryStorageOps}; use common::log::error; use common::parse_rfc3339_to_timestamp; @@ -29,7 +34,7 @@ use ethereum_types::{Address, H256}; use futures::compat::Future01CompatExt; use futures::future::try_join_all; use mm2_err_handle::map_to_mm::MapToMmResult; -use mm2_net::transport::{send_post_request_to_uri, KomodefiProxyAuthValidation}; +use mm2_net::transport::send_post_request_to_uri; use mm2_number::BigUint; use regex::Regex; use serde::Deserialize; @@ -42,7 +47,6 @@ use web3::types::TransactionId; #[cfg(not(target_arch = "wasm32"))] use mm2_net::native_http::send_request_to_uri; -use crate::eth::v2_activation::generate_signed_message; #[cfg(target_arch = "wasm32")] use mm2_net::wasm::http::send_request_to_uri; @@ -153,6 +157,9 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult MmResult<(), UpdateNftError> { let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; + let p2p_ctx = P2PContext::fetch_from_mm_arc(&ctx); let storage = nft_ctx.lock_db().await?; for chain in req.chains.iter() { let transfer_history_initialized = NftTransferHistoryStorageOps::is_initialized(&storage, chain).await?; let from_block = if transfer_history_initialized { + #[cfg(not(target_arch = "wasm32"))] + NftMigrationOps::migrate_tx_history_if_needed(&storage, chain).await?; let last_transfer_block = NftTransferHistoryStorageOps::get_last_block_number(&storage, chain).await?; last_transfer_block.map(|b| b + 1) } else { @@ -237,14 +247,20 @@ pub async fn update_nft(ctx: MmArc, req: UpdateNftReq) -> MmResult<(), UpdateNft }) }, }; - let my_address = eth_coin.my_address()?; - let signed_message = - generate_signed_message(req.proxy_auth, chain, my_address, ð_coin.priv_key_policy).await?; + let proxy_sign = if req.komodo_proxy { + let uri = Uri::from_str(req.url.as_ref()).map_err(|e| UpdateNftError::Internal(e.to_string()))?; + let proxy_sign = RawMessage::sign(p2p_ctx.keypair(), &uri, 0, common::PROXY_REQUEST_EXPIRATION_SEC) + .map_err(|e| UpdateNftError::Internal(e.to_string()))?; + Some(proxy_sign) + } else { + None + }; + let wrapper = UrlSignWrapper { chain, orig_url: &req.url, url_antispam: &req.url_antispam, - signed_message: signed_message.as_ref(), + proxy_sign, }; let nft_transfers = get_moralis_nft_transfers(&ctx, from_block, eth_coin, &wrapper).await?; @@ -460,27 +476,23 @@ fn prepare_uri_for_blocklist_endpoint( /// `possible_phishing` flags are set to true. pub async fn refresh_nft_metadata(ctx: MmArc, req: RefreshMetadataReq) -> MmResult<(), UpdateNftError> { let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?; + let p2p_ctx = P2PContext::fetch_from_mm_arc(&ctx); let storage = nft_ctx.lock_db().await?; - // TODO activate and use global NFT instead of ETH coin after adding enable nft using coin conf support - let coin_enum = lp_coinfind_or_err(&ctx, req.chain.to_ticker()).await?; - let eth_coin = match coin_enum { - MmCoinEnum::EthCoin(eth_coin) => eth_coin, - _ => { - return MmError::err(UpdateNftError::CoinDoesntSupportNft { - coin: coin_enum.ticker().to_owned(), - }) - }, + let proxy_sign = if req.komodo_proxy { + let uri = Uri::from_str(req.url.as_ref()).map_err(|e| UpdateNftError::Internal(e.to_string()))?; + let proxy_sign = RawMessage::sign(p2p_ctx.keypair(), &uri, 0, common::PROXY_REQUEST_EXPIRATION_SEC) + .map_err(|e| UpdateNftError::Internal(e.to_string()))?; + Some(proxy_sign) + } else { + None }; - let my_address = eth_coin.my_address()?; - let signed_message = - generate_signed_message(req.proxy_auth, &req.chain, my_address, ð_coin.priv_key_policy).await?; let wrapper = UrlSignWrapper { chain: &req.chain, orig_url: &req.url, url_antispam: &req.url_antispam, - signed_message: signed_message.as_ref(), + proxy_sign, }; let token_address_str = eth_addr_to_hex(&req.token_address); @@ -636,7 +648,7 @@ async fn get_moralis_nft_list(ctx: &MmArc, wrapper: &UrlSignWrapper<'_>) -> MmRe loop { // Create a new URL instance from uri_without_cursor and modify its query to include the cursor if present let uri = format!("{}{}", uri_without_cursor, cursor); - let response = build_and_send_request(uri.as_str(), wrapper.signed_message).await?; + let response = build_and_send_request(uri.as_str(), &wrapper.proxy_sign).await?; if let Some(nfts_list) = response["result"].as_array() { for nft_json in nfts_list { let nft_moralis = NftFromMoralis::deserialize(nft_json)?; @@ -668,7 +680,7 @@ pub(crate) async fn get_nfts_for_activation( chain: &Chain, my_address: &Address, orig_url: &Url, - signed_message: Option<&KomodefiProxyAuthValidation>, + proxy_sign: Option, ) -> MmResult, GetNftInfoError> { let mut nfts_map = HashMap::new(); let uri_without_cursor = construct_moralis_uri_for_nft(orig_url, ð_addr_to_hex(my_address), chain)?; @@ -678,7 +690,7 @@ pub(crate) async fn get_nfts_for_activation( loop { // Create a new URL instance from uri_without_cursor and modify its query to include the cursor if present let uri = format!("{}{}", uri_without_cursor, cursor); - let response = build_and_send_request(uri.as_str(), signed_message).await?; + let response = build_and_send_request(uri.as_str(), &proxy_sign).await?; if let Some(nfts_list) = response["result"].as_array() { process_nft_list_for_activation(nfts_list, chain, &mut nfts_map)?; // if cursor is not null, there are other NFTs on next page, @@ -759,7 +771,7 @@ async fn get_moralis_nft_transfers( loop { // Create a new URL instance from uri_without_cursor and modify its query to include the cursor if present let uri = format!("{}{}", uri_without_cursor, cursor); - let response = build_and_send_request(uri.as_str(), wrapper.signed_message).await?; + let response = build_and_send_request(uri.as_str(), &wrapper.proxy_sign).await?; if let Some(transfer_list) = response["result"].as_array() { process_transfer_list(transfer_list, chain, wallet_address.as_str(), ð_coin, &mut res_list).await?; // if the cursor is not null, there are other NFTs transfers on next page, @@ -901,7 +913,7 @@ async fn get_moralis_metadata( .append_pair(MORALIS_FORMAT_QUERY_NAME, MORALIS_FORMAT_QUERY_VALUE); drop_mutability!(uri); - let response = build_and_send_request(uri.as_str(), wrapper.signed_message).await?; + let response = build_and_send_request(uri.as_str(), &wrapper.proxy_sign).await?; let nft_moralis: NftFromMoralis = serde_json::from_str(&response.to_string())?; let contract_type = match nft_moralis.contract_type { Some(contract_type) => contract_type, @@ -1565,14 +1577,11 @@ struct UrlSignWrapper<'a> { chain: &'a Chain, orig_url: &'a Url, url_antispam: &'a Url, - signed_message: Option<&'a KomodefiProxyAuthValidation>, + proxy_sign: Option, } -async fn build_and_send_request( - uri: &str, - signed_message: Option<&KomodefiProxyAuthValidation>, -) -> MmResult { - let payload = signed_message.map(|msg| serde_json::to_string(&msg)).transpose()?; +async fn build_and_send_request(uri: &str, proxy_sign: &Option) -> MmResult { + let payload = proxy_sign.as_ref().map(|msg| serde_json::to_string(&msg)).transpose()?; let response = send_request_to_uri(uri, payload.as_deref()).await?; Ok(response) } diff --git a/mm2src/coins/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs index 92e9c62d30..b10f68d05d 100644 --- a/mm2src/coins/nft/nft_structs.rs +++ b/mm2src/coins/nft/nft_structs.rs @@ -21,6 +21,9 @@ use crate::nft::nft_errors::{LockDBError, ParseChainTypeError, ParseContractType use crate::nft::storage::{NftListStorageOps, NftTransferHistoryStorageOps}; use crate::{TransactionType, TxFeeDetails, WithdrawFee}; +#[cfg(not(target_arch = "wasm32"))] +use crate::nft::storage::NftMigrationOps; + cfg_native! { use db_common::async_sql_conn::AsyncConnection; use futures::lock::Mutex as AsyncMutex; @@ -98,7 +101,8 @@ pub struct RefreshMetadataReq { /// URL used to validate if the fetched contract addresses are associated /// with spam contracts or if domain fields in the fetched metadata match known phishing domains. pub(crate) url_antispam: Url, - pub(crate) proxy_auth: bool, + #[serde(default)] + pub(crate) komodo_proxy: bool, } /// Represents blockchains which are supported by NFT feature. @@ -437,7 +441,8 @@ pub struct WithdrawErc1155 { #[serde(deserialize_with = "deserialize_token_id")] pub(crate) token_id: BigUint, /// Optional amount of the token to withdraw. Defaults to 1 if not specified. - pub(crate) amount: Option, + #[serde(deserialize_with = "deserialize_opt_biguint")] + pub(crate) amount: Option, /// If set to `true`, withdraws the maximum amount available. Overrides the `amount` field. #[serde(default)] pub(crate) max: bool, @@ -488,7 +493,7 @@ pub struct TransactionNftDetails { pub(crate) token_address: String, #[serde(serialize_with = "serialize_token_id")] pub(crate) token_id: BigUint, - pub(crate) amount: BigDecimal, + pub(crate) amount: BigUint, pub(crate) fee_details: Option, /// The coin transaction belongs to pub(crate) coin: String, @@ -661,7 +666,8 @@ pub struct UpdateNftReq { /// URL used to validate if the fetched contract addresses are associated /// with spam contracts or if domain fields in the fetched metadata match known phishing domains. pub(crate) url_antispam: Url, - pub(crate) proxy_auth: bool, + #[serde(default)] + pub(crate) komodo_proxy: bool, } /// Represents a unique identifier for an NFT, consisting of its token address and token ID. @@ -751,7 +757,7 @@ impl NftCtx { #[cfg(not(target_arch = "wasm32"))] pub(crate) async fn lock_db( &self, - ) -> MmResult { + ) -> MmResult { Ok(self.nft_cache_db.lock().await) } @@ -804,6 +810,19 @@ where BigUint::from_str(&s).map_err(serde::de::Error::custom) } +/// Custom deserialization function for optional BigUint. +fn deserialize_opt_biguint<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let opt: Option = Option::deserialize(deserializer)?; + if let Some(s) = opt { + BigUint::from_str(&s).map(Some).map_err(serde::de::Error::custom) + } else { + Ok(None) + } +} + /// Request parameters for clearing NFT data from the database. #[derive(Debug, Deserialize)] pub struct ClearNftDbReq { diff --git a/mm2src/coins/nft/nft_tests.rs b/mm2src/coins/nft/nft_tests.rs index 05f732a9ee..71001d8f21 100644 --- a/mm2src/coins/nft/nft_tests.rs +++ b/mm2src/coins/nft/nft_tests.rs @@ -463,7 +463,12 @@ cross_test!(test_add_get_transfers, { .clone(); assert_eq!(transfer1.block_number, 28056721); let transfer2 = storage - .get_transfer_by_tx_hash_and_log_index(&chain, TX_HASH.to_string(), LOG_INDEX) + .get_transfer_by_tx_hash_log_index_token_id( + &chain, + TX_HASH.to_string(), + LOG_INDEX, + BigUint::from_str("214300047253").unwrap(), + ) .await .unwrap() .unwrap(); diff --git a/mm2src/coins/nft/storage/db_test_helpers.rs b/mm2src/coins/nft/storage/db_test_helpers.rs index d59b845661..2565be8f2e 100644 --- a/mm2src/coins/nft/storage/db_test_helpers.rs +++ b/mm2src/coins/nft/storage/db_test_helpers.rs @@ -258,7 +258,7 @@ pub(crate) fn nft_transfer_history() -> Vec { transaction_index: Some(198), log_index: 495, value: Default::default(), - transaction_type: Some("Single".to_string()), + transaction_type: Some("Batch".to_string()), token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), from_address: Address::from_str("0x6fad0ec6bb76914b2a2a800686acc22970645820").unwrap(), to_address: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(), @@ -284,15 +284,15 @@ pub(crate) fn nft_transfer_history() -> Vec { confirmations: 0, }; - // Same as transfer1 but with different log_index, meaning that transfer1 and transfer2 are part of one batch/multi token transaction + // Same as transfer1 (identical tx hash and log index) but with different token_id, meaning that transfer1 and transfer2 are part of one batch/multi token transaction let transfer2 = NftTransferHistory { common: NftTransferCommon { block_hash: Some("0x3d68b78391fb3cf8570df27036214f7e9a5a6a45d309197936f51d826041bfe7".to_string()), transaction_hash: "0x1e9f04e9b571b283bde02c98c2a97da39b2bb665b57c1f2b0b733f9b681debbe".to_string(), transaction_index: Some(198), - log_index: 496, + log_index: 495, value: Default::default(), - transaction_type: Some("Single".to_string()), + transaction_type: Some("Batch".to_string()), token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(), from_address: Address::from_str("0x6fad0ec6bb76914b2a2a800686acc22970645820").unwrap(), to_address: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(), diff --git a/mm2src/coins/nft/storage/mod.rs b/mm2src/coins/nft/storage/mod.rs index ad255100c3..3eed941b5d 100644 --- a/mm2src/coins/nft/storage/mod.rs +++ b/mm2src/coins/nft/storage/mod.rs @@ -156,11 +156,12 @@ pub trait NftTransferHistoryStorageOps { token_id: BigUint, ) -> MmResult, Self::Error>; - async fn get_transfer_by_tx_hash_and_log_index( + async fn get_transfer_by_tx_hash_log_index_token_id( &self, chain: &Chain, transaction_hash: String, log_index: u32, + token_id: BigUint, ) -> MmResult, Self::Error>; /// Updates the metadata for NFT transfers identified by their token address and ID. @@ -243,3 +244,10 @@ pub(crate) struct TransferDetailsJson { pub(crate) to_address: Address, pub(crate) fee_details: Option, } + +#[async_trait] +pub trait NftMigrationOps { + type Error: NftStorageError; + + async fn migrate_tx_history_if_needed(&self, chain: &Chain) -> MmResult<(), Self::Error>; +} diff --git a/mm2src/coins/nft/storage/sql_storage.rs b/mm2src/coins/nft/storage/sql_storage.rs index 6844b261d9..bffdaef27b 100644 --- a/mm2src/coins/nft/storage/sql_storage.rs +++ b/mm2src/coins/nft/storage/sql_storage.rs @@ -2,10 +2,10 @@ use crate::nft::eth_addr_to_hex; use crate::nft::nft_structs::{Chain, ContractType, ConvertChain, Nft, NftCommon, NftList, NftListFilters, NftTokenAddrId, NftTransferCommon, NftTransferHistory, NftTransferHistoryFilters, NftsTransferHistoryList, TransferMeta, UriMeta}; -use crate::nft::storage::{get_offset_limit, NftDetailsJson, NftListStorageOps, NftStorageError, +use crate::nft::storage::{get_offset_limit, NftDetailsJson, NftListStorageOps, NftMigrationOps, NftStorageError, NftTransferHistoryStorageOps, RemoveNftResult, TransferDetailsJson}; use async_trait::async_trait; -use db_common::async_sql_conn::{AsyncConnError, AsyncConnection}; +use db_common::async_sql_conn::{AsyncConnError, AsyncConnection, InternalError}; use db_common::sql_build::{SqlCondition, SqlQuery}; use db_common::sqlite::rusqlite::types::{FromSqlError, Type}; use db_common::sqlite::rusqlite::{Connection, Error as SqlError, Result as SqlResult, Row, Statement}; @@ -22,6 +22,8 @@ use std::convert::TryInto; use std::num::NonZeroUsize; use std::str::FromStr; +const CURRENT_SCHEMA_VERSION_TX_HISTORY: i32 = 2; + impl Chain { fn nft_list_table_name(&self) -> SqlResult { let name = self.to_ticker().to_owned() + "_nft_list"; @@ -42,6 +44,12 @@ fn scanned_nft_blocks_table_name() -> SqlResult { Ok(safe_name) } +fn schema_versions_table_name() -> SqlResult { + let name = "schema_versions".to_string(); + let safe_name = SafeTableName::new(&name)?; + Ok(safe_name) +} + fn create_nft_list_table_sql(chain: &Chain) -> MmResult { let safe_table_name = chain.nft_list_table_name()?; let sql = format!( @@ -82,6 +90,11 @@ fn create_nft_list_table_sql(chain: &Chain) -> MmResult { fn create_transfer_history_table_sql(chain: &Chain) -> Result { let safe_table_name = chain.transfer_history_table_name()?; + create_transfer_history_table_sql_custom_name(&safe_table_name) +} + +/// Supports [CURRENT_SCHEMA_VERSION_TX_HISTORY] +fn create_transfer_history_table_sql_custom_name(safe_table_name: &SafeTableName) -> Result { let sql = format!( "CREATE TABLE IF NOT EXISTS {} ( transaction_hash VARCHAR(256) NOT NULL, @@ -103,7 +116,7 @@ fn create_transfer_history_table_sql(chain: &Chain) -> Result image_domain TEXT, token_name TEXT, details_json TEXT, - PRIMARY KEY (transaction_hash, log_index) + PRIMARY KEY (transaction_hash, log_index, token_id) );", safe_table_name.inner() ); @@ -122,6 +135,18 @@ fn create_scanned_nft_blocks_sql() -> Result { Ok(sql) } +fn create_schema_versions_sql() -> Result { + let safe_table_name = schema_versions_table_name()?; + let sql = format!( + "CREATE TABLE IF NOT EXISTS {} ( + table_name TEXT PRIMARY KEY, + version INTEGER NOT NULL + );", + safe_table_name.inner() + ); + Ok(sql) +} + impl NftStorageError for AsyncConnError {} fn get_nft_list_builder_preimage(chains: Vec, filters: Option) -> Result { @@ -432,6 +457,15 @@ fn upsert_last_scanned_block_sql() -> Result { Ok(sql) } +fn insert_schema_version_sql() -> Result { + let schema_table = schema_versions_table_name()?; + let sql = format!( + "INSERT INTO {} (table_name, version) VALUES (?1, ?2) ON CONFLICT(table_name) DO NOTHING;", + schema_table.inner() + ); + Ok(sql) +} + fn refresh_nft_metadata_sql(chain: &Chain) -> Result { let safe_table_name = chain.nft_list_table_name()?; let sql = format!( @@ -462,12 +496,25 @@ fn update_transfer_spam_by_token_addr_id(chain: &Chain) -> Result Result { - let sql = format!( +/// Generates the SQL command to insert or update the schema version in the `schema_versions` table. +/// +/// This function creates an SQL command that attempts to insert a new row with the specified +/// `table_name` and `version`. If a row with the same `table_name` already exists, the `version` +/// field is updated to the new value provided. +fn update_schema_version_sql(schema_versions: &SafeTableName) -> String { + format!( + "INSERT INTO {} (table_name, version) + VALUES (?1, ?2) + ON CONFLICT(table_name) DO UPDATE SET version = excluded.version;", + schema_versions.inner() + ) +} + +fn select_last_block_number_sql(safe_table_name: SafeTableName) -> String { + format!( "SELECT block_number FROM {} ORDER BY block_number DESC LIMIT 1", safe_table_name.inner() - ); - Ok(sql) + ) } fn select_last_scanned_block_sql() -> MmResult { @@ -540,6 +587,13 @@ fn get_transfers_with_empty_meta_builder<'a>(conn: &'a Connection, chain: &'a Ch Ok(sql_builder) } +fn get_schema_version_stmt(conn: &Connection) -> Result { + let table_name = schema_versions_table_name()?; + let sql = format!("SELECT version FROM {} WHERE table_name = ?1;", table_name.inner()); + let stmt = conn.prepare(&sql)?; + Ok(stmt) +} + fn is_table_empty(conn: &Connection, safe_table_name: SafeTableName) -> Result { let query = format!("SELECT COUNT(*) FROM {}", safe_table_name.inner()); conn.query_row(&query, [], |row| row.get::<_, i64>(0)) @@ -777,7 +831,7 @@ impl NftListStorageOps for AsyncMutexGuard<'_, AsyncConnection> { async fn get_last_block_number(&self, chain: &Chain) -> MmResult, Self::Error> { let table_name = chain.nft_list_table_name()?; - let sql = select_last_block_number_sql(table_name)?; + let sql = select_last_block_number_sql(table_name); self.call(move |conn| { let block_number = query_single_row(conn, &sql, [], block_number_from_row)?; Ok(block_number) @@ -969,8 +1023,15 @@ impl NftTransferHistoryStorageOps for AsyncMutexGuard<'_, AsyncConnection> { async fn init(&self, chain: &Chain) -> MmResult<(), Self::Error> { let sql_transfer_history = create_transfer_history_table_sql(chain)?; + let table_name = chain.transfer_history_table_name()?; self.call(move |conn| { conn.execute(&sql_transfer_history, []).map(|_| ())?; + conn.execute(&create_schema_versions_sql()?, []).map(|_| ())?; + conn.execute(&insert_schema_version_sql()?, [ + table_name.inner(), + &CURRENT_SCHEMA_VERSION_TX_HISTORY.to_string(), + ]) + .map(|_| ())?; Ok(()) }) .await @@ -978,11 +1039,10 @@ impl NftTransferHistoryStorageOps for AsyncMutexGuard<'_, AsyncConnection> { } async fn is_initialized(&self, chain: &Chain) -> MmResult { - let table_name = chain.transfer_history_table_name()?; + let table = chain.transfer_history_table_name()?; self.call(move |conn| { - let nft_list_initialized = - query_single_row(conn, CHECK_TABLE_EXISTS_SQL, [table_name.inner()], string_from_row)?; - Ok(nft_list_initialized.is_some()) + let table_exists = query_single_row(conn, CHECK_TABLE_EXISTS_SQL, [table.inner()], string_from_row)?; + Ok(table_exists.is_some()) }) .await .map_to_mm(AsyncConnError::from) @@ -1077,7 +1137,7 @@ impl NftTransferHistoryStorageOps for AsyncMutexGuard<'_, AsyncConnection> { async fn get_last_block_number(&self, chain: &Chain) -> MmResult, Self::Error> { let table_name = chain.transfer_history_table_name()?; - let sql = select_last_block_number_sql(table_name)?; + let sql = select_last_block_number_sql(table_name); self.call(move |conn| { let block_number = query_single_row(conn, &sql, [], block_number_from_row)?; Ok(block_number) @@ -1121,22 +1181,23 @@ impl NftTransferHistoryStorageOps for AsyncMutexGuard<'_, AsyncConnection> { .map_to_mm(AsyncConnError::from) } - async fn get_transfer_by_tx_hash_and_log_index( + async fn get_transfer_by_tx_hash_log_index_token_id( &self, chain: &Chain, transaction_hash: String, log_index: u32, + token_id: BigUint, ) -> MmResult, Self::Error> { let table_name = chain.transfer_history_table_name()?; let sql = format!( - "SELECT * FROM {} WHERE transaction_hash=?1 AND log_index = ?2", + "SELECT * FROM {} WHERE transaction_hash=?1 AND log_index = ?2 AND token_id = ?3", table_name.inner() ); self.call(move |conn| { let transfer = query_single_row( conn, &sql, - [transaction_hash, log_index.to_string()], + [transaction_hash, log_index.to_string(), token_id.to_string()], transfer_history_from_row, )?; Ok(transfer) @@ -1285,11 +1346,18 @@ impl NftTransferHistoryStorageOps for AsyncMutexGuard<'_, AsyncConnection> { } async fn clear_history_data(&self, chain: &Chain) -> MmResult<(), Self::Error> { - let table_name = chain.transfer_history_table_name()?; + let history_table_name = chain.transfer_history_table_name()?; + let schema_table_name = schema_versions_table_name()?; + let dlt_schema_sql = format!("DELETE from {} where table_name=?1", schema_table_name.inner()); self.call(move |conn| { let sql_transaction = conn.transaction()?; - sql_transaction.execute(&format!("DROP TABLE IF EXISTS {};", table_name.inner()), [])?; + sql_transaction.execute(&format!("DROP TABLE IF EXISTS {};", history_table_name.inner()), [])?; + sql_transaction.execute(&dlt_schema_sql, [history_table_name.inner()])?; sql_transaction.commit()?; + if is_table_empty(conn, schema_table_name.clone())? { + conn.execute(&format!("DROP TABLE IF EXISTS {};", schema_table_name.inner()), []) + .map(|_| ())?; + } Ok(()) }) .await @@ -1297,12 +1365,14 @@ impl NftTransferHistoryStorageOps for AsyncMutexGuard<'_, AsyncConnection> { } async fn clear_all_history_data(&self) -> MmResult<(), Self::Error> { + let schema_table = schema_versions_table_name()?; self.call(move |conn| { let sql_transaction = conn.transaction()?; for chain in Chain::variant_list().into_iter() { let table_name = chain.transfer_history_table_name()?; sql_transaction.execute(&format!("DROP TABLE IF EXISTS {};", table_name.inner()), [])?; } + sql_transaction.execute(&format!("DROP TABLE IF EXISTS {};", schema_table.inner()), [])?; sql_transaction.commit()?; Ok(()) }) @@ -1310,3 +1380,112 @@ impl NftTransferHistoryStorageOps for AsyncMutexGuard<'_, AsyncConnection> { .map_to_mm(AsyncConnError::from) } } + +fn migrate_tx_history_table_from_schema_0_to_2( + conn: &mut Connection, + history_table: &SafeTableName, + schema_table: &SafeTableName, +) -> Result<(), AsyncConnError> { + if has_primary_key_duplication(conn, history_table)? { + return Err(AsyncConnError::Internal(InternalError( + "Primary key duplication occurred in old nft tx history table".to_string(), + ))); + } + + // Start a transaction to ensure all operations are atomic + let sql_tx = conn.transaction()?; + + // Create the temporary table with the new schema + let temp_table_name = SafeTableName::new(format!("{}_temp", history_table.inner()).as_str())?; + sql_tx.execute(&create_transfer_history_table_sql_custom_name(&temp_table_name)?, [])?; + + // I don't think we need to batch the data copy process here. + // It's unlikely that the table will grow to 1 million+ rows (as an example). + let copy_data_sql = format!( + "INSERT INTO {} SELECT * FROM {};", + temp_table_name.inner(), + history_table.inner() + ); + sql_tx.execute(©_data_sql, [])?; + + let drop_old_table_sql = format!("DROP TABLE IF EXISTS {};", history_table.inner()); + sql_tx.execute(&drop_old_table_sql, [])?; + + let rename_table_sql = format!( + "ALTER TABLE {} RENAME TO {};", + temp_table_name.inner(), + history_table.inner() + ); + sql_tx.execute(&rename_table_sql, [])?; + + sql_tx.execute(&update_schema_version_sql(schema_table), [ + history_table.inner().to_string(), + CURRENT_SCHEMA_VERSION_TX_HISTORY.to_string(), + ])?; + + sql_tx.commit()?; + + Ok(()) +} + +/// Query to check for duplicates based on the primary key columns from tx history table version 2 +fn has_primary_key_duplication(conn: &Connection, safe_table_name: &SafeTableName) -> Result { + let query = format!( + "SELECT EXISTS ( + SELECT 1 + FROM {} + GROUP BY transaction_hash, log_index, token_id + HAVING COUNT(*) > 1 + );", + safe_table_name.inner() + ); + // return true if duplicates exist, false otherwise + conn.query_row(&query, [], |row| row.get::<_, i32>(0)) + .map(|exists| exists == 1) +} + +#[async_trait] +impl NftMigrationOps for AsyncMutexGuard<'_, AsyncConnection> { + type Error = AsyncConnError; + + async fn migrate_tx_history_if_needed(&self, chain: &Chain) -> MmResult<(), Self::Error> { + let history_table = chain.transfer_history_table_name()?; + let schema_table = schema_versions_table_name()?; + self.call(move |conn| { + let schema_table_exists = + query_single_row(conn, CHECK_TABLE_EXISTS_SQL, [schema_table.inner()], string_from_row)?; + + let mut version = if schema_table_exists.is_some() { + get_schema_version_stmt(conn)? + .query_row([history_table.inner()], |row| row.get(0)) + .unwrap_or(0) + } else { + conn.execute(&create_schema_versions_sql()?, []).map(|_| ())?; + 0 + }; + + while version < CURRENT_SCHEMA_VERSION_TX_HISTORY { + match version { + 0 => { + migrate_tx_history_table_from_schema_0_to_2(conn, &history_table, &schema_table)?; + }, + 1 => { + // The Tx History SQL schema didn't have version 1, but let's handle this case + // for consistency with IndexedDB versioning, where the current Tx History schema is at version 2. + }, + unsupported_version => { + return Err(AsyncConnError::Internal(InternalError(format!( + "Unsupported schema version {}", + unsupported_version + )))); + }, + } + version += 1; + } + + Ok(()) + }) + .await + .map_to_mm(AsyncConnError::from) + } +} diff --git a/mm2src/coins/nft/storage/wasm/nft_idb.rs b/mm2src/coins/nft/storage/wasm/nft_idb.rs index 054f1c058e..775a871589 100644 --- a/mm2src/coins/nft/storage/wasm/nft_idb.rs +++ b/mm2src/coins/nft/storage/wasm/nft_idb.rs @@ -3,7 +3,8 @@ use async_trait::async_trait; use mm2_db::indexed_db::InitDbResult; use mm2_db::indexed_db::{DbIdentifier, DbInstance, DbLocked, IndexedDb, IndexedDbBuilder}; -const DB_VERSION: u32 = 1; +/// prim key was changed in NftTransferHistoryTable, schemas of the other tables remain the same. +const DB_VERSION: u32 = 2; /// Represents a locked instance of the `NftCacheIDB` database. /// diff --git a/mm2src/coins/nft/storage/wasm/wasm_storage.rs b/mm2src/coins/nft/storage/wasm/wasm_storage.rs index e5ea955918..99e76ba04f 100644 --- a/mm2src/coins/nft/storage/wasm/wasm_storage.rs +++ b/mm2src/coins/nft/storage/wasm/wasm_storage.rs @@ -6,11 +6,10 @@ use crate::nft::storage::wasm::{WasmNftCacheError, WasmNftCacheResult}; use crate::nft::storage::{get_offset_limit, NftListStorageOps, NftTokenAddrId, NftTransferHistoryFilters, NftTransferHistoryStorageOps, RemoveNftResult}; use async_trait::async_trait; -use common::is_initial_upgrade; use ethereum_types::Address; -use mm2_db::indexed_db::{BeBigUint, DbTable, DbUpgrader, MultiIndex, OnUpgradeResult, TableSignature}; +use mm2_db::indexed_db::{BeBigUint, DbTable, DbUpgrader, MultiIndex, OnUpgradeError, OnUpgradeResult, TableSignature}; use mm2_err_handle::map_to_mm::MapToMmResult; -use mm2_err_handle::prelude::MmResult; +use mm2_err_handle::prelude::{MmError, MmResult}; use mm2_number::BigUint; use num_traits::ToPrimitive; use serde_json::{self as json, Value as Json}; @@ -547,18 +546,20 @@ impl NftTransferHistoryStorageOps for NftCacheIDBLocked<'_> { .collect() } - async fn get_transfer_by_tx_hash_and_log_index( + async fn get_transfer_by_tx_hash_log_index_token_id( &self, chain: &Chain, transaction_hash: String, log_index: u32, + token_id: BigUint, ) -> MmResult, Self::Error> { let db_transaction = self.get_inner().transaction().await?; let table = db_transaction.table::().await?; - let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_INDEX) + let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_TOKEN_ID_INDEX) .with_value(chain.to_string())? .with_value(&transaction_hash)? - .with_value(log_index)?; + .with_value(log_index)? + .with_value(BeBigUint::from(token_id))?; if let Some((_item_id, item)) = table.get_item_by_unique_multi_index(index_keys).await? { Ok(Some(transfer_details_from_item(item)?)) @@ -602,10 +603,11 @@ impl NftTransferHistoryStorageOps for NftCacheIDBLocked<'_> { } drop_mutability!(transfer); - let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_INDEX) + let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_TOKEN_ID_INDEX) .with_value(&chain_str)? .with_value(&transfer.common.transaction_hash)? - .with_value(transfer.common.log_index)?; + .with_value(transfer.common.log_index)? + .with_value(BeBigUint::from(transfer.token_id.clone()))?; let item = NftTransferHistoryTable::from_transfer_history(&transfer)?; table.replace_item_by_unique_multi_index(index_keys, &item).await?; @@ -691,10 +693,11 @@ impl NftTransferHistoryStorageOps for NftCacheIDBLocked<'_> { transfer.common.possible_spam = possible_spam; drop_mutability!(transfer); - let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_INDEX) + let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_TOKEN_ID_INDEX) .with_value(&chain_str)? .with_value(&transfer.common.transaction_hash)? - .with_value(transfer.common.log_index)?; + .with_value(transfer.common.log_index)? + .with_value(BeBigUint::from(transfer.token_id.clone()))?; let item = NftTransferHistoryTable::from_transfer_history(&transfer)?; table.replace_item_by_unique_multi_index(index_keys, &item).await?; @@ -777,10 +780,11 @@ async fn update_transfer_phishing_for_index( transfer.possible_phishing = possible_phishing; drop_mutability!(transfer); let transfer_item = NftTransferHistoryTable::from_transfer_history(&transfer)?; - let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_INDEX) + let index_keys = MultiIndex::new(NftTransferHistoryTable::CHAIN_TX_HASH_LOG_INDEX_TOKEN_ID_INDEX) .with_value(chain)? .with_value(&transfer.common.transaction_hash)? - .with_value(transfer.common.log_index)?; + .with_value(transfer.common.log_index)? + .with_value(BeBigUint::from(transfer.token_id))?; table .replace_item_by_unique_multi_index(index_keys, &transfer_item) .await?; @@ -876,8 +880,8 @@ pub(crate) struct NftListTable { } impl NftListTable { - const CHAIN_ANIMATION_DOMAIN_INDEX: &str = "chain_animation_domain_index"; - const CHAIN_EXTERNAL_DOMAIN_INDEX: &str = "chain_external_domain_index"; + const CHAIN_ANIMATION_DOMAIN_INDEX: &'static str = "chain_animation_domain_index"; + const CHAIN_EXTERNAL_DOMAIN_INDEX: &'static str = "chain_external_domain_index"; fn from_nft(nft: &Nft) -> WasmNftCacheResult { let details_json = json::to_value(nft).map_to_mm(|e| WasmNftCacheError::ErrorSerializing(e.to_string()))?; @@ -902,26 +906,45 @@ impl NftListTable { impl TableSignature for NftListTable { const TABLE_NAME: &'static str = "nft_list_cache_table"; - fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { - if is_initial_upgrade(old_version, new_version) { - let table = upgrader.create_table(Self::TABLE_NAME)?; - table.create_multi_index( - CHAIN_TOKEN_ADD_TOKEN_ID_INDEX, - &["chain", "token_address", "token_id"], - true, - )?; - table.create_multi_index(CHAIN_BLOCK_NUMBER_INDEX, &["chain", "block_number"], false)?; - table.create_multi_index(CHAIN_TOKEN_ADD_INDEX, &["chain", "token_address"], false)?; - table.create_multi_index(CHAIN_TOKEN_DOMAIN_INDEX, &["chain", "token_domain"], false)?; - table.create_multi_index(CHAIN_IMAGE_DOMAIN_INDEX, &["chain", "image_domain"], false)?; - table.create_multi_index( - Self::CHAIN_ANIMATION_DOMAIN_INDEX, - &["chain", "animation_domain"], - false, - )?; - table.create_multi_index(Self::CHAIN_EXTERNAL_DOMAIN_INDEX, &["chain", "external_domain"], false)?; - table.create_index("chain", false)?; - table.create_index("block_number", false)?; + fn on_upgrade_needed(upgrader: &DbUpgrader, mut old_version: u32, new_version: u32) -> OnUpgradeResult<()> { + while old_version < new_version { + match old_version { + 0 => { + let table = upgrader.create_table(Self::TABLE_NAME)?; + table.create_multi_index( + CHAIN_TOKEN_ADD_TOKEN_ID_INDEX, + &["chain", "token_address", "token_id"], + true, + )?; + table.create_multi_index(CHAIN_BLOCK_NUMBER_INDEX, &["chain", "block_number"], false)?; + table.create_multi_index(CHAIN_TOKEN_ADD_INDEX, &["chain", "token_address"], false)?; + table.create_multi_index(CHAIN_TOKEN_DOMAIN_INDEX, &["chain", "token_domain"], false)?; + table.create_multi_index(CHAIN_IMAGE_DOMAIN_INDEX, &["chain", "image_domain"], false)?; + table.create_multi_index( + Self::CHAIN_ANIMATION_DOMAIN_INDEX, + &["chain", "animation_domain"], + false, + )?; + table.create_multi_index( + Self::CHAIN_EXTERNAL_DOMAIN_INDEX, + &["chain", "external_domain"], + false, + )?; + table.create_index("chain", false)?; + table.create_index("block_number", false)?; + }, + 1 => { + // nothing to change + }, + unsupported_version => { + return MmError::err(OnUpgradeError::UnsupportedVersion { + unsupported_version, + old_version, + new_version, + }) + }, + } + old_version += 1; } Ok(()) } @@ -951,7 +974,10 @@ pub(crate) struct NftTransferHistoryTable { } impl NftTransferHistoryTable { - const CHAIN_TX_HASH_LOG_INDEX_INDEX: &str = "chain_tx_hash_log_index_index"; + // old prim key index for DB_VERSION = 1 + const CHAIN_TX_HASH_LOG_INDEX_INDEX: &'static str = "chain_tx_hash_log_index_index"; + // prim key multi index for DB_VERSION = 2 + const CHAIN_TX_HASH_LOG_INDEX_TOKEN_ID_INDEX: &'static str = "chain_tx_hash_log_index_token_id_index"; fn from_transfer_history(transfer: &NftTransferHistory) -> WasmNftCacheResult { let details_json = @@ -983,25 +1009,47 @@ impl NftTransferHistoryTable { impl TableSignature for NftTransferHistoryTable { const TABLE_NAME: &'static str = "nft_transfer_history_cache_table"; - fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { - if is_initial_upgrade(old_version, new_version) { - let table = upgrader.create_table(Self::TABLE_NAME)?; - table.create_multi_index( - CHAIN_TOKEN_ADD_TOKEN_ID_INDEX, - &["chain", "token_address", "token_id"], - false, - )?; - table.create_multi_index( - Self::CHAIN_TX_HASH_LOG_INDEX_INDEX, - &["chain", "transaction_hash", "log_index"], - true, - )?; - table.create_multi_index(CHAIN_BLOCK_NUMBER_INDEX, &["chain", "block_number"], false)?; - table.create_multi_index(CHAIN_TOKEN_ADD_INDEX, &["chain", "token_address"], false)?; - table.create_multi_index(CHAIN_TOKEN_DOMAIN_INDEX, &["chain", "token_domain"], false)?; - table.create_multi_index(CHAIN_IMAGE_DOMAIN_INDEX, &["chain", "image_domain"], false)?; - table.create_index("block_number", false)?; - table.create_index("chain", false)?; + fn on_upgrade_needed(upgrader: &DbUpgrader, mut old_version: u32, new_version: u32) -> OnUpgradeResult<()> { + while old_version < new_version { + match old_version { + 0 => { + let table = upgrader.create_table(Self::TABLE_NAME)?; + table.create_multi_index( + Self::CHAIN_TX_HASH_LOG_INDEX_INDEX, + &["chain", "transaction_hash", "log_index"], + true, + )?; + table.create_multi_index( + CHAIN_TOKEN_ADD_TOKEN_ID_INDEX, + &["chain", "token_address", "token_id"], + false, + )?; + table.create_multi_index(CHAIN_BLOCK_NUMBER_INDEX, &["chain", "block_number"], false)?; + table.create_multi_index(CHAIN_TOKEN_ADD_INDEX, &["chain", "token_address"], false)?; + table.create_multi_index(CHAIN_TOKEN_DOMAIN_INDEX, &["chain", "token_domain"], false)?; + table.create_multi_index(CHAIN_IMAGE_DOMAIN_INDEX, &["chain", "image_domain"], false)?; + table.create_index("block_number", false)?; + table.create_index("chain", false)?; + }, + 1 => { + let table = upgrader.open_table(Self::TABLE_NAME)?; + // When we change indexes during `onupgradeneeded`, IndexedDB automatically updates it with the existing records + table.create_multi_index( + Self::CHAIN_TX_HASH_LOG_INDEX_TOKEN_ID_INDEX, + &["chain", "transaction_hash", "log_index", "token_id"], + true, + )?; + table.delete_index(Self::CHAIN_TX_HASH_LOG_INDEX_INDEX)?; + }, + unsupported_version => { + return MmError::err(OnUpgradeError::UnsupportedVersion { + unsupported_version, + old_version, + new_version, + }) + }, + } + old_version += 1; } Ok(()) } @@ -1016,10 +1064,25 @@ pub(crate) struct LastScannedBlockTable { impl TableSignature for LastScannedBlockTable { const TABLE_NAME: &'static str = "last_scanned_block_table"; - fn on_upgrade_needed(upgrader: &DbUpgrader, old_version: u32, new_version: u32) -> OnUpgradeResult<()> { - if is_initial_upgrade(old_version, new_version) { - let table = upgrader.create_table(Self::TABLE_NAME)?; - table.create_index("chain", true)?; + fn on_upgrade_needed(upgrader: &DbUpgrader, mut old_version: u32, new_version: u32) -> OnUpgradeResult<()> { + while old_version < new_version { + match old_version { + 0 => { + let table = upgrader.create_table(Self::TABLE_NAME)?; + table.create_index("chain", true)?; + }, + 1 => { + // nothing to change + }, + unsupported_version => { + return MmError::err(OnUpgradeError::UnsupportedVersion { + unsupported_version, + old_version, + new_version, + }) + }, + } + old_version += 1; } Ok(()) } diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index 3ee9e7761b..df14fea09a 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -56,6 +56,7 @@ use serde_json::{self as json, Value as Json}; use serialization::{deserialize, serialize, CoinVariant}; use std::collections::{HashMap, HashSet}; use std::convert::TryInto; +use std::num::TryFromIntError; use std::ops::{Deref, Neg}; #[cfg(not(target_arch = "wasm32"))] use std::path::PathBuf; use std::str::FromStr; @@ -452,11 +453,11 @@ pub enum Qrc20AbiError { #[display(fmt = "Invalid QRC20 ABI params: {}", _0)] InvalidParams(String), #[display(fmt = "QRC20 ABI error: {}", _0)] - AbiError(String), + ABIError(String), } impl From for Qrc20AbiError { - fn from(e: ethabi::Error) -> Qrc20AbiError { Qrc20AbiError::AbiError(e.to_string()) } + fn from(e: ethabi::Error) -> Qrc20AbiError { Qrc20AbiError::ABIError(e.to_string()) } } impl From for ValidatePaymentError { @@ -758,52 +759,42 @@ impl UtxoCommonOps for Qrc20Coin { #[async_trait] impl SwapOps for Qrc20Coin { - fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut { - let to_address = try_tx_fus!(self.contract_address_from_raw_pubkey(fee_addr)); - let amount = try_tx_fus!(wei_from_big_decimal(&dex_fee.fee_amount().into(), self.utxo.decimals)); + async fn send_taker_fee( + &self, + fee_addr: &[u8], + dex_fee: DexFee, + _uuid: &[u8], + _expire_at: u64, + ) -> TransactionResult { + let to_address = try_tx_s!(self.contract_address_from_raw_pubkey(fee_addr)); + let amount = try_tx_s!(wei_from_big_decimal(&dex_fee.fee_amount().into(), self.utxo.decimals)); let transfer_output = - try_tx_fus!(self.transfer_output(to_address, amount, QRC20_GAS_LIMIT_DEFAULT, QRC20_GAS_PRICE_DEFAULT)); - let outputs = vec![transfer_output]; - - let selfi = self.clone(); - let fut = async move { selfi.send_contract_calls(outputs).await }; - - Box::new(fut.boxed().compat()) + try_tx_s!(self.transfer_output(to_address, amount, QRC20_GAS_LIMIT_DEFAULT, QRC20_GAS_PRICE_DEFAULT)); + self.send_contract_calls(vec![transfer_output]).await } - fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { - let time_lock = try_tx_fus!(maker_payment_args.time_lock.try_into()); - let taker_addr = try_tx_fus!(self.contract_address_from_raw_pubkey(maker_payment_args.other_pubkey)); + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + let time_lock = try_tx_s!(maker_payment_args.time_lock.try_into()); + let taker_addr = try_tx_s!(self.contract_address_from_raw_pubkey(maker_payment_args.other_pubkey)); let id = qrc20_swap_id(time_lock, maker_payment_args.secret_hash); - let value = try_tx_fus!(wei_from_big_decimal(&maker_payment_args.amount, self.utxo.decimals)); + let value = try_tx_s!(wei_from_big_decimal(&maker_payment_args.amount, self.utxo.decimals)); let secret_hash = Vec::from(maker_payment_args.secret_hash); - let swap_contract_address = try_tx_fus!(maker_payment_args.swap_contract_address.try_to_address()); + let swap_contract_address = try_tx_s!(maker_payment_args.swap_contract_address.try_to_address()); - let selfi = self.clone(); - let fut = async move { - selfi - .send_hash_time_locked_payment(id, value, time_lock, secret_hash, taker_addr, swap_contract_address) - .await - }; - Box::new(fut.boxed().compat()) + self.send_hash_time_locked_payment(id, value, time_lock, secret_hash, taker_addr, swap_contract_address) + .await } #[inline] - fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { - let time_lock = try_tx_fus!(taker_payment_args.time_lock.try_into()); - let maker_addr = try_tx_fus!(self.contract_address_from_raw_pubkey(taker_payment_args.other_pubkey)); + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + let time_lock = try_tx_s!(taker_payment_args.time_lock.try_into()); + let maker_addr = try_tx_s!(self.contract_address_from_raw_pubkey(taker_payment_args.other_pubkey)); let id = qrc20_swap_id(time_lock, taker_payment_args.secret_hash); - let value = try_tx_fus!(wei_from_big_decimal(&taker_payment_args.amount, self.utxo.decimals)); + let value = try_tx_s!(wei_from_big_decimal(&taker_payment_args.amount, self.utxo.decimals)); let secret_hash = Vec::from(taker_payment_args.secret_hash); - let swap_contract_address = try_tx_fus!(taker_payment_args.swap_contract_address.try_to_address()); - - let selfi = self.clone(); - let fut = async move { - selfi - .send_hash_time_locked_payment(id, value, time_lock, secret_hash, maker_addr, swap_contract_address) - .await - }; - Box::new(fut.boxed().compat()) + let swap_contract_address = try_tx_s!(taker_payment_args.swap_contract_address.try_to_address()); + self.send_hash_time_locked_payment(id, value, time_lock, secret_hash, maker_addr, swap_contract_address) + .await } #[inline] @@ -855,39 +846,36 @@ impl SwapOps for Qrc20Coin { } #[inline] - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { - let fee_tx = validate_fee_args.fee_tx; - let min_block_number = validate_fee_args.min_block_number; - let fee_tx = match fee_tx { + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { + let fee_tx = match validate_fee_args.fee_tx { TransactionEnum::UtxoTx(tx) => tx, - _ => panic!("Unexpected TransactionEnum"), + fee_tx => { + return MmError::err(ValidatePaymentError::InternalError(format!( + "Invalid fee tx type. fee tx: {:?}", + fee_tx + ))) + }, }; let fee_tx_hash = fee_tx.hash().reversed().into(); - let inputs_signed_by_pub = try_f!(check_all_utxo_inputs_signed_by_pub( - fee_tx, - validate_fee_args.expected_sender - )); + let inputs_signed_by_pub = check_all_utxo_inputs_signed_by_pub(fee_tx, validate_fee_args.expected_sender)?; if !inputs_signed_by_pub { - return Box::new(futures01::future::err( - ValidatePaymentError::WrongPaymentTx("The dex fee was sent from wrong address".to_string()).into(), + return MmError::err(ValidatePaymentError::WrongPaymentTx( + "The dex fee was sent from wrong address".to_string(), )); } - let fee_addr = try_f!(self + let fee_addr = self .contract_address_from_raw_pubkey(validate_fee_args.fee_addr) - .map_to_mm(ValidatePaymentError::WrongPaymentTx)); - let expected_value = try_f!(wei_from_big_decimal( - &validate_fee_args.dex_fee.fee_amount().into(), - self.utxo.decimals - )); - - let selfi = self.clone(); - let fut = async move { - selfi - .validate_fee_impl(fee_tx_hash, fee_addr, expected_value, min_block_number) - .await - .map_to_mm(ValidatePaymentError::WrongPaymentTx) - }; - Box::new(fut.boxed().compat()) + .map_to_mm(ValidatePaymentError::WrongPaymentTx)?; + let expected_value = wei_from_big_decimal(&validate_fee_args.dex_fee.fee_amount().into(), self.utxo.decimals)?; + + self.validate_fee_impl( + fee_tx_hash, + fee_addr, + expected_value, + validate_fee_args.min_block_number, + ) + .await + .map_to_mm(ValidatePaymentError::WrongPaymentTx) } #[inline] @@ -943,24 +931,23 @@ impl SwapOps for Qrc20Coin { } #[inline] - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, - ) -> Box, Error = String> + Send> { - let search_from_block = if_my_payment_sent_args.search_from_block; - let swap_id = qrc20_swap_id( - try_fus!(if_my_payment_sent_args.time_lock.try_into()), - if_my_payment_sent_args.secret_hash, - ); - let swap_contract_address = try_fus!(if_my_payment_sent_args.swap_contract_address.try_to_address()); + ) -> Result, String> { + let time_lock = if_my_payment_sent_args + .time_lock + .try_into() + .map_err(|e: TryFromIntError| e.to_string())?; + let swap_id = qrc20_swap_id(time_lock, if_my_payment_sent_args.secret_hash); + let swap_contract_address = if_my_payment_sent_args.swap_contract_address.try_to_address()?; - let selfi = self.clone(); - let fut = async move { - selfi - .check_if_my_payment_sent_impl(swap_contract_address, swap_id, search_from_block) - .await - }; - Box::new(fut.boxed().compat()) + self.check_if_my_payment_sent_impl( + swap_contract_address, + swap_id, + if_my_payment_sent_args.search_from_block, + ) + .await } #[inline] @@ -1267,23 +1254,11 @@ impl MarketCoinOps for Qrc20Coin { Box::new(fut.boxed().compat()) } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { - let tx: UtxoTx = try_tx_fus!(deserialize(args.tx_bytes).map_err(|e| ERRL!("{:?}", e))); - - let selfi = self.clone(); - let WaitForHTLCTxSpendArgs { - check_every, - from_block, - wait_until, - .. - } = args; - let fut = async move { - selfi - .wait_for_tx_spend_impl(tx, wait_until, from_block, check_every) - .map_err(TransactionErr::Plain) - .await - }; - Box::new(fut.boxed().compat()) + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { + let tx: UtxoTx = try_tx_s!(deserialize(args.tx_bytes).map_err(|e| ERRL!("{:?}", e))); + self.wait_for_tx_spend_impl(tx, args.wait_until, args.from_block, args.check_every) + .map_err(TransactionErr::Plain) + .await } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { diff --git a/mm2src/coins/qrc20/qrc20_tests.rs b/mm2src/coins/qrc20/qrc20_tests.rs index 2caf87c3bf..3e6dbc94dd 100644 --- a/mm2src/coins/qrc20/qrc20_tests.rs +++ b/mm2src/coins/qrc20/qrc20_tests.rs @@ -1,6 +1,6 @@ use super::*; use crate::{DexFee, TxFeeDetails, WaitForHTLCTxSpendArgs}; -use common::{block_on, wait_until_sec, DEX_FEE_ADDR_RAW_PUBKEY}; +use common::{block_on, block_on_f01, wait_until_sec, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::Secp256k1Secret; use itertools::Itertools; use keys::Address; @@ -96,7 +96,7 @@ fn test_withdraw_to_p2sh_address_should_fail() { memo: None, ibc_source_channel: None, }; - let err = coin.withdraw(req).wait().unwrap_err().into_inner(); + let err = block_on_f01(coin.withdraw(req)).unwrap_err().into_inner(); let expect = WithdrawError::InvalidAddress("QRC20 can be sent to P2PKH addresses only".to_owned()); assert_eq!(err, expect); } @@ -112,19 +112,22 @@ fn test_withdraw_impl_fee_details() { let (_ctx, coin) = qrc20_coin_for_test(priv_key, None); Qrc20Coin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); let withdraw_req = WithdrawRequest { @@ -140,7 +143,7 @@ fn test_withdraw_impl_fee_details() { memo: None, ibc_source_channel: None, }; - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); let expected: Qrc20FeeDetails = json::from_value(json!({ "coin": "QTUM", @@ -287,7 +290,7 @@ fn test_wait_for_confirmations_excepted() { wait_until, check_every, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); // tx_hash: ed53b97deb2ad76974c972cb084f6ba63bd9f16c91c4a39106a20c6d14599b2a // `erc20Payment` contract call excepted @@ -299,7 +302,7 @@ fn test_wait_for_confirmations_excepted() { wait_until, check_every, }; - let error = coin.wait_for_confirmations(confirm_payment_input).wait().unwrap_err(); + let error = block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap_err(); log!("error: {:?}", error); assert!(error.contains("Contract call failed with an error: Revert")); @@ -313,7 +316,7 @@ fn test_wait_for_confirmations_excepted() { wait_until, check_every, }; - let error = coin.wait_for_confirmations(confirm_payment_input).wait().unwrap_err(); + let error = block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap_err(); log!("error: {:?}", error); assert!(error.contains("Contract call failed with an error: Revert")); } @@ -333,67 +336,59 @@ fn test_validate_fee() { let amount = BigDecimal::from_str("0.01").unwrap(); - let result = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &tx, - expected_sender: &sender_pub, - fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee: &DexFee::Standard(amount.clone().into()), - min_block_number: 0, - uuid: &[], - }) - .wait(); + let result = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &tx, + expected_sender: &sender_pub, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee: &DexFee::Standard(amount.clone().into()), + min_block_number: 0, + uuid: &[], + })); assert!(result.is_ok()); let fee_addr_dif = hex::decode("03bc2c7ba671bae4a6fc835244c9762b41647b9827d4780a89a949b984a8ddcc05").unwrap(); - let err = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &tx, - expected_sender: &sender_pub, - fee_addr: &fee_addr_dif, - dex_fee: &DexFee::Standard(amount.clone().into()), - min_block_number: 0, - uuid: &[], - }) - .wait() - .expect_err("Expected an error") - .into_inner(); + let err = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &tx, + expected_sender: &sender_pub, + fee_addr: &fee_addr_dif, + dex_fee: &DexFee::Standard(amount.clone().into()), + min_block_number: 0, + uuid: &[], + })) + .expect_err("Expected an error") + .into_inner(); log!("error: {:?}", err); match err { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("QRC20 Fee tx was sent to wrong address")), _ => panic!("Expected `WrongPaymentTx` wrong receiver address, found {:?}", err), } - let err = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &tx, - expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, - fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee: &DexFee::Standard(amount.clone().into()), - min_block_number: 0, - uuid: &[], - }) - .wait() - .expect_err("Expected an error") - .into_inner(); + let err = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &tx, + expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee: &DexFee::Standard(amount.clone().into()), + min_block_number: 0, + uuid: &[], + })) + .expect_err("Expected an error") + .into_inner(); log!("error: {:?}", err); match err { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("was sent from wrong address")), _ => panic!("Expected `WrongPaymentTx` wrong sender address, found {:?}", err), } - let err = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &tx, - expected_sender: &sender_pub, - fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee: &DexFee::Standard(amount.clone().into()), - min_block_number: 2000000, - uuid: &[], - }) - .wait() - .expect_err("Expected an error") - .into_inner(); + let err = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &tx, + expected_sender: &sender_pub, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee: &DexFee::Standard(amount.clone().into()), + min_block_number: 2000000, + uuid: &[], + })) + .expect_err("Expected an error") + .into_inner(); log!("error: {:?}", err); match err { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("confirmed before min_block")), @@ -401,18 +396,16 @@ fn test_validate_fee() { } let amount_dif = BigDecimal::from_str("0.02").unwrap(); - let err = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &tx, - expected_sender: &sender_pub, - fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee: &DexFee::Standard(amount_dif.into()), - min_block_number: 0, - uuid: &[], - }) - .wait() - .expect_err("Expected an error") - .into_inner(); + let err = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &tx, + expected_sender: &sender_pub, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee: &DexFee::Standard(amount_dif.into()), + min_block_number: 0, + uuid: &[], + })) + .expect_err("Expected an error") + .into_inner(); log!("error: {:?}", err); match err { ValidatePaymentError::WrongPaymentTx(err) => { @@ -424,18 +417,16 @@ fn test_validate_fee() { // QTUM tx "8a51f0ffd45f34974de50f07c5bf2f0949da4e88433f8f75191953a442cf9310" let tx = TransactionEnum::UtxoTx("020000000113640281c9332caeddd02a8dd0d784809e1ad87bda3c972d89d5ae41f5494b85010000006a47304402207c5c904a93310b8672f4ecdbab356b65dd869a426e92f1064a567be7ccfc61ff02203e4173b9467127f7de4682513a21efb5980e66dbed4da91dff46534b8e77c7ef012102baefe72b3591de2070c0da3853226b00f082d72daa417688b61cb18c1d543d1afeffffff020001b2c4000000001976a9149e032d4b0090a11dc40fe6c47601499a35d55fbb88acbc4dd20c2f0000001976a9144208fa7be80dcf972f767194ad365950495064a488ac76e70800".into()); let sender_pub = hex::decode("02baefe72b3591de2070c0da3853226b00f082d72daa417688b61cb18c1d543d1a").unwrap(); - let err = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &tx, - expected_sender: &sender_pub, - fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee: &DexFee::Standard(amount.into()), - min_block_number: 0, - uuid: &[], - }) - .wait() - .expect_err("Expected an error") - .into_inner(); + let err = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &tx, + expected_sender: &sender_pub, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee: &DexFee::Standard(amount.into()), + min_block_number: 0, + uuid: &[], + })) + .expect_err("Expected an error") + .into_inner(); log!("error: {:?}", err); match err { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Expected 'transfer' contract call")), @@ -462,18 +453,16 @@ fn test_wait_for_tx_spend_malicious() { let payment_tx = hex::decode("01000000016601daa208531d20532c460d0c86b74a275f4a126bbffcf4eafdf33835af2859010000006a47304402205825657548bc1b5acf3f4bb2f89635a02b04f3228cd08126e63c5834888e7ac402207ca05fa0a629a31908a97a508e15076e925f8e621b155312b7526a6666b06a76012103693bff1b39e8b5a306810023c29b95397eb395530b106b1820ea235fd81d9ce9ffffffff020000000000000000e35403a0860101284cc49b415b2a8620ad3b72361a5aeba5dffd333fb64750089d935a1ec974d6a91ef4f24ff6ba0000000000000000000000000000000000000000000000000000000001312d00000000000000000000000000d362e096e873eb7907e205fadc6175c6fec7bc44000000000000000000000000783cf0be521101942da509846ea476e683aad8324b6b2e5444c2639cc0fb7bcea5afba3f3cdce239000000000000000000000000000000000000000000000000000000000000000000000000000000005f855c7614ba8b71f3544b93e2f681f996da519a98ace0107ac2203de400000000001976a9149e032d4b0090a11dc40fe6c47601499a35d55fbb88ac415d855f").unwrap(); let wait_until = now_sec() + 1; let from_block = 696245; - let found = coin - .wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { - tx_bytes: &payment_tx, - secret_hash: &[], - wait_until, - from_block, - swap_contract_address: &coin.swap_contract_address(), - check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, - watcher_reward: false, - }) - .wait() - .unwrap(); + let found = block_on(coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { + tx_bytes: &payment_tx, + secret_hash: &[], + wait_until, + from_block, + swap_contract_address: &coin.swap_contract_address(), + check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, + watcher_reward: false, + })) + .unwrap(); let spend_tx = match found { TransactionEnum::UtxoTx(tx) => tx, @@ -731,7 +720,7 @@ fn test_get_trade_fee() { // check if the coin's tx fee is expected check_tx_fee(&coin, ActualTxFee::FixedPerKb(EXPECTED_TX_FEE as u64)); - let actual_trade_fee = coin.get_trade_fee().wait().unwrap(); + let actual_trade_fee = block_on_f01(coin.get_trade_fee()).unwrap(); let expected_trade_fee_amount = big_decimal_from_sat( 2 * CONTRACT_CALL_GAS_FEE + SWAP_PAYMENT_GAS_FEE + EXPECTED_TX_FEE, coin.utxo.decimals, @@ -905,10 +894,8 @@ fn test_receiver_trade_preimage() { // check if the coin's tx fee is expected check_tx_fee(&coin, ActualTxFee::FixedPerKb(EXPECTED_TX_FEE as u64)); - let actual = coin - .get_receiver_trade_fee(FeeApproxStage::WithoutApprox) - .wait() - .expect("!get_receiver_trade_fee"); + let actual = + block_on_f01(coin.get_receiver_trade_fee(FeeApproxStage::WithoutApprox)).expect("!get_receiver_trade_fee"); // only one contract call should be included into the expected trade fee let expected_receiver_fee = big_decimal_from_sat(CONTRACT_CALL_GAS_FEE + EXPECTED_TX_FEE, coin.utxo.decimals); let expected = TradeFee { @@ -935,7 +922,7 @@ fn test_taker_fee_tx_fee() { spendable: BigDecimal::from(5u32), unspendable: BigDecimal::from(0u32), }; - assert_eq!(coin.my_balance().wait().expect("!my_balance"), expected_balance); + assert_eq!(block_on_f01(coin.my_balance()).expect("!my_balance"), expected_balance); let dex_fee_amount = BigDecimal::from(5u32); let actual = block_on(coin.get_fee_to_send_taker_fee( diff --git a/mm2src/coins/qrc20/swap.rs b/mm2src/coins/qrc20/swap.rs index a6162f6421..7370926684 100644 --- a/mm2src/coins/qrc20/swap.rs +++ b/mm2src/coins/qrc20/swap.rs @@ -346,7 +346,23 @@ impl Qrc20Coin { receiver, secret_hash, .. - } = try_s!(self.erc20_payment_details_from_tx(&tx).await); + } = try_s!( + retry_on_err!(async { self.erc20_payment_details_from_tx(&tx).await }) + .until_ready() + .repeat_every_secs(check_every) + .until_s(wait_until) + .inspect_err({ + let tx_hash = tx.hash().reversed(); + move |e| { + error!( + "Failed to retrieve QRC20 payment details from transaction {} \ + will retry in {} seconds. Error: {:?}", + tx_hash, check_every, e + ) + } + }) + .await + ); loop { // Try to find a 'receiverSpend' contract call. diff --git a/mm2src/coins/rpc_command/account_balance.rs b/mm2src/coins/rpc_command/account_balance.rs index c0cb1bf09b..7e6587b788 100644 --- a/mm2src/coins/rpc_command/account_balance.rs +++ b/mm2src/coins/rpc_command/account_balance.rs @@ -63,10 +63,10 @@ pub async fn account_balance( req: HDAccountBalanceRequest, ) -> MmResult { match lp_coinfind_or_err(&ctx, &req.coin).await? { - MmCoinEnum::UtxoCoin(utxo) => Ok(HDAccountBalanceResponseEnum::Single( + MmCoinEnum::UtxoCoin(utxo) => Ok(HDAccountBalanceResponseEnum::Map( utxo.account_balance_rpc(req.params).await?, )), - MmCoinEnum::QtumCoin(qtum) => Ok(HDAccountBalanceResponseEnum::Single( + MmCoinEnum::QtumCoin(qtum) => Ok(HDAccountBalanceResponseEnum::Map( qtum.account_balance_rpc(req.params).await?, )), MmCoinEnum::EthCoin(eth) => Ok(HDAccountBalanceResponseEnum::Map( diff --git a/mm2src/coins/rpc_command/get_new_address.rs b/mm2src/coins/rpc_command/get_new_address.rs index 9bba74cf75..35796de9c2 100644 --- a/mm2src/coins/rpc_command/get_new_address.rs +++ b/mm2src/coins/rpc_command/get_new_address.rs @@ -320,7 +320,7 @@ impl RpcTask for InitGetNewAddressTask { } match self.coin { - MmCoinEnum::UtxoCoin(ref utxo) => Ok(GetNewAddressResponseEnum::Single( + MmCoinEnum::UtxoCoin(ref utxo) => Ok(GetNewAddressResponseEnum::Map( get_new_address_helper( &self.ctx, utxo, @@ -330,7 +330,7 @@ impl RpcTask for InitGetNewAddressTask { ) .await?, )), - MmCoinEnum::QtumCoin(ref qtum) => Ok(GetNewAddressResponseEnum::Single( + MmCoinEnum::QtumCoin(ref qtum) => Ok(GetNewAddressResponseEnum::Map( get_new_address_helper( &self.ctx, qtum, @@ -362,10 +362,10 @@ pub async fn get_new_address( ) -> MmResult { let coin = lp_coinfind_or_err(&ctx, &req.coin).await?; match coin { - MmCoinEnum::UtxoCoin(utxo) => Ok(GetNewAddressResponseEnum::Single( + MmCoinEnum::UtxoCoin(utxo) => Ok(GetNewAddressResponseEnum::Map( utxo.get_new_address_rpc_without_conf(req.params).await?, )), - MmCoinEnum::QtumCoin(qtum) => Ok(GetNewAddressResponseEnum::Single( + MmCoinEnum::QtumCoin(qtum) => Ok(GetNewAddressResponseEnum::Map( qtum.get_new_address_rpc_without_conf(req.params).await?, )), MmCoinEnum::EthCoin(eth) => Ok(GetNewAddressResponseEnum::Map( @@ -472,7 +472,7 @@ pub(crate) mod common_impl { Ok(GetNewAddressResponse { new_address: HDAddressBalance { - address: address.to_string(), + address: coin.address_formatter()(&address), derivation_path: RpcDerivationPath(hd_address.derivation_path().clone()), chain, balance, @@ -510,13 +510,14 @@ pub(crate) mod common_impl { let address = hd_address.address(); let balance = coin.known_address_balance(&address).await?; - coin.prepare_addresses_for_balance_stream_if_enabled(HashSet::from([address.to_string()])) + let formatted_address = coin.address_formatter()(&address); + coin.prepare_addresses_for_balance_stream_if_enabled(HashSet::from([formatted_address.clone()])) .await .map_err(|e| GetNewAddressRpcError::FailedScripthashSubscription(e.to_string()))?; Ok(GetNewAddressResponse { new_address: HDAddressBalance { - address: address.to_string(), + address: formatted_address, derivation_path: RpcDerivationPath(hd_address.derivation_path().clone()), chain, balance, diff --git a/mm2src/coins/rpc_command/init_account_balance.rs b/mm2src/coins/rpc_command/init_account_balance.rs index 94745f65e5..39e92cb12a 100644 --- a/mm2src/coins/rpc_command/init_account_balance.rs +++ b/mm2src/coins/rpc_command/init_account_balance.rs @@ -73,10 +73,10 @@ impl RpcTask for InitAccountBalanceTask { _task_handle: InitAccountBalanceTaskHandleShared, ) -> Result> { match self.coin { - MmCoinEnum::UtxoCoin(ref utxo) => Ok(HDAccountBalanceEnum::Single( + MmCoinEnum::UtxoCoin(ref utxo) => Ok(HDAccountBalanceEnum::Map( utxo.init_account_balance_rpc(self.req.params.clone()).await?, )), - MmCoinEnum::QtumCoin(ref qtum) => Ok(HDAccountBalanceEnum::Single( + MmCoinEnum::QtumCoin(ref qtum) => Ok(HDAccountBalanceEnum::Map( qtum.init_account_balance_rpc(self.req.params.clone()).await?, )), MmCoinEnum::EthCoin(ref eth) => Ok(HDAccountBalanceEnum::Map( diff --git a/mm2src/coins/rpc_command/init_create_account.rs b/mm2src/coins/rpc_command/init_create_account.rs index 012009d04b..833998a1c3 100644 --- a/mm2src/coins/rpc_command/init_create_account.rs +++ b/mm2src/coins/rpc_command/init_create_account.rs @@ -286,7 +286,7 @@ impl RpcTask for InitCreateAccountTask { } match self.coin { - MmCoinEnum::UtxoCoin(ref utxo) => Ok(HDAccountBalanceEnum::Single( + MmCoinEnum::UtxoCoin(ref utxo) => Ok(HDAccountBalanceEnum::Map( create_new_account_helper( &self.ctx, utxo, @@ -298,7 +298,7 @@ impl RpcTask for InitCreateAccountTask { ) .await?, )), - MmCoinEnum::QtumCoin(ref qtum) => Ok(HDAccountBalanceEnum::Single( + MmCoinEnum::QtumCoin(ref qtum) => Ok(HDAccountBalanceEnum::Map( create_new_account_helper( &self.ctx, qtum, diff --git a/mm2src/coins/rpc_command/init_scan_for_new_addresses.rs b/mm2src/coins/rpc_command/init_scan_for_new_addresses.rs index 4eabda46e9..eaf51277b6 100644 --- a/mm2src/coins/rpc_command/init_scan_for_new_addresses.rs +++ b/mm2src/coins/rpc_command/init_scan_for_new_addresses.rs @@ -92,10 +92,10 @@ impl RpcTask for InitScanAddressesTask { async fn run(&mut self, _task_handle: ScanAddressesTaskHandleShared) -> Result> { match self.coin { - MmCoinEnum::UtxoCoin(ref utxo) => Ok(ScanAddressesResponseEnum::Single( + MmCoinEnum::UtxoCoin(ref utxo) => Ok(ScanAddressesResponseEnum::Map( utxo.init_scan_for_new_addresses_rpc(self.req.params.clone()).await?, )), - MmCoinEnum::QtumCoin(ref qtum) => Ok(ScanAddressesResponseEnum::Single( + MmCoinEnum::QtumCoin(ref qtum) => Ok(ScanAddressesResponseEnum::Map( qtum.init_scan_for_new_addresses_rpc(self.req.params.clone()).await?, )), MmCoinEnum::EthCoin(ref eth) => Ok(ScanAddressesResponseEnum::Map( diff --git a/mm2src/coins/sia/address.rs b/mm2src/coins/sia/address.rs deleted file mode 100644 index 5218a08bc8..0000000000 --- a/mm2src/coins/sia/address.rs +++ /dev/null @@ -1,167 +0,0 @@ -use crate::sia::blake2b_internal::standard_unlock_hash; -use blake2b_simd::Params; -use ed25519_dalek::PublicKey; -use hex::FromHexError; -use rpc::v1::types::H256; -use serde::{Deserialize, Serialize}; -use std::convert::TryInto; -use std::fmt; -use std::str::FromStr; - -// TODO this could probably include the checksum within the data type -// generating the checksum on the fly is how Sia Go does this however -#[derive(Debug, Clone, PartialEq)] -pub struct Address(pub H256); - -impl Address { - pub fn str_without_prefix(&self) -> String { - let bytes = self.0 .0.as_ref(); - let checksum = blake2b_checksum(bytes); - format!("{}{}", hex::encode(bytes), hex::encode(checksum)) - } -} - -impl fmt::Display for Address { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "addr:{}", self.str_without_prefix()) } -} - -impl fmt::Display for ParseAddressError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Failed to parse Address: {:?}", self) } -} - -#[derive(Debug, Deserialize, Serialize)] -pub enum ParseAddressError { - #[serde(rename = "Address must begin with addr: prefix")] - MissingPrefix, - InvalidHexEncoding(String), - InvalidChecksum, - InvalidLength, - // Add other error kinds as needed -} - -impl From for ParseAddressError { - fn from(e: FromHexError) -> Self { ParseAddressError::InvalidHexEncoding(e.to_string()) } -} - -impl FromStr for Address { - type Err = ParseAddressError; - - fn from_str(s: &str) -> Result { - if !s.starts_with("addr:") { - return Err(ParseAddressError::MissingPrefix); - } - - let without_prefix = &s[5..]; - if without_prefix.len() != (32 + 6) * 2 { - return Err(ParseAddressError::InvalidLength); - } - - let (address_hex, checksum_hex) = without_prefix.split_at(32 * 2); - - let address_bytes: [u8; 32] = hex::decode(address_hex) - .map_err(ParseAddressError::from)? - .try_into() - .expect("length is 32 bytes"); - - let checksum = hex::decode(checksum_hex).map_err(ParseAddressError::from)?; - let checksum_bytes: [u8; 6] = checksum.try_into().expect("length is 6 bytes"); - - if checksum_bytes != blake2b_checksum(&address_bytes) { - return Err(ParseAddressError::InvalidChecksum); - } - - Ok(Address(H256::from(address_bytes))) - } -} - -// Sia uses the first 6 bytes of blake2b(preimage) appended -// to address as checksum -fn blake2b_checksum(preimage: &[u8]) -> [u8; 6] { - let hash = Params::new().hash_length(32).to_state().update(preimage).finalize(); - hash.as_array()[0..6].try_into().expect("array is 64 bytes long") -} - -pub fn v1_standard_address_from_pubkey(pubkey: &PublicKey) -> Address { - let hash = standard_unlock_hash(pubkey); - Address(hash) -} - -#[test] -fn test_v1_standard_address_from_pubkey() { - let pubkey = PublicKey::from_bytes( - &hex::decode("8a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c").unwrap(), - ) - .unwrap(); - let address = v1_standard_address_from_pubkey(&pubkey); - assert_eq!( - format!("{}", address), - "addr:c959f9b423b662c36ee58057b8157acedb4095cfeb7926e4ba44cd9ee1f49a5b7803c7501a7b" - ) -} - -#[test] -fn test_blake2b_checksum() { - let checksum = - blake2b_checksum(&hex::decode("591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a884").unwrap()); - let expected: [u8; 6] = hex::decode("0be0653e411f").unwrap().try_into().unwrap(); - assert_eq!(checksum, expected); -} - -#[test] -fn test_address_display() { - let address = Address("591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a884".into()); - let address_str = "addr:591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e411f"; - assert_eq!(format!("{}", address), address_str); -} - -#[test] -fn test_address_fromstr() { - let address1 = Address("591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a884".into()); - - let address2 = - Address::from_str("addr:591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e411f").unwrap(); - assert_eq!(address1, address2); -} - -#[test] -fn test_address_fromstr_bad_length() { - let address = Address::from_str("addr:dead"); - assert!(matches!(address, Err(ParseAddressError::InvalidLength))); -} - -#[test] -fn test_address_fromstr_odd_length() { - let address = Address::from_str("addr:f00"); - assert!(matches!(address, Err(ParseAddressError::InvalidLength))); -} - -#[test] -fn test_address_fromstr_invalid_hex() { - let address = - Address::from_str("addr:591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e41gg"); - assert!(matches!(address, Err(ParseAddressError::InvalidHexEncoding(_)))); -} - -#[test] -fn test_address_fromstr_missing_prefix() { - let address = Address::from_str("591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e41gg"); - assert!(matches!(address, Err(ParseAddressError::MissingPrefix))); -} - -#[test] -fn test_address_fromstr_invalid_checksum() { - let address = - Address::from_str("addr:591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a884ffffffffffff"); - assert!(matches!(address, Err(ParseAddressError::InvalidChecksum))); -} - -#[test] -fn test_address_str_without_prefix() { - let address = - Address::from_str("addr:591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e411f").unwrap(); - - assert_eq!( - address.str_without_prefix(), - "591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e411f" - ); -} diff --git a/mm2src/coins/sia/blake2b_internal.rs b/mm2src/coins/sia/blake2b_internal.rs deleted file mode 100644 index 39c4f7c82b..0000000000 --- a/mm2src/coins/sia/blake2b_internal.rs +++ /dev/null @@ -1,326 +0,0 @@ -use blake2b_simd::Params; -use ed25519_dalek::PublicKey; -use rpc::v1::types::H256; -use std::default::Default; - -#[cfg(test)] use hex; -#[cfg(test)] use std::convert::TryInto; - -const LEAF_HASH_PREFIX: [u8; 1] = [0u8]; -const NODE_HASH_PREFIX: [u8; 1] = [1u8]; - -pub const ED25519_IDENTIFIER: [u8; 16] = [ - 0x65, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, -]; - -// Precomputed hash values used for all standard v1 addresses -// a standard address has 1 ed25519 public key, requires 1 signature and has a timelock of 0 -// https://github.com/SiaFoundation/core/blob/b5b08cde6b7d0f1b3a6f09b8aa9d0b817e769efb/types/hash.go#L94 -const STANDARD_TIMELOCK_BLAKE2B_HASH: [u8; 32] = [ - 0x51, 0x87, 0xb7, 0xa8, 0x02, 0x1b, 0xf4, 0xf2, 0xc0, 0x04, 0xea, 0x3a, 0x54, 0xcf, 0xec, 0xe1, 0x75, 0x4f, 0x11, - 0xc7, 0x62, 0x4d, 0x23, 0x63, 0xc7, 0xf4, 0xcf, 0x4f, 0xdd, 0xd1, 0x44, 0x1e, -]; - -const STANDARD_SIGS_REQUIRED_BLAKE2B_HASH: [u8; 32] = [ - 0xb3, 0x60, 0x10, 0xeb, 0x28, 0x5c, 0x15, 0x4a, 0x8c, 0xd6, 0x30, 0x84, 0xac, 0xbe, 0x7e, 0xac, 0x0c, 0x4d, 0x62, - 0x5a, 0xb4, 0xe1, 0xa7, 0x6e, 0x62, 0x4a, 0x87, 0x98, 0xcb, 0x63, 0x49, 0x7b, -]; - -#[derive(Debug, PartialEq)] -pub struct Accumulator { - trees: [H256; 64], - num_leaves: u64, -} - -impl Default for Accumulator { - fn default() -> Self { - Accumulator { - trees: [H256::default(); 64], // Initialize all bytes to zero - num_leaves: 0, - } - } -} - -impl Accumulator { - // Check if there is a tree at the given height - fn has_tree_at_height(&self, height: u64) -> bool { self.num_leaves & (1 << height) != 0 } - - // Add a leaf to the accumulator - pub fn add_leaf(&mut self, h: H256) { - let mut i = 0; - let mut new_hash = h; - while self.has_tree_at_height(i) { - new_hash = hash_blake2b_pair(&NODE_HASH_PREFIX, &self.trees[i as usize].0, &new_hash.0); - i += 1; - } - self.trees[i as usize] = new_hash; - self.num_leaves += 1; - } - - // Calulate the root hash of the Merkle tree - pub fn root(&self) -> H256 { - // trailing_zeros determines the height Merkle tree accumulator where the current lowest single leaf is located - let i = self.num_leaves.trailing_zeros() as u64; - if i == 64 { - return H256::default(); // Return all zeros if no leaves - } - let mut root = self.trees[i as usize]; - for j in i + 1..64 { - if self.has_tree_at_height(j) { - root = hash_blake2b_pair(&NODE_HASH_PREFIX, &self.trees[j as usize].0, &root.0); - } - } - root - } -} - -pub fn sigs_required_leaf(sigs_required: u64) -> H256 { - let sigs_required_array: [u8; 8] = sigs_required.to_le_bytes(); - let mut combined = Vec::new(); - combined.extend_from_slice(&LEAF_HASH_PREFIX); - combined.extend_from_slice(&sigs_required_array); - - hash_blake2b_single(&combined) -} - -// public key leaf is -// blake2b(leafHashPrefix + 16_byte_ascii_algorithm_identifier + public_key_length_u64 + public_key) -pub fn public_key_leaf(pubkey: &PublicKey) -> H256 { - let mut combined = Vec::new(); - combined.extend_from_slice(&LEAF_HASH_PREFIX); - combined.extend_from_slice(&ED25519_IDENTIFIER); - combined.extend_from_slice(&32u64.to_le_bytes()); - combined.extend_from_slice(pubkey.as_bytes()); - hash_blake2b_single(&combined) -} - -pub fn timelock_leaf(timelock: u64) -> H256 { - let timelock: [u8; 8] = timelock.to_le_bytes(); - let mut combined = Vec::new(); - combined.extend_from_slice(&LEAF_HASH_PREFIX); - combined.extend_from_slice(&timelock); - - hash_blake2b_single(&combined) -} - -// https://github.com/SiaFoundation/core/blob/b5b08cde6b7d0f1b3a6f09b8aa9d0b817e769efb/types/hash.go#L96 -// An UnlockHash is the Merkle root of UnlockConditions. Since the standard -// UnlockConditions use a single public key, the Merkle tree is: -// -// ┌─────────┴──────────┐ -// ┌─────┴─────┐ │ -// timelock pubkey sigsrequired -pub fn standard_unlock_hash(pubkey: &PublicKey) -> H256 { - let pubkey_leaf = public_key_leaf(pubkey); - let timelock_pubkey_node = hash_blake2b_pair(&NODE_HASH_PREFIX, &STANDARD_TIMELOCK_BLAKE2B_HASH, &pubkey_leaf.0); - hash_blake2b_pair( - &NODE_HASH_PREFIX, - &timelock_pubkey_node.0, - &STANDARD_SIGS_REQUIRED_BLAKE2B_HASH, - ) -} - -pub fn hash_blake2b_single(preimage: &[u8]) -> H256 { - let hash = Params::new().hash_length(32).to_state().update(preimage).finalize(); - let ret_array = hash.as_array(); - ret_array[0..32].into() -} - -fn hash_blake2b_pair(prefix: &[u8], leaf1: &[u8], leaf2: &[u8]) -> H256 { - let hash = Params::new() - .hash_length(32) - .to_state() - .update(prefix) - .update(leaf1) - .update(leaf2) - .finalize(); - let ret_array = hash.as_array(); - ret_array[0..32].into() -} - -#[test] -fn test_accumulator_new() { - let default_accumulator = Accumulator::default(); - - let expected = Accumulator { - trees: [H256::from("0000000000000000000000000000000000000000000000000000000000000000"); 64], - num_leaves: 0, - }; - assert_eq!(default_accumulator, expected) -} - -#[test] -fn test_accumulator_root_default() { assert_eq!(Accumulator::default().root(), H256::default()) } - -#[test] -fn test_accumulator_root() { - let mut accumulator = Accumulator::default(); - - let timelock_leaf = timelock_leaf(0u64); - accumulator.add_leaf(timelock_leaf); - - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let pubkey_leaf = public_key_leaf(&pubkey); - accumulator.add_leaf(pubkey_leaf); - - let sigs_required_leaf = sigs_required_leaf(1u64); - accumulator.add_leaf(sigs_required_leaf); - - let expected = H256::from("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515d"); - assert_eq!(accumulator.root(), expected); -} - -#[test] -fn test_accumulator_add_leaf_standard_unlock_hash() { - let mut accumulator = Accumulator::default(); - - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - - let pubkey_leaf = public_key_leaf(&pubkey); - let timelock_leaf = timelock_leaf(0u64); - let sigs_required_leaf = sigs_required_leaf(1u64); - - accumulator.add_leaf(timelock_leaf); - accumulator.add_leaf(pubkey_leaf); - accumulator.add_leaf(sigs_required_leaf); - - let root = accumulator.root(); - let expected = H256::from("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515d"); - assert_eq!(root, expected) -} - -#[test] -fn test_accumulator_add_leaf_2of2_multisig_unlock_hash() { - let mut accumulator = Accumulator::default(); - - let pubkey1 = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let pubkey2 = PublicKey::from_bytes( - &hex::decode("0101010000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - - let pubkey1_leaf = public_key_leaf(&pubkey1); - let pubkey2_leaf = public_key_leaf(&pubkey2); - - let timelock_leaf = timelock_leaf(0u64); - let sigs_required_leaf = sigs_required_leaf(2u64); - - accumulator.add_leaf(timelock_leaf); - accumulator.add_leaf(pubkey1_leaf); - accumulator.add_leaf(pubkey2_leaf); - accumulator.add_leaf(sigs_required_leaf); - - let root = accumulator.root(); - let expected = H256::from("1e94357817d236167e54970a8c08bbd41b37bfceeeb52f6c1ce6dd01d50ea1e7"); - assert_eq!(root, expected) -} - -#[test] -fn test_accumulator_add_leaf_1of2_multisig_unlock_hash() { - let mut accumulator = Accumulator::default(); - - let pubkey1 = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let pubkey2 = PublicKey::from_bytes( - &hex::decode("0101010000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - - let pubkey1_leaf = public_key_leaf(&pubkey1); - let pubkey2_leaf = public_key_leaf(&pubkey2); - - let timelock_leaf = timelock_leaf(0u64); - let sigs_required_leaf = sigs_required_leaf(1u64); - - accumulator.add_leaf(timelock_leaf); - accumulator.add_leaf(pubkey1_leaf); - accumulator.add_leaf(pubkey2_leaf); - accumulator.add_leaf(sigs_required_leaf); - - let root = accumulator.root(); - let expected = H256::from("d7f84e3423da09d111a17f64290c8d05e1cbe4cab2b6bed49e3a4d2f659f0585"); - assert_eq!(root, expected) -} - -#[test] -fn test_standard_unlock_hash() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - - let hash = standard_unlock_hash(&pubkey); - let expected = H256::from("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515d"); - assert_eq!(hash, expected) -} - -#[test] -fn test_hash_blake2b_pair() { - let left: [u8; 32] = hex::decode("cdcce3978a58ceb6c8480d218646db4eae85eb9ea9c2f5138fbacb4ce2c701e3") - .unwrap() - .try_into() - .unwrap(); - let right: [u8; 32] = hex::decode("b36010eb285c154a8cd63084acbe7eac0c4d625ab4e1a76e624a8798cb63497b") - .unwrap() - .try_into() - .unwrap(); - - let hash = hash_blake2b_pair(&NODE_HASH_PREFIX, &left, &right); - let expected = H256::from("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515d"); - assert_eq!(hash, expected) -} - -#[test] -fn test_create_ed25519_identifier() { - let mut ed25519_identifier: [u8; 16] = [0; 16]; - - let bytes = "ed25519".as_bytes(); - for (i, &byte) in bytes.iter().enumerate() { - ed25519_identifier[i] = byte; - } - assert_eq!(ed25519_identifier, ED25519_IDENTIFIER); -} - -#[test] -fn test_timelock_leaf() { - let hash = timelock_leaf(0); - let expected = H256::from(STANDARD_TIMELOCK_BLAKE2B_HASH); - assert_eq!(hash, expected) -} - -#[test] -fn test_sigs_required_leaf() { - let hash = sigs_required_leaf(1u64); - let expected = H256::from(STANDARD_SIGS_REQUIRED_BLAKE2B_HASH); - assert_eq!(hash, expected) -} - -#[test] -fn test_hash_blake2b_single() { - let hash = hash_blake2b_single(&hex::decode("006564323535313900000000000000000020000000000000000102030000000000000000000000000000000000000000000000000000000000").unwrap()); - let expected = H256::from("21ce940603a2ee3a283685f6bfb4b122254894fd1ed3eb59434aadbf00c75d5b"); - assert_eq!(hash, expected) -} - -#[test] -fn test_public_key_leaf() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - - let hash = public_key_leaf(&pubkey); - let expected = H256::from("21ce940603a2ee3a283685f6bfb4b122254894fd1ed3eb59434aadbf00c75d5b"); - assert_eq!(hash, expected) -} diff --git a/mm2src/coins/sia/encoding.rs b/mm2src/coins/sia/encoding.rs deleted file mode 100644 index 5b6516101a..0000000000 --- a/mm2src/coins/sia/encoding.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::sia::blake2b_internal::hash_blake2b_single; -use rpc::v1::types::H256; - -// https://github.com/SiaFoundation/core/blob/092850cc52d3d981b19c66cd327b5d945b3c18d3/types/encoding.go#L16 -// TODO go implementation limits this to 1024 bytes, should we? -#[derive(Default)] -pub struct Encoder { - pub buffer: Vec, -} - -impl Encoder { - pub fn reset(&mut self) { self.buffer.clear(); } - - /// writes a length-prefixed []byte to the underlying stream. - pub fn write_len_prefixed_bytes(&mut self, data: &[u8]) { - self.buffer.extend_from_slice(&data.len().to_le_bytes()); - self.buffer.extend_from_slice(data); - } - - pub fn write_slice(&mut self, data: &[u8]) { self.buffer.extend_from_slice(data); } - - pub fn write_u8(&mut self, u: u8) { self.buffer.extend_from_slice(&[u]) } - - pub fn write_u64(&mut self, u: u64) { self.buffer.extend_from_slice(&u.to_le_bytes()); } - - pub fn write_distinguisher(&mut self, p: &str) { self.buffer.extend_from_slice(format!("sia/{}|", p).as_bytes()); } - - pub fn write_bool(&mut self, b: bool) { self.buffer.push(b as u8) } - - pub fn hash(&self) -> H256 { hash_blake2b_single(&self.buffer) } -} - -#[test] -fn test_encoder_default_hash() { - assert_eq!( - Encoder::default().hash(), - H256::from("0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8") - ) -} - -#[test] -fn test_encoder_write_bytes() { - let mut encoder = Encoder::default(); - encoder.write_len_prefixed_bytes(&[1, 2, 3, 4]); - assert_eq!( - encoder.hash(), - H256::from("d4a72b52e2e1f40e20ee40ea6d5080a1b1f76164786defbb7691a4427f3388f5") - ); -} - -#[test] -fn test_encoder_write_u8() { - let mut encoder = Encoder::default(); - encoder.write_u8(1); - assert_eq!( - encoder.hash(), - H256::from("ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25") - ); -} - -#[test] -fn test_encoder_write_u64() { - let mut encoder = Encoder::default(); - encoder.write_u64(1); - assert_eq!( - encoder.hash(), - H256::from("1dbd7d0b561a41d23c2a469ad42fbd70d5438bae826f6fd607413190c37c363b") - ); -} - -#[test] -fn test_encoder_write_distiguisher() { - let mut encoder = Encoder::default(); - encoder.write_distinguisher("test"); - assert_eq!( - encoder.hash(), - H256::from("25fb524721bf98a9a1233a53c40e7e198971b003bf23c24f59d547a1bb837f9c") - ); -} - -#[test] -fn test_encoder_write_bool() { - let mut encoder = Encoder::default(); - encoder.write_bool(true); - assert_eq!( - encoder.hash(), - H256::from("ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25") - ); -} - -#[test] -fn test_encoder_reset() { - let mut encoder = Encoder::default(); - encoder.write_bool(true); - assert_eq!( - encoder.hash(), - H256::from("ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25") - ); - - encoder.reset(); - encoder.write_bool(false); - assert_eq!( - encoder.hash(), - H256::from("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314") - ); -} - -#[test] -fn test_encoder_complex() { - let mut encoder = Encoder::default(); - encoder.write_distinguisher("test"); - encoder.write_bool(true); - encoder.write_u8(1); - encoder.write_len_prefixed_bytes(&[1, 2, 3, 4]); - assert_eq!( - encoder.hash(), - H256::from("b66d7a9bef9fb303fe0e41f6b5c5af410303e428c4ff9231f6eb381248693221") - ); -} diff --git a/mm2src/coins/sia/http_client.rs b/mm2src/coins/sia/http_client.rs deleted file mode 100644 index 774dcd08e1..0000000000 --- a/mm2src/coins/sia/http_client.rs +++ /dev/null @@ -1,208 +0,0 @@ -use crate::sia::address::Address; -use crate::sia::SiaHttpConf; -use base64::engine::general_purpose::STANDARD as BASE64; -use base64::Engine as _; // required for .encode() method -use core::fmt::Display; -use core::time::Duration; -use mm2_number::MmNumber; -use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION}; -use reqwest::{Client, Error, Url}; -use serde::de::DeserializeOwned; -use std::ops::Deref; -use std::sync::Arc; - -#[cfg(test)] use std::str::FromStr; - -const ENDPOINT_CONSENSUS_TIP: &str = "api/consensus/tip"; - -/// HTTP(s) client for Sia-protocol coins -#[derive(Debug)] -pub struct SiaHttpClientImpl { - /// Name of coin the http client is intended to work with - pub coin_ticker: String, - /// The uri to send requests to - pub uri: String, - /// Value of Authorization header password, e.g. "Basic base64(:password)" - pub auth: String, -} - -#[derive(Clone, Debug)] -pub struct SiaApiClient(pub Arc); - -impl Deref for SiaApiClient { - type Target = SiaApiClientImpl; - fn deref(&self) -> &SiaApiClientImpl { &self.0 } -} - -impl SiaApiClient { - pub fn new(_coin_ticker: &str, http_conf: SiaHttpConf) -> Result { - let new_arc = SiaApiClientImpl::new(http_conf.url, &http_conf.auth)?; - Ok(SiaApiClient(Arc::new(new_arc))) - } -} - -#[derive(Debug)] -pub struct SiaApiClientImpl { - client: Client, - base_url: Url, -} - -// this is neccesary to show the URL in error messages returned to the user -// this can be removed in favor of using ".with_url()" once reqwest is updated to v0.11.23 -#[derive(Debug)] -pub struct ReqwestErrorWithUrl { - error: Error, - url: Url, -} - -impl Display for ReqwestErrorWithUrl { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "Error: {}, URL: {}", self.error, self.url) - } -} - -#[derive(Debug, Display)] -pub enum SiaApiClientError { - Timeout(String), - BuildError(String), - ApiUnreachable(String), - ReqwestError(ReqwestErrorWithUrl), - UrlParse(url::ParseError), -} - -impl From for String { - fn from(e: SiaApiClientError) -> Self { format!("{:?}", e) } -} - -async fn fetch_and_parse(client: &Client, url: Url) -> Result { - client - .get(url.clone()) - .send() - .await - .map_err(|e| { - SiaApiClientError::ReqwestError(ReqwestErrorWithUrl { - error: e, - url: url.clone(), - }) - })? - .json::() - .await - .map_err(|e| SiaApiClientError::ReqwestError(ReqwestErrorWithUrl { error: e, url })) -} - -// https://github.com/SiaFoundation/core/blob/4e46803f702891e7a83a415b7fcd7543b13e715e/types/types.go#L181 -#[derive(Deserialize, Serialize, Debug)] -pub struct GetConsensusTipResponse { - pub height: u64, - pub id: String, // TODO this can match "BlockID" type -} - -// https://github.com/SiaFoundation/walletd/blob/9574e69ff0bf84de1235b68e78db2a41d5e27516/api/api.go#L36 -// https://github.com/SiaFoundation/walletd/blob/9574e69ff0bf84de1235b68e78db2a41d5e27516/wallet/wallet.go#L25 -#[derive(Deserialize, Serialize, Debug)] -pub struct GetAddressesBalanceResponse { - pub siacoins: MmNumber, - #[serde(rename = "immatureSiacoins")] - pub immature_siacoins: MmNumber, - pub siafunds: u64, -} - -impl SiaApiClientImpl { - fn new(base_url: Url, password: &str) -> Result { - let mut headers = HeaderMap::new(); - let auth_value = format!("Basic {}", BASE64.encode(format!(":{}", password))); - headers.insert( - AUTHORIZATION, - HeaderValue::from_str(&auth_value).map_err(|e| SiaApiClientError::BuildError(e.to_string()))?, - ); - - let client = Client::builder() - .default_headers(headers) - .timeout(Duration::from_secs(10)) // TODO make this configurable - .build() - .map_err(|e| { - SiaApiClientError::ReqwestError(ReqwestErrorWithUrl { - error: e, - url: base_url.clone(), - }) - })?; - Ok(SiaApiClientImpl { client, base_url }) - } - - pub async fn get_consensus_tip(&self) -> Result { - let base_url = self.base_url.clone(); - let endpoint_url = base_url - .join(ENDPOINT_CONSENSUS_TIP) - .map_err(SiaApiClientError::UrlParse)?; - - fetch_and_parse::(&self.client, endpoint_url).await - } - - pub async fn get_addresses_balance( - &self, - address: &Address, - ) -> Result { - self.get_addresses_balance_str(&address.str_without_prefix()).await - } - - // use get_addresses_balance whenever possible to rely on Address deserialization - pub async fn get_addresses_balance_str( - &self, - address: &str, - ) -> Result { - let base_url = self.base_url.clone(); - - let endpoint_path = format!("api/addresses/{}/balance", address); - let endpoint_url = base_url.join(&endpoint_path).map_err(SiaApiClientError::UrlParse)?; - - fetch_and_parse::(&self.client, endpoint_url).await - } - - pub async fn get_height(&self) -> Result { - let resp = self.get_consensus_tip().await?; - Ok(resp.height) - } -} - -#[tokio::test] -#[ignore] -async fn test_api_client_timeout() { - let api_client = SiaApiClientImpl::new(Url::parse("http://foo").unwrap(), "password").unwrap(); - let result = api_client.get_consensus_tip().await; - assert!(matches!(result, Err(SiaApiClientError::Timeout(_)))); -} - -// TODO all of the following must be adapted to use Docker Sia node -#[tokio::test] -#[ignore] -async fn test_api_client_invalid_auth() { - let api_client = SiaApiClientImpl::new(Url::parse("http://127.0.0.1:9980").unwrap(), "password").unwrap(); - let result = api_client.get_consensus_tip().await; - assert!(matches!(result, Err(SiaApiClientError::BuildError(_)))); -} - -// TODO must be adapted to use Docker Sia node -#[tokio::test] -#[ignore] -async fn test_api_client() { - let api_client = SiaApiClientImpl::new(Url::parse("http://127.0.0.1:9980").unwrap(), "password").unwrap(); - let _result = api_client.get_consensus_tip().await.unwrap(); -} - -#[tokio::test] -#[ignore] -async fn test_api_get_addresses_balance() { - let api_client = SiaApiClientImpl::new(Url::parse("http://127.0.0.1:9980").unwrap(), "password").unwrap(); - let address = - Address::from_str("addr:591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e411f").unwrap(); - let result = api_client.get_addresses_balance(&address).await.unwrap(); - println!("ret {:?}", result); -} - -#[tokio::test] -#[ignore] -async fn test_api_get_addresses_balance_invalid() { - let api_client = SiaApiClientImpl::new(Url::parse("http://127.0.0.1:9980").unwrap(), "password").unwrap(); - let result = api_client.get_addresses_balance_str("what").await.unwrap(); - println!("ret {:?}", result); -} diff --git a/mm2src/coins/sia/spend_policy.rs b/mm2src/coins/sia/spend_policy.rs deleted file mode 100644 index 6c28f7250a..0000000000 --- a/mm2src/coins/sia/spend_policy.rs +++ /dev/null @@ -1,322 +0,0 @@ -use crate::sia::address::Address; -use crate::sia::blake2b_internal::{public_key_leaf, sigs_required_leaf, standard_unlock_hash, timelock_leaf, - Accumulator, ED25519_IDENTIFIER}; -use crate::sia::encoding::Encoder; -use ed25519_dalek::PublicKey; -use rpc::v1::types::H256; - -#[cfg(test)] use std::str::FromStr; - -const POLICY_VERSION: u8 = 1u8; - -#[derive(Debug, Clone)] -pub enum SpendPolicy { - Above(u64), - After(u64), - PublicKey(PublicKey), - Hash(H256), - Threshold(PolicyTypeThreshold), - Opaque(Address), - UnlockConditions(PolicyTypeUnlockConditions), // For v1 compatibility -} - -impl SpendPolicy { - pub fn to_u8(&self) -> u8 { - match self { - SpendPolicy::Above(_) => 1, - SpendPolicy::After(_) => 2, - SpendPolicy::PublicKey(_) => 3, - SpendPolicy::Hash(_) => 4, - SpendPolicy::Threshold(_) => 5, - SpendPolicy::Opaque(_) => 6, - SpendPolicy::UnlockConditions(_) => 7, - } - } - - pub fn encode(&self) -> Encoder { - let mut encoder = Encoder::default(); - encoder.write_u8(POLICY_VERSION); - encoder.write_slice(&self.encode_wo_prefix().buffer); - encoder - } - - pub fn encode_wo_prefix(&self) -> Encoder { - let mut encoder = Encoder::default(); - let opcode = self.to_u8(); - match self { - SpendPolicy::Above(height) => { - encoder.write_u8(opcode); - encoder.write_u64(*height); - }, - SpendPolicy::After(time) => { - encoder.write_u8(opcode); - encoder.write_u64(*time); - }, - SpendPolicy::PublicKey(pubkey) => { - encoder.write_u8(opcode); - encoder.write_slice(&pubkey.to_bytes()); - }, - SpendPolicy::Hash(hash) => { - encoder.write_u8(opcode); - encoder.write_slice(&hash.0); - }, - SpendPolicy::Threshold(PolicyTypeThreshold { n, of }) => { - encoder.write_u8(opcode); - encoder.write_u8(*n); - encoder.write_u8(of.len() as u8); - for policy in of { - encoder.write_slice(&policy.encode_wo_prefix().buffer); - } - }, - SpendPolicy::Opaque(p) => { - encoder.write_u8(opcode); - encoder.write_slice(&p.0 .0); - }, - SpendPolicy::UnlockConditions(PolicyTypeUnlockConditions(unlock_condition)) => { - encoder.write_u8(opcode); - encoder.write_u64(unlock_condition.timelock); - encoder.write_u64(unlock_condition.pubkeys.len() as u64); - for pubkey in &unlock_condition.pubkeys { - encoder.write_slice(&ED25519_IDENTIFIER); - encoder.write_slice(&pubkey.to_bytes()); - } - encoder.write_u64(unlock_condition.sigs_required); - }, - } - encoder - } - - fn address(&self) -> Address { - if let SpendPolicy::UnlockConditions(PolicyTypeUnlockConditions(unlock_condition)) = self { - return unlock_condition.address(); - } - let mut encoder = Encoder::default(); - encoder.write_distinguisher("address"); - - // if self is a threshold policy, we need to convert all of its subpolicies to opaque - let mut new_policy = self.clone(); - if let SpendPolicy::Threshold(ref mut p) = new_policy { - p.of = p.of.iter().map(SpendPolicy::opaque).collect(); - } - - let encoded_policy = new_policy.encode(); - encoder.write_slice(&encoded_policy.buffer); - Address(encoder.hash()) - } - - pub fn above(height: u64) -> Self { SpendPolicy::Above(height) } - - pub fn after(time: u64) -> Self { SpendPolicy::After(time) } - - pub fn public_key(pk: PublicKey) -> Self { SpendPolicy::PublicKey(pk) } - - pub fn hash(h: H256) -> Self { SpendPolicy::Hash(h) } - - pub fn threshold(n: u8, of: Vec) -> Self { SpendPolicy::Threshold(PolicyTypeThreshold { n, of }) } - - pub fn opaque(p: &SpendPolicy) -> Self { SpendPolicy::Opaque(p.address()) } - - pub fn anyone_can_spend() -> Self { SpendPolicy::threshold(0, vec![]) } -} - -#[derive(Debug, Clone)] -pub struct PolicyTypeThreshold { - pub n: u8, - pub of: Vec, -} - -// Compatibility with Sia's "UnlockConditions" -#[derive(Debug, Clone)] -pub struct PolicyTypeUnlockConditions(UnlockCondition); - -#[derive(Debug, Clone)] -pub struct UnlockCondition { - pubkeys: Vec, - timelock: u64, - sigs_required: u64, -} - -impl UnlockCondition { - pub fn new(pubkeys: Vec, timelock: u64, sigs_required: u64) -> Self { - // TODO check go implementation to see if there should be limitations or checks imposed here - UnlockCondition { - pubkeys, - timelock, - sigs_required, - } - } - - pub fn unlock_hash(&self) -> H256 { - // almost all UnlockConditions are standard, so optimize for that case - if self.timelock == 0 && self.pubkeys.len() == 1 && self.sigs_required == 1 { - return standard_unlock_hash(&self.pubkeys[0]); - } - - let mut accumulator = Accumulator::default(); - - accumulator.add_leaf(timelock_leaf(self.timelock)); - - for pubkey in &self.pubkeys { - accumulator.add_leaf(public_key_leaf(pubkey)); - } - - accumulator.add_leaf(sigs_required_leaf(self.sigs_required)); - accumulator.root() - } - - pub fn address(&self) -> Address { Address(self.unlock_hash()) } -} - -#[test] -fn test_unlock_condition_unlock_hash_standard() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let unlock_condition = UnlockCondition::new(vec![pubkey], 0, 1); - - let hash = unlock_condition.unlock_hash(); - let expected = H256::from("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515d"); - assert_eq!(hash, expected); - - let hash = standard_unlock_hash(&pubkey); - assert_eq!(hash, expected); -} - -#[test] -fn test_unlock_condition_unlock_hash_2of2_multisig() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let pubkey2 = PublicKey::from_bytes( - &hex::decode("0101010000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let unlock_condition = UnlockCondition::new(vec![pubkey, pubkey2], 0, 2); - - let hash = unlock_condition.unlock_hash(); - let expected = H256::from("1e94357817d236167e54970a8c08bbd41b37bfceeeb52f6c1ce6dd01d50ea1e7"); - assert_eq!(hash, expected); -} - -#[test] -fn test_unlock_condition_unlock_hash_1of2_multisig() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let pubkey2 = PublicKey::from_bytes( - &hex::decode("0101010000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let unlock_condition = UnlockCondition::new(vec![pubkey, pubkey2], 0, 1); - - let hash = unlock_condition.unlock_hash(); - let expected = H256::from("d7f84e3423da09d111a17f64290c8d05e1cbe4cab2b6bed49e3a4d2f659f0585"); - assert_eq!(hash, expected); -} - -#[test] -fn test_spend_policy_encode_above() { - let policy = SpendPolicy::above(1); - - let hash = policy.encode().hash(); - let expected = H256::from("bebf6cbdfb440a92e3e5d832ac30fe5d226ff6b352ed3a9398b7d35f086a8ab6"); - assert_eq!(hash, expected); - - let address = policy.address(); - let expected = - Address::from_str("addr:188b997bb99dee13e95f92c3ea150bd76b3ec72e5ba57b0d57439a1a6e2865e9b25ea5d1825e").unwrap(); - assert_eq!(address, expected); -} - -#[test] -fn test_spend_policy_encode_after() { - let policy = SpendPolicy::after(1); - - let hash = policy.encode().hash(); - let expected = H256::from("07b0f28eafd87a082ad11dc4724e1c491821260821a30bec68254444f97d9311"); - assert_eq!(hash, expected); - - let address = policy.address(); - let expected = - Address::from_str("addr:60c74e0ce5cede0f13f83b0132cb195c995bc7688c9fac34bbf2b14e14394b8bbe2991bc017f").unwrap(); - assert_eq!(address, expected); -} - -#[test] -fn test_spend_policy_encode_pubkey() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let policy = SpendPolicy::PublicKey(pubkey); - - let hash = policy.encode().hash(); - let expected = H256::from("4355c8f80f6e5a98b70c9c2f9a22f17747989b4744783c90439b2b034f698bfe"); - assert_eq!(hash, expected); - - let address = policy.address(); - let expected = - Address::from_str("addr:55a7793237722c6df8222fd512063cb74228085ef1805c5184713648c159b919ac792fbad0e1").unwrap(); - assert_eq!(address, expected); -} - -#[test] -fn test_spend_policy_encode_hash() { - let hash = H256::from("0102030000000000000000000000000000000000000000000000000000000000"); - let policy = SpendPolicy::Hash(hash); - - let encoded = policy.encode(); - let hash = encoded.hash(); - let expected = H256::from("9938967aefa6cbecc1f1620d2df5170d6811d4b2f47a879b621c1099a3b0628a"); - assert_eq!(hash, expected); - - let address = policy.address(); - let expected = - Address::from_str("addr:a4d5a06d8d3c2e45aa26627858ce8e881505ae3c9d122a1d282c7824163751936cffb347e435").unwrap(); - assert_eq!(address, expected); -} - -#[test] -fn test_spend_policy_encode_threshold() { - let policy = SpendPolicy::Threshold(PolicyTypeThreshold { - n: 1, - of: vec![SpendPolicy::above(1), SpendPolicy::after(1)], - }); - - let encoded = policy.encode(); - let hash = encoded.hash(); - let expected = H256::from("7d792df6cd0b5e0f795287b3bf4087bbcc4c1bd0c52880a552cdda3e5e33d802"); - assert_eq!(hash, expected); - - let address = policy.address(); - let expected = - Address::from_str("addr:4179b53aba165e46e4c85b3c8766bb758fb6f0bfa5721550b81981a3ec38efc460557dc1ded4").unwrap(); - assert_eq!(address, expected); -} - -#[test] -fn test_spend_policy_encode_unlock_condition() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let unlock_condition = UnlockCondition::new(vec![pubkey], 0, 1); - - let sub_policy = SpendPolicy::UnlockConditions(PolicyTypeUnlockConditions(unlock_condition)); - let base_address = sub_policy.address(); - let expected = - Address::from_str("addr:72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515dd64b9a56043a").unwrap(); - assert_eq!(base_address, expected); - - let policy = SpendPolicy::Threshold(PolicyTypeThreshold { - n: 1, - of: vec![sub_policy], - }); - let address = policy.address(); - let expected = - Address::from_str("addr:1498a58c843ce66740e52421632d67a0f6991ea96db1fc97c29e46f89ae56e3534078876331d").unwrap(); - assert_eq!(address, expected); -} diff --git a/mm2src/coins/sia.rs b/mm2src/coins/siacoin.rs similarity index 80% rename from mm2src/coins/sia.rs rename to mm2src/coins/siacoin.rs index 446a507070..bc57aaaf10 100644 --- a/mm2src/coins/sia.rs +++ b/mm2src/coins/siacoin.rs @@ -1,5 +1,5 @@ -use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, RawTransactionFut, RawTransactionRequest, SwapOps, - TradeFee, TransactionEnum, TransactionFut}; +use super::{BalanceError, CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, RawTransactionFut, + RawTransactionRequest, SwapOps, TradeFee, TransactionEnum, TransactionFut}; use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, DexFee, FeeApproxStage, FoundSwapTxSpend, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, @@ -14,25 +14,22 @@ use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPay WithdrawRequest}; use async_trait::async_trait; use common::executor::AbortedError; +pub use ed25519_dalek::{Keypair, PublicKey, SecretKey, Signature}; use futures::{FutureExt, TryFutureExt}; use futures01::Future; use keys::KeyPair; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; -use mm2_number::{BigDecimal, MmNumber}; +use mm2_number::{BigDecimal, BigInt, MmNumber}; use rpc::v1::types::Bytes as BytesJson; use serde_json::Value as Json; use std::ops::Deref; use std::sync::Arc; -use url::Url; -pub mod address; -use address::v1_standard_address_from_pubkey; -pub mod blake2b_internal; -pub mod encoding; -pub mod http_client; -use http_client::{SiaApiClient, SiaApiClientError}; -pub mod spend_policy; +use sia_rust::http_client::{SiaApiClient, SiaApiClientError, SiaHttpConf}; +use sia_rust::spend_policy::SpendPolicy; + +pub mod sia_hd_wallet; #[derive(Clone)] pub struct SiaCoin(SiaArc); @@ -54,12 +51,6 @@ pub struct SiaCoinConf { pub foo: u32, } -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct SiaHttpConf { - pub url: Url, - pub auth: String, -} - // TODO see https://github.com/KomodoPlatform/komodo-defi-framework/pull/2086#discussion_r1521660384 // for additional fields needed #[derive(Clone, Debug, Deserialize, Serialize)] @@ -93,7 +84,7 @@ impl<'a> SiaConfBuilder<'a> { pub struct SiaCoinFields { /// SIA coin config pub conf: SiaCoinConf, - pub priv_key_policy: PrivKeyPolicy, + pub priv_key_policy: PrivKeyPolicy, /// HTTP(s) client pub http_client: SiaApiClient, } @@ -118,7 +109,7 @@ pub struct SiaCoinBuilder<'a> { ctx: &'a MmArc, ticker: &'a str, conf: &'a Json, - key_pair: ed25519_dalek::Keypair, + key_pair: Keypair, params: &'a SiaCoinActivationParams, } @@ -127,7 +118,7 @@ impl<'a> SiaCoinBuilder<'a> { ctx: &'a MmArc, ticker: &'a str, conf: &'a Json, - key_pair: ed25519_dalek::Keypair, + key_pair: Keypair, params: &'a SiaCoinActivationParams, ) -> Self { SiaCoinBuilder { @@ -140,15 +131,22 @@ impl<'a> SiaCoinBuilder<'a> { } } -fn generate_keypair_from_slice(priv_key: &[u8]) -> Result { - let secret_key = ed25519_dalek::SecretKey::from_bytes(priv_key).map_err(SiaCoinBuildError::EllipticCurveError)?; - let public_key = ed25519_dalek::PublicKey::from(&secret_key); - Ok(ed25519_dalek::Keypair { +fn generate_keypair_from_slice(priv_key: &[u8]) -> Result { + let secret_key = SecretKey::from_bytes(priv_key).map_err(SiaCoinBuildError::EllipticCurveError)?; + let public_key = PublicKey::from(&secret_key); + Ok(Keypair { secret: secret_key, public: public_key, }) } +/// Convert hastings amount to siacoin amount +fn siacoin_from_hastings(hastings: u128) -> BigDecimal { + let hastings = BigInt::from(hastings); + let decimals = BigInt::from(10u128.pow(24)); + BigDecimal::from(hastings) / BigDecimal::from(decimals) +} + impl From for SiaCoinBuildError { fn from(e: SiaConfError) -> Self { SiaCoinBuildError::ConfError(e) } } @@ -174,8 +172,9 @@ impl<'a> SiaCoinBuilder<'a> { let conf = SiaConfBuilder::new(self.conf, self.ticker()).build()?; let sia_fields = SiaCoinFields { conf, - http_client: SiaApiClient::new(self.ticker(), self.params.http_conf.clone()) - .map_err(SiaCoinBuildError::ClientError)?, + http_client: SiaApiClient::new(self.params.http_conf.clone()) + .map_err(SiaCoinBuildError::ClientError) + .await?, priv_key_policy: PrivKeyPolicy::Iguana(self.key_pair), }; let sia_arc = SiaArc::new(sia_fields); @@ -306,9 +305,15 @@ impl MarketCoinOps for SiaCoin { ) .into()); }, + #[cfg(target_arch = "wasm32")] + PrivKeyPolicy::Metamask(_) => { + return Err(MyAddressError::UnexpectedDerivationMethod( + "Metamask not supported. Must use iguana seed.".to_string(), + ) + .into()); + }, }; - - let address = v1_standard_address_from_pubkey(&key_pair.public); + let address = SpendPolicy::PublicKey(key_pair.public).address(); Ok(address.to_string()) } @@ -323,14 +328,30 @@ impl MarketCoinOps for SiaCoin { } fn my_balance(&self) -> BalanceFut { + let coin = self.clone(); let fut = async move { + let my_address = match &coin.0.priv_key_policy { + PrivKeyPolicy::Iguana(key_pair) => SpendPolicy::PublicKey(key_pair.public).address(), + _ => { + return MmError::err(BalanceError::UnexpectedDerivationMethod( + UnexpectedDerivationMethod::ExpectedSingleAddress, + )) + }, + }; + let balance = coin + .0 + .http_client + .address_balance(my_address) + .await + .map_to_mm(|e| BalanceError::Transport(e.to_string()))?; Ok(CoinBalance { - spendable: BigDecimal::default(), - unspendable: BigDecimal::default(), + spendable: siacoin_from_hastings(balance.siacoins.to_u128()), + unspendable: siacoin_from_hastings(balance.immature_siacoins.to_u128()), }) }; Box::new(fut.boxed().compat()) } + fn base_coin_balance(&self) -> BalanceFut { unimplemented!() } fn platform_ticker(&self) -> &str { "FOO" } // TODO Alright @@ -349,7 +370,7 @@ impl MarketCoinOps for SiaCoin { unimplemented!() } - fn wait_for_htlc_tx_spend(&self, _args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { unimplemented!() } + async fn wait_for_htlc_tx_spend(&self, _args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { unimplemented!() } fn tx_enum_from_bytes(&self, _bytes: &[u8]) -> Result> { MmError::err(TxMarshalingErr::NotSupported( @@ -360,7 +381,7 @@ impl MarketCoinOps for SiaCoin { fn current_block(&self) -> Box + Send> { let http_client = self.0.http_client.clone(); // Clone the client - let height_fut = async move { http_client.get_height().await.map_err(|e| e.to_string()) } + let height_fut = async move { http_client.current_height().await.map_err(|e| e.to_string()) } .boxed() // Make the future 'static by boxing .compat(); // Convert to a futures 0.1-compatible future @@ -378,13 +399,23 @@ impl MarketCoinOps for SiaCoin { #[async_trait] impl SwapOps for SiaCoin { - fn send_taker_fee(&self, _fee_addr: &[u8], _dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut { + async fn send_taker_fee( + &self, + _fee_addr: &[u8], + _dex_fee: DexFee, + _uuid: &[u8], + _expire_at: u64, + ) -> TransactionResult { unimplemented!() } - fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } + async fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + unimplemented!() + } - fn send_taker_payment(&self, _taker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } + async fn send_taker_payment(&self, _taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + unimplemented!() + } async fn send_maker_spends_taker_payment( &self, @@ -414,7 +445,9 @@ impl SwapOps for SiaCoin { unimplemented!() } - fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { unimplemented!() } + async fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { + unimplemented!() + } async fn validate_maker_payment(&self, _input: ValidatePaymentInput) -> ValidatePaymentResult<()> { unimplemented!() @@ -424,10 +457,10 @@ impl SwapOps for SiaCoin { unimplemented!() } - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, - _if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { + _if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, + ) -> Result, String> { unimplemented!() } @@ -473,9 +506,7 @@ impl SwapOps for SiaCoin { fn derive_htlc_pubkey(&self, _swap_unique_data: &[u8]) -> Vec { unimplemented!() } - fn can_refund_htlc(&self, _locktime: u64) -> Box + Send + '_> { - unimplemented!() - } + async fn can_refund_htlc(&self, _locktime: u64) -> Result { unimplemented!() } fn validate_other_pubkey(&self, _raw_pubkey: &[u8]) -> MmResult<(), ValidateOtherPubKeyErr> { unimplemented!() } @@ -596,3 +627,29 @@ impl WatcherOps for SiaCoin { unimplemented!() } } + +#[cfg(test)] +mod tests { + use super::*; + use mm2_number::BigDecimal; + use std::str::FromStr; + + #[test] + fn test_siacoin_from_hastings() { + let hastings = u128::MAX; + let siacoin = siacoin_from_hastings(hastings); + assert_eq!( + siacoin, + BigDecimal::from_str("340282366920938.463463374607431768211455").unwrap() + ); + + let hastings = 0; + let siacoin = siacoin_from_hastings(hastings); + assert_eq!(siacoin, BigDecimal::from_str("0").unwrap()); + + // Total supply of Siacoin + let hastings = 57769875000000000000000000000000000; + let siacoin = siacoin_from_hastings(hastings); + assert_eq!(siacoin, BigDecimal::from_str("57769875000").unwrap()); + } +} diff --git a/mm2src/coins/siacoin/sia_hd_wallet.rs b/mm2src/coins/siacoin/sia_hd_wallet.rs new file mode 100644 index 0000000000..4c6a288ef5 --- /dev/null +++ b/mm2src/coins/siacoin/sia_hd_wallet.rs @@ -0,0 +1,44 @@ +use crate::hd_wallet::{HDAccount, HDAddress, HDWallet}; +use bip32::{ExtendedPublicKey, PrivateKeyBytes, PublicKey as bip32PublicKey, PublicKeyBytes, Result as bip32Result}; +use sia_rust::types::Address; +use sia_rust::PublicKey; + +pub struct SiaPublicKey(pub PublicKey); + +pub type SiaHDAddress = HDAddress; +pub type SiaHDAccount = HDAccount; +pub type SiaHDWallet = HDWallet; +pub type Ed25519ExtendedPublicKey = ExtendedPublicKey; + +impl bip32PublicKey for SiaPublicKey { + fn from_bytes(_bytes: PublicKeyBytes) -> bip32Result { + todo!() + //Ok(secp256k1_ffi::PublicKey::from_slice(&bytes)?) + } + + fn to_bytes(&self) -> PublicKeyBytes { + todo!() + // self.serialize() + } + + fn derive_child(&self, _other: PrivateKeyBytes) -> bip32Result { + todo!() + // use secp256k1_ffi::{Secp256k1, VerifyOnly}; + // let engine = Secp256k1::::verification_only(); + + // let mut child_key = *self; + // child_key + // .add_exp_assign(&engine, &other) + // .map_err(|_| Error::Crypto)?; + + // Ok(child_key) + } +} + +// coin type 1991 +// path component 0x800007c7 + +#[test] +fn test_something() { + println!("This is a test"); +} diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs deleted file mode 100644 index 2b9abef1ca..0000000000 --- a/mm2src/coins/solana.rs +++ /dev/null @@ -1,1098 +0,0 @@ -use std::{collections::HashMap, - convert::{TryFrom, TryInto}, - fmt::Debug, - ops::Deref, - str::FromStr, - sync::{Arc, Mutex}}; - -use async_trait::async_trait; -use base58::ToBase58; -use bincode::{deserialize, serialize}; -use bitcrypto::sha256; -use common::{async_blocking, - executor::{abortable_queue::AbortableQueue, AbortableSystem, AbortedError}, - log::error, - now_sec}; -use crypto::HDPathToCoin; -use derive_more::Display; -use futures::{compat::Future01CompatExt, - {FutureExt, TryFutureExt}}; -use futures01::Future; -use keys::KeyPair; -use mm2_core::mm_ctx::MmArc; -use mm2_err_handle::prelude::*; -use mm2_number::{BigDecimal, MmNumber}; -use num_traits::ToPrimitive; -use rpc::v1::types::Bytes as BytesJson; -pub use satomic_swap::{instruction::AtomicSwapInstruction, STORAGE_SPACE_ALLOCATED}; -use serde_json::{self as json, Value as Json}; -use solana_client::{client_error::{ClientError, ClientErrorKind}, - rpc_client::RpcClient, - rpc_request::TokenAccountsFilter}; -pub use solana_sdk::transaction::Transaction as SolTransaction; -use solana_sdk::{commitment_config::{CommitmentConfig, CommitmentLevel}, - instruction::{AccountMeta, Instruction}, - native_token::sol_to_lamports, - program_error::ProgramError, - pubkey::{ParsePubkeyError, Pubkey}, - signature::{Keypair as SolKeypair, Signer}}; -use spl_token::solana_program; - -use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, SwapOps, TradeFee, Transaction, TransactionEnum, - TransactionErr, WatcherOps}; -use crate::coin_errors::{MyAddressError, ValidatePaymentResult}; -use crate::hd_wallet::HDPathAccountToAddressId; -use crate::solana::{solana_common::{lamports_to_sol, PrepareTransferData, SufficientBalanceError}, - spl::SplTokenInfo}; -use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, DexFee, - FeeApproxStage, FoundSwapTxSpend, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, - PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, - PrivKeyPolicyNotAllowed, RawTransactionError, RawTransactionFut, RawTransactionRequest, - RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, - SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignRawTransactionRequest, SignatureResult, - SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, TradePreimageValue, - TransactionData, TransactionDetails, TransactionFut, TransactionResult, TransactionType, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, - ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherReward, WatcherRewardError, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, - WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; - -pub mod solana_common; -mod solana_decode_tx_helpers; -pub mod spl; - -#[cfg(test)] mod solana_common_tests; -#[cfg(test)] mod solana_tests; -#[cfg(test)] mod spl_tests; - -pub const SOLANA_DEFAULT_DECIMALS: u64 = 9; -pub const LAMPORTS_DUMMY_AMOUNT: u64 = 10; - -#[async_trait] -pub trait SolanaCommonOps { - fn rpc(&self) -> &RpcClient; - - fn is_token(&self) -> bool; - - async fn check_balance_and_prepare_transfer( - &self, - max: bool, - amount: BigDecimal, - fees: u64, - ) -> Result>; -} - -impl From for BalanceError { - fn from(e: ClientError) -> Self { - match e.kind { - ClientErrorKind::Io(e) => BalanceError::Transport(e.to_string()), - ClientErrorKind::Reqwest(e) => BalanceError::Transport(e.to_string()), - ClientErrorKind::RpcError(e) => BalanceError::Transport(format!("{:?}", e)), - ClientErrorKind::SerdeJson(e) => BalanceError::InvalidResponse(e.to_string()), - ClientErrorKind::Custom(e) => BalanceError::Internal(e), - ClientErrorKind::SigningError(_) - | ClientErrorKind::TransactionError(_) - | ClientErrorKind::FaucetError(_) => BalanceError::Internal("not_reacheable".to_string()), - } - } -} - -impl From for BalanceError { - fn from(e: ParsePubkeyError) -> Self { BalanceError::Internal(format!("{:?}", e)) } -} - -impl From for WithdrawError { - fn from(e: ClientError) -> Self { - match e.kind { - ClientErrorKind::Io(e) => WithdrawError::Transport(e.to_string()), - ClientErrorKind::Reqwest(e) => WithdrawError::Transport(e.to_string()), - ClientErrorKind::RpcError(e) => WithdrawError::Transport(format!("{:?}", e)), - ClientErrorKind::SerdeJson(e) => WithdrawError::InternalError(e.to_string()), - ClientErrorKind::Custom(e) => WithdrawError::InternalError(e), - ClientErrorKind::SigningError(_) - | ClientErrorKind::TransactionError(_) - | ClientErrorKind::FaucetError(_) => WithdrawError::InternalError("not_reacheable".to_string()), - } - } -} - -impl From for WithdrawError { - fn from(e: ParsePubkeyError) -> Self { WithdrawError::InvalidAddress(format!("{:?}", e)) } -} - -impl From for WithdrawError { - fn from(e: ProgramError) -> Self { WithdrawError::InternalError(format!("{:?}", e)) } -} - -#[derive(Debug)] -pub enum AccountError { - NotFundedError(String), - ParsePubKeyError(String), - ClientError(ClientErrorKind), -} - -impl From for AccountError { - fn from(e: ClientError) -> Self { AccountError::ClientError(e.kind) } -} - -impl From for AccountError { - fn from(e: ParsePubkeyError) -> Self { AccountError::ParsePubKeyError(format!("{:?}", e)) } -} - -impl From for WithdrawError { - fn from(e: AccountError) -> Self { - match e { - AccountError::NotFundedError(_) => WithdrawError::ZeroBalanceToWithdrawMax, - AccountError::ParsePubKeyError(err) => WithdrawError::InternalError(err), - AccountError::ClientError(e) => WithdrawError::Transport(format!("{:?}", e)), - } - } -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct SolanaActivationParams { - confirmation_commitment: CommitmentLevel, - client_url: String, - #[serde(default)] - path_to_address: HDPathAccountToAddressId, -} - -#[derive(Debug, Display)] -pub enum SolanaFromLegacyReqErr { - InvalidCommitmentLevel(String), - InvalidClientParsing(json::Error), - ClientNoAvailableNodes(String), -} - -#[derive(Debug, Display)] -pub enum KeyPairCreationError { - #[display(fmt = "Signature error: {}", _0)] - SignatureError(ed25519_dalek::SignatureError), - #[display(fmt = "KeyPairFromSeed error: {}", _0)] - KeyPairFromSeed(String), -} - -impl From for KeyPairCreationError { - fn from(e: ed25519_dalek::SignatureError) -> Self { KeyPairCreationError::SignatureError(e) } -} - -fn generate_keypair_from_slice(priv_key: &[u8]) -> Result> { - let secret_key = ed25519_dalek::SecretKey::from_bytes(priv_key)?; - let public_key = ed25519_dalek::PublicKey::from(&secret_key); - let key_pair = ed25519_dalek::Keypair { - secret: secret_key, - public: public_key, - }; - solana_sdk::signature::keypair_from_seed(key_pair.to_bytes().as_ref()) - .map_to_mm(|e| KeyPairCreationError::KeyPairFromSeed(e.to_string())) -} - -pub async fn solana_coin_with_policy( - ctx: &MmArc, - ticker: &str, - conf: &Json, - params: SolanaActivationParams, - priv_key_policy: PrivKeyBuildPolicy, -) -> Result { - let client = RpcClient::new_with_commitment(params.client_url.clone(), CommitmentConfig { - commitment: params.confirmation_commitment, - }); - let decimals = conf["decimals"].as_u64().unwrap_or(SOLANA_DEFAULT_DECIMALS) as u8; - - let priv_key = match priv_key_policy { - PrivKeyBuildPolicy::IguanaPrivKey(priv_key) => priv_key, - PrivKeyBuildPolicy::GlobalHDAccount(global_hd) => { - let path_to_coin: HDPathToCoin = try_s!(json::from_value(conf["derivation_path"].clone())); - let derivation_path = try_s!(params.path_to_address.to_derivation_path(&path_to_coin)); - try_s!(global_hd.derive_secp256k1_secret(&derivation_path)) - }, - PrivKeyBuildPolicy::Trezor => return ERR!("{}", PrivKeyPolicyNotAllowed::HardwareWalletNotSupported), - }; - - let key_pair = try_s!(generate_keypair_from_slice(priv_key.as_slice())); - let my_address = key_pair.pubkey().to_string(); - let spl_tokens_infos = Arc::new(Mutex::new(HashMap::new())); - - // Create an abortable system linked to the `MmCtx` so if the context is stopped via `MmArc::stop`, - // all spawned futures related to `SolanaCoin` will be aborted as well. - let abortable_system: AbortableQueue = try_s!(ctx.abortable_system.create_subsystem()); - - let solana_coin = SolanaCoin(Arc::new(SolanaCoinImpl { - my_address, - key_pair, - ticker: ticker.to_string(), - client, - decimals, - spl_tokens_infos, - abortable_system, - })); - Ok(solana_coin) -} - -/// pImpl idiom. -pub struct SolanaCoinImpl { - ticker: String, - key_pair: SolKeypair, - client: RpcClient, - decimals: u8, - my_address: String, - spl_tokens_infos: Arc>>, - /// This spawner is used to spawn coin's related futures that should be aborted on coin deactivation - /// and on [`MmArc::stop`]. - pub abortable_system: AbortableQueue, -} - -#[derive(Clone)] -pub struct SolanaCoin(Arc); -impl Deref for SolanaCoin { - type Target = SolanaCoinImpl; - fn deref(&self) -> &SolanaCoinImpl { &self.0 } -} - -#[async_trait] -impl SolanaCommonOps for SolanaCoin { - fn rpc(&self) -> &RpcClient { &self.client } - - fn is_token(&self) -> bool { false } - - async fn check_balance_and_prepare_transfer( - &self, - max: bool, - amount: BigDecimal, - fees: u64, - ) -> Result> { - solana_common::check_balance_and_prepare_transfer(self, max, amount, fees).await - } -} - -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -pub struct SolanaFeeDetails { - pub amount: BigDecimal, -} - -async fn withdraw_base_coin_impl(coin: SolanaCoin, req: WithdrawRequest) -> WithdrawResult { - let (hash, fees) = coin.estimate_withdraw_fees().await?; - let res = coin - .check_balance_and_prepare_transfer(req.max, req.amount.clone(), fees) - .await?; - let to = solana_sdk::pubkey::Pubkey::try_from(&*req.to)?; - let tx = solana_sdk::system_transaction::transfer(&coin.key_pair, &to, res.lamports_to_send, hash); - let serialized_tx = serialize(&tx).map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; - let total_amount = lamports_to_sol(res.lamports_to_send); - let received_by_me = if req.to == coin.my_address { - total_amount.clone() - } else { - 0.into() - }; - let spent_by_me = &total_amount + &res.sol_required; - Ok(TransactionDetails { - tx: TransactionData::new_signed(serialized_tx.into(), tx.signatures[0].to_string()), - from: vec![coin.my_address.clone()], - to: vec![req.to], - total_amount: spent_by_me.clone(), - my_balance_change: &received_by_me - &spent_by_me, - spent_by_me, - received_by_me, - block_height: 0, - timestamp: now_sec(), - fee_details: Some( - SolanaFeeDetails { - amount: res.sol_required, - } - .into(), - ), - coin: coin.ticker.clone(), - internal_id: vec![].into(), - kmd_rewards: None, - transaction_type: TransactionType::StandardTransfer, - memo: None, - }) -} - -async fn withdraw_impl(coin: SolanaCoin, req: WithdrawRequest) -> WithdrawResult { - let validate_address_result = coin.validate_address(&req.to); - if !validate_address_result.is_valid { - return MmError::err(WithdrawError::InvalidAddress( - validate_address_result.reason.unwrap_or_else(|| "Unknown".to_string()), - )); - } - withdraw_base_coin_impl(coin, req).await -} - -type SolTxFut = Box + Send + 'static>; - -impl Transaction for SolTransaction { - fn tx_hex(&self) -> Vec { - serialize(self).unwrap_or_else(|e| { - error!("Error serializing SolTransaction: {}", e); - vec![] - }) - } - - fn tx_hash_as_bytes(&self) -> BytesJson { - let hash = match self.signatures.get(0) { - Some(signature) => signature, - None => { - error!("No signature found in SolTransaction"); - return BytesJson(Vec::new()); - }, - }; - BytesJson(Vec::from(hash.as_ref())) - } -} - -impl SolanaCoin { - pub async fn estimate_withdraw_fees(&self) -> Result<(solana_sdk::hash::Hash, u64), MmError> { - let hash = async_blocking({ - let coin = self.clone(); - move || coin.rpc().get_latest_blockhash() - }) - .await?; - let to = self.key_pair.pubkey(); - - let tx = solana_sdk::system_transaction::transfer(&self.key_pair, &to, LAMPORTS_DUMMY_AMOUNT, hash); - let fees = async_blocking({ - let coin = self.clone(); - move || coin.rpc().get_fee_for_message(tx.message()) - }) - .await?; - Ok((hash, fees)) - } - - pub async fn my_balance_spl(&self, infos: SplTokenInfo) -> Result> { - let token_accounts = async_blocking({ - let coin = self.clone(); - move || { - coin.rpc().get_token_accounts_by_owner( - &coin.key_pair.pubkey(), - TokenAccountsFilter::Mint(infos.token_contract_address), - ) - } - }) - .await?; - if token_accounts.is_empty() { - return Ok(CoinBalance { - spendable: Default::default(), - unspendable: Default::default(), - }); - } - let actual_token_pubkey = - Pubkey::from_str(&token_accounts[0].pubkey).map_err(|e| BalanceError::Internal(format!("{:?}", e)))?; - let amount = async_blocking({ - let coin = self.clone(); - move || coin.rpc().get_token_account_balance(&actual_token_pubkey) - }) - .await?; - let balance = - BigDecimal::from_str(&amount.ui_amount_string).map_to_mm(|e| BalanceError::Internal(e.to_string()))?; - Ok(CoinBalance { - spendable: balance, - unspendable: Default::default(), - }) - } - - fn my_balance_impl(&self) -> BalanceFut { - let coin = self.clone(); - let fut = async_blocking(move || { - // this is blocking IO - let res = coin.rpc().get_balance(&coin.key_pair.pubkey())?; - Ok(lamports_to_sol(res)) - }); - Box::new(fut.boxed().compat()) - } - - pub fn add_spl_token_info(&self, ticker: String, info: SplTokenInfo) { - self.spl_tokens_infos.lock().unwrap().insert(ticker, info); - } - - /// WARNING - /// Be very careful using this function since it returns dereferenced clone - /// of value behind the MutexGuard and makes it non-thread-safe. - pub fn get_spl_tokens_infos(&self) -> HashMap { - let guard = self.spl_tokens_infos.lock().unwrap(); - (*guard).clone() - } - - fn send_hash_time_locked_payment(&self, args: SendPaymentArgs<'_>) -> SolTxFut { - let receiver = Pubkey::new(args.other_pubkey); - let swap_program_id = Pubkey::new(try_tx_fus_opt!( - args.swap_contract_address, - format!( - "Unable to extract Bytes from args.swap_contract_address ( {:?} )", - args.swap_contract_address - ) - )); - let amount = sol_to_lamports(try_tx_fus_opt!( - args.amount.to_f64(), - format!("Unable to extract value from args.amount ( {:?} )", args.amount) - )); - let secret_hash: [u8; 32] = try_tx_fus!(<[u8; 32]>::try_from(args.secret_hash)); - let (vault_pda, vault_pda_data, vault_bump_seed, vault_bump_seed_data, rent_exemption_lamports) = - try_tx_fus!(self.create_vaults(args.time_lock, secret_hash, swap_program_id, STORAGE_SPACE_ALLOCATED)); - let swap_instruction = AtomicSwapInstruction::LamportsPayment { - secret_hash, - lock_time: args.time_lock, - amount, - receiver, - rent_exemption_lamports, - vault_bump_seed, - vault_bump_seed_data, - }; - - let accounts = vec![ - AccountMeta::new(self.key_pair.pubkey(), true), - AccountMeta::new(vault_pda_data, false), - AccountMeta::new(vault_pda, false), - AccountMeta::new(solana_program::system_program::id(), false), - ]; - self.sign_and_send_swap_transaction_fut(swap_program_id, accounts, swap_instruction.pack()) - } - - fn spend_hash_time_locked_payment(&self, args: SpendPaymentArgs) -> SolTxFut { - let sender = Pubkey::new(args.other_pubkey); - let swap_program_id = Pubkey::new(try_tx_fus_opt!( - args.swap_contract_address.as_ref(), - format!( - "Unable to extract Bytes from args.swap_contract_address ( {:?} )", - args.swap_contract_address - ) - )); - let secret: [u8; 32] = try_tx_fus!(<[u8; 32]>::try_from(args.secret)); - let secret_hash = sha256(secret.as_slice()).take(); - let (lock_time, tx_secret_hash, amount, token_program) = - try_tx_fus!(self.get_swap_transaction_details(args.other_payment_tx)); - if secret_hash != tx_secret_hash { - try_tx_fus_err!(format!( - "Provided secret_hash {:?} does not match transaction secret_hash {:?}", - secret_hash, tx_secret_hash - )); - } - let (vault_pda, vault_pda_data, vault_bump_seed, vault_bump_seed_data, _rent_exemption_lamports) = - try_tx_fus!(self.create_vaults(lock_time, secret_hash, swap_program_id, STORAGE_SPACE_ALLOCATED)); - let swap_instruction = AtomicSwapInstruction::ReceiverSpend { - secret, - lock_time, - amount, - sender, - token_program, - vault_bump_seed, - vault_bump_seed_data, - }; - let accounts = vec![ - AccountMeta::new(self.key_pair.pubkey(), true), - AccountMeta::new(vault_pda_data, false), - AccountMeta::new(vault_pda, false), - AccountMeta::new(solana_program::system_program::id(), false), - ]; - self.sign_and_send_swap_transaction_fut(swap_program_id, accounts, swap_instruction.pack()) - } - - fn refund_hash_time_locked_payment(&self, args: RefundPaymentArgs) -> SolTxFut { - let receiver = Pubkey::new(args.other_pubkey); - let swap_program_id = Pubkey::new(try_tx_fus_opt!( - args.swap_contract_address.as_ref(), - format!( - "Unable to extract Bytes from args.swap_contract_address ( {:?} )", - args.swap_contract_address - ) - )); - let (lock_time, secret_hash, amount, token_program) = - try_tx_fus!(self.get_swap_transaction_details(args.payment_tx)); - let (vault_pda, vault_pda_data, vault_bump_seed, vault_bump_seed_data, _rent_exemption_lamports) = - try_tx_fus!(self.create_vaults(lock_time, secret_hash, swap_program_id, STORAGE_SPACE_ALLOCATED)); - let swap_instruction = AtomicSwapInstruction::SenderRefund { - secret_hash, - lock_time, - amount, - receiver, - token_program, - vault_bump_seed, - vault_bump_seed_data, - }; - let accounts = vec![ - AccountMeta::new(self.key_pair.pubkey(), true), // Marked as signer - AccountMeta::new(vault_pda_data, false), // Not a signer - AccountMeta::new(vault_pda, false), // Not a signer - AccountMeta::new(solana_program::system_program::id(), false), //system_program must be included - ]; - self.sign_and_send_swap_transaction_fut(swap_program_id, accounts, swap_instruction.pack()) - } - - fn get_swap_transaction_details(&self, tx_hex: &[u8]) -> Result<(u64, [u8; 32], u64, Pubkey), Box> { - let transaction: SolTransaction = deserialize(tx_hex) - .map_err(|e| Box::new(TransactionErr::Plain(ERRL!("error deserializing tx_hex: {:?}", e))))?; - - let instruction = transaction - .message - .instructions - .get(0) - .ok_or_else(|| Box::new(TransactionErr::Plain(ERRL!("Instruction not found in message"))))?; - - let instruction_data = &instruction.data[..]; - let instruction = AtomicSwapInstruction::unpack(instruction_data[0], instruction_data) - .map_err(|e| Box::new(TransactionErr::Plain(ERRL!("error unpacking tx data: {:?}", e))))?; - - match instruction { - AtomicSwapInstruction::LamportsPayment { - secret_hash, - lock_time, - amount, - .. - } => Ok((lock_time, secret_hash, amount, Pubkey::new_from_array([0; 32]))), - AtomicSwapInstruction::SPLTokenPayment { - secret_hash, - lock_time, - amount, - token_program, - .. - } => Ok((lock_time, secret_hash, amount, token_program)), - AtomicSwapInstruction::ReceiverSpend { - secret, - lock_time, - amount, - token_program, - .. - } => Ok((lock_time, sha256(&secret).take(), amount, token_program)), - AtomicSwapInstruction::SenderRefund { - secret_hash, - lock_time, - amount, - token_program, - .. - } => Ok((lock_time, secret_hash, amount, token_program)), - } - } - - fn sign_and_send_swap_transaction_fut( - &self, - program_id: Pubkey, - accounts: Vec, - data: Vec, - ) -> SolTxFut { - let coin = self.clone(); - Box::new( - async move { coin.sign_and_send_swap_transaction(program_id, accounts, data).await } - .boxed() - .compat(), - ) - } - - pub async fn sign_and_send_swap_transaction( - &self, - program_id: Pubkey, - accounts: Vec, - data: Vec, - ) -> Result { - // Construct the instruction to send to the program - // The parameters here depend on your specific program's requirements - let instruction = Instruction { - program_id, - accounts, // Specify account metas here - data, // Pass data to the program here - }; - - // Create a transaction - let recent_blockhash = self - .client - .get_latest_blockhash() - .map_err(|e| TransactionErr::Plain(format!("Failed to get recent blockhash: {:?}", e)))?; - - let transaction: SolTransaction = SolTransaction::new_signed_with_payer( - &[instruction], - Some(&self.key_pair.pubkey()), //payer pubkey - &[&self.key_pair], //payer - recent_blockhash, - ); - - // Send the transaction - let tx = self - .client - .send_and_confirm_transaction(&transaction) - .map(|_signature| transaction) - .map_err(|e| TransactionErr::Plain(ERRL!("Solana ClientError: {:?}", e)))?; - - Ok(tx) - } - - fn create_vaults( - &self, - lock_time: u64, - secret_hash: [u8; 32], - program_id: Pubkey, - space: u64, - ) -> Result<(Pubkey, Pubkey, u8, u8, u64), Box> { - let seeds: &[&[u8]] = &[b"swap", &lock_time.to_le_bytes()[..], &secret_hash[..]]; - let (vault_pda, bump_seed) = Pubkey::find_program_address(seeds, &program_id); - - let seeds_data: &[&[u8]] = &[b"swap_data", &lock_time.to_le_bytes()[..], &secret_hash[..]]; - let (vault_pda_data, bump_seed_data) = Pubkey::find_program_address(seeds_data, &program_id); - - let rent_exemption_lamports = self - .client - .get_minimum_balance_for_rent_exemption( - space - .try_into() - .map_err(|e| Box::new(TransactionErr::Plain(ERRL!("unable to convert space: {:?}", e))))?, - ) - .map_err(|e| { - Box::new(TransactionErr::Plain(ERRL!( - "error get_minimum_balance_for_rent_exemption: {:?}", - e - ))) - })?; - - Ok(( - vault_pda, - vault_pda_data, - bump_seed, - bump_seed_data, - rent_exemption_lamports, - )) - } -} - -#[async_trait] -impl MarketCoinOps for SolanaCoin { - fn ticker(&self) -> &str { &self.ticker } - - fn my_address(&self) -> MmResult { Ok(self.my_address.clone()) } - - async fn get_public_key(&self) -> Result> { - Ok(self.key_pair.pubkey().to_string()) - } - - fn sign_message_hash(&self, _message: &str) -> Option<[u8; 32]> { unimplemented!() } - - fn sign_message(&self, message: &str) -> SignatureResult { solana_common::sign_message(self, message) } - - fn verify_message(&self, signature: &str, message: &str, pubkey_bs58: &str) -> VerificationResult { - solana_common::verify_message(self, signature, message, pubkey_bs58) - } - - fn my_balance(&self) -> BalanceFut { - let decimals = self.decimals as u64; - let fut = self.my_balance_impl().and_then(move |result| { - Ok(CoinBalance { - spendable: result.with_prec(decimals), - unspendable: 0.into(), - }) - }); - Box::new(fut) - } - - fn base_coin_balance(&self) -> BalanceFut { - let decimals = self.decimals as u64; - let fut = self - .my_balance_impl() - .and_then(move |result| Ok(result.with_prec(decimals))); - Box::new(fut) - } - - fn platform_ticker(&self) -> &str { self.ticker() } - - fn send_raw_tx(&self, tx: &str) -> Box + Send> { - let coin = self.clone(); - let tx = tx.to_owned(); - let fut = async_blocking(move || { - let bytes = hex::decode(tx).map_to_mm(|e| e).map_err(|e| format!("{:?}", e))?; - let tx: SolTransaction = deserialize(bytes.as_slice()) - .map_to_mm(|e| e) - .map_err(|e| format!("{:?}", e))?; - // this is blocking IO - let signature = coin.rpc().send_transaction(&tx).map_err(|e| format!("{:?}", e))?; - Ok(signature.to_string()) - }); - Box::new(fut.boxed().compat()) - } - - fn send_raw_tx_bytes(&self, tx: &[u8]) -> Box + Send> { - let coin = self.clone(); - let tx = tx.to_owned(); - let fut = async_blocking(move || { - let tx = try_s!(deserialize(tx.as_slice())); - // this is blocking IO - let signature = coin.rpc().send_transaction(&tx).map_err(|e| format!("{:?}", e))?; - Ok(signature.to_string()) - }); - Box::new(fut.boxed().compat()) - } - - #[inline(always)] - async fn sign_raw_tx(&self, _args: &SignRawTransactionRequest) -> RawTransactionResult { - MmError::err(RawTransactionError::NotImplemented { - coin: self.ticker().to_string(), - }) - } - - fn wait_for_confirmations(&self, _input: ConfirmPaymentInput) -> Box + Send> { - unimplemented!() - } - - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { unimplemented!() } - - fn tx_enum_from_bytes(&self, _bytes: &[u8]) -> Result> { - MmError::err(TxMarshalingErr::NotSupported( - "tx_enum_from_bytes is not supported for Solana yet.".to_string(), - )) - } - - fn current_block(&self) -> Box + Send> { - let coin = self.clone(); - let fut = async_blocking(move || coin.rpc().get_block_height().map_err(|e| format!("{:?}", e))); - Box::new(fut.boxed().compat()) - } - - fn display_priv_key(&self) -> Result { Ok(self.key_pair.secret().to_bytes()[..].to_base58()) } - - fn min_tx_amount(&self) -> BigDecimal { BigDecimal::from(0) } - - fn min_trading_vol(&self) -> MmNumber { MmNumber::from("0.00777") } - - fn is_trezor(&self) -> bool { unimplemented!() } -} - -#[async_trait] -impl SwapOps for SolanaCoin { - fn send_taker_fee(&self, _fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut { - unimplemented!() - } - - fn send_maker_payment(&self, maker_payment: SendPaymentArgs) -> TransactionFut { - Box::new( - self.send_hash_time_locked_payment(maker_payment) - .map(TransactionEnum::from), - ) - } - - fn send_taker_payment(&self, taker_payment: SendPaymentArgs) -> TransactionFut { - Box::new( - self.send_hash_time_locked_payment(taker_payment) - .map(TransactionEnum::from), - ) - } - - async fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SpendPaymentArgs<'_>, - ) -> TransactionResult { - self.spend_hash_time_locked_payment(maker_spends_payment_args) - .compat() - .await - .map(TransactionEnum::from) - } - - async fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SpendPaymentArgs<'_>, - ) -> TransactionResult { - self.spend_hash_time_locked_payment(taker_spends_payment_args) - .compat() - .await - .map(TransactionEnum::from) - } - - async fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { - self.refund_hash_time_locked_payment(taker_refunds_payment_args) - .map(TransactionEnum::from) - .compat() - .await - } - - async fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionResult { - self.refund_hash_time_locked_payment(maker_refunds_payment_args) - .map(TransactionEnum::from) - .compat() - .await - } - - fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { unimplemented!() } - - async fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentResult<()> { - unimplemented!() - } - - async fn validate_taker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentResult<()> { - unimplemented!() - } - - fn check_if_my_payment_sent( - &self, - _if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { - unimplemented!() - } - - async fn search_for_swap_tx_spend_my( - &self, - _: SearchForSwapTxSpendInput<'_>, - ) -> Result, String> { - unimplemented!() - } - - async fn search_for_swap_tx_spend_other( - &self, - _: SearchForSwapTxSpendInput<'_>, - ) -> Result, String> { - unimplemented!() - } - - fn check_tx_signed_by_pub(&self, tx: &[u8], expected_pub: &[u8]) -> Result> { - unimplemented!(); - } - - async fn extract_secret( - &self, - secret_hash: &[u8], - spend_tx: &[u8], - watcher_reward: bool, - ) -> Result, String> { - unimplemented!() - } - - fn is_auto_refundable(&self) -> bool { false } - - async fn wait_for_htlc_refund(&self, _tx: &[u8], _locktime: u64) -> RefundResult<()> { - MmError::err(RefundError::Internal( - "wait_for_htlc_refund is not supported for this coin!".into(), - )) - } - - fn negotiate_swap_contract_addr( - &self, - _other_side_address: Option<&[u8]>, - ) -> Result, MmError> { - unimplemented!() - } - - #[inline] - fn derive_htlc_key_pair(&self, _swap_unique_data: &[u8]) -> KeyPair { todo!() } - - #[inline] - fn derive_htlc_pubkey(&self, swap_unique_data: &[u8]) -> Vec { - self.derive_htlc_key_pair(swap_unique_data).public_slice().to_vec() - } - - fn validate_other_pubkey(&self, _raw_pubkey: &[u8]) -> MmResult<(), ValidateOtherPubKeyErr> { unimplemented!() } - - async fn maker_payment_instructions( - &self, - _args: PaymentInstructionArgs<'_>, - ) -> Result>, MmError> { - unimplemented!() - } - - async fn taker_payment_instructions( - &self, - _args: PaymentInstructionArgs<'_>, - ) -> Result>, MmError> { - unimplemented!() - } - - fn validate_maker_payment_instructions( - &self, - _instructions: &[u8], - _args: PaymentInstructionArgs<'_>, - ) -> Result> { - unimplemented!() - } - - fn validate_taker_payment_instructions( - &self, - _instructions: &[u8], - _args: PaymentInstructionArgs<'_>, - ) -> Result> { - unimplemented!() - } -} - -#[async_trait] -impl TakerSwapMakerCoin for SolanaCoin { - async fn on_taker_payment_refund_start(&self, _maker_payment: &[u8]) -> RefundResult<()> { Ok(()) } - - async fn on_taker_payment_refund_success(&self, _maker_payment: &[u8]) -> RefundResult<()> { Ok(()) } -} - -#[async_trait] -impl MakerSwapTakerCoin for SolanaCoin { - async fn on_maker_payment_refund_start(&self, _taker_payment: &[u8]) -> RefundResult<()> { Ok(()) } - - async fn on_maker_payment_refund_success(&self, _taker_payment: &[u8]) -> RefundResult<()> { Ok(()) } -} - -#[async_trait] -impl WatcherOps for SolanaCoin { - fn create_maker_payment_spend_preimage( - &self, - _maker_payment_tx: &[u8], - _time_lock: u64, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_unique_data: &[u8], - ) -> TransactionFut { - unimplemented!(); - } - - fn send_maker_payment_spend_preimage(&self, _input: SendMakerPaymentSpendPreimageInput) -> TransactionFut { - unimplemented!(); - } - - fn create_taker_payment_refund_preimage( - &self, - _taker_payment_tx: &[u8], - _time_lock: u64, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_contract_address: &Option, - _swap_unique_data: &[u8], - ) -> TransactionFut { - unimplemented!(); - } - - fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { - unimplemented!(); - } - - fn watcher_validate_taker_fee(&self, input: WatcherValidateTakerFeeInput) -> ValidatePaymentFut<()> { - unimplemented!(); - } - - fn watcher_validate_taker_payment(&self, _input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { - unimplemented!(); - } - - fn taker_validates_payment_spend_or_refund(&self, _input: ValidateWatcherSpendInput) -> ValidatePaymentFut<()> { - unimplemented!() - } - - async fn watcher_search_for_swap_tx_spend( - &self, - input: WatcherSearchForSwapTxSpendInput<'_>, - ) -> Result, String> { - unimplemented!(); - } - - async fn get_taker_watcher_reward( - &self, - other_coin: &MmCoinEnum, - coin_amount: Option, - other_coin_amount: Option, - reward_amount: Option, - wait_until: u64, - ) -> Result> { - unimplemented!(); - } - - async fn get_maker_watcher_reward( - &self, - other_coin: &MmCoinEnum, - reward_amount: Option, - wait_until: u64, - ) -> Result, MmError> { - unimplemented!(); - } -} - -#[async_trait] -impl MmCoin for SolanaCoin { - fn is_asset_chain(&self) -> bool { false } - - fn spawner(&self) -> CoinFutSpawner { CoinFutSpawner::new(&self.abortable_system) } - - fn withdraw(&self, req: WithdrawRequest) -> WithdrawFut { - Box::new(Box::pin(withdraw_impl(self.clone(), req)).compat()) - } - - fn get_raw_transaction(&self, _req: RawTransactionRequest) -> RawTransactionFut { unimplemented!() } - - fn get_tx_hex_by_hash(&self, tx_hash: Vec) -> RawTransactionFut { unimplemented!() } - - fn decimals(&self) -> u8 { self.decimals } - - fn convert_to_address(&self, _from: &str, _to_address_format: Json) -> Result { unimplemented!() } - - fn validate_address(&self, address: &str) -> ValidateAddressResult { - if address.len() != 44 { - return ValidateAddressResult { - is_valid: false, - reason: Some("Invalid address length".to_string()), - }; - } - let result = Pubkey::try_from(address); - match result { - Ok(pubkey) => { - if pubkey.is_on_curve() { - ValidateAddressResult { - is_valid: true, - reason: None, - } - } else { - ValidateAddressResult { - is_valid: false, - reason: Some("not_on_curve".to_string()), - } - } - }, - Err(err) => ValidateAddressResult { - is_valid: false, - reason: Some(format!("{:?}", err)), - }, - } - } - - fn process_history_loop(&self, _ctx: MmArc) -> Box + Send> { 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!() } - - async fn get_sender_trade_fee( - &self, - _value: TradePreimageValue, - _stage: FeeApproxStage, - _include_refund_fee: bool, - ) -> TradePreimageResult { - unimplemented!() - } - - fn get_receiver_trade_fee(&self, _stage: FeeApproxStage) -> TradePreimageFut { unimplemented!() } - - async fn get_fee_to_send_taker_fee( - &self, - _dex_fee_amount: DexFee, - _stage: FeeApproxStage, - ) -> TradePreimageResult { - unimplemented!() - } - - fn required_confirmations(&self) -> u64 { 1 } - - fn requires_notarization(&self) -> bool { false } - - fn set_required_confirmations(&self, _confirmations: u64) { unimplemented!() } - - fn set_requires_notarization(&self, _requires_nota: bool) { unimplemented!() } - - fn swap_contract_address(&self) -> Option { unimplemented!() } - - fn fallback_swap_contract(&self) -> Option { unimplemented!() } - - fn mature_confirmations(&self) -> Option { None } - - fn coin_protocol_info(&self, _amount_to_receive: Option) -> Vec { Vec::new() } - - fn is_coin_protocol_supported( - &self, - _info: &Option>, - _amount_to_send: Option, - _locktime: u64, - _is_maker: bool, - ) -> bool { - true - } - - fn on_disabled(&self) -> Result<(), AbortedError> { AbortableSystem::abort_all(&self.abortable_system) } - - fn on_token_deactivated(&self, _ticker: &str) { unimplemented!() } -} diff --git a/mm2src/coins/solana/solana_common.rs b/mm2src/coins/solana/solana_common.rs deleted file mode 100644 index 3f74c1caff..0000000000 --- a/mm2src/coins/solana/solana_common.rs +++ /dev/null @@ -1,181 +0,0 @@ -use crate::solana::SolanaCommonOps; -use crate::{BalanceError, MarketCoinOps, NumConversError, SignatureError, SignatureResult, SolanaCoin, - UnexpectedDerivationMethod, VerificationError, VerificationResult, WithdrawError}; -use base58::FromBase58; -use derive_more::Display; -use futures::compat::Future01CompatExt; -use mm2_err_handle::prelude::*; -use mm2_number::bigdecimal::{BigDecimal, ToPrimitive}; -use solana_sdk::native_token::LAMPORTS_PER_SOL; -use solana_sdk::signature::{Signature, Signer}; -use std::str::FromStr; - -#[derive(Debug, Display)] -pub enum SufficientBalanceError { - #[display( - fmt = "Not enough {} to withdraw: available {}, required at least {}", - coin, - available, - required - )] - NotSufficientBalance { - coin: String, - available: BigDecimal, - required: BigDecimal, - }, - #[display(fmt = "The amount {} is too small, required at least {}", amount, threshold)] - AmountTooLow { amount: BigDecimal, threshold: BigDecimal }, - #[display(fmt = "{}", _0)] - UnexpectedDerivationMethod(UnexpectedDerivationMethod), - #[display(fmt = "Wallet storage error: {}", _0)] - WalletStorageError(String), - #[display(fmt = "Invalid response: {}", _0)] - InvalidResponse(String), - #[display(fmt = "Transport: {}", _0)] - Transport(String), - #[display(fmt = "Internal: {}", _0)] - Internal(String), -} - -impl From for SufficientBalanceError { - fn from(e: NumConversError) -> Self { SufficientBalanceError::Internal(e.to_string()) } -} - -impl From for SufficientBalanceError { - fn from(e: BalanceError) -> Self { - match e { - BalanceError::Transport(e) => SufficientBalanceError::Transport(e), - BalanceError::InvalidResponse(e) => SufficientBalanceError::InvalidResponse(e), - BalanceError::UnexpectedDerivationMethod(e) => SufficientBalanceError::UnexpectedDerivationMethod(e), - BalanceError::Internal(e) => SufficientBalanceError::Internal(e), - BalanceError::WalletStorageError(e) => SufficientBalanceError::WalletStorageError(e), - } - } -} -impl From for WithdrawError { - fn from(e: SufficientBalanceError) -> Self { - match e { - SufficientBalanceError::NotSufficientBalance { - coin, - available, - required, - } => WithdrawError::NotSufficientBalance { - coin, - available, - required, - }, - SufficientBalanceError::UnexpectedDerivationMethod(e) => WithdrawError::from(e), - SufficientBalanceError::InvalidResponse(e) | SufficientBalanceError::Transport(e) => { - WithdrawError::Transport(e) - }, - SufficientBalanceError::Internal(e) | SufficientBalanceError::WalletStorageError(e) => { - WithdrawError::InternalError(e) - }, - SufficientBalanceError::AmountTooLow { amount, threshold } => { - WithdrawError::AmountTooLow { amount, threshold } - }, - } - } -} - -pub struct PrepareTransferData { - pub to_send: BigDecimal, - pub my_balance: BigDecimal, - pub sol_required: BigDecimal, - pub lamports_to_send: u64, -} - -pub fn lamports_to_sol(lamports: u64) -> BigDecimal { BigDecimal::from(lamports) / BigDecimal::from(LAMPORTS_PER_SOL) } - -pub fn sol_to_lamports(sol: &BigDecimal) -> Result> { - let maybe_lamports = (sol * BigDecimal::from(LAMPORTS_PER_SOL)).to_u64(); - match maybe_lamports { - None => MmError::err(NumConversError("Error when converting sol to lamports".to_string())), - Some(lamports) => Ok(lamports), - } -} - -pub fn ui_amount_to_amount(ui_amount: BigDecimal, decimals: u8) -> Result> { - let maybe_amount = (ui_amount * BigDecimal::from(10_u64.pow(decimals as u32))).to_u64(); - match maybe_amount { - None => MmError::err(NumConversError("Error when converting ui amount to amount".to_string())), - Some(amount) => Ok(amount), - } -} - -pub fn amount_to_ui_amount(amount: u64, decimals: u8) -> BigDecimal { - BigDecimal::from(amount) / BigDecimal::from(10_u64.pow(decimals as u32)) -} - -pub fn sign_message(coin: &SolanaCoin, message: &str) -> SignatureResult { - let signature = coin - .key_pair - .try_sign_message(message.as_bytes()) - .map_err(|e| SignatureError::InternalError(e.to_string()))?; - Ok(signature.to_string()) -} - -pub fn verify_message( - coin: &SolanaCoin, - signature: &str, - message: &str, - pubkey_bs58: &str, -) -> VerificationResult { - let pubkey = pubkey_bs58.from_base58()?; - let signature = - Signature::from_str(signature).map_err(|e| VerificationError::SignatureDecodingError(e.to_string()))?; - let is_valid = signature.verify(&pubkey, message.as_bytes()); - Ok(is_valid) -} - -pub async fn check_balance_and_prepare_transfer( - coin: &T, - max: bool, - amount: BigDecimal, - fees: u64, -) -> Result> -where - T: SolanaCommonOps + MarketCoinOps, -{ - let base_balance = coin.base_coin_balance().compat().await?; - let sol_required = lamports_to_sol(fees); - if base_balance < sol_required { - return MmError::err(SufficientBalanceError::NotSufficientBalance { - coin: coin.platform_ticker().to_string(), - available: base_balance.clone(), - required: sol_required.clone(), - }); - } - - let my_balance = coin.my_balance().compat().await?.spendable; - let to_send = if max { my_balance.clone() } else { amount.clone() }; - let to_check = if max || coin.is_token() { - to_send.clone() - } else { - &to_send + &sol_required - }; - if to_check > my_balance { - return MmError::err(SufficientBalanceError::NotSufficientBalance { - coin: coin.ticker().to_string(), - available: my_balance, - required: to_check, - }); - } - - let lamports_to_send = if !coin.is_token() { - if max { - sol_to_lamports(&my_balance)? - sol_to_lamports(&sol_required)? - } else { - sol_to_lamports(&amount)? - } - } else { - 0_u64 - }; - - Ok(PrepareTransferData { - to_send, - my_balance, - sol_required, - lamports_to_send, - }) -} diff --git a/mm2src/coins/solana/solana_common_tests.rs b/mm2src/coins/solana/solana_common_tests.rs deleted file mode 100644 index 6bb5b833d1..0000000000 --- a/mm2src/coins/solana/solana_common_tests.rs +++ /dev/null @@ -1,96 +0,0 @@ -use super::*; -use crate::solana::spl::{SplToken, SplTokenFields}; -use crypto::privkey::{bip39_seed_from_passphrase, key_pair_from_seed}; -use ed25519_dalek_bip32::{DerivationPath, ExtendedSecretKey}; -use mm2_core::mm_ctx::MmCtxBuilder; -use solana_client::rpc_client::RpcClient; -use solana_sdk::commitment_config::{CommitmentConfig, CommitmentLevel}; -use std::str::FromStr; - -pub enum SolanaNet { - //Mainnet, - Testnet, - Devnet, -} - -pub fn solana_net_to_url(net_type: SolanaNet) -> String { - match net_type { - //SolanaNet::Mainnet => "https://api.mainnet-beta.solana.com".to_string(), - SolanaNet::Testnet => "https://api.testnet.solana.com/".to_string(), - SolanaNet::Devnet => "https://api.devnet.solana.com".to_string(), - } -} - -pub fn generate_key_pair_from_seed(seed: &str) -> SolKeypair { - let derivation_path = DerivationPath::from_str("m/44'/501'/0'").unwrap(); - let seed = bip39_seed_from_passphrase(seed).unwrap(); - - let ext = ExtendedSecretKey::from_seed(&seed.0) - .unwrap() - .derive(&derivation_path) - .unwrap(); - let pub_key = ext.public_key(); - let pair = ed25519_dalek::Keypair { - secret: ext.secret_key, - public: pub_key, - }; - - solana_sdk::signature::keypair_from_seed(pair.to_bytes().as_ref()).unwrap() -} - -pub fn generate_key_pair_from_iguana_seed(seed: String) -> SolKeypair { - let key_pair = key_pair_from_seed(seed.as_str()).unwrap(); - let secret_key = ed25519_dalek::SecretKey::from_bytes(key_pair.private().secret.as_slice()).unwrap(); - let public_key = ed25519_dalek::PublicKey::from(&secret_key); - let other_key_pair = ed25519_dalek::Keypair { - secret: secret_key, - public: public_key, - }; - solana_sdk::signature::keypair_from_seed(other_key_pair.to_bytes().as_ref()).unwrap() -} - -pub fn spl_coin_for_test( - solana_coin: SolanaCoin, - ticker: String, - decimals: u8, - token_contract_address: Pubkey, -) -> SplToken { - SplToken { - conf: Arc::new(SplTokenFields { - decimals, - ticker, - token_contract_address, - abortable_system: AbortableQueue::default(), - }), - platform_coin: solana_coin, - } -} - -pub fn solana_coin_for_test(seed: String, net_type: SolanaNet) -> (MmArc, SolanaCoin) { - let url = solana_net_to_url(net_type); - let client = RpcClient::new_with_commitment(url, CommitmentConfig { - commitment: CommitmentLevel::Finalized, - }); - let conf = json!({ - "coins":[ - {"coin":"SOL","name":"solana","protocol":{"type":"SOL"},"rpcport":80,"mm2":1} - ] - }); - let ctx = MmCtxBuilder::new().with_conf(conf).into_mm_arc(); - let (ticker, decimals) = ("SOL".to_string(), 8); - let key_pair = generate_key_pair_from_iguana_seed(seed); - let my_address = key_pair.pubkey().to_string(); - let spl_tokens_infos = Arc::new(Mutex::new(HashMap::new())); - let spawner = AbortableQueue::default(); - - let solana_coin = SolanaCoin(Arc::new(SolanaCoinImpl { - decimals, - my_address, - key_pair, - ticker, - client, - spl_tokens_infos, - abortable_system: spawner, - })); - (ctx, solana_coin) -} diff --git a/mm2src/coins/solana/solana_decode_tx_helpers.rs b/mm2src/coins/solana/solana_decode_tx_helpers.rs deleted file mode 100644 index bd22fc044e..0000000000 --- a/mm2src/coins/solana/solana_decode_tx_helpers.rs +++ /dev/null @@ -1,222 +0,0 @@ -extern crate serde_derive; - -use crate::{NumConversResult, SolanaCoin, SolanaFeeDetails, TransactionData, TransactionDetails, TransactionType}; -use mm2_number::BigDecimal; -use solana_sdk::native_token::lamports_to_sol; -use std::convert::TryFrom; - -#[derive(Debug, Serialize, Deserialize)] -pub struct SolanaConfirmedTransaction { - slot: u64, - transaction: Transaction, - meta: Meta, - #[serde(rename = "blockTime")] - block_time: u64, -} - -#[allow(dead_code)] -impl SolanaConfirmedTransaction { - pub fn extract_account_index(&self, address: String) -> usize { - // find the equivalent of index_of(needle) in rust, and return result later - let mut idx = 0_usize; - for account in self.transaction.message.account_keys.iter() { - if account.pubkey == address { - return idx; - } - idx += 1; - } - idx - } - - pub fn extract_solana_transactions(&self, solana_coin: &SolanaCoin) -> NumConversResult> { - let mut transactions = Vec::new(); - let account_idx = self.extract_account_index(solana_coin.my_address.clone()); - for instruction in self.transaction.message.instructions.iter() { - if instruction.is_solana_transfer() { - let lamports = instruction.parsed.info.lamports.unwrap_or_default(); - let amount = BigDecimal::try_from(lamports_to_sol(lamports))?; - let is_self_transfer = instruction.parsed.info.source == instruction.parsed.info.destination; - let am_i_sender = instruction.parsed.info.source == solana_coin.my_address; - let spent_by_me = if am_i_sender && !is_self_transfer { - amount.clone() - } else { - 0.into() - }; - let received_by_me = if is_self_transfer { amount.clone() } else { 0.into() }; - let my_balance_change = if am_i_sender { - BigDecimal::try_from(lamports_to_sol( - self.meta.pre_balances[account_idx] - self.meta.post_balances[account_idx], - ))? - } else { - BigDecimal::try_from(lamports_to_sol( - self.meta.post_balances[account_idx] - self.meta.pre_balances[account_idx], - ))? - }; - let fee = BigDecimal::try_from(lamports_to_sol(self.meta.fee))?; - let tx = TransactionDetails { - tx: TransactionData::new_signed(Default::default(), self.transaction.signatures[0].to_string()), - from: vec![instruction.parsed.info.source.clone()], - to: vec![instruction.parsed.info.destination.clone()], - total_amount: amount, - spent_by_me, - received_by_me, - my_balance_change, - block_height: self.slot, - timestamp: self.block_time, - fee_details: Some(SolanaFeeDetails { amount: fee }.into()), - coin: solana_coin.ticker.clone(), - internal_id: Default::default(), - kmd_rewards: None, - transaction_type: TransactionType::StandardTransfer, - memo: None, - }; - transactions.push(tx); - } - } - Ok(transactions) - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Meta { - err: Option, - status: Status, - fee: u64, - #[serde(rename = "preBalances")] - pre_balances: Vec, - #[serde(rename = "postBalances")] - post_balances: Vec, - #[serde(rename = "innerInstructions")] - inner_instructions: Vec>, - #[serde(rename = "logMessages")] - log_messages: Vec, - #[serde(rename = "preTokenBalances")] - pre_token_balances: Vec, - #[serde(rename = "postTokenBalances")] - post_token_balances: Vec, - rewards: Vec>, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct TokenBalance { - #[serde(rename = "accountIndex")] - account_index: u64, - mint: String, - #[serde(rename = "uiTokenAmount")] - ui_token_amount: TokenAmount, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct TokenAmount { - #[serde(rename = "uiAmount")] - ui_amount: f64, - decimals: u64, - amount: String, - #[serde(rename = "uiAmountString")] - ui_amount_string: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Status { - #[serde(rename = "Ok")] - ok: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Transaction { - signatures: Vec, - message: Message, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Message { - #[serde(rename = "accountKeys")] - account_keys: Vec, - #[serde(rename = "recentBlockhash")] - recent_blockhash: String, - instructions: Vec, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct AccountKey { - pubkey: String, - writable: bool, - signer: bool, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Instruction { - program: Program, - #[serde(rename = "programId")] - program_id: String, - parsed: Parsed, -} - -#[allow(dead_code)] -impl Instruction { - pub fn is_solana_transfer(&self) -> bool { - let is_system = match self.program { - Program::SplToken => return false, - Program::System => true, - }; - let is_transfer = match self.parsed.parsed_type { - Type::Transfer => true, - Type::TransferChecked => true, - Type::Unknown => false, - }; - is_system && is_transfer && self.parsed.info.lamports.is_some() - } - - // Will be used later - pub fn is_spl_transfer(&self) -> bool { - let is_spl_token = match self.program { - Program::SplToken => true, - Program::System => return false, - }; - let is_transfer = match self.parsed.parsed_type { - Type::Transfer => true, - Type::TransferChecked => true, - Type::Unknown => false, - }; - is_spl_token && is_transfer - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Parsed { - info: Info, - #[serde(rename = "type")] - parsed_type: Type, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Info { - destination: String, - lamports: Option, - source: String, - mint: Option, - #[serde(rename = "multisigAuthority")] - multisig_authority: Option, - signers: Option>, - #[serde(rename = "tokenAmount")] - token_amount: Option, - authority: Option, - amount: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum Type { - #[serde(rename = "transfer")] - Transfer, - #[serde(rename = "transferChecked")] - TransferChecked, - Unknown, -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum Program { - #[serde(rename = "spl-token")] - SplToken, - #[serde(rename = "system")] - System, -} diff --git a/mm2src/coins/solana/solana_tests.rs b/mm2src/coins/solana/solana_tests.rs deleted file mode 100644 index fe2b104293..0000000000 --- a/mm2src/coins/solana/solana_tests.rs +++ /dev/null @@ -1,436 +0,0 @@ -use base58::ToBase58; -use common::{block_on, Future01CompatExt}; -use rpc::v1::types::Bytes; -use solana_client::rpc_request::TokenAccountsFilter; -use solana_sdk::{bs58, - signature::{Signature, Signer}}; -use solana_transaction_status::UiTransactionEncoding; -use std::{ops::Neg, str::FromStr}; - -use super::solana_common_tests::{generate_key_pair_from_iguana_seed, generate_key_pair_from_seed, - solana_coin_for_test, SolanaNet}; -use super::solana_decode_tx_helpers::SolanaConfirmedTransaction; -use super::*; -use crate::{MarketCoinOps, SwapTxTypeWithSecretHash}; - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn solana_keypair_from_secp() { - let solana_key_pair = generate_key_pair_from_iguana_seed("federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string()); - assert_eq!( - "FJktmyjV9aBHEShT4hfnLpr9ELywdwVtEL1w1rSWgbVf", - solana_key_pair.pubkey().to_string() - ); - - let other_solana_keypair = generate_key_pair_from_iguana_seed("bob passphrase".to_string()); - assert_eq!( - "B7KMMHyc3eYguUMneXRznY1NWh91HoVA2muVJetstYKE", - other_solana_keypair.pubkey().to_string() - ); -} - -// Research tests -// TODO remove `ignore` attribute once the test is stable. -#[test] -#[ignore] -#[cfg(not(target_arch = "wasm32"))] -fn solana_prerequisites() { - // same test as trustwallet - { - let fin = - generate_key_pair_from_seed("hood vacant left trim hard mushroom device flavor ask better arrest again"); - let public_address = fin.pubkey().to_string(); - let priv_key = &fin.secret().to_bytes()[..].to_base58(); - assert_eq!(public_address.len(), 44); - assert_eq!(public_address, "4rmosKwMH7zeaXGbej1PFybZBUyuUNQLf8RfyzCcYvkx"); - assert_eq!(priv_key, "CZtxt17aTfDrJrzwBWdVqcmFwVVptW8EX7RRnth9tT3M"); - let client = solana_client::rpc_client::RpcClient::new("https://api.testnet.solana.com/".to_string()); - let balance = client.get_balance(&fin.pubkey()).expect("Expect to retrieve balance"); - assert_eq!(balance, 0); - } - - { - let key_pair = generate_key_pair_from_iguana_seed("passphrase not really secure".to_string()); - let public_address = key_pair.pubkey().to_string(); - assert_eq!(public_address.len(), 44); - assert_eq!(public_address, "2jTgfhf98GosnKSCXjL5YSiEa3MLrmR42yy9kZZq1i2c"); - let client = solana_client::rpc_client::RpcClient::new("https://api.testnet.solana.com/".to_string()); - let balance = client - .get_balance(&key_pair.pubkey()) - .expect("Expect to retrieve balance"); - assert_eq!(lamports_to_sol(balance), BigDecimal::from(0)); - assert_eq!(balance, 0); - - // This will fetch all the balance from all tokens - let token_accounts = client - .get_token_accounts_by_owner(&key_pair.pubkey(), TokenAccountsFilter::ProgramId(spl_token::id())) - .expect(""); - log!("{:?}", token_accounts); - let actual_token_pubkey = solana_sdk::pubkey::Pubkey::from_str(token_accounts[0].pubkey.as_str()).unwrap(); - let amount = client.get_token_account_balance(&actual_token_pubkey).unwrap(); - assert_ne!(amount.ui_amount_string.as_str(), "0"); - } -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn solana_coin_creation() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - assert_eq!( - sol_coin.my_address().unwrap(), - "FJktmyjV9aBHEShT4hfnLpr9ELywdwVtEL1w1rSWgbVf" - ); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn solana_my_balance() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - let res = block_on(sol_coin.my_balance().compat()).unwrap(); - assert_ne!(res.spendable, BigDecimal::from(0)); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn solana_block_height() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - let res = block_on(sol_coin.current_block().compat()).unwrap(); - log!("block is : {}", res); - assert!(res > 0); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn solana_validate_address() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - - // invalid len - let res = sol_coin.validate_address("invalidaddressobviously"); - assert!(!res.is_valid); - let res = sol_coin.validate_address("GMtMFbuVgjDnzsBd3LLBfM4X8RyYcDGCM92tPq2PG6B2"); - assert!(res.is_valid); - - // Typo - let res = sol_coin.validate_address("Fr8fraJXAe1cFU81mF7NhHTrUzXjZAJkQE1gUQ11riH"); - assert!(!res.is_valid); - - // invalid len - let res = sol_coin.validate_address("r8fraJXAe1cFU81mF7NhHTrUzXjZAJkQE1gUQ11riHn"); - assert!(!res.is_valid); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn test_sign_message() { - let passphrase = "spice describe gravity federal blast come thank unfair canal monkey style afraid".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - let signature = sol_coin.sign_message("test").unwrap(); - assert_eq!( - signature, - "4dzKwEteN8nch76zPMEjPX19RsaQwGTxsbtfg2bwGTkGenLfrdm31zvn9GH5rvaJBwivp6ESXx1KYR672ngs3UfF" - ); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn test_verify_message() { - let passphrase = "spice describe gravity federal blast come thank unfair canal monkey style afraid".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - let is_valid = sol_coin - .verify_message( - "4dzKwEteN8nch76zPMEjPX19RsaQwGTxsbtfg2bwGTkGenLfrdm31zvn9GH5rvaJBwivp6ESXx1KYR672ngs3UfF", - "test", - "8UF6jSVE1jW8mSiGqt8Hft1rLwPjdKLaTfhkNozFwoAG", - ) - .unwrap(); - assert!(is_valid); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn solana_transaction_simulations() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Devnet); - let request_amount = BigDecimal::try_from(0.0001).unwrap(); - let valid_tx_details = block_on( - sol_coin - .withdraw(WithdrawRequest { - coin: "SOL".to_string(), - from: None, - to: sol_coin.my_address.clone(), - amount: request_amount.clone(), - max: false, - fee: None, - memo: None, - ibc_source_channel: None, - }) - .compat(), - ) - .unwrap(); - let (_, fees) = block_on(sol_coin.estimate_withdraw_fees()).unwrap(); - let sol_required = lamports_to_sol(fees); - let expected_spent_by_me = &request_amount + &sol_required; - assert_eq!(valid_tx_details.spent_by_me, expected_spent_by_me); - assert_eq!(valid_tx_details.received_by_me, request_amount); - assert_eq!(valid_tx_details.total_amount, expected_spent_by_me); - assert_eq!(valid_tx_details.my_balance_change, sol_required.neg()); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn solana_transaction_zero_balance() { - let passphrase = "fake passphrase".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Devnet); - let invalid_tx_details = block_on( - sol_coin - .withdraw(WithdrawRequest { - coin: "SOL".to_string(), - from: None, - to: sol_coin.my_address.clone(), - amount: BigDecimal::from_str("0.000001").unwrap(), - max: false, - fee: None, - memo: None, - ibc_source_channel: None, - }) - .compat(), - ); - let error = invalid_tx_details.unwrap_err(); - let (_, fees) = block_on(sol_coin.estimate_withdraw_fees()).unwrap(); - let sol_required = lamports_to_sol(fees); - match error.into_inner() { - WithdrawError::NotSufficientBalance { required, .. } => { - assert_eq!(required, sol_required); - }, - e => panic!("Unexpected err {:?}", e), - }; -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn solana_transaction_simulations_not_enough_for_fees() { - let passphrase = "non existent passphrase".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Devnet); - let invalid_tx_details = block_on( - sol_coin - .withdraw(WithdrawRequest { - coin: "SOL".to_string(), - from: None, - to: sol_coin.my_address.clone(), - amount: BigDecimal::from(1), - max: false, - fee: None, - memo: None, - ibc_source_channel: None, - }) - .compat(), - ); - let error = invalid_tx_details.unwrap_err(); - let (_, fees) = block_on(sol_coin.estimate_withdraw_fees()).unwrap(); - let sol_required = lamports_to_sol(fees); - match error.into_inner() { - WithdrawError::NotSufficientBalance { - coin: _, - available, - required, - } => { - assert_eq!(available, 0.into()); - assert_eq!(required, sol_required); - }, - e => panic!("Unexpected err {:?}", e), - }; -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn solana_transaction_simulations_max() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Devnet); - let valid_tx_details = block_on( - sol_coin - .withdraw(WithdrawRequest { - coin: "SOL".to_string(), - from: None, - to: sol_coin.my_address.clone(), - amount: BigDecimal::from(0), - max: true, - fee: None, - memo: None, - ibc_source_channel: None, - }) - .compat(), - ) - .unwrap(); - let balance = block_on(sol_coin.my_balance().compat()).unwrap().spendable; - let (_, fees) = block_on(sol_coin.estimate_withdraw_fees()).unwrap(); - let sol_required = lamports_to_sol(fees); - assert_eq!(valid_tx_details.my_balance_change, sol_required.clone().neg()); - assert_eq!(valid_tx_details.total_amount, balance); - assert_eq!(valid_tx_details.spent_by_me, balance); - assert_eq!(valid_tx_details.received_by_me, &balance - &sol_required); - log!("{:?}", valid_tx_details); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn solana_test_transactions() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Devnet); - let valid_tx_details = block_on( - sol_coin - .withdraw(WithdrawRequest { - coin: "SOL".to_string(), - from: None, - to: sol_coin.my_address.clone(), - amount: BigDecimal::try_from(0.0001).unwrap(), - max: false, - fee: None, - memo: None, - ibc_source_channel: None, - }) - .compat(), - ) - .unwrap(); - log!("{:?}", valid_tx_details); - - let tx_str = hex::encode(&*valid_tx_details.tx.tx_hex().unwrap().0); - let res = block_on(sol_coin.send_raw_tx(&tx_str).compat()).unwrap(); - - let res2 = block_on( - sol_coin - .send_raw_tx_bytes(&valid_tx_details.tx.tx_hex().unwrap().0) - .compat(), - ) - .unwrap(); - assert_eq!(res, res2); - - //log!("{:?}", res); -} - -// This test is just a unit test for brainstorming around tx_history for base_coin. -#[test] -#[ignore] -#[cfg(not(target_arch = "wasm32"))] -fn solana_test_tx_history() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - let res = sol_coin - .client - .get_signatures_for_address(&sol_coin.key_pair.pubkey()) - .unwrap(); - let mut history = Vec::new(); - for cur in res.iter() { - let signature = Signature::from_str(cur.signature.clone().as_str()).unwrap(); - let res = sol_coin - .client - .get_transaction(&signature, UiTransactionEncoding::JsonParsed) - .unwrap(); - log!("{}", serde_json::to_string(&res).unwrap()); - let parsed = serde_json::to_value(&res).unwrap(); - let tx_infos: SolanaConfirmedTransaction = serde_json::from_value(parsed).unwrap(); - let mut txs = tx_infos.extract_solana_transactions(&sol_coin).unwrap(); - history.append(&mut txs); - } - log!("{}", serde_json::to_string(&history).unwrap()); -} - -#[test] -fn solana_coin_send_and_refund_maker_payment() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, coin) = solana_coin_for_test(passphrase, SolanaNet::Devnet); - let solana_program_id = "3fystoi7pB1cnDEbRRpSjFJA4fG3W2vQQZ21jSrBc11B"; - let solana_program_id = bs58::decode(solana_program_id).into_vec().unwrap_or_else(|e| { - log!("Failed to decode program ID: {}", e); - Vec::new() - }); - - let pk_data = [1; 32]; - let time_lock = now_sec() - 3600; - let taker_pub = coin.key_pair.pubkey().to_string(); - let taker_pub = Pubkey::from_str(taker_pub.as_str()).unwrap(); - let secret = [0; 32]; - let secret_hash = sha256(&secret); - - let args = SendPaymentArgs { - time_lock_duration: 0, - time_lock, - other_pubkey: taker_pub.as_ref(), - secret_hash: secret_hash.as_slice(), - amount: "0.01".parse().unwrap(), - swap_contract_address: &Some(Bytes::from(solana_program_id.clone())), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: None, - wait_for_confirmation_until: 0, - }; - let tx = coin.send_maker_payment(args).wait().unwrap(); - log!("swap tx {:?}", tx); - - let refund_args = RefundPaymentArgs { - payment_tx: &tx.tx_hex(), - time_lock, - other_pubkey: taker_pub.as_ref(), - tx_type_with_secret_hash: SwapTxTypeWithSecretHash::TakerOrMakerPayment { - maker_secret_hash: secret_hash.as_slice(), - }, - swap_contract_address: &Some(Bytes::from(solana_program_id)), - swap_unique_data: pk_data.as_slice(), - watcher_reward: false, - }; - let refund_tx = block_on(coin.send_maker_refunds_payment(refund_args)).unwrap(); - log!("refund tx {:?}", refund_tx); -} - -#[test] -fn solana_coin_send_and_spend_maker_payment() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, coin) = solana_coin_for_test(passphrase, SolanaNet::Devnet); - let solana_program_id = "3fystoi7pB1cnDEbRRpSjFJA4fG3W2vQQZ21jSrBc11B"; - let solana_program_id = bs58::decode(solana_program_id).into_vec().unwrap_or_else(|e| { - log!("Failed to decode program ID: {}", e); - Vec::new() - }); - - let pk_data = [1; 32]; - let lock_time = now_sec() - 1000; - let taker_pub = coin.key_pair.pubkey().to_string(); - let taker_pub = Pubkey::from_str(taker_pub.as_str()).unwrap(); - let secret = [0; 32]; - let secret_hash = sha256(&secret); - - let maker_payment_args = SendPaymentArgs { - time_lock_duration: 0, - time_lock: lock_time, - other_pubkey: taker_pub.as_ref(), - secret_hash: secret_hash.as_slice(), - amount: "0.01".parse().unwrap(), - swap_contract_address: &Some(Bytes::from(solana_program_id.clone())), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: None, - wait_for_confirmation_until: 0, - }; - - let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); - log!("swap tx {:?}", tx); - - let maker_pub = taker_pub; - - let spends_payment_args = SpendPaymentArgs { - other_payment_tx: &tx.tx_hex(), - time_lock: lock_time, - other_pubkey: maker_pub.as_ref(), - secret: &secret, - secret_hash: secret_hash.as_slice(), - swap_contract_address: &Some(Bytes::from(solana_program_id)), - swap_unique_data: pk_data.as_slice(), - watcher_reward: false, - }; - - let spend_tx = block_on(coin.send_taker_spends_maker_payment(spends_payment_args)).unwrap(); - log!("spend tx {}", hex::encode(spend_tx.tx_hash_as_bytes().0)); -} diff --git a/mm2src/coins/solana/spl.rs b/mm2src/coins/solana/spl.rs deleted file mode 100644 index 7bc720e5e7..0000000000 --- a/mm2src/coins/solana/spl.rs +++ /dev/null @@ -1,600 +0,0 @@ -use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, SwapOps, TradeFee, TransactionEnum, WatcherOps}; -use crate::coin_errors::{MyAddressError, ValidatePaymentResult}; -use crate::solana::solana_common::{ui_amount_to_amount, PrepareTransferData, SufficientBalanceError}; -use crate::solana::{solana_common, AccountError, SolanaCommonOps, SolanaFeeDetails}; -use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, DexFee, FeeApproxStage, - FoundSwapTxSpend, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, - PaymentInstructions, PaymentInstructionsErr, RawTransactionError, RawTransactionFut, - RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, RefundResult, - SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignRawTransactionRequest, - SignatureResult, SolanaCoin, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, - TradePreimageValue, TransactionData, TransactionDetails, TransactionFut, TransactionResult, - TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, - WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; -use async_trait::async_trait; -use bincode::serialize; -use common::executor::{abortable_queue::AbortableQueue, AbortableSystem, AbortedError}; -use common::{async_blocking, now_sec}; -use futures::{FutureExt, TryFutureExt}; -use futures01::Future; -use keys::KeyPair; -use mm2_core::mm_ctx::MmArc; -use mm2_err_handle::prelude::*; -use mm2_number::{BigDecimal, MmNumber}; -use rpc::v1::types::Bytes as BytesJson; -use serde_json::Value as Json; -use solana_client::{rpc_client::RpcClient, rpc_request::TokenAccountsFilter}; -use solana_sdk::message::Message; -use solana_sdk::transaction::Transaction; -use solana_sdk::{pubkey::Pubkey, signature::Signer}; -use spl_associated_token_account::{create_associated_token_account, get_associated_token_address}; -use std::{convert::TryFrom, - fmt::{Debug, Formatter, Result as FmtResult}, - str::FromStr, - sync::Arc}; - -#[derive(Debug)] -pub enum SplTokenCreationError { - InvalidPubkey(String), - Internal(String), -} - -impl From for SplTokenCreationError { - fn from(e: AbortedError) -> Self { SplTokenCreationError::Internal(e.to_string()) } -} - -pub struct SplTokenFields { - pub decimals: u8, - pub ticker: String, - pub token_contract_address: Pubkey, - pub abortable_system: AbortableQueue, -} - -#[derive(Clone, Debug)] -pub struct SplTokenInfo { - pub token_contract_address: Pubkey, - pub decimals: u8, -} - -#[derive(Debug)] -pub struct SplProtocolConf { - pub platform_coin_ticker: String, - pub decimals: u8, - pub token_contract_address: String, -} - -#[derive(Clone)] -pub struct SplToken { - pub conf: Arc, - pub platform_coin: SolanaCoin, -} - -impl Debug for SplToken { - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { f.write_str(&self.conf.ticker) } -} - -impl SplToken { - pub fn new( - decimals: u8, - ticker: String, - token_address: String, - platform_coin: SolanaCoin, - ) -> MmResult { - let token_contract_address = solana_sdk::pubkey::Pubkey::from_str(&token_address) - .map_err(|e| MmError::new(SplTokenCreationError::InvalidPubkey(format!("{:?}", e))))?; - - // Create an abortable system linked to the `platform_coin` so if the platform coin is disabled, - // all spawned futures related to `SplToken` will be aborted as well. - let abortable_system = platform_coin.abortable_system.create_subsystem()?; - - let conf = Arc::new(SplTokenFields { - decimals, - ticker, - token_contract_address, - abortable_system, - }); - Ok(SplToken { conf, platform_coin }) - } - - pub fn get_info(&self) -> SplTokenInfo { - SplTokenInfo { - token_contract_address: self.conf.token_contract_address, - decimals: self.decimals(), - } - } -} - -async fn withdraw_spl_token_impl(coin: SplToken, req: WithdrawRequest) -> WithdrawResult { - let (hash, fees) = coin.platform_coin.estimate_withdraw_fees().await?; - let res = coin - .check_balance_and_prepare_transfer(req.max, req.amount.clone(), fees) - .await?; - let system_destination_pubkey = solana_sdk::pubkey::Pubkey::try_from(&*req.to)?; - let contract_key = coin.get_underlying_contract_pubkey(); - let auth_key = coin.platform_coin.key_pair.pubkey(); - let funding_address = coin.get_pubkey().await?; - let dest_token_address = get_associated_token_address(&system_destination_pubkey, &contract_key); - let mut instructions = Vec::with_capacity(1); - let account_info = async_blocking({ - let coin = coin.clone(); - move || coin.rpc().get_account(&dest_token_address) - }) - .await; - if account_info.is_err() { - let instruction_creation = create_associated_token_account(&auth_key, &dest_token_address, &contract_key); - instructions.push(instruction_creation); - } - let amount = ui_amount_to_amount(req.amount, coin.conf.decimals)?; - let instruction_transfer_checked = spl_token::instruction::transfer_checked( - &spl_token::id(), - &funding_address, - &contract_key, - &dest_token_address, - &auth_key, - &[&auth_key], - amount, - coin.conf.decimals, - )?; - instructions.push(instruction_transfer_checked); - let msg = Message::new(&instructions, Some(&auth_key)); - let signers = vec![&coin.platform_coin.key_pair]; - let tx = Transaction::new(&signers, msg, hash); - let serialized_tx = serialize(&tx).map_to_mm(|e| WithdrawError::InternalError(e.to_string()))?; - let received_by_me = if req.to == coin.platform_coin.my_address { - res.to_send.clone() - } else { - 0.into() - }; - Ok(TransactionDetails { - tx: TransactionData::new_signed(serialized_tx.into(), tx.signatures[0].to_string()), - from: vec![coin.platform_coin.my_address.clone()], - to: vec![req.to], - total_amount: res.to_send.clone(), - spent_by_me: res.to_send.clone(), - my_balance_change: &received_by_me - &res.to_send, - received_by_me, - block_height: 0, - timestamp: now_sec(), - fee_details: Some( - SolanaFeeDetails { - amount: res.sol_required, - } - .into(), - ), - coin: coin.conf.ticker.clone(), - internal_id: vec![].into(), - kmd_rewards: None, - transaction_type: TransactionType::StandardTransfer, - memo: None, - }) -} - -async fn withdraw_impl(coin: SplToken, req: WithdrawRequest) -> WithdrawResult { - let validate_address_result = coin.validate_address(&req.to); - if !validate_address_result.is_valid { - return MmError::err(WithdrawError::InvalidAddress( - validate_address_result.reason.unwrap_or_else(|| "Unknown".to_string()), - )); - } - withdraw_spl_token_impl(coin, req).await -} - -#[async_trait] -impl SolanaCommonOps for SplToken { - fn rpc(&self) -> &RpcClient { &self.platform_coin.client } - - fn is_token(&self) -> bool { true } - - async fn check_balance_and_prepare_transfer( - &self, - max: bool, - amount: BigDecimal, - fees: u64, - ) -> Result> { - solana_common::check_balance_and_prepare_transfer(self, max, amount, fees).await - } -} - -impl SplToken { - fn get_underlying_contract_pubkey(&self) -> Pubkey { self.conf.token_contract_address } - - async fn get_pubkey(&self) -> Result> { - let coin = self.clone(); - let token_accounts = async_blocking(move || { - coin.rpc().get_token_accounts_by_owner( - &coin.platform_coin.key_pair.pubkey(), - TokenAccountsFilter::Mint(coin.get_underlying_contract_pubkey()), - ) - }) - .await?; - if token_accounts.is_empty() { - return MmError::err(AccountError::NotFundedError("account_not_funded".to_string())); - } - Ok(Pubkey::from_str(&token_accounts[0].pubkey)?) - } - - fn my_balance_impl(&self) -> BalanceFut { - let coin = self.clone(); - let fut = async move { - coin.platform_coin - .my_balance_spl(SplTokenInfo { - token_contract_address: coin.conf.token_contract_address, - decimals: coin.conf.decimals, - }) - .await - }; - Box::new(fut.boxed().compat()) - } -} - -#[async_trait] -impl MarketCoinOps for SplToken { - fn ticker(&self) -> &str { &self.conf.ticker } - - fn my_address(&self) -> MmResult { Ok(self.platform_coin.my_address.clone()) } - - async fn get_public_key(&self) -> Result> { unimplemented!() } - - fn sign_message_hash(&self, _message: &str) -> Option<[u8; 32]> { unimplemented!() } - - fn sign_message(&self, message: &str) -> SignatureResult { - solana_common::sign_message(&self.platform_coin, message) - } - - fn verify_message(&self, signature: &str, message: &str, pubkey_bs58: &str) -> VerificationResult { - solana_common::verify_message(&self.platform_coin, signature, message, pubkey_bs58) - } - - fn my_balance(&self) -> BalanceFut { - let fut = self.my_balance_impl().and_then(Ok); - Box::new(fut) - } - - fn base_coin_balance(&self) -> BalanceFut { self.platform_coin.base_coin_balance() } - - fn platform_ticker(&self) -> &str { self.platform_coin.ticker() } - - #[inline(always)] - fn send_raw_tx(&self, tx: &str) -> Box + Send> { - self.platform_coin.send_raw_tx(tx) - } - - #[inline(always)] - fn send_raw_tx_bytes(&self, tx: &[u8]) -> Box + Send> { - self.platform_coin.send_raw_tx_bytes(tx) - } - - #[inline(always)] - async fn sign_raw_tx(&self, _args: &SignRawTransactionRequest) -> RawTransactionResult { - MmError::err(RawTransactionError::NotImplemented { - coin: self.ticker().to_string(), - }) - } - - fn wait_for_confirmations(&self, _input: ConfirmPaymentInput) -> Box + Send> { - unimplemented!() - } - - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { unimplemented!() } - - fn tx_enum_from_bytes(&self, _bytes: &[u8]) -> Result> { - MmError::err(TxMarshalingErr::NotSupported( - "tx_enum_from_bytes is not supported for Spl yet.".to_string(), - )) - } - - fn current_block(&self) -> Box + Send> { self.platform_coin.current_block() } - - fn display_priv_key(&self) -> Result { self.platform_coin.display_priv_key() } - - fn min_tx_amount(&self) -> BigDecimal { BigDecimal::from(0) } - - fn min_trading_vol(&self) -> MmNumber { MmNumber::from("0.00777") } - - fn is_trezor(&self) -> bool { self.platform_coin.is_trezor() } -} - -#[async_trait] -impl SwapOps for SplToken { - fn send_taker_fee(&self, _fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut { - unimplemented!() - } - - fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } - - fn send_taker_payment(&self, _taker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } - - async fn send_maker_spends_taker_payment( - &self, - _maker_spends_payment_args: SpendPaymentArgs<'_>, - ) -> TransactionResult { - unimplemented!() - } - - async fn send_taker_spends_maker_payment( - &self, - _taker_spends_payment_args: SpendPaymentArgs<'_>, - ) -> TransactionResult { - unimplemented!() - } - - async fn send_taker_refunds_payment( - &self, - _taker_refunds_payment_args: RefundPaymentArgs<'_>, - ) -> TransactionResult { - unimplemented!() - } - - async fn send_maker_refunds_payment( - &self, - _maker_refunds_payment_args: RefundPaymentArgs<'_>, - ) -> TransactionResult { - todo!() - } - - fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { unimplemented!() } - - async fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentResult<()> { - unimplemented!() - } - - async fn validate_taker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentResult<()> { - unimplemented!() - } - - fn check_if_my_payment_sent( - &self, - _if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { - unimplemented!() - } - - async fn search_for_swap_tx_spend_my( - &self, - _: SearchForSwapTxSpendInput<'_>, - ) -> Result, String> { - unimplemented!() - } - - async fn search_for_swap_tx_spend_other( - &self, - _: SearchForSwapTxSpendInput<'_>, - ) -> Result, String> { - unimplemented!() - } - - fn check_tx_signed_by_pub(&self, tx: &[u8], expected_pub: &[u8]) -> Result> { - unimplemented!(); - } - - async fn extract_secret( - &self, - secret_hash: &[u8], - spend_tx: &[u8], - watcher_reward: bool, - ) -> Result, String> { - unimplemented!() - } - - fn is_auto_refundable(&self) -> bool { false } - - async fn wait_for_htlc_refund(&self, _tx: &[u8], _locktime: u64) -> RefundResult<()> { - MmError::err(RefundError::Internal( - "wait_for_htlc_refund is not supported for this coin!".into(), - )) - } - - fn negotiate_swap_contract_addr( - &self, - _other_side_address: Option<&[u8]>, - ) -> Result, MmError> { - unimplemented!() - } - - #[inline] - fn derive_htlc_key_pair(&self, _swap_unique_data: &[u8]) -> KeyPair { todo!() } - - #[inline] - fn derive_htlc_pubkey(&self, swap_unique_data: &[u8]) -> Vec { - self.derive_htlc_key_pair(swap_unique_data).public_slice().to_vec() - } - - fn validate_other_pubkey(&self, _raw_pubkey: &[u8]) -> MmResult<(), ValidateOtherPubKeyErr> { unimplemented!() } - - async fn maker_payment_instructions( - &self, - _args: PaymentInstructionArgs<'_>, - ) -> Result>, MmError> { - unimplemented!() - } - - async fn taker_payment_instructions( - &self, - _args: PaymentInstructionArgs<'_>, - ) -> Result>, MmError> { - unimplemented!() - } - - fn validate_maker_payment_instructions( - &self, - _instructions: &[u8], - _args: PaymentInstructionArgs<'_>, - ) -> Result> { - unimplemented!() - } - - fn validate_taker_payment_instructions( - &self, - _instructions: &[u8], - _args: PaymentInstructionArgs<'_>, - ) -> Result> { - unimplemented!() - } -} - -#[async_trait] -impl TakerSwapMakerCoin for SplToken { - async fn on_taker_payment_refund_start(&self, _maker_payment: &[u8]) -> RefundResult<()> { Ok(()) } - - async fn on_taker_payment_refund_success(&self, _maker_payment: &[u8]) -> RefundResult<()> { Ok(()) } -} - -#[async_trait] -impl MakerSwapTakerCoin for SplToken { - async fn on_maker_payment_refund_start(&self, _taker_payment: &[u8]) -> RefundResult<()> { Ok(()) } - - async fn on_maker_payment_refund_success(&self, _taker_payment: &[u8]) -> RefundResult<()> { Ok(()) } -} - -#[async_trait] -impl WatcherOps for SplToken { - fn send_maker_payment_spend_preimage(&self, _input: SendMakerPaymentSpendPreimageInput) -> TransactionFut { - unimplemented!(); - } - - fn create_taker_payment_refund_preimage( - &self, - _taker_payment_tx: &[u8], - _time_lock: u64, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_contract_address: &Option, - _swap_unique_data: &[u8], - ) -> TransactionFut { - unimplemented!(); - } - - fn create_maker_payment_spend_preimage( - &self, - _maker_payment_tx: &[u8], - _time_lock: u64, - _maker_pub: &[u8], - _secret_hash: &[u8], - _swap_unique_data: &[u8], - ) -> TransactionFut { - unimplemented!(); - } - - fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { - unimplemented!(); - } - - fn watcher_validate_taker_fee(&self, _input: WatcherValidateTakerFeeInput) -> ValidatePaymentFut<()> { - unimplemented!(); - } - - fn watcher_validate_taker_payment(&self, _input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { - unimplemented!(); - } - - fn taker_validates_payment_spend_or_refund(&self, _input: ValidateWatcherSpendInput) -> ValidatePaymentFut<()> { - unimplemented!() - } - - async fn watcher_search_for_swap_tx_spend( - &self, - input: WatcherSearchForSwapTxSpendInput<'_>, - ) -> Result, String> { - unimplemented!(); - } - - async fn get_taker_watcher_reward( - &self, - other_coin: &MmCoinEnum, - coin_amount: Option, - other_coin_amount: Option, - reward_amount: Option, - wait_until: u64, - ) -> Result> { - unimplemented!(); - } - - async fn get_maker_watcher_reward( - &self, - other_coin: &MmCoinEnum, - reward_amount: Option, - wait_until: u64, - ) -> Result, MmError> { - unimplemented!(); - } -} - -#[async_trait] -impl MmCoin for SplToken { - fn is_asset_chain(&self) -> bool { false } - - fn spawner(&self) -> CoinFutSpawner { CoinFutSpawner::new(&self.conf.abortable_system) } - - fn withdraw(&self, req: WithdrawRequest) -> WithdrawFut { - Box::new(Box::pin(withdraw_impl(self.clone(), req)).compat()) - } - - fn get_raw_transaction(&self, _req: RawTransactionRequest) -> RawTransactionFut { unimplemented!() } - - fn get_tx_hex_by_hash(&self, tx_hash: Vec) -> RawTransactionFut { unimplemented!() } - - fn decimals(&self) -> u8 { self.conf.decimals } - - fn convert_to_address(&self, _from: &str, _to_address_format: Json) -> Result { unimplemented!() } - - fn validate_address(&self, address: &str) -> ValidateAddressResult { self.platform_coin.validate_address(address) } - - fn process_history_loop(&self, _ctx: MmArc) -> Box + Send> { 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!() } - - async fn get_sender_trade_fee( - &self, - _value: TradePreimageValue, - _stage: FeeApproxStage, - _include_refund_fee: bool, - ) -> TradePreimageResult { - unimplemented!() - } - - fn get_receiver_trade_fee(&self, _stage: FeeApproxStage) -> TradePreimageFut { unimplemented!() } - - async fn get_fee_to_send_taker_fee( - &self, - _dex_fee_amount: DexFee, - _stage: FeeApproxStage, - ) -> TradePreimageResult { - unimplemented!() - } - - fn required_confirmations(&self) -> u64 { 1 } - - fn requires_notarization(&self) -> bool { false } - - fn set_required_confirmations(&self, _confirmations: u64) { unimplemented!() } - - fn set_requires_notarization(&self, _requires_nota: bool) { unimplemented!() } - - fn swap_contract_address(&self) -> Option { unimplemented!() } - - fn fallback_swap_contract(&self) -> Option { unimplemented!() } - - fn mature_confirmations(&self) -> Option { Some(1) } - - fn coin_protocol_info(&self, _amount_to_receive: Option) -> Vec { Vec::new() } - - fn is_coin_protocol_supported( - &self, - _info: &Option>, - _amount_to_send: Option, - _locktime: u64, - _is_maker: bool, - ) -> bool { - true - } - - fn on_disabled(&self) -> Result<(), AbortedError> { self.conf.abortable_system.abort_all() } - - fn on_token_deactivated(&self, _ticker: &str) {} -} diff --git a/mm2src/coins/solana/spl_tests.rs b/mm2src/coins/solana/spl_tests.rs deleted file mode 100644 index 10943e6e33..0000000000 --- a/mm2src/coins/solana/spl_tests.rs +++ /dev/null @@ -1,138 +0,0 @@ -use super::*; -use crate::{solana::solana_common_tests::solana_coin_for_test, - solana::solana_common_tests::{spl_coin_for_test, SolanaNet}}; -use common::{block_on, Future01CompatExt}; -use std::ops::Neg; -use std::str::FromStr; - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn spl_coin_creation() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - let sol_spl_usdc_coin = spl_coin_for_test( - sol_coin, - "USDC".to_string(), - 6, - solana_sdk::pubkey::Pubkey::from_str("CpMah17kQEL2wqyMKt3mZBdTnZbkbfx4nqmQMFDP5vwp").unwrap(), - ); - - log!("address: {}", sol_spl_usdc_coin.my_address().unwrap()); - assert_eq!( - sol_spl_usdc_coin.my_address().unwrap(), - "FJktmyjV9aBHEShT4hfnLpr9ELywdwVtEL1w1rSWgbVf" - ); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn test_sign_message() { - let passphrase = "spice describe gravity federal blast come thank unfair canal monkey style afraid".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - let sol_spl_usdc_coin = spl_coin_for_test( - sol_coin, - "USDC".to_string(), - 6, - solana_sdk::pubkey::Pubkey::from_str("CpMah17kQEL2wqyMKt3mZBdTnZbkbfx4nqmQMFDP5vwp").unwrap(), - ); - let signature = sol_spl_usdc_coin.sign_message("test").unwrap(); - assert_eq!( - signature, - "4dzKwEteN8nch76zPMEjPX19RsaQwGTxsbtfg2bwGTkGenLfrdm31zvn9GH5rvaJBwivp6ESXx1KYR672ngs3UfF" - ); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn test_verify_message() { - let passphrase = "spice describe gravity federal blast come thank unfair canal monkey style afraid".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - let sol_spl_usdc_coin = spl_coin_for_test( - sol_coin, - "USDC".to_string(), - 6, - solana_sdk::pubkey::Pubkey::from_str("CpMah17kQEL2wqyMKt3mZBdTnZbkbfx4nqmQMFDP5vwp").unwrap(), - ); - let is_valid = sol_spl_usdc_coin - .verify_message( - "4dzKwEteN8nch76zPMEjPX19RsaQwGTxsbtfg2bwGTkGenLfrdm31zvn9GH5rvaJBwivp6ESXx1KYR672ngs3UfF", - "test", - "8UF6jSVE1jW8mSiGqt8Hft1rLwPjdKLaTfhkNozFwoAG", - ) - .unwrap(); - assert!(is_valid); -} - -#[test] -#[cfg(not(target_arch = "wasm32"))] -fn spl_my_balance() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - let sol_spl_usdc_coin = spl_coin_for_test( - sol_coin.clone(), - "USDC".to_string(), - 6, - solana_sdk::pubkey::Pubkey::from_str("CpMah17kQEL2wqyMKt3mZBdTnZbkbfx4nqmQMFDP5vwp").unwrap(), - ); - - let res = block_on(sol_spl_usdc_coin.my_balance().compat()).unwrap(); - assert_ne!(res.spendable, BigDecimal::from(0)); - assert!(res.spendable < BigDecimal::from(10)); - - let sol_spl_wsol_coin = spl_coin_for_test( - sol_coin, - "WSOL".to_string(), - 8, - solana_sdk::pubkey::Pubkey::from_str("So11111111111111111111111111111111111111112").unwrap(), - ); - let res = block_on(sol_spl_wsol_coin.my_balance().compat()).unwrap(); - assert_eq!(res.spendable, BigDecimal::from(0)); -} - -// Stop ignoring when Solana is released -#[test] -#[ignore] -#[cfg(not(target_arch = "wasm32"))] -fn test_spl_transactions() { - let passphrase = "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron".to_string(); - let (_, sol_coin) = solana_coin_for_test(passphrase, SolanaNet::Testnet); - let usdc_sol_coin = spl_coin_for_test( - sol_coin, - "USDC".to_string(), - 6, - solana_sdk::pubkey::Pubkey::from_str("CpMah17kQEL2wqyMKt3mZBdTnZbkbfx4nqmQMFDP5vwp").unwrap(), - ); - let withdraw_amount = BigDecimal::from_str("0.0001").unwrap(); - let valid_tx_details = block_on( - usdc_sol_coin - .withdraw(WithdrawRequest { - coin: "USDC".to_string(), - from: None, - to: "AYJmtzc9D4KU6xsDzhKShFyYKUNXY622j9QoQEo4LfpX".to_string(), - amount: withdraw_amount.clone(), - max: false, - fee: None, - memo: None, - ibc_source_channel: None, - }) - .compat(), - ) - .unwrap(); - log!("{:?}", valid_tx_details); - assert_eq!(valid_tx_details.total_amount, withdraw_amount); - assert_eq!(valid_tx_details.my_balance_change, withdraw_amount.neg()); - assert_eq!(valid_tx_details.coin, "USDC".to_string()); - assert_ne!(valid_tx_details.timestamp, 0); - - let tx_str = hex::encode(&*valid_tx_details.tx.tx_hex().unwrap().0); - let res = block_on(usdc_sol_coin.send_raw_tx(&tx_str).compat()).unwrap(); - log!("{:?}", res); - - let res2 = block_on( - usdc_sol_coin - .send_raw_tx_bytes(&valid_tx_details.tx.tx_hex().unwrap().0) - .compat(), - ) - .unwrap(); - assert_eq!(res, res2); -} diff --git a/mm2src/coins/tendermint/htlc/iris/htlc.rs b/mm2src/coins/tendermint/htlc/iris/htlc.rs index 09561a4048..59cfb5cd73 100644 --- a/mm2src/coins/tendermint/htlc/iris/htlc.rs +++ b/mm2src/coins/tendermint/htlc/iris/htlc.rs @@ -1,12 +1,9 @@ use super::htlc_proto::{IrisClaimHtlcProto, IrisCreateHtlcProto}; -use cosmrs::proto::traits::TypeUrl; +use cosmrs::proto::traits::Name; use cosmrs::{tx::Msg, AccountId, Coin, ErrorReport}; use std::convert::TryFrom; -pub(crate) const IRIS_CREATE_HTLC_TYPE_URL: &str = "/irismod.htlc.MsgCreateHTLC"; -pub(crate) const IRIS_CLAIM_HTLC_TYPE_URL: &str = "/irismod.htlc.MsgClaimHTLC"; - #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct IrisCreateHtlcMsg { /// Sender's address. @@ -87,8 +84,9 @@ impl From<&IrisCreateHtlcMsg> for IrisCreateHtlcProto { } } -impl TypeUrl for IrisCreateHtlcProto { - const TYPE_URL: &'static str = IRIS_CREATE_HTLC_TYPE_URL; +impl Name for IrisCreateHtlcProto { + const NAME: &'static str = "MsgCreateHTLC"; + const PACKAGE: &'static str = "irismod.htlc"; } #[derive(Clone)] @@ -141,6 +139,7 @@ impl From<&IrisClaimHtlcMsg> for IrisClaimHtlcProto { } } -impl TypeUrl for IrisClaimHtlcProto { - const TYPE_URL: &'static str = IRIS_CLAIM_HTLC_TYPE_URL; +impl Name for IrisClaimHtlcProto { + const NAME: &'static str = "MsgClaimHTLC"; + const PACKAGE: &'static str = "irismod.htlc"; } diff --git a/mm2src/coins/tendermint/htlc/nucleus/htlc.rs b/mm2src/coins/tendermint/htlc/nucleus/htlc.rs index a768f7fd4a..2c423285bf 100644 --- a/mm2src/coins/tendermint/htlc/nucleus/htlc.rs +++ b/mm2src/coins/tendermint/htlc/nucleus/htlc.rs @@ -1,12 +1,9 @@ use super::htlc_proto::{NucleusClaimHtlcProto, NucleusCreateHtlcProto}; -use cosmrs::proto::traits::TypeUrl; +use cosmrs::proto::traits::Name; use cosmrs::{tx::Msg, AccountId, Coin, ErrorReport}; use std::convert::TryFrom; -pub(crate) const NUCLEUS_CREATE_HTLC_TYPE_URL: &str = "/nucleus.htlc.MsgCreateHTLC"; -pub(crate) const NUCLEUS_CLAIM_HTLC_TYPE_URL: &str = "/nucleus.htlc.MsgClaimHTLC"; - #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) struct NucleusCreateHtlcMsg { /// Sender's address. @@ -72,8 +69,9 @@ impl From<&NucleusCreateHtlcMsg> for NucleusCreateHtlcProto { } } -impl TypeUrl for NucleusCreateHtlcProto { - const TYPE_URL: &'static str = NUCLEUS_CREATE_HTLC_TYPE_URL; +impl Name for NucleusCreateHtlcProto { + const NAME: &'static str = "MsgCreateHTLC"; + const PACKAGE: &'static str = "nucleus.htlc"; } #[derive(Clone)] @@ -126,6 +124,7 @@ impl From<&NucleusClaimHtlcMsg> for NucleusClaimHtlcProto { } } -impl TypeUrl for NucleusClaimHtlcProto { - const TYPE_URL: &'static str = NUCLEUS_CLAIM_HTLC_TYPE_URL; +impl Name for NucleusClaimHtlcProto { + const NAME: &'static str = "MsgClaimHTLC"; + const PACKAGE: &'static str = "nucleus.htlc"; } diff --git a/mm2src/coins/tendermint/ibc/mod.rs b/mm2src/coins/tendermint/ibc/mod.rs index 51df375ab1..9e1c905398 100644 --- a/mm2src/coins/tendermint/ibc/mod.rs +++ b/mm2src/coins/tendermint/ibc/mod.rs @@ -1,7 +1,6 @@ mod ibc_proto; pub(crate) mod transfer_v1; -pub(crate) const IBC_TRANSFER_TYPE_URL: &str = "/ibc.applications.transfer.v1.MsgTransfer"; pub(crate) const IBC_OUT_SOURCE_PORT: &str = "transfer"; pub(crate) const IBC_OUT_TIMEOUT_IN_NANOS: u64 = 60000000000 * 15; // 15 minutes pub(crate) const IBC_GAS_LIMIT_DEFAULT: u64 = 150_000; diff --git a/mm2src/coins/tendermint/ibc/transfer_v1.rs b/mm2src/coins/tendermint/ibc/transfer_v1.rs index c5780e32b7..22bad1fefe 100644 --- a/mm2src/coins/tendermint/ibc/transfer_v1.rs +++ b/mm2src/coins/tendermint/ibc/transfer_v1.rs @@ -1,6 +1,5 @@ use super::{ibc_proto::IBCTransferV1Proto, IBC_OUT_SOURCE_PORT, IBC_OUT_TIMEOUT_IN_NANOS}; -use crate::tendermint::ibc::IBC_TRANSFER_TYPE_URL; -use cosmrs::proto::traits::TypeUrl; +use cosmrs::proto::traits::Name; use cosmrs::{tx::Msg, AccountId, Coin, ErrorReport}; use std::convert::TryFrom; @@ -99,6 +98,7 @@ impl From<&MsgTransfer> for IBCTransferV1Proto { } } -impl TypeUrl for IBCTransferV1Proto { - const TYPE_URL: &'static str = IBC_TRANSFER_TYPE_URL; +impl Name for IBCTransferV1Proto { + const NAME: &'static str = "MsgTransfer"; + const PACKAGE: &'static str = "ibc.applications.transfer.v1"; } diff --git a/mm2src/coins/tendermint/rpc/tendermint_native_rpc.rs b/mm2src/coins/tendermint/rpc/tendermint_native_rpc.rs index 27ae5e7a0e..5da40d622d 100644 --- a/mm2src/coins/tendermint/rpc/tendermint_native_rpc.rs +++ b/mm2src/coins/tendermint/rpc/tendermint_native_rpc.rs @@ -5,6 +5,8 @@ use cosmrs::tendermint::block::Height; use cosmrs::tendermint::evidence::Evidence; use cosmrs::tendermint::Genesis; use cosmrs::tendermint::Hash; +use http::Uri; +use mm2_p2p::Keypair; use serde::{de::DeserializeOwned, Serialize}; use std::fmt; use std::time::Duration; @@ -292,22 +294,25 @@ pub struct HttpClient { impl HttpClient { /// Construct a new Tendermint RPC HTTP/S client connecting to the given /// URL. - pub fn new(url: U) -> Result + pub fn new(url: U, proxy_sign_keypair: Option) -> Result where U: TryInto, { let url = url.try_into()?; Ok(Self { inner: if url.0.is_secure() { - sealed::HttpClient::new_https(url.try_into()?) + sealed::HttpClient::new_https(url.try_into()?, proxy_sign_keypair) } else { - sealed::HttpClient::new_http(url.try_into()?) + sealed::HttpClient::new_http(url.try_into()?, proxy_sign_keypair) }, }) } #[inline] - pub fn uri(&self) -> http::Uri { self.inner.uri() } + pub fn uri(&self) -> Uri { self.inner.uri() } + + #[inline] + pub fn proxy_sign_keypair(&self) -> &Option { self.inner.proxy_sign_keypair() } } #[async_trait] @@ -370,11 +375,15 @@ impl TryFrom for hyper::Uri { mod sealed { use common::log::debug; + use common::X_AUTH_PAYLOAD; + use http::HeaderValue; use hyper::body::Buf; use hyper::client::connect::Connect; use hyper::client::HttpConnector; use hyper::{header, Uri}; use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder}; + use mm2_p2p::Keypair; + use proxy_signature::RawMessage; use std::io::Read; use tendermint_rpc::{Error, Response, SimpleRequest}; @@ -392,10 +401,17 @@ mod sealed { pub struct HyperClient { uri: Uri, inner: hyper::Client, + proxy_sign_keypair: Option, } impl HyperClient { - pub fn new(uri: Uri, inner: hyper::Client) -> Self { Self { uri, inner } } + pub fn new(uri: Uri, inner: hyper::Client, proxy_sign_keypair: Option) -> Self { + Self { + uri, + inner, + proxy_sign_keypair, + } + } } impl HyperClient @@ -421,21 +437,41 @@ mod sealed { impl HyperClient { /// Build a request using the given Tendermint RPC request. pub fn build_request(&self, request: R) -> Result, Error> { - let request_body = request.into_json(); + let body_bytes = request.into_json().into_bytes(); + let body_size = body_bytes.len(); let mut request = hyper::Request::builder() .method("POST") .uri(&self.uri) - .body(hyper::Body::from(request_body.into_bytes())) + .body(hyper::Body::from(body_bytes)) .map_err(|e| Error::client_internal(e.to_string()))?; { + let request_uri = request.uri().clone(); let headers = request.headers_mut(); - headers.insert(header::CONTENT_TYPE, "application/json".parse().unwrap()); + headers.insert(header::CONTENT_TYPE, HeaderValue::from_static(common::APPLICATION_JSON)); headers.insert( header::USER_AGENT, format!("tendermint.rs/{}", env!("CARGO_PKG_VERSION")).parse().unwrap(), ); + + if let Some(proxy_sign_keypair) = &self.proxy_sign_keypair { + let proxy_sign = RawMessage::sign( + proxy_sign_keypair, + &request_uri, + body_size, + common::PROXY_REQUEST_EXPIRATION_SEC, + ) + .map_err(|e| Error::client_internal(e.to_string()))?; + + let proxy_sign_serialized = + serde_json::to_string(&proxy_sign).map_err(|e| Error::client_internal(e.to_string()))?; + + let header_value = HeaderValue::from_str(&proxy_sign_serialized) + .map_err(|e| Error::client_internal(e.to_string()))?; + + headers.insert(X_AUTH_PAYLOAD, header_value); + } } Ok(request) @@ -454,10 +490,16 @@ mod sealed { } impl HttpClient { - pub fn new_http(uri: Uri) -> Self { Self::Http(HyperClient::new(uri, hyper::Client::new())) } + pub fn new_http(uri: Uri, proxy_sign_keypair: Option) -> Self { + Self::Http(HyperClient::new(uri, hyper::Client::new(), proxy_sign_keypair)) + } - pub fn new_https(uri: Uri) -> Self { - Self::Https(HyperClient::new(uri, hyper::Client::builder().build(https_connector()))) + pub fn new_https(uri: Uri, proxy_sign_keypair: Option) -> Self { + Self::Https(HyperClient::new( + uri, + hyper::Client::builder().build(https_connector()), + proxy_sign_keypair, + )) } pub async fn perform(&self, request: R) -> Result @@ -476,6 +518,13 @@ mod sealed { HttpClient::Https(client) => client.uri.clone(), } } + + pub fn proxy_sign_keypair(&self) -> &Option { + match self { + HttpClient::Http(client) => &client.proxy_sign_keypair, + HttpClient::Https(client) => &client.proxy_sign_keypair, + } + } } async fn response_to_string(response: hyper::Response) -> Result { diff --git a/mm2src/coins/tendermint/rpc/tendermint_wasm_rpc.rs b/mm2src/coins/tendermint/rpc/tendermint_wasm_rpc.rs index 3909ce2c25..9224ea997f 100644 --- a/mm2src/coins/tendermint/rpc/tendermint_wasm_rpc.rs +++ b/mm2src/coins/tendermint/rpc/tendermint_wasm_rpc.rs @@ -1,4 +1,4 @@ -use common::APPLICATION_JSON; +use common::{APPLICATION_JSON, PROXY_REQUEST_EXPIRATION_SEC, X_AUTH_PAYLOAD}; use cosmrs::tendermint::block::Height; use derive_more::Display; use http::header::{ACCEPT, CONTENT_TYPE}; @@ -6,6 +6,8 @@ use http::uri::InvalidUri; use http::{StatusCode, Uri}; use mm2_net::transport::SlurpError; use mm2_net::wasm::http::FetchRequest; +use mm2_p2p::Keypair; +use proxy_signature::RawMessage; use std::str::FromStr; use tendermint_rpc::endpoint::{abci_info, broadcast}; pub use tendermint_rpc::endpoint::{abci_query::{AbciQuery, Request as AbciRequest}, @@ -20,6 +22,7 @@ use tendermint_rpc::Response; #[derive(Debug, Clone)] pub struct HttpClient { uri: String, + proxy_sign_keypair: Option, } #[derive(Debug, Display)] @@ -35,6 +38,7 @@ impl From for HttpClientInitError { pub enum PerformError { TendermintRpc(TendermintRpcError), Slurp(SlurpError), + Internal(String), #[display(fmt = "Request failed with status code {}, response {}", status_code, response)] StatusCode { status_code: StatusCode, @@ -51,27 +55,43 @@ impl From for PerformError { } impl HttpClient { - pub(crate) fn new(url: &str) -> Result { + pub(crate) fn new(url: &str, proxy_sign_keypair: Option) -> Result { Uri::from_str(url)?; - Ok(HttpClient { uri: url.to_owned() }) + Ok(HttpClient { + uri: url.to_owned(), + proxy_sign_keypair, + }) } #[inline] pub fn uri(&self) -> http::Uri { Uri::from_str(&self.uri).expect("This should never happen.") } + #[inline] + pub fn proxy_sign_keypair(&self) -> &Option { &self.proxy_sign_keypair } + pub(crate) async fn perform(&self, request: R) -> Result where R: SimpleRequest, { - let request_str = request.into_json(); - let (status_code, response_str) = FetchRequest::post(&self.uri) - .cors() - .body_utf8(request_str) - .header(ACCEPT.as_str(), APPLICATION_JSON) - .header(CONTENT_TYPE.as_str(), APPLICATION_JSON) - .request_str() - .await - .map_err(|e| e.into_inner())?; + let body_bytes = request.into_json().into_bytes(); + let body_size = body_bytes.len(); + + let mut req = FetchRequest::post(&self.uri).cors().body_bytes(body_bytes); + req = req.header(ACCEPT.as_str(), APPLICATION_JSON); + req = req.header(CONTENT_TYPE.as_str(), APPLICATION_JSON); + + if let Some(proxy_sign_keypair) = &self.proxy_sign_keypair { + let proxy_sign = RawMessage::sign(proxy_sign_keypair, &self.uri(), body_size, PROXY_REQUEST_EXPIRATION_SEC) + .map_err(|e| PerformError::Internal(e.to_string()))?; + + let proxy_sign_serialized = + serde_json::to_string(&proxy_sign).map_err(|e| PerformError::Internal(e.to_string()))?; + + req = req.header(X_AUTH_PAYLOAD, &proxy_sign_serialized); + } + + let (status_code, response_str) = req.request_str().await.map_err(|e| e.into_inner())?; + if !status_code.is_success() { return Err(PerformError::StatusCode { status_code, @@ -118,7 +138,7 @@ mod tests { #[wasm_bindgen_test] async fn test_get_abci_info() { - let client = HttpClient::new("https://rpc.sentry-02.theta-testnet.polypore.xyz").unwrap(); + let client = HttpClient::new("https://rpc.sentry-02.theta-testnet.polypore.xyz", None).unwrap(); client.abci_info().await.unwrap(); } } diff --git a/mm2src/coins/tendermint/tendermint_balance_events.rs b/mm2src/coins/tendermint/tendermint_balance_events.rs index 7b8cd8ca03..c512cf8277 100644 --- a/mm2src/coins/tendermint/tendermint_balance_events.rs +++ b/mm2src/coins/tendermint/tendermint_balance_events.rs @@ -1,14 +1,14 @@ use async_trait::async_trait; use common::{executor::{AbortSettings, SpawnAbortable}, - http_uri_to_ws_address, log}; + http_uri_to_ws_address, log, PROXY_REQUEST_EXPIRATION_SEC}; use futures::channel::oneshot::{self, Receiver, Sender}; use futures_util::{SinkExt, StreamExt}; -use jsonrpc_core::MethodCall; use jsonrpc_core::{Id as RpcId, Params as RpcParams, Value as RpcValue, Version as RpcVersion}; use mm2_core::mm_ctx::MmArc; use mm2_event_stream::{behaviour::{EventBehaviour, EventInitStatus}, ErrorEventName, Event, EventName, EventStreamConfiguration}; use mm2_number::BigDecimal; +use proxy_signature::RawMessage; use std::collections::{HashMap, HashSet}; use super::TendermintCoin; @@ -21,15 +21,28 @@ impl EventBehaviour for TendermintCoin { fn error_event_name() -> ErrorEventName { ErrorEventName::CoinBalanceError } async fn handle(self, _interval: f64, tx: oneshot::Sender) { - fn generate_subscription_query(query_filter: String) -> String { + fn generate_subscription_query( + query_filter: String, + proxy_sign_keypair: &Option, + uri: &http::Uri, + ) -> String { let mut params = serde_json::Map::with_capacity(1); params.insert("query".to_owned(), RpcValue::String(query_filter)); - let q = MethodCall { - id: RpcId::Num(0), - jsonrpc: Some(RpcVersion::V2), - method: "subscribe".to_owned(), - params: RpcParams::Map(params), + let mut q = json!({ + "id": RpcId::Num(0), + "jsonrpc": Some(RpcVersion::V2), + "method": "subscribe".to_owned(), + "params": RpcParams::Map(params), + }); + + const BODY_SIZE: usize = 0; + if let Some(proxy_sign_keypair) = proxy_sign_keypair { + if let Ok(proxy_sign) = + RawMessage::sign(proxy_sign_keypair, uri, BODY_SIZE, PROXY_REQUEST_EXPIRATION_SEC) + { + q["proxy_sign"] = serde_json::to_value(proxy_sign).expect("This should never happen"); + } }; serde_json::to_string(&q).expect("This should never happen") @@ -48,25 +61,33 @@ impl EventBehaviour for TendermintCoin { let account_id = self.account_id.to_string(); let mut current_balances: HashMap = HashMap::new(); - let receiver_q = generate_subscription_query(format!("coin_received.receiver = '{}'", account_id)); - let receiver_q = tokio_tungstenite_wasm::Message::Text(receiver_q); - - let spender_q = generate_subscription_query(format!("coin_spent.spender = '{}'", account_id)); - let spender_q = tokio_tungstenite_wasm::Message::Text(spender_q); - tx.send(EventInitStatus::Success) .expect("Receiver is dropped, which should never happen."); loop { - let node_uri = match self.rpc_client().await { - Ok(client) => client.uri(), + let client = match self.rpc_client().await { + Ok(client) => client, Err(e) => { log::error!("{e}"); continue; }, }; - let socket_address = format!("{}/{}", http_uri_to_ws_address(node_uri), "websocket"); + let receiver_q = generate_subscription_query( + format!("coin_received.receiver = '{}'", account_id), + client.proxy_sign_keypair(), + &client.uri(), + ); + let receiver_q = tokio_tungstenite_wasm::Message::Text(receiver_q); + + let spender_q = generate_subscription_query( + format!("coin_spent.spender = '{}'", account_id), + client.proxy_sign_keypair(), + &client.uri(), + ); + let spender_q = tokio_tungstenite_wasm::Message::Text(spender_q); + + let socket_address = format!("{}/{}", http_uri_to_ws_address(client.uri()), "websocket"); let mut wsocket = match tokio_tungstenite_wasm::connect(&socket_address).await { Ok(ws) => ws, diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index f3854ba4ee..323637599d 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -66,6 +66,7 @@ use mm2_core::mm_ctx::{MmArc, MmWeak}; use mm2_err_handle::prelude::*; use mm2_git::{FileMetadata, GitController, GithubClient, RepositoryOperations, GITHUB_API_URI}; use mm2_number::MmNumber; +use mm2_p2p::p2p_ctx::P2PContext; use parking_lot::Mutex as PaMutex; use primitives::hash::H256; use regex::Regex; @@ -133,6 +134,23 @@ impl TendermintKeyPair { } } +#[derive(Clone, Deserialize)] +pub struct RpcNode { + url: String, + #[serde(default)] + komodo_proxy: bool, +} + +impl RpcNode { + #[cfg(test)] + fn for_test(url: &str) -> Self { + Self { + url: url.to_string(), + komodo_proxy: false, + } + } +} + #[async_trait] pub trait TendermintCommons { fn platform_denom(&self) -> &Denom; @@ -616,12 +634,12 @@ impl TendermintCoin { ticker: String, conf: TendermintConf, protocol_info: TendermintProtocolInfo, - rpc_urls: Vec, + nodes: Vec, tx_history: bool, activation_policy: TendermintActivationPolicy, is_keplr_from_ledger: bool, ) -> MmResult { - if rpc_urls.is_empty() { + if nodes.is_empty() { return MmError::err(TendermintInitError { ticker, kind: TendermintInitErrorKind::EmptyRpcUrls, @@ -635,7 +653,7 @@ impl TendermintCoin { kind: TendermintInitErrorKind::CouldNotGenerateAccountId(e.to_string()), })?; - let rpc_clients = clients_from_urls(rpc_urls.as_ref()).mm_err(|kind| TendermintInitError { + let rpc_clients = clients_from_urls(ctx, nodes).mm_err(|kind| TendermintInitError { ticker: ticker.clone(), kind, })?; @@ -2064,18 +2082,28 @@ impl TendermintCoin { } } -fn clients_from_urls(rpc_urls: &[String]) -> MmResult, TendermintInitErrorKind> { - if rpc_urls.is_empty() { +fn clients_from_urls(ctx: &MmArc, nodes: Vec) -> MmResult, TendermintInitErrorKind> { + if nodes.is_empty() { return MmError::err(TendermintInitErrorKind::EmptyRpcUrls); } + + let p2p_keypair = if nodes.iter().any(|n| n.komodo_proxy) { + let p2p_ctx = P2PContext::fetch_from_mm_arc(ctx); + Some(p2p_ctx.keypair().clone()) + } else { + None + }; + let mut clients = Vec::new(); let mut errors = Vec::new(); + // check that all urls are valid // keep all invalid urls in one vector to show all of them in error - for url in rpc_urls.iter() { - match HttpClient::new(url.as_str()) { + for node in nodes.iter() { + let proxy_sign_keypair = if node.komodo_proxy { p2p_keypair.clone() } else { None }; + match HttpClient::new(node.url.as_str(), proxy_sign_keypair) { Ok(client) => clients.push(client), - Err(e) => errors.push(format!("Url {} is invalid, got error {}", url, e)), + Err(e) => errors.push(format!("Url {} is invalid, got error {}", node.url, e)), } } drop_mutability!(clients); @@ -2134,6 +2162,10 @@ impl MmCoin for TendermintCoin { fn wallet_only(&self, ctx: &MmArc) -> bool { let coin_conf = crate::coin_conf(ctx, self.ticker()); + // If coin is not in config, it means that it was added manually (a custom token) and should be treated as wallet only + if coin_conf.is_null() { + return true; + } let wallet_only_conf = coin_conf["wallet_only"].as_bool().unwrap_or(false); wallet_only_conf || self.is_keplr_from_ledger @@ -2503,13 +2535,13 @@ impl MarketCoinOps for TendermintCoin { let broadcast_res = try_s!(try_s!(coin.rpc_client().await).broadcast_tx_commit(tx_bytes).await); if broadcast_res.check_tx.log.contains(ACCOUNT_SEQUENCE_ERR) - || broadcast_res.deliver_tx.log.contains(ACCOUNT_SEQUENCE_ERR) + || broadcast_res.tx_result.log.contains(ACCOUNT_SEQUENCE_ERR) { return ERR!( "{}. check_tx log: {}, deliver_tx log: {}", ACCOUNT_SEQUENCE_ERR, broadcast_res.check_tx.log, - broadcast_res.deliver_tx.log + broadcast_res.tx_result.log ); } @@ -2517,8 +2549,8 @@ impl MarketCoinOps for TendermintCoin { return ERR!("Tx check failed {:?}", broadcast_res.check_tx); } - if !broadcast_res.deliver_tx.code.is_ok() { - return ERR!("Tx deliver failed {:?}", broadcast_res.deliver_tx); + if !broadcast_res.tx_result.code.is_ok() { + return ERR!("Tx deliver failed {:?}", broadcast_res.tx_result); } Ok(broadcast_res.hash.to_string()) }; @@ -2568,14 +2600,14 @@ impl MarketCoinOps for TendermintCoin { Box::new(fut.boxed().compat()) } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { - let tx = try_tx_fus!(cosmrs::Tx::from_bytes(args.tx_bytes)); - let first_message = try_tx_fus!(tx.body.messages.first().ok_or("Tx body couldn't be read.")); - let htlc_proto = try_tx_fus!(CreateHtlcProto::decode( - try_tx_fus!(HtlcType::from_str(&self.account_prefix)), + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { + let tx = try_tx_s!(cosmrs::Tx::from_bytes(args.tx_bytes)); + let first_message = try_tx_s!(tx.body.messages.first().ok_or("Tx body couldn't be read.")); + let htlc_proto = try_tx_s!(CreateHtlcProto::decode( + try_tx_s!(HtlcType::from_str(&self.account_prefix)), first_message.value.as_slice() )); - let htlc = try_tx_fus!(CreateHtlcMsg::try_from(htlc_proto)); + let htlc = try_tx_s!(CreateHtlcMsg::try_from(htlc_proto)); let htlc_id = self.calculate_htlc_id(htlc.sender(), htlc.to(), htlc.amount(), args.secret_hash); let events_string = format!("claim_htlc.id='{}'", htlc_id); @@ -2590,38 +2622,32 @@ impl MarketCoinOps for TendermintCoin { }; let encoded_request = request.encode_to_vec(); - let coin = self.clone(); - let wait_until = args.wait_until; - let fut = async move { - loop { - let response = try_tx_s!( - try_tx_s!(coin.rpc_client().await) - .abci_query( - Some(ABCI_GET_TXS_EVENT_PATH.to_string()), - encoded_request.as_slice(), - ABCI_REQUEST_HEIGHT, - ABCI_REQUEST_PROVE - ) - .await - ); - let response = try_tx_s!(GetTxsEventResponse::decode(response.value.as_slice())); - if let Some(tx) = response.txs.first() { - return Ok(TransactionEnum::CosmosTransaction(CosmosTransaction { - data: TxRaw { - body_bytes: tx.body.as_ref().map(Message::encode_to_vec).unwrap_or_default(), - auth_info_bytes: tx.auth_info.as_ref().map(Message::encode_to_vec).unwrap_or_default(), - signatures: tx.signatures.clone(), - }, - })); - } - Timer::sleep(5.).await; - if get_utc_timestamp() > wait_until as i64 { - return Err(TransactionErr::Plain("Waited too long".into())); - } + loop { + let response = try_tx_s!( + try_tx_s!(self.rpc_client().await) + .abci_query( + Some(ABCI_GET_TXS_EVENT_PATH.to_string()), + encoded_request.as_slice(), + ABCI_REQUEST_HEIGHT, + ABCI_REQUEST_PROVE + ) + .await + ); + let response = try_tx_s!(GetTxsEventResponse::decode(response.value.as_slice())); + if let Some(tx) = response.txs.first() { + return Ok(TransactionEnum::CosmosTransaction(CosmosTransaction { + data: TxRaw { + body_bytes: tx.body.as_ref().map(Message::encode_to_vec).unwrap_or_default(), + auth_info_bytes: tx.auth_info.as_ref().map(Message::encode_to_vec).unwrap_or_default(), + signatures: tx.signatures.clone(), + }, + })); } - }; - - Box::new(fut.boxed().compat()) + Timer::sleep(5.).await; + if get_utc_timestamp() > args.wait_until as i64 { + return Err(TransactionErr::Plain("Waited too long".into())); + } + } } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { @@ -2663,7 +2689,7 @@ impl MarketCoinOps for TendermintCoin { #[async_trait] #[allow(unused_variables)] impl SwapOps for TendermintCoin { - fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8], expire_at: u64) -> TransactionFut { + async fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8], expire_at: u64) -> TransactionResult { self.send_taker_fee_for_denom( fee_addr, dex_fee.fee_amount().into(), @@ -2672,9 +2698,11 @@ impl SwapOps for TendermintCoin { uuid, expire_at, ) + .compat() + .await } - fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { self.send_htlc_for_denom( maker_payment_args.time_lock_duration, maker_payment_args.other_pubkey, @@ -2683,9 +2711,11 @@ impl SwapOps for TendermintCoin { self.denom.clone(), self.decimals, ) + .compat() + .await } - fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { self.send_htlc_for_denom( taker_payment_args.time_lock_duration, taker_payment_args.other_pubkey, @@ -2694,6 +2724,8 @@ impl SwapOps for TendermintCoin { self.denom.clone(), self.decimals, ) + .compat() + .await } async fn send_maker_spends_taker_payment( @@ -2830,7 +2862,7 @@ impl SwapOps for TendermintCoin { )) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { self.validate_fee_for_denom( validate_fee_args.fee_tx, validate_fee_args.expected_sender, @@ -2840,6 +2872,8 @@ impl SwapOps for TendermintCoin { validate_fee_args.uuid, self.denom.to_string(), ) + .compat() + .await } async fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentResult<()> { @@ -2852,10 +2886,10 @@ impl SwapOps for TendermintCoin { .await } - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, - if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { + if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, + ) -> Result, String> { self.check_if_my_payment_sent_for_denom( self.decimals, self.denom.clone(), @@ -2863,6 +2897,8 @@ impl SwapOps for TendermintCoin { if_my_payment_sent_args.secret_hash, if_my_payment_sent_args.amount, ) + .compat() + .await } async fn search_for_swap_tx_spend_my( @@ -3361,7 +3397,7 @@ pub mod tendermint_coin_tests { #[test] fn test_htlc_create_and_claim() { - let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; + let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; let protocol_conf = get_iris_protocol(); @@ -3382,7 +3418,7 @@ pub mod tendermint_coin_tests { "IRIS".to_string(), conf, protocol_conf, - rpc_urls, + nodes, false, activation_policy, false, @@ -3430,26 +3466,23 @@ pub mod tendermint_coin_tests { fee, timeout_height, TX_DEFAULT_MEMO.into(), - Duration::from_secs(10), + Duration::from_secs(20), ); block_on(async { send_tx_fut.await.unwrap(); }); // >> END HTLC CREATION - let htlc_spent = block_on( - coin.check_if_my_payment_sent(CheckIfMyPaymentSentArgs { - time_lock: 0, - other_pub: IRIS_TESTNET_HTLC_PAIR2_PUB_KEY, - secret_hash: sha256(&sec).as_slice(), - search_from_block: current_block, - swap_contract_address: &None, - swap_unique_data: &[], - amount: &amount_dec, - payment_instructions: &None, - }) - .compat(), - ) + let htlc_spent = block_on(coin.check_if_my_payment_sent(CheckIfMyPaymentSentArgs { + time_lock: 0, + other_pub: IRIS_TESTNET_HTLC_PAIR2_PUB_KEY, + secret_hash: sha256(&sec).as_slice(), + search_from_block: current_block, + swap_contract_address: &None, + swap_unique_data: &[], + amount: &amount_dec, + payment_instructions: &None, + })) .unwrap(); assert!(htlc_spent.is_some()); @@ -3487,7 +3520,7 @@ pub mod tendermint_coin_tests { #[test] fn try_query_claim_htlc_txs_and_get_secret() { - let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; + let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; let protocol_conf = get_iris_usdc_ibc_protocol(); @@ -3508,7 +3541,7 @@ pub mod tendermint_coin_tests { "USDC-IBC".to_string(), conf, protocol_conf, - rpc_urls, + nodes, false, activation_policy, false, @@ -3550,7 +3583,7 @@ pub mod tendermint_coin_tests { #[test] fn wait_for_tx_spend_test() { - let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; + let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; let protocol_conf = get_iris_usdc_ibc_protocol(); @@ -3571,7 +3604,7 @@ pub mod tendermint_coin_tests { "USDC-IBC".to_string(), conf, protocol_conf, - rpc_urls, + nodes, false, activation_policy, false, @@ -3602,18 +3635,15 @@ pub mod tendermint_coin_tests { let encoded_tx = tx.encode_to_vec(); let secret_hash = hex::decode("0C34C71EBA2A51738699F9F3D6DAFFB15BE576E8ED543203485791B5DA39D10D").unwrap(); - let spend_tx = block_on( - coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { - tx_bytes: &encoded_tx, - secret_hash: &secret_hash, - wait_until: get_utc_timestamp() as u64, - from_block: 0, - swap_contract_address: &None, - check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, - watcher_reward: false, - }) - .compat(), - ) + let spend_tx = block_on(coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { + tx_bytes: &encoded_tx, + secret_hash: &secret_hash, + wait_until: get_utc_timestamp() as u64, + from_block: 0, + swap_contract_address: &None, + check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, + watcher_reward: false, + })) .unwrap(); // https://nyancat.iobscan.io/#/tx?txHash=565C820C1F95556ADC251F16244AAD4E4274772F41BC13F958C9C2F89A14D137 @@ -3624,7 +3654,7 @@ pub mod tendermint_coin_tests { #[test] fn validate_taker_fee_test() { - let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; + let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; let protocol_conf = get_iris_protocol(); @@ -3645,7 +3675,7 @@ pub mod tendermint_coin_tests { "IRIS-TEST".to_string(), conf, protocol_conf, - rpc_urls, + nodes, false, activation_policy, false, @@ -3663,18 +3693,16 @@ pub mod tendermint_coin_tests { }); let invalid_amount: MmNumber = 1.into(); - let error = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &create_htlc_tx, - expected_sender: &[], - fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee: &DexFee::Standard(invalid_amount.clone()), - min_block_number: 0, - uuid: &[1; 16], - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &create_htlc_tx, + expected_sender: &[], + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee: &DexFee::Standard(invalid_amount.clone()), + min_block_number: 0, + uuid: &[1; 16], + })) + .unwrap_err() + .into_inner(); println!("{}", error); match error { ValidatePaymentError::TxDeserializationError(err) => { @@ -3697,18 +3725,16 @@ pub mod tendermint_coin_tests { data: TxRaw::decode(random_transfer_tx_bytes.as_slice()).unwrap(), }); - let error = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &random_transfer_tx, - expected_sender: &[], - fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee: &DexFee::Standard(invalid_amount.clone()), - min_block_number: 0, - uuid: &[1; 16], - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &random_transfer_tx, + expected_sender: &[], + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee: &DexFee::Standard(invalid_amount.clone()), + min_block_number: 0, + uuid: &[1; 16], + })) + .unwrap_err() + .into_inner(); println!("{}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("sent to wrong address")), @@ -3730,18 +3756,16 @@ pub mod tendermint_coin_tests { data: TxRaw::decode(dex_fee_tx.encode_to_vec().as_slice()).unwrap(), }); - let error = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &dex_fee_tx, - expected_sender: &[], - fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee: &DexFee::Standard(invalid_amount), - min_block_number: 0, - uuid: &[1; 16], - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &dex_fee_tx, + expected_sender: &[], + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee: &DexFee::Standard(invalid_amount), + min_block_number: 0, + uuid: &[1; 16], + })) + .unwrap_err() + .into_inner(); println!("{}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Invalid amount")), @@ -3750,18 +3774,16 @@ pub mod tendermint_coin_tests { let valid_amount: BigDecimal = "0.0001".parse().unwrap(); // valid amount but invalid sender - let error = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &dex_fee_tx, - expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, - fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee: &DexFee::Standard(valid_amount.clone().into()), - min_block_number: 0, - uuid: &[1; 16], - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &dex_fee_tx, + expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee: &DexFee::Standard(valid_amount.clone().into()), + min_block_number: 0, + uuid: &[1; 16], + })) + .unwrap_err() + .into_inner(); println!("{}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Invalid sender")), @@ -3769,18 +3791,16 @@ pub mod tendermint_coin_tests { } // invalid memo - let error = coin - .validate_fee(ValidateFeeArgs { - fee_tx: &dex_fee_tx, - expected_sender: &pubkey, - fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - dex_fee: &DexFee::Standard(valid_amount.into()), - min_block_number: 0, - uuid: &[1; 16], - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on(coin.validate_fee(ValidateFeeArgs { + fee_tx: &dex_fee_tx, + expected_sender: &pubkey, + fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, + dex_fee: &DexFee::Standard(valid_amount.into()), + min_block_number: 0, + uuid: &[1; 16], + })) + .unwrap_err() + .into_inner(); println!("{}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Invalid memo")), @@ -3821,7 +3841,7 @@ pub mod tendermint_coin_tests { #[test] fn validate_payment_test() { - let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; + let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; let protocol_conf = get_iris_protocol(); @@ -3842,7 +3862,7 @@ pub mod tendermint_coin_tests { "IRIS-TEST".to_string(), conf, protocol_conf, - rpc_urls, + nodes, false, activation_policy, false, @@ -3904,7 +3924,7 @@ pub mod tendermint_coin_tests { #[test] fn test_search_for_swap_tx_spend_spent() { - let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; + let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; let protocol_conf = get_iris_protocol(); @@ -3925,7 +3945,7 @@ pub mod tendermint_coin_tests { "IRIS-TEST".to_string(), conf, protocol_conf, - rpc_urls, + nodes, false, activation_policy, false, @@ -3980,7 +4000,7 @@ pub mod tendermint_coin_tests { #[test] fn test_search_for_swap_tx_spend_refunded() { - let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; + let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; let protocol_conf = get_iris_protocol(); @@ -4001,7 +4021,7 @@ pub mod tendermint_coin_tests { "IRIS-TEST".to_string(), conf, protocol_conf, - rpc_urls, + nodes, false, activation_policy, false, @@ -4054,7 +4074,7 @@ pub mod tendermint_coin_tests { #[test] fn test_get_tx_status_code_or_none() { - let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; + let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; let protocol_conf = get_iris_usdc_ibc_protocol(); let conf = TendermintConf { @@ -4073,7 +4093,7 @@ pub mod tendermint_coin_tests { "USDC-IBC".to_string(), conf, protocol_conf, - rpc_urls, + nodes, false, activation_policy, false, @@ -4109,7 +4129,7 @@ pub mod tendermint_coin_tests { fn test_wait_for_confirmations() { const CHECK_INTERVAL: u64 = 2; - let rpc_urls = vec![IRIS_TESTNET_RPC_URL.to_string()]; + let nodes = vec![RpcNode::for_test(IRIS_TESTNET_RPC_URL)]; let protocol_conf = get_iris_usdc_ibc_protocol(); let conf = TendermintConf { @@ -4128,7 +4148,7 @@ pub mod tendermint_coin_tests { "USDC-IBC".to_string(), conf, protocol_conf, - rpc_urls, + nodes, false, activation_policy, false, diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 3e63589926..e5cc90f895 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -104,37 +104,46 @@ impl TendermintToken { #[async_trait] #[allow(unused_variables)] impl SwapOps for TendermintToken { - fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8], expire_at: u64) -> TransactionFut { - self.platform_coin.send_taker_fee_for_denom( - fee_addr, - dex_fee.fee_amount().into(), - self.denom.clone(), - self.decimals, - uuid, - expire_at, - ) - } - - fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { - self.platform_coin.send_htlc_for_denom( - maker_payment_args.time_lock_duration, - maker_payment_args.other_pubkey, - maker_payment_args.secret_hash, - maker_payment_args.amount, - self.denom.clone(), - self.decimals, - ) - } - - fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { - self.platform_coin.send_htlc_for_denom( - taker_payment_args.time_lock_duration, - taker_payment_args.other_pubkey, - taker_payment_args.secret_hash, - taker_payment_args.amount, - self.denom.clone(), - self.decimals, - ) + async fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8], expire_at: u64) -> TransactionResult { + self.platform_coin + .send_taker_fee_for_denom( + fee_addr, + dex_fee.fee_amount().into(), + self.denom.clone(), + self.decimals, + uuid, + expire_at, + ) + .compat() + .await + } + + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + self.platform_coin + .send_htlc_for_denom( + maker_payment_args.time_lock_duration, + maker_payment_args.other_pubkey, + maker_payment_args.secret_hash, + maker_payment_args.amount, + self.denom.clone(), + self.decimals, + ) + .compat() + .await + } + + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + self.platform_coin + .send_htlc_for_denom( + taker_payment_args.time_lock_duration, + taker_payment_args.other_pubkey, + taker_payment_args.secret_hash, + taker_payment_args.amount, + self.denom.clone(), + self.decimals, + ) + .compat() + .await } async fn send_maker_spends_taker_payment( @@ -167,16 +176,19 @@ impl SwapOps for TendermintToken { )) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { - self.platform_coin.validate_fee_for_denom( - validate_fee_args.fee_tx, - validate_fee_args.expected_sender, - validate_fee_args.fee_addr, - &validate_fee_args.dex_fee.fee_amount().into(), - self.decimals, - validate_fee_args.uuid, - self.denom.to_string(), - ) + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { + self.platform_coin + .validate_fee_for_denom( + validate_fee_args.fee_tx, + validate_fee_args.expected_sender, + validate_fee_args.fee_addr, + &validate_fee_args.dex_fee.fee_amount().into(), + self.decimals, + validate_fee_args.uuid, + self.denom.to_string(), + ) + .compat() + .await } async fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentResult<()> { @@ -191,17 +203,20 @@ impl SwapOps for TendermintToken { .await } - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, - if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { - self.platform_coin.check_if_my_payment_sent_for_denom( - self.decimals, - self.denom.clone(), - if_my_payment_sent_args.other_pub, - if_my_payment_sent_args.secret_hash, - if_my_payment_sent_args.amount, - ) + if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, + ) -> Result, String> { + self.platform_coin + .check_if_my_payment_sent_for_denom( + self.decimals, + self.denom.clone(), + if_my_payment_sent_args.other_pub, + if_my_payment_sent_args.secret_hash, + if_my_payment_sent_args.amount, + ) + .compat() + .await } async fn search_for_swap_tx_spend_my( @@ -438,16 +453,18 @@ impl MarketCoinOps for TendermintToken { self.platform_coin.wait_for_confirmations(input) } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { - self.platform_coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { - tx_bytes: args.tx_bytes, - secret_hash: args.secret_hash, - wait_until: args.wait_until, - from_block: args.from_block, - swap_contract_address: args.swap_contract_address, - check_every: args.check_every, - watcher_reward: false, - }) + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { + self.platform_coin + .wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { + tx_bytes: args.tx_bytes, + secret_hash: args.secret_hash, + wait_until: args.wait_until, + from_block: args.from_block, + swap_contract_address: args.swap_contract_address, + check_every: args.check_every, + watcher_reward: false, + }) + .await } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { @@ -474,6 +491,10 @@ impl MmCoin for TendermintToken { fn wallet_only(&self, ctx: &MmArc) -> bool { let coin_conf = crate::coin_conf(ctx, self.ticker()); + // If coin is not in config, it means that it was added manually (a custom token) and should be treated as wallet only + if coin_conf.is_null() { + return true; + } let wallet_only_conf = coin_conf["wallet_only"].as_bool().unwrap_or(false); wallet_only_conf || self.platform_coin.is_keplr_from_ledger diff --git a/mm2src/coins/tendermint/tendermint_tx_history_v2.rs b/mm2src/coins/tendermint/tendermint_tx_history_v2.rs index 1c8adf8de3..3dc95e7443 100644 --- a/mm2src/coins/tendermint/tendermint_tx_history_v2.rs +++ b/mm2src/coins/tendermint/tendermint_tx_history_v2.rs @@ -28,10 +28,24 @@ use std::convert::Infallible; const TX_PAGE_SIZE: u8 = 50; const DEFAULT_TRANSFER_EVENT_COUNT: usize = 1; + +const TRANSFER_EVENT: &str = "transfer"; + const CREATE_HTLC_EVENT: &str = "create_htlc"; const CLAIM_HTLC_EVENT: &str = "claim_htlc"; -const TRANSFER_EVENT: &str = "transfer"; -const ACCEPTED_EVENTS: &[&str] = &[CREATE_HTLC_EVENT, CLAIM_HTLC_EVENT, TRANSFER_EVENT]; + +const IBC_SEND_EVENT: &str = "ibc_transfer"; +const IBC_RECEIVE_EVENT: &str = "fungible_token_packet"; +const IBC_NFT_RECEIVE_EVENT: &str = "non_fungible_token_packet"; + +const ACCEPTED_EVENTS: &[&str] = &[ + TRANSFER_EVENT, + CREATE_HTLC_EVENT, + CLAIM_HTLC_EVENT, + IBC_SEND_EVENT, + IBC_RECEIVE_EVENT, + IBC_NFT_RECEIVE_EVENT, +]; const RECEIVER_TAG_KEY: &str = "receiver"; const RECEIVER_TAG_KEY_BASE64: &str = "cmVjZWl2ZXI="; @@ -387,6 +401,8 @@ where Standard, CreateHtlc, ClaimHtlc, + IBCSend, + IBCReceive, } #[derive(Clone)] @@ -398,7 +414,28 @@ where transfer_event_type: TransferEventType, } - // updates sender and receiver addresses if tx is htlc, and if not leaves as it is. + /// Reads sender and receiver addresses properly from an IBC event. + fn read_real_ibc_addresses(transfer_details: &mut TransferDetails, msg_event: &Event) { + transfer_details.transfer_event_type = match msg_event.kind.as_str() { + IBC_SEND_EVENT => TransferEventType::IBCSend, + IBC_RECEIVE_EVENT | IBC_NFT_RECEIVE_EVENT => TransferEventType::IBCReceive, + _ => unreachable!("`read_real_ibc_addresses` shouldn't be called for non-IBC events."), + }; + + transfer_details.from = some_or_return!(get_value_from_event_attributes( + &msg_event.attributes, + SENDER_TAG_KEY, + SENDER_TAG_KEY_BASE64 + )); + + transfer_details.to = some_or_return!(get_value_from_event_attributes( + &msg_event.attributes, + RECEIVER_TAG_KEY, + RECEIVER_TAG_KEY_BASE64, + )); + } + + /// Reads sender and receiver addresses properly from an HTLC event. fn read_real_htlc_addresses(transfer_details: &mut TransferDetails, msg_event: &&Event) { match msg_event.kind.as_str() { CREATE_HTLC_EVENT => { @@ -425,14 +462,14 @@ where transfer_details.transfer_event_type = TransferEventType::ClaimHtlc; }, - _ => {}, + _ => unreachable!("`read_real_htlc_addresses` shouldn't be called for non-HTLC events."), } } fn parse_transfer_values_from_events(tx_events: Vec<&Event>) -> Vec { let mut transfer_details_list: Vec = vec![]; - for (index, event) in tx_events.iter().enumerate() { + for event in tx_events.iter() { if event.kind.as_str() == TRANSFER_EVENT { let amount_with_denoms = some_or_continue!(get_value_from_event_attributes( &event.attributes, @@ -469,14 +506,20 @@ where transfer_event_type: TransferEventType::default(), }; - if index != 0 { - // If previous message is htlc related, that means current transfer - // addresses will be wrong. - if let Some(prev_event) = tx_events.get(index - 1) { - if [CREATE_HTLC_EVENT, CLAIM_HTLC_EVENT].contains(&prev_event.kind.as_str()) { - read_real_htlc_addresses(&mut tx_details, prev_event); - } - }; + // For HTLC transactions, the sender and receiver addresses in the "transfer" event will be incorrect. + // Use `read_real_htlc_addresses` to handle them properly. + if let Some(htlc_event) = tx_events + .iter() + .find(|e| [CREATE_HTLC_EVENT, CLAIM_HTLC_EVENT].contains(&e.kind.as_str())) + { + read_real_htlc_addresses(&mut tx_details, htlc_event); + } + // For IBC transactions, the sender and receiver addresses in the "transfer" event will be incorrect. + // Use `read_real_ibc_addresses` to handle them properly. + else if let Some(ibc_event) = tx_events.iter().find(|e| { + [IBC_SEND_EVENT, IBC_RECEIVE_EVENT, IBC_NFT_RECEIVE_EVENT].contains(&e.kind.as_str()) + }) { + read_real_ibc_addresses(&mut tx_details, ibc_event); } // sum the amounts coins and pairs are same @@ -561,7 +604,7 @@ where } }, TransferEventType::ClaimHtlc => Some((vec![my_address], vec![])), - TransferEventType::Standard => { + TransferEventType::Standard | TransferEventType::IBCSend | TransferEventType::IBCReceive => { Some((vec![transfer_details.from.clone()], vec![transfer_details.to.clone()])) }, } diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index c077bf60bd..43765ab0ba 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -1,8 +1,8 @@ #![allow(clippy::all)] -use super::{CoinBalance, FundingTxSpend, HistorySyncState, MarketCoinOps, MmCoin, RawTransactionFut, - RawTransactionRequest, SearchForFundingSpendErr, SwapOps, TradeFee, TransactionEnum, TransactionFut, - WaitForTakerPaymentSpendError}; +use super::{CoinBalance, CommonSwapOpsV2, FundingTxSpend, HistorySyncState, MarketCoinOps, MmCoin, RawTransactionFut, + RawTransactionRequest, RefundTakerPaymentArgs, SearchForFundingSpendErr, SwapOps, TradeFee, + TransactionEnum, TransactionFut, WaitForPaymentSpendError}; use crate::coin_errors::ValidatePaymentResult; use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, GenPreimageResult, GenTakerFundingSpendArgs, @@ -92,7 +92,7 @@ impl MarketCoinOps for TestCoin { unimplemented!() } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { unimplemented!() } + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { unimplemented!() } fn tx_enum_from_bytes(&self, _bytes: &[u8]) -> Result> { MmError::err(TxMarshalingErr::NotSupported( @@ -114,13 +114,17 @@ impl MarketCoinOps for TestCoin { #[async_trait] #[mockable] impl SwapOps for TestCoin { - fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8], _expire_at: u64) -> TransactionFut { + async fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8], expire_at: u64) -> TransactionResult { unimplemented!() } - fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + unimplemented!() + } - fn send_taker_payment(&self, _taker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + unimplemented!() + } async fn send_maker_spends_taker_payment( &self, @@ -150,7 +154,9 @@ impl SwapOps for TestCoin { unimplemented!() } - fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { unimplemented!() } + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { + unimplemented!() + } async fn validate_maker_payment(&self, _input: ValidatePaymentInput) -> ValidatePaymentResult<()> { unimplemented!() @@ -160,10 +166,10 @@ impl SwapOps for TestCoin { unimplemented!() } - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, - _if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { + if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, + ) -> Result, String> { unimplemented!() } @@ -209,9 +215,7 @@ impl SwapOps for TestCoin { fn derive_htlc_pubkey(&self, _swap_unique_data: &[u8]) -> Vec { unimplemented!() } - fn can_refund_htlc(&self, locktime: u64) -> Box + Send + '_> { - unimplemented!() - } + async fn can_refund_htlc(&self, locktime: u64) -> Result { unimplemented!() } fn validate_other_pubkey(&self, raw_pubkey: &[u8]) -> MmResult<(), ValidateOtherPubKeyErr> { unimplemented!() } @@ -472,7 +476,10 @@ impl TakerCoinSwapOpsV2 for TestCoin { unimplemented!() } - async fn refund_taker_funding_timelock(&self, args: RefundPaymentArgs<'_>) -> Result { + async fn refund_taker_funding_timelock( + &self, + args: RefundTakerPaymentArgs<'_>, + ) -> Result { todo!() } @@ -517,7 +524,10 @@ impl TakerCoinSwapOpsV2 for TestCoin { todo!() } - async fn refund_combined_taker_payment(&self, args: RefundPaymentArgs<'_>) -> Result { + async fn refund_combined_taker_payment( + &self, + args: RefundTakerPaymentArgs<'_>, + ) -> Result { unimplemented!() } @@ -552,9 +562,13 @@ impl TakerCoinSwapOpsV2 for TestCoin { taker_payment: &Self::Tx, from_block: u64, wait_until: u64, - ) -> MmResult { + ) -> MmResult { unimplemented!() } +} + +impl CommonSwapOpsV2 for TestCoin { + fn derive_htlc_pubkey_v2(&self, _swap_unique_data: &[u8]) -> Self::Pubkey { todo!() } - fn derive_htlc_pubkey_v2(&self, swap_unique_data: &[u8]) -> Self::Pubkey { todo!() } + fn derive_htlc_pubkey_v2_bytes(&self, _swap_unique_data: &[u8]) -> Vec { todo!() } } diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index 328863865c..6d98451c7f 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -99,16 +99,15 @@ use utxo_hd_wallet::UtxoHDWallet; use utxo_signer::with_key_pair::sign_tx; use utxo_signer::{TxProvider, TxProviderError, UtxoSignTxError, UtxoSignTxResult}; -use self::rpc_clients::{electrum_script_hash, ElectrumClient, ElectrumRpcRequest, EstimateFeeMethod, EstimateFeeMode, - NativeClient, UnspentInfo, UnspentMap, UtxoRpcClientEnum, UtxoRpcError, UtxoRpcFut, - UtxoRpcResult}; +use self::rpc_clients::{electrum_script_hash, ElectrumClient, ElectrumConnectionSettings, EstimateFeeMethod, + EstimateFeeMode, NativeClient, UnspentInfo, UnspentMap, UtxoRpcClientEnum, UtxoRpcError, + UtxoRpcFut, UtxoRpcResult}; use super::{big_decimal_from_sat_unsigned, BalanceError, BalanceFut, BalanceResult, CoinBalance, CoinFutSpawner, CoinsContext, DerivationMethod, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, KmdRewardsDetails, MarketCoinOps, MmCoin, NumConversError, NumConversResult, PrivKeyActivationPolicy, PrivKeyPolicy, - PrivKeyPolicyNotAllowed, RawTransactionFut, RpcTransportEventHandler, RpcTransportEventHandlerShared, - TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, Transaction, TransactionDetails, - TransactionEnum, TransactionErr, UnexpectedDerivationMethod, VerificationError, WithdrawError, - WithdrawRequest}; + PrivKeyPolicyNotAllowed, RawTransactionFut, TradeFee, TradePreimageError, TradePreimageFut, + TradePreimageResult, Transaction, TransactionDetails, TransactionEnum, TransactionErr, + UnexpectedDerivationMethod, VerificationError, WithdrawError, WithdrawRequest}; use crate::coin_balance::{EnableCoinScanPolicy, EnabledCoinBalanceParams, HDAddressBalanceScanner}; use crate::hd_wallet::{HDAccountOps, HDAddressOps, HDPathAccountToAddressId, HDWalletCoinOps, HDWalletOps}; use crate::utxo::tx_cache::UtxoVerboseCacheShared; @@ -143,7 +142,6 @@ pub type RecentlySpentOutPointsGuard<'a> = AsyncMutexGuard<'a, RecentlySpentOutP pub enum ScripthashNotification { Triggered(String), SubscribeToAddresses(HashSet
), - RefreshSubscriptions, } pub type ScripthashNotificationSender = Option>; @@ -1386,43 +1384,6 @@ pub fn coin_daemon_data_dir(name: &str, is_asset_chain: bool) -> PathBuf { data_dir } -enum ElectrumProtoVerifierEvent { - Connected(String), - Disconnected(String), -} - -/// Electrum protocol version verifier. -/// The structure is used to handle the `on_connected` event and notify `electrum_version_loop`. -struct ElectrumProtoVerifier { - on_event_tx: UnboundedSender, -} - -impl ElectrumProtoVerifier { - fn into_shared(self) -> RpcTransportEventHandlerShared { Arc::new(self) } -} - -impl RpcTransportEventHandler for ElectrumProtoVerifier { - 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> { - try_s!(self - .on_event_tx - .unbounded_send(ElectrumProtoVerifierEvent::Connected(address))); - Ok(()) - } - - fn on_disconnected(&self, address: String) -> Result<(), String> { - try_s!(self - .on_event_tx - .unbounded_send(ElectrumProtoVerifierEvent::Disconnected(address))); - Ok(()) - } -} - #[derive(Clone, Debug, Deserialize, Serialize)] pub struct UtxoActivationParams { pub mode: UtxoRpcMode, @@ -1472,7 +1433,13 @@ impl UtxoActivationParams { Some("electrum") => { let servers = json::from_value(req["servers"].clone()).map_to_mm(UtxoFromLegacyReqErr::InvalidElectrumServers)?; - UtxoRpcMode::Electrum { servers } + let min_connected = req["min_connected"].as_u64().map(|m| m as usize); + let max_connected = req["max_connected"].as_u64().map(|m| m as usize); + UtxoRpcMode::Electrum { + servers, + min_connected, + max_connected, + } }, _ => return MmError::err(UtxoFromLegacyReqErr::UnexpectedMethod), }; @@ -1524,7 +1491,14 @@ impl UtxoActivationParams { #[serde(tag = "rpc", content = "rpc_data")] pub enum UtxoRpcMode { Native, - Electrum { servers: Vec }, + Electrum { + /// The settings of each electrum server. + servers: Vec, + /// The minimum number of connections to electrum servers to keep alive/maintained at all times. + min_connected: Option, + /// The maximum number of connections to electrum servers to not exceed at any time. + max_connected: Option, + }, } impl UtxoRpcMode { diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index f2cf7b4251..d71b6538e3 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -13,18 +13,18 @@ use crate::utxo::utxo_common::{big_decimal_from_sat_unsigned, utxo_prepare_addre use crate::utxo::utxo_hd_wallet::{UtxoHDAccount, UtxoHDAddress}; use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; -use crate::{coin_balance, BlockHeightAndTime, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinProtocol, - CoinWithDerivationMethod, CoinWithPrivKeyPolicy, ConfirmPaymentInput, DexFee, GetWithdrawSenderAddress, - IguanaBalanceOps, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, - PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, - RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, RefundPaymentArgs, - RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, - SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, - TradePreimageValue, TransactionFut, TransactionResult, TransactionType, TxFeeDetails, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, - ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, - WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, +use crate::{coin_balance, BlockHeightAndTime, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinBalanceMap, + CoinProtocol, CoinWithDerivationMethod, CoinWithPrivKeyPolicy, ConfirmPaymentInput, DexFee, + GetWithdrawSenderAddress, IguanaBalanceOps, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, + NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, + PrivKeyBuildPolicy, RawTransactionFut, RawTransactionRequest, RawTransactionResult, RefundError, + RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, + SendPaymentArgs, SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, SwapOps, + TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TransactionResult, TransactionType, TxFeeDetails, + TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, + WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut}; use common::executor::{AbortableSystem, AbortedError}; use common::log::warn; @@ -870,18 +870,30 @@ impl UtxoCommonOps for BchCoin { #[async_trait] impl SwapOps for BchCoin { #[inline] - fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut { + async fn send_taker_fee( + &self, + fee_addr: &[u8], + dex_fee: DexFee, + _uuid: &[u8], + _expire_at: u64, + ) -> TransactionResult { utxo_common::send_taker_fee(self.clone(), fee_addr, dex_fee) + .compat() + .await } #[inline] - fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { utxo_common::send_maker_payment(self.clone(), maker_payment_args) + .compat() + .await } #[inline] - fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { utxo_common::send_taker_payment(self.clone(), taker_payment_args) + .compat() + .await } #[inline] @@ -910,10 +922,15 @@ impl SwapOps for BchCoin { utxo_common::send_maker_refunds_payment(self.clone(), maker_refunds_payment_args).await } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { let tx = match validate_fee_args.fee_tx { TransactionEnum::UtxoTx(tx) => tx.clone(), - _ => panic!(), + fee_tx => { + return MmError::err(ValidatePaymentError::InternalError(format!( + "Invalid fee tx type. fee tx: {:?}", + fee_tx + ))) + }, }; utxo_common::validate_fee( self.clone(), @@ -924,6 +941,8 @@ impl SwapOps for BchCoin { validate_fee_args.min_block_number, validate_fee_args.fee_addr, ) + .compat() + .await } #[inline] @@ -937,17 +956,23 @@ impl SwapOps for BchCoin { } #[inline] - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, - if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { + if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, + ) -> Result, String> { + let time_lock = if_my_payment_sent_args + .time_lock + .try_into() + .map_err(|e: TryFromIntError| e.to_string())?; utxo_common::check_if_my_payment_sent( self.clone(), - try_fus!(if_my_payment_sent_args.time_lock.try_into()), + time_lock, if_my_payment_sent_args.other_pub, if_my_payment_sent_args.secret_hash, if_my_payment_sent_args.swap_unique_data, ) + .compat() + .await } #[inline] @@ -990,13 +1015,10 @@ impl SwapOps for BchCoin { } #[inline] - fn can_refund_htlc(&self, locktime: u64) -> Box + Send + '_> { - Box::new( - utxo_common::can_refund_htlc(self, locktime) - .boxed() - .map_err(|e| ERRL!("{}", e)) - .compat(), - ) + async fn can_refund_htlc(&self, locktime: u64) -> Result { + utxo_common::can_refund_htlc(self, locktime) + .await + .map_err(|e| ERRL!("{}", e)) } #[inline] @@ -1229,7 +1251,7 @@ impl MarketCoinOps for BchCoin { utxo_common::wait_for_confirmations(&self.utxo_arc, input) } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { utxo_common::wait_for_output_spend( self.clone(), args.tx_bytes, @@ -1238,6 +1260,7 @@ impl MarketCoinOps for BchCoin { args.wait_until, args.check_every, ) + .await } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { @@ -1428,7 +1451,7 @@ impl HDCoinWithdrawOps for BchCoin {} #[async_trait] impl HDWalletBalanceOps for BchCoin { type HDAddressScanner = UtxoAddressScanner; - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn produce_hd_address_scanner(&self) -> BalanceResult { utxo_common::produce_hd_address_scanner(self).await @@ -1465,14 +1488,21 @@ impl HDWalletBalanceOps for BchCoin { } async fn known_address_balance(&self, address: &Address) -> BalanceResult { - utxo_common::address_balance(self, address).await + let balance = utxo_common::address_balance(self, address).await?; + Ok(HashMap::from([(self.ticker().to_string(), balance)])) } async fn known_addresses_balances( &self, addresses: Vec
, ) -> BalanceResult> { - utxo_common::addresses_balances(self, addresses).await + let ticker = self.ticker().to_string(); + let balances = utxo_common::addresses_balances(self, addresses).await?; + + balances + .into_iter() + .map(|(address, balance)| Ok((address, HashMap::from([(ticker.clone(), balance)])))) + .collect() } async fn prepare_addresses_for_balance_stream_if_enabled( diff --git a/mm2src/coins/utxo/pb.rs b/mm2src/coins/utxo/pb.rs index 3ae572bc23..a9fb741a11 100644 --- a/mm2src/coins/utxo/pb.rs +++ b/mm2src/coins/utxo/pb.rs @@ -1,3 +1,4 @@ +// This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GetMempoolInfoRequest {} diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index d3442d6690..c5fbc67293 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -23,7 +23,7 @@ use crate::utxo::utxo_builder::{MergeUtxoArcOps, UtxoCoinBuildError, UtxoCoinBui use crate::utxo::utxo_hd_wallet::{UtxoHDAccount, UtxoHDAddress}; use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; -use crate::{eth, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, +use crate::{eth, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinBalanceMap, CoinWithDerivationMethod, CoinWithPrivKeyPolicy, ConfirmPaymentInput, DelegationError, DelegationFut, DexFee, GetWithdrawSenderAddress, IguanaBalanceOps, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, @@ -510,18 +510,30 @@ impl UtxoStandardOps for QtumCoin { #[async_trait] impl SwapOps for QtumCoin { #[inline] - fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut { + async fn send_taker_fee( + &self, + fee_addr: &[u8], + dex_fee: DexFee, + _uuid: &[u8], + _expire_at: u64, + ) -> TransactionResult { utxo_common::send_taker_fee(self.clone(), fee_addr, dex_fee) + .compat() + .await } #[inline] - fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { utxo_common::send_maker_payment(self.clone(), maker_payment_args) + .compat() + .await } #[inline] - fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { utxo_common::send_taker_payment(self.clone(), taker_payment_args) + .compat() + .await } #[inline] @@ -550,10 +562,15 @@ impl SwapOps for QtumCoin { utxo_common::send_maker_refunds_payment(self.clone(), maker_refunds_payment_args).await } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { let tx = match validate_fee_args.fee_tx { TransactionEnum::UtxoTx(tx) => tx.clone(), - _ => panic!(), + fee_tx => { + return MmError::err(ValidatePaymentError::InternalError(format!( + "Invalid fee tx type. fee tx: {:?}", + fee_tx + ))) + }, }; utxo_common::validate_fee( self.clone(), @@ -564,6 +581,8 @@ impl SwapOps for QtumCoin { validate_fee_args.min_block_number, validate_fee_args.fee_addr, ) + .compat() + .await } #[inline] @@ -577,17 +596,23 @@ impl SwapOps for QtumCoin { } #[inline] - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, - if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { + if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, + ) -> Result, String> { + let time_lock = if_my_payment_sent_args + .time_lock + .try_into() + .map_err(|e: TryFromIntError| e.to_string())?; utxo_common::check_if_my_payment_sent( self.clone(), - try_fus!(if_my_payment_sent_args.time_lock.try_into()), + time_lock, if_my_payment_sent_args.other_pub, if_my_payment_sent_args.secret_hash, if_my_payment_sent_args.swap_unique_data, ) + .compat() + .await } #[inline] @@ -630,13 +655,10 @@ impl SwapOps for QtumCoin { } #[inline] - fn can_refund_htlc(&self, locktime: u64) -> Box + Send + '_> { - Box::new( - utxo_common::can_refund_htlc(self, locktime) - .boxed() - .map_err(|e| ERRL!("{}", e)) - .compat(), - ) + async fn can_refund_htlc(&self, locktime: u64) -> Result { + utxo_common::can_refund_htlc(self, locktime) + .await + .map_err(|e| ERRL!("{}", e)) } #[inline] @@ -849,7 +871,7 @@ impl MarketCoinOps for QtumCoin { utxo_common::wait_for_confirmations(&self.utxo_arc, input) } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { utxo_common::wait_for_output_spend( self.clone(), args.tx_bytes, @@ -858,6 +880,7 @@ impl MarketCoinOps for QtumCoin { args.wait_until, args.check_every, ) + .await } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { @@ -1040,9 +1063,12 @@ impl CoinWithDerivationMethod for QtumCoin { #[async_trait] impl IguanaBalanceOps for QtumCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; - async fn iguana_balances(&self) -> BalanceResult { self.my_balance().compat().await } + async fn iguana_balances(&self) -> BalanceResult { + let balance = self.my_balance().compat().await?; + Ok(HashMap::from([(self.ticker().to_string(), balance)])) + } } #[async_trait] @@ -1081,7 +1107,7 @@ impl HDCoinWithdrawOps for QtumCoin {} #[async_trait] impl HDWalletBalanceOps for QtumCoin { type HDAddressScanner = UtxoAddressScanner; - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn produce_hd_address_scanner(&self) -> BalanceResult { utxo_common::produce_hd_address_scanner(self).await @@ -1118,14 +1144,21 @@ impl HDWalletBalanceOps for QtumCoin { } async fn known_address_balance(&self, address: &Address) -> BalanceResult { - utxo_common::address_balance(self, address).await + let balance = utxo_common::address_balance(self, address).await?; + Ok(HashMap::from([(self.ticker().to_string(), balance)])) } async fn known_addresses_balances( &self, addresses: Vec
, ) -> BalanceResult> { - utxo_common::addresses_balances(self, addresses).await + let ticker = self.ticker().to_string(); + let balances = utxo_common::addresses_balances(self, addresses).await?; + + balances + .into_iter() + .map(|(address, balance)| Ok((address, HashMap::from([(ticker.clone(), balance)])))) + .collect() } async fn prepare_addresses_for_balance_stream_if_enabled( @@ -1138,7 +1171,7 @@ impl HDWalletBalanceOps for QtumCoin { #[async_trait] impl GetNewAddressRpcOps for QtumCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn get_new_address_rpc_without_conf( &self, @@ -1161,7 +1194,7 @@ impl GetNewAddressRpcOps for QtumCoin { #[async_trait] impl AccountBalanceRpcOps for QtumCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn account_balance_rpc( &self, @@ -1173,7 +1206,7 @@ impl AccountBalanceRpcOps for QtumCoin { #[async_trait] impl InitAccountBalanceRpcOps for QtumCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn init_account_balance_rpc( &self, @@ -1185,7 +1218,7 @@ impl InitAccountBalanceRpcOps for QtumCoin { #[async_trait] impl InitScanAddressesRpcOps for QtumCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn init_scan_for_new_addresses_rpc( &self, @@ -1197,7 +1230,7 @@ impl InitScanAddressesRpcOps for QtumCoin { #[async_trait] impl InitCreateAccountRpcOps for QtumCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn init_create_account_rpc( &self, diff --git a/mm2src/coins/utxo/qtum_delegation.rs b/mm2src/coins/utxo/qtum_delegation.rs index ad9aec86e1..f7573fafbf 100644 --- a/mm2src/coins/utxo/qtum_delegation.rs +++ b/mm2src/coins/utxo/qtum_delegation.rs @@ -45,7 +45,7 @@ pub enum QtumStakingAbiError { #[display(fmt = "Invalid QRC20 ABI params: {}", _0)] InvalidParams(String), #[display(fmt = "QRC20 ABI error: {}", _0)] - AbiError(String), + ABIError(String), #[display(fmt = "Qtum POD error: {}", _0)] PodSigningError(String), #[display(fmt = "Internal error: {}", _0)] @@ -56,7 +56,7 @@ impl From for QtumStakingAbiError { fn from(e: Qrc20AbiError) -> Self { match e { Qrc20AbiError::InvalidParams(e) => QtumStakingAbiError::InvalidParams(e), - Qrc20AbiError::AbiError(e) => QtumStakingAbiError::AbiError(e), + Qrc20AbiError::ABIError(e) => QtumStakingAbiError::ABIError(e), } } } @@ -66,7 +66,7 @@ impl From for DelegationError { } impl From for QtumStakingAbiError { - fn from(e: ethabi::Error) -> QtumStakingAbiError { QtumStakingAbiError::AbiError(e.to_string()) } + fn from(e: ethabi::Error) -> QtumStakingAbiError { QtumStakingAbiError::ABIError(e.to_string()) } } impl From for DelegationError { diff --git a/mm2src/coins/utxo/rpc_clients.rs b/mm2src/coins/utxo/rpc_clients.rs index f5150e4a9a..fce6afa82a 100644 --- a/mm2src/coins/utxo/rpc_clients.rs +++ b/mm2src/coins/utxo/rpc_clients.rs @@ -1,78 +1,51 @@ #![cfg_attr(target_arch = "wasm32", allow(unused_macros))] #![cfg_attr(target_arch = "wasm32", allow(dead_code))] -use crate::utxo::utxo_block_header_storage::BlockHeaderStorage; -use crate::utxo::{output_script, output_script_p2pk, sat_from_big_decimal, GetBlockHeaderError, GetConfirmedTxError, - GetTxError, GetTxHeightError, NumConversResult, ScripthashNotification}; -use crate::{big_decimal_from_sat_unsigned, MyAddressError, NumConversError, RpcTransportEventHandler, - RpcTransportEventHandlerShared}; -use async_trait::async_trait; -use chain::{BlockHeader, BlockHeaderBits, BlockHeaderNonce, OutPoint, Transaction as UtxoTx, TransactionInput, - TxHashAlgo}; -use common::custom_futures::{select_ok_sequential, timeout::FutureTimerExt}; +mod electrum_rpc; +pub use electrum_rpc::*; + +use crate::utxo::{sat_from_big_decimal, GetBlockHeaderError, GetTxError, NumConversError, NumConversResult}; +use crate::{big_decimal_from_sat_unsigned, MyAddressError, RpcTransportEventHandlerShared}; +use chain::{OutPoint, Transaction as UtxoTx, TransactionInput, TxHashAlgo}; use common::custom_iter::TryIntoGroupMap; -use common::executor::{abortable_queue, abortable_queue::AbortableQueue, AbortableSystem, SpawnFuture, Timer}; -use common::jsonrpc_client::{JsonRpcBatchClient, JsonRpcBatchResponse, JsonRpcClient, JsonRpcError, JsonRpcErrorType, - JsonRpcId, JsonRpcMultiClient, JsonRpcRemoteAddr, JsonRpcRequest, JsonRpcRequestEnum, - JsonRpcResponse, JsonRpcResponseEnum, JsonRpcResponseFut, RpcRes}; -use common::log::{debug, LogOnError}; +use common::executor::Timer; +use common::jsonrpc_client::{JsonRpcBatchClient, JsonRpcClient, JsonRpcError, JsonRpcErrorType, JsonRpcRequest, + JsonRpcRequestEnum, JsonRpcResponseFut, RpcRes}; use common::log::{error, info, warn}; -use common::{median, now_float, now_ms, now_sec, OrdRange}; -use derive_more::Display; +use common::{median, now_sec}; use enum_derives::EnumFromStringify; -use futures::channel::oneshot as async_oneshot; -use futures::compat::{Future01CompatExt, Stream01CompatExt}; -use futures::future::{join_all, FutureExt, TryFutureExt}; -use futures::lock::Mutex as AsyncMutex; -use futures::{select, StreamExt}; -use futures01::future::select_ok; -use futures01::sync::mpsc; -use futures01::{Future, Sink, Stream}; -use http::Uri; -use itertools::Itertools; use keys::hash::H256; use keys::Address; use mm2_err_handle::prelude::*; -use mm2_number::{BigDecimal, BigInt, MmNumber}; -use mm2_rpc::data::legacy::ElectrumProtocol; -#[cfg(test)] use mocktopus::macros::*; +use mm2_number::{BigDecimal, MmNumber}; use rpc::v1::types::{Bytes as BytesJson, Transaction as RpcTransaction, H256 as H256Json}; use script::Script; -use serde_json::{self as json, Value as Json}; -use serialization::{deserialize, serialize, serialize_with_flags, CoinVariant, CompactInteger, Reader, - SERIALIZE_TRANSACTION_WITNESS}; -use sha2::{Digest, Sha256}; -use spv_validation::helpers_validation::SPVError; -use spv_validation::storage::BlockHeaderStorageOps; -use std::collections::hash_map::Entry; +use serialization::{deserialize, serialize, serialize_with_flags, CoinVariant, SERIALIZE_TRANSACTION_WITNESS}; + use std::collections::HashMap; -use std::convert::TryInto; use std::fmt; -use std::io; -use std::net::{SocketAddr, ToSocketAddrs}; +use std::fmt::Debug; use std::num::NonZeroU64; use std::ops::Deref; use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering}; use std::sync::Arc; -use std::time::Duration; -use super::ScripthashNotificationSender; +use async_trait::async_trait; +use derive_more::Display; +use futures::channel::oneshot as async_oneshot; +use futures::compat::Future01CompatExt; +use futures::future::{FutureExt, TryFutureExt}; +use futures::lock::Mutex as AsyncMutex; +use futures01::Future; +#[cfg(test)] use mocktopus::macros::*; +use serde_json::{self as json, Value as Json}; cfg_native! { - use futures::future::Either; - use futures::io::Error; + use crate::RpcTransportEventHandler; + use common::jsonrpc_client::{JsonRpcRemoteAddr, JsonRpcResponseEnum}; + use http::header::AUTHORIZATION; use http::{Request, StatusCode}; - use rustls::client::ServerCertVerified; - use rustls::{Certificate, ClientConfig, ServerName, OwnedTrustAnchor, RootCertStore}; - use std::convert::TryFrom; - use std::pin::Pin; - use std::task::{Context, Poll}; - use std::time::SystemTime; - use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, ReadBuf}; - use tokio::net::TcpStream; - use tokio_rustls::{client::TlsStream, TlsConnector}; - use webpki_roots::TLS_SERVER_ROOTS; } pub const NO_TX_ERROR_CODE: &str = "'code': -5"; @@ -80,39 +53,15 @@ const RESPONSE_TOO_LARGE_CODE: i16 = -32600; const TX_NOT_FOUND_RETRIES: u8 = 10; pub type AddressesByLabelResult = HashMap; -pub type JsonRpcPendingRequestsShared = Arc>; -pub type JsonRpcPendingRequests = HashMap>; pub type UnspentMap = HashMap>; -type ElectrumTxHistory = Vec; -type ElectrumScriptHash = String; -type ScriptHashUnspents = Vec; - #[derive(Debug, Deserialize)] #[allow(dead_code)] pub struct AddressPurpose { purpose: String, } -/// Skips the server certificate verification on TLS connection -pub struct NoCertificateVerification {} - -#[cfg(not(target_arch = "wasm32"))] -impl rustls::client::ServerCertVerifier for NoCertificateVerification { - fn verify_server_cert( - &self, - _: &Certificate, - _: &[Certificate], - _: &ServerName, - _: &mut dyn Iterator, - _: &[u8], - _: SystemTime, - ) -> Result { - Ok(rustls::client::ServerCertVerified::assertion()) - } -} - -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum UtxoRpcClientEnum { Native(NativeClient), Electrum(ElectrumClient), @@ -145,15 +94,6 @@ impl Deref for UtxoRpcClientEnum { } } -impl Clone for UtxoRpcClientEnum { - fn clone(&self) -> Self { - match self { - UtxoRpcClientEnum::Native(c) => UtxoRpcClientEnum::Native(c.clone()), - UtxoRpcClientEnum::Electrum(c) => UtxoRpcClientEnum::Electrum(c.clone()), - } - } -} - impl UtxoRpcClientEnum { pub fn wait_for_confirmations( &self, @@ -306,6 +246,14 @@ pub struct SpentOutputInfo { pub spent_in_block: BlockHashOrHeight, } +#[allow(clippy::upper_case_acronyms)] +#[derive(Debug, Deserialize, Serialize)] +pub enum EstimateFeeMode { + ECONOMICAL, + CONSERVATIVE, + UNSET, +} + pub type UtxoRpcResult = Result>; pub type UtxoRpcFut = Box> + Send + 'static>; @@ -381,7 +329,8 @@ pub trait UtxoRpcClientOps: fmt::Debug + Send + Sync + 'static { /// Submits the raw `tx` transaction (serialized, hex-encoded) to blockchain network. fn send_raw_transaction(&self, tx: BytesJson) -> UtxoRpcFut; - fn blockchain_scripthash_subscribe(&self, scripthash: String) -> UtxoRpcFut; + /// Subscribe to scripthash notifications from `server_address` for the given `scripthash`. + fn blockchain_scripthash_subscribe_using(&self, server_address: &str, scripthash: String) -> UtxoRpcFut; /// Returns raw transaction (serialized, hex-encoded) by the given `txid`. fn get_transaction_bytes(&self, txid: &H256Json) -> UtxoRpcFut; @@ -656,6 +605,66 @@ pub struct ListUnspentArgs { addresses: Vec, } +#[derive(Debug)] +struct ConcurrentRequestState { + is_running: bool, + subscribers: Vec>, +} + +impl ConcurrentRequestState { + fn new() -> Self { + ConcurrentRequestState { + is_running: false, + subscribers: Vec::new(), + } + } +} + +#[derive(Debug)] +pub struct ConcurrentRequestMap { + inner: AsyncMutex>>, +} + +impl Default for ConcurrentRequestMap { + fn default() -> Self { + ConcurrentRequestMap { + inner: AsyncMutex::new(HashMap::new()), + } + } +} + +impl ConcurrentRequestMap { + pub fn new() -> ConcurrentRequestMap { ConcurrentRequestMap::default() } + + async fn wrap_request(&self, request_arg: K, request_fut: RpcRes) -> Result { + let mut map = self.inner.lock().await; + let state = map + .entry(request_arg.clone()) + .or_insert_with(ConcurrentRequestState::new); + if state.is_running { + let (tx, rx) = async_oneshot::channel(); + state.subscribers.push(tx); + // drop here to avoid holding the lock during await + drop(map); + rx.await.unwrap() + } else { + state.is_running = true; + // drop here to avoid holding the lock during await + drop(map); + let request_res = request_fut.compat().await; + let mut map = self.inner.lock().await; + let state = map.get_mut(&request_arg).unwrap(); + for sub in state.subscribers.drain(..) { + if sub.send(request_res.clone()).is_err() { + warn!("subscriber is dropped"); + } + } + state.is_running = false; + request_res + } + } +} + /// RPC client for UTXO based coins /// https://developer.bitcoin.org/reference/rpc/index.html - Bitcoin RPC API reference /// Other coins have additional methods or miss some of these @@ -711,7 +720,7 @@ impl UtxoJsonRpcClientInfo for NativeClientImpl { impl JsonRpcClient for NativeClientImpl { fn version(&self) -> &'static str { "1.0" } - fn next_id(&self) -> String { self.request_id.fetch_add(1, AtomicOrdering::Relaxed).to_string() } + fn next_id(&self) -> u64 { self.request_id.fetch_add(1, AtomicOrdering::Relaxed) } fn client_info(&self) -> String { UtxoJsonRpcClientInfo::client_info(self) } @@ -732,10 +741,10 @@ impl JsonRpcClient for NativeClientImpl { self.event_handlers.on_outgoing_request(request_body.as_bytes()); let uri = self.uri.clone(); - + let auth = self.auth.clone(); let http_request = try_f!(Request::builder() .method("POST") - .header(AUTHORIZATION, self.auth.clone()) + .header(AUTHORIZATION, auth) .uri(uri.clone()) .body(Vec::from(request_body)) .map_err(|e| JsonRpcErrorType::InvalidRequest(e.to_string()))); @@ -828,7 +837,7 @@ impl UtxoRpcClientOps for NativeClient { Box::new(rpc_func!(self, "sendrawtransaction", tx).map_to_mm_fut(UtxoRpcError::from)) } - fn blockchain_scripthash_subscribe(&self, _scripthash: String) -> UtxoRpcFut { + fn blockchain_scripthash_subscribe_using(&self, _: &str, _scripthash: String) -> UtxoRpcFut { Box::new(futures01::future::err( UtxoRpcError::Internal("blockchain_scripthash_subscribe` is not supported for Native Clients".to_owned()) .into(), @@ -1225,1858 +1234,6 @@ impl NativeClientImpl { } } -#[derive(Clone, Debug, Deserialize)] -pub struct ElectrumUnspent { - pub height: Option, - pub tx_hash: H256Json, - pub tx_pos: u32, - pub value: u64, -} - -#[derive(Clone, Debug, Deserialize)] -#[serde(untagged)] -pub enum ElectrumNonce { - Number(u64), - Hash(H256Json), -} - -#[allow(clippy::from_over_into)] -impl Into for ElectrumNonce { - fn into(self) -> BlockHeaderNonce { - match self { - ElectrumNonce::Number(n) => BlockHeaderNonce::U32(n as u32), - ElectrumNonce::Hash(h) => BlockHeaderNonce::H256(h.into()), - } - } -} - -#[derive(Debug, Deserialize)] -pub struct ElectrumBlockHeadersRes { - pub count: u64, - pub hex: BytesJson, - #[allow(dead_code)] - max: u64, -} - -/// The block header compatible with Electrum 1.2 -#[derive(Clone, Debug, Deserialize)] -pub struct ElectrumBlockHeaderV12 { - pub bits: u64, - pub block_height: u64, - pub merkle_root: H256Json, - pub nonce: ElectrumNonce, - pub prev_block_hash: H256Json, - pub timestamp: u64, - pub version: u64, -} - -impl ElectrumBlockHeaderV12 { - fn as_block_header(&self) -> BlockHeader { - BlockHeader { - version: self.version as u32, - previous_header_hash: self.prev_block_hash.into(), - merkle_root_hash: self.merkle_root.into(), - claim_trie_root: None, - hash_final_sapling_root: None, - time: self.timestamp as u32, - bits: BlockHeaderBits::U32(self.bits as u32), - nonce: self.nonce.clone().into(), - solution: None, - aux_pow: None, - prog_pow: None, - mtp_pow: None, - is_verus: false, - hash_state_root: None, - hash_utxo_root: None, - prevout_stake: None, - vch_block_sig_dlgt: None, - n_height: None, - n_nonce_u64: None, - mix_hash: None, - } - } - - #[inline] - pub fn as_hex(&self) -> String { - let block_header = self.as_block_header(); - let serialized = serialize(&block_header); - hex::encode(serialized) - } - - #[inline] - pub fn hash(&self) -> H256Json { - let block_header = self.as_block_header(); - BlockHeader::hash(&block_header).into() - } -} - -/// The block header compatible with Electrum 1.4 -#[derive(Clone, Debug, Deserialize)] -pub struct ElectrumBlockHeaderV14 { - pub height: u64, - pub hex: BytesJson, -} - -impl ElectrumBlockHeaderV14 { - pub fn hash(&self) -> H256Json { self.hex.clone().into_vec()[..].into() } -} - -#[derive(Clone, Debug, Deserialize)] -#[serde(untagged)] -pub enum ElectrumBlockHeader { - V12(ElectrumBlockHeaderV12), - V14(ElectrumBlockHeaderV14), -} - -/// The merkle branch of a confirmed transaction -#[derive(Clone, Debug, Deserialize)] -pub struct TxMerkleBranch { - pub merkle: Vec, - pub block_height: u64, - pub pos: usize, -} - -#[derive(Clone)] -pub struct ConfirmedTransactionInfo { - pub tx: UtxoTx, - pub header: BlockHeader, - pub index: u64, - pub height: u64, -} - -#[derive(Debug, PartialEq)] -pub struct BestBlock { - pub height: u64, - pub hash: H256Json, -} - -impl From for BestBlock { - fn from(block_header: ElectrumBlockHeader) -> Self { - BestBlock { - height: block_header.block_height(), - hash: block_header.block_hash(), - } - } -} - -#[allow(clippy::upper_case_acronyms)] -#[derive(Debug, Deserialize, Serialize)] -pub enum EstimateFeeMode { - ECONOMICAL, - CONSERVATIVE, - UNSET, -} - -impl ElectrumBlockHeader { - pub fn block_height(&self) -> u64 { - match self { - ElectrumBlockHeader::V12(h) => h.block_height, - ElectrumBlockHeader::V14(h) => h.height, - } - } - - fn block_hash(&self) -> H256Json { - match self { - ElectrumBlockHeader::V12(h) => h.hash(), - ElectrumBlockHeader::V14(h) => h.hash(), - } - } -} - -#[derive(Debug, Deserialize)] -pub struct ElectrumTxHistoryItem { - pub height: i64, - pub tx_hash: H256Json, - pub fee: Option, -} - -#[derive(Clone, Debug, Deserialize)] -pub struct ElectrumBalance { - pub(crate) confirmed: i128, - pub(crate) unconfirmed: i128, -} - -impl ElectrumBalance { - #[inline] - pub fn to_big_decimal(&self, decimals: u8) -> BigDecimal { - let balance_sat = BigInt::from(self.confirmed) + BigInt::from(self.unconfirmed); - BigDecimal::from(balance_sat) / BigDecimal::from(10u64.pow(decimals as u32)) - } -} - -#[inline] -fn sha_256(input: &[u8]) -> Vec { - let mut sha = Sha256::new(); - sha.update(input); - sha.finalize().to_vec() -} - -#[inline] -pub fn electrum_script_hash(script: &[u8]) -> Vec { - let mut result = sha_256(script); - result.reverse(); - result -} - -#[derive(Debug, Deserialize, Serialize)] -/// Deserializable Electrum protocol version representation for RPC -/// https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-methods.html#server.version -pub struct ElectrumProtocolVersion { - pub server_software_version: String, - pub protocol_version: String, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -/// Electrum request RPC representation -pub struct ElectrumRpcRequest { - pub url: String, - #[serde(default)] - pub protocol: ElectrumProtocol, - #[serde(default)] - pub disable_cert_verification: bool, -} - -/// Electrum client configuration -#[allow(clippy::upper_case_acronyms)] -#[cfg(not(target_arch = "wasm32"))] -#[derive(Clone, Debug, Serialize)] -enum ElectrumConfig { - TCP, - SSL { dns_name: String, skip_validation: bool }, -} - -/// Electrum client configuration -#[allow(clippy::upper_case_acronyms)] -#[cfg(target_arch = "wasm32")] -#[derive(Clone, Debug, Serialize)] -enum ElectrumConfig { - WS, - WSS, -} - -fn addr_to_socket_addr(input: &str) -> Result { - let mut addr = match input.to_socket_addrs() { - Ok(a) => a, - Err(e) => return ERR!("{} resolve error {:?}", input, e), - }; - match addr.next() { - Some(a) => Ok(a), - None => ERR!("{} resolved to None.", input), - } -} - -#[cfg(not(target_arch = "wasm32"))] -fn server_name_from_domain(dns_name: &str) -> Result { - match ServerName::try_from(dns_name) { - Ok(dns_name) if matches!(dns_name, ServerName::DnsName(_)) => Ok(dns_name), - _ => ERR!("Couldn't parse DNS name from '{}'", dns_name), - } -} - -/// Attempts to process the request (parse url, etc), build up the config and create new electrum connection -/// The function takes `abortable_system` that will be used to spawn Electrum's related futures. -#[cfg(not(target_arch = "wasm32"))] -pub fn spawn_electrum( - req: &ElectrumRpcRequest, - event_handlers: Vec, - scripthash_notification_sender: &ScripthashNotificationSender, - abortable_system: AbortableQueue, -) -> Result { - let config = match req.protocol { - ElectrumProtocol::TCP => ElectrumConfig::TCP, - ElectrumProtocol::SSL => { - let uri: Uri = try_s!(req.url.parse()); - let host = uri - .host() - .ok_or(ERRL!("Couldn't retrieve host from addr {}", req.url))?; - - try_s!(server_name_from_domain(host)); - - ElectrumConfig::SSL { - dns_name: host.into(), - skip_validation: req.disable_cert_verification, - } - }, - ElectrumProtocol::WS | ElectrumProtocol::WSS => { - return ERR!("'ws' and 'wss' protocols are not supported yet. Consider using 'TCP' or 'SSL'") - }, - }; - - Ok(electrum_connect( - req.url.clone(), - config, - event_handlers, - scripthash_notification_sender, - abortable_system, - )) -} - -/// Attempts to process the request (parse url, etc), build up the config and create new electrum connection -/// The function takes `abortable_system` that will be used to spawn Electrum's related futures. -#[cfg(target_arch = "wasm32")] -pub fn spawn_electrum( - req: &ElectrumRpcRequest, - event_handlers: Vec, - scripthash_notification_sender: &ScripthashNotificationSender, - abortable_system: AbortableQueue, -) -> Result { - let mut url = req.url.clone(); - let uri: Uri = try_s!(req.url.parse()); - - if uri.scheme().is_some() { - return ERR!( - "There has not to be a scheme in the url: {}. \ - 'ws://' scheme is used by default. \ - Consider using 'protocol: \"WSS\"' in the electrum request to switch to the 'wss://' scheme.", - url - ); - } - - let config = match req.protocol { - ElectrumProtocol::WS => { - url.insert_str(0, "ws://"); - ElectrumConfig::WS - }, - ElectrumProtocol::WSS => { - url.insert_str(0, "wss://"); - ElectrumConfig::WSS - }, - ElectrumProtocol::TCP | ElectrumProtocol::SSL => { - return ERR!("'TCP' and 'SSL' are not supported in a browser. Please use 'WS' or 'WSS' protocols"); - }, - }; - - Ok(electrum_connect( - url, - config, - event_handlers, - scripthash_notification_sender, - abortable_system, - )) -} - -/// Represents the active Electrum connection to selected address -pub struct ElectrumConnection { - /// The client connected to this SocketAddr - addr: String, - /// Configuration - #[allow(dead_code)] - config: ElectrumConfig, - /// The Sender forwarding requests to writing part of underlying stream - tx: Arc>>>>, - /// Responses are stored here - responses: JsonRpcPendingRequestsShared, - /// Selected protocol version. The value is initialized after the server.version RPC call. - protocol_version: AsyncMutex>, - /// This spawner is used to spawn Electrum's related futures that should be aborted on coin deactivation. - /// and on [`MmArc::stop`]. - /// This field is not used directly, but it holds all abort handles of futures spawned at `electrum_connect`. - /// - /// Please also note that this abortable system is a subsystem of [`ElectrumClientImpl::abortable_system`]. - /// For more info see [`ElectrumClientImpl::add_server`]. - _abortable_system: AbortableQueue, -} - -impl ElectrumConnection { - async fn is_connected(&self) -> bool { self.tx.lock().await.is_some() } - - async fn set_protocol_version(&self, version: f32) { self.protocol_version.lock().await.replace(version); } - - async fn reset_protocol_version(&self) { *self.protocol_version.lock().await = None; } -} - -#[derive(Debug)] -struct ConcurrentRequestState { - is_running: bool, - subscribers: Vec>, -} - -impl ConcurrentRequestState { - fn new() -> Self { - ConcurrentRequestState { - is_running: false, - subscribers: Vec::new(), - } - } -} - -#[derive(Debug)] -pub struct ConcurrentRequestMap { - inner: AsyncMutex>>, -} - -impl Default for ConcurrentRequestMap { - fn default() -> Self { - ConcurrentRequestMap { - inner: AsyncMutex::new(HashMap::new()), - } - } -} - -impl ConcurrentRequestMap { - pub fn new() -> ConcurrentRequestMap { ConcurrentRequestMap::default() } - - async fn wrap_request(&self, request_arg: K, request_fut: RpcRes) -> Result { - let mut map = self.inner.lock().await; - let state = map - .entry(request_arg.clone()) - .or_insert_with(ConcurrentRequestState::new); - if state.is_running { - let (tx, rx) = async_oneshot::channel(); - state.subscribers.push(tx); - // drop here to avoid holding the lock during await - drop(map); - rx.await.unwrap() - } else { - // drop here to avoid holding the lock during await - drop(map); - let request_res = request_fut.compat().await; - let mut map = self.inner.lock().await; - let state = map.get_mut(&request_arg).unwrap(); - for sub in state.subscribers.drain(..) { - if sub.send(request_res.clone()).is_err() { - warn!("subscriber is dropped"); - } - } - state.is_running = false; - request_res - } - } -} - -#[derive(Debug)] -pub struct ElectrumClientImpl { - coin_ticker: String, - connections: AsyncMutex>, - next_id: AtomicU64, - event_handlers: Vec, - protocol_version: OrdRange, - get_balance_concurrent_map: ConcurrentRequestMap, - list_unspent_concurrent_map: ConcurrentRequestMap>, - block_headers_storage: BlockHeaderStorage, - /// This spawner is used to spawn Electrum's related futures that should be aborted on coin deactivation, - /// and on [`MmArc::stop`]. - /// - /// Please also note that this abortable system is a subsystem of [`UtxoCoinFields::abortable_system`]. - abortable_system: AbortableQueue, - negotiate_version: bool, - /// This is used for balance event streaming implementation for UTXOs. - /// If balance event streaming isn't enabled, this value will always be `None`; otherwise, - /// it will be used for sending scripthash messages to trigger re-connections, re-fetching the balances, etc. - pub(crate) scripthash_notification_sender: ScripthashNotificationSender, -} - -async fn electrum_request_multi( - client: ElectrumClient, - request: JsonRpcRequestEnum, -) -> Result<(JsonRpcRemoteAddr, JsonRpcResponseEnum), JsonRpcErrorType> { - let mut futures = vec![]; - let connections = client.connections.lock().await; - for (i, connection) in connections.iter().enumerate() { - if client.negotiate_version && connection.protocol_version.lock().await.is_none() { - continue; - } - - let connection_addr = connection.addr.clone(); - let json = json::to_string(&request).map_err(|e| JsonRpcErrorType::InvalidRequest(e.to_string()))?; - if let Some(tx) = &*connection.tx.lock().await { - let fut = electrum_request( - json, - request.rpc_id(), - tx.clone(), - connection.responses.clone(), - ELECTRUM_TIMEOUT / (connections.len() - i) as u64, - ) - .map(|response| (JsonRpcRemoteAddr(connection_addr), response)); - futures.push(fut) - } - } - drop(connections); - - if futures.is_empty() { - return Err(JsonRpcErrorType::Transport( - "All electrums are currently disconnected".to_string(), - )); - } - - if let JsonRpcRequestEnum::Single(single) = &request { - if single.method == "server.ping" { - // server.ping must be sent to all servers to keep all connections alive - return select_ok(futures).map(|(result, _)| result).compat().await; - } - } - - let (res, no_of_failed_requests) = select_ok_sequential(futures) - .compat() - .await - .map_err(|e| JsonRpcErrorType::Transport(format!("{:?}", e)))?; - client.rotate_servers(no_of_failed_requests).await; - - Ok(res) -} - -async fn electrum_request_to( - client: ElectrumClient, - request: JsonRpcRequestEnum, - to_addr: String, -) -> Result<(JsonRpcRemoteAddr, JsonRpcResponseEnum), JsonRpcErrorType> { - let (tx, responses) = { - let connections = client.connections.lock().await; - let connection = connections - .iter() - .find(|c| c.addr == to_addr) - .ok_or_else(|| JsonRpcErrorType::Internal(format!("Unknown destination address {}", to_addr)))?; - let responses = connection.responses.clone(); - let tx = { - match &*connection.tx.lock().await { - Some(tx) => tx.clone(), - None => { - return Err(JsonRpcErrorType::Transport(format!( - "Connection {} is not established yet", - to_addr - ))) - }, - } - }; - (tx, responses) - }; - let json = json::to_string(&request).map_err(|err| JsonRpcErrorType::InvalidRequest(err.to_string()))?; - let response = electrum_request(json, request.rpc_id(), tx, responses, ELECTRUM_TIMEOUT) - .compat() - .await?; - Ok((JsonRpcRemoteAddr(to_addr.to_owned()), response)) -} - -impl ElectrumClientImpl { - pub fn spawner(&self) -> abortable_queue::WeakSpawner { self.abortable_system.weak_spawner() } - - /// Create an Electrum connection and spawn a green thread actor to handle it. - pub async fn add_server(&self, req: &ElectrumRpcRequest) -> Result<(), String> { - let subsystem = try_s!(self.abortable_system.create_subsystem()); - let connection = try_s!(spawn_electrum( - req, - self.event_handlers.clone(), - &self.scripthash_notification_sender, - subsystem, - )); - self.connections.lock().await.push(connection); - Ok(()) - } - - /// Remove an Electrum connection and stop corresponding spawned actor. - 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) - .ok_or(ERRL!("Unknown electrum address {}", server_addr))?; - // shutdown_tx will be closed immediately on the connection drop - connections.remove(pos); - Ok(()) - } - - /// Moves the Electrum servers that fail in a multi request to the end. - pub async fn rotate_servers(&self, no_of_rotations: usize) { - let mut connections = self.connections.lock().await; - connections.rotate_left(no_of_rotations); - } - - /// Check if one of the spawned connections is connected. - pub async fn is_connected(&self) -> bool { - for connection in self.connections.lock().await.iter() { - if connection.is_connected().await { - return true; - } - } - false - } - - /// Check if all connections have been removed. - pub async fn is_connections_pool_empty(&self) -> bool { self.connections.lock().await.is_empty() } - - 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 { - for connection in self.connections.lock().await.iter() { - if connection.protocol_version.lock().await.is_some() { - return true; - } - } - false - } - - /// 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) - .ok_or(ERRL!("Unknown electrum address {}", server_addr))?; - con.set_protocol_version(version).await; - - if let Some(sender) = &self.scripthash_notification_sender { - sender - .unbounded_send(ScripthashNotification::RefreshSubscriptions) - .map_err(|e| ERRL!("Failed sending scripthash message. {}", e))?; - } - - Ok(()) - } - - /// Reset the protocol version for the specified server. - pub async fn reset_protocol_version(&self, server_addr: &str) -> Result<(), String> { - let connections = self.connections.lock().await; - let con = connections - .iter() - .find(|con| con.addr == server_addr) - .ok_or(ERRL!("Unknown electrum address {}", server_addr))?; - con.reset_protocol_version().await; - Ok(()) - } - - /// Get available protocol versions. - pub fn protocol_version(&self) -> &OrdRange { &self.protocol_version } - - /// Get block headers storage. - pub fn block_headers_storage(&self) -> &BlockHeaderStorage { &self.block_headers_storage } -} - -#[derive(Clone, Debug)] -pub struct ElectrumClient(pub Arc); -impl Deref for ElectrumClient { - type Target = ElectrumClientImpl; - fn deref(&self) -> &ElectrumClientImpl { &self.0 } -} - -const BLOCKCHAIN_HEADERS_SUB_ID: &str = "blockchain.headers.subscribe"; - -const BLOCKCHAIN_SCRIPTHASH_SUB_ID: &str = "blockchain.scripthash.subscribe"; - -impl UtxoJsonRpcClientInfo for ElectrumClient { - 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 client_info(&self) -> String { UtxoJsonRpcClientInfo::client_info(self) } - - fn transport(&self, request: JsonRpcRequestEnum) -> JsonRpcResponseFut { - Box::new(electrum_request_multi(self.clone(), request).boxed().compat()) - } -} - -impl JsonRpcBatchClient for ElectrumClient {} - -impl JsonRpcMultiClient for ElectrumClient { - fn transport_exact(&self, to_addr: String, request: JsonRpcRequestEnum) -> JsonRpcResponseFut { - Box::new(electrum_request_to(self.clone(), request, to_addr).boxed().compat()) - } -} - -impl ElectrumClient { - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#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 { - 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) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-headers-subscribe - pub fn get_block_count_from(&self, server_address: &str) -> RpcRes { - Box::new( - rpc_func_from!(self, server_address, BLOCKCHAIN_HEADERS_SUB_ID) - .map(|r: ElectrumBlockHeader| r.block_height()), - ) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-block-headers - pub fn get_block_headers_from( - &self, - server_address: &str, - start_height: u64, - count: NonZeroU64, - ) -> RpcRes { - rpc_func_from!(self, server_address, "blockchain.block.headers", start_height, count) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-listunspent - /// It can return duplicates sometimes: https://github.com/artemii235/SuperNET/issues/269 - /// We should remove them to build valid transactions - pub fn scripthash_list_unspent(&self, hash: &str) -> RpcRes> { - let request_fut = 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, unspent.tx_pos)) { - Entry::Occupied(_) => false, - Entry::Vacant(e) => { - e.insert(true); - true - }, - }) - .collect(); - Ok(unspents) - }, - )); - let arc = self.clone(); - let hash = hash.to_owned(); - let fut = async move { arc.list_unspent_concurrent_map.wrap_request(hash, request_fut).await }; - Box::new(fut.boxed().compat()) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-listunspent - /// It can return duplicates sometimes: https://github.com/artemii235/SuperNET/issues/269 - /// We should remove them to build valid transactions. - /// Please note the function returns `ScriptHashUnspents` elements in the same order in which they were requested. - pub fn scripthash_list_unspent_batch(&self, hashes: Vec) -> RpcRes> { - let requests = hashes - .iter() - .map(|hash| rpc_req!(self, "blockchain.scripthash.listunspent", hash)); - Box::new(self.batch_rpc(requests).map(move |unspents: Vec| { - unspents - .into_iter() - .map(|hash_unspents| { - hash_unspents - .into_iter() - .unique_by(|unspent| (unspent.tx_hash, unspent.tx_pos)) - .collect::>() - }) - .collect() - })) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-get-history - pub fn scripthash_get_history(&self, hash: &str) -> RpcRes { - rpc_func!(self, "blockchain.scripthash.get_history", hash) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-get-history - /// Requests history of the `hashes` in a batch and returns them in the same order they were requested. - pub fn scripthash_get_history_batch(&self, hashes: I) -> RpcRes> - where - I: IntoIterator, - { - let requests = hashes - .into_iter() - .map(|hash| rpc_req!(self, "blockchain.scripthash.get_history", hash)); - self.batch_rpc(requests) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-gethistory - pub fn scripthash_get_balance(&self, hash: &str) -> RpcRes { - let arc = self.clone(); - let hash = hash.to_owned(); - let fut = async move { - let request = rpc_func!(arc, "blockchain.scripthash.get_balance", &hash); - arc.get_balance_concurrent_map.wrap_request(hash, request).await - }; - Box::new(fut.boxed().compat()) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-gethistory - /// Requests balances in a batch and returns them in the same order they were requested. - pub fn scripthash_get_balances(&self, hashes: I) -> RpcRes> - where - I: IntoIterator, - { - let requests = hashes - .into_iter() - .map(|hash| rpc_req!(self, "blockchain.scripthash.get_balance", &hash)); - self.batch_rpc(requests) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-headers-subscribe - pub fn blockchain_headers_subscribe(&self) -> RpcRes { - rpc_func!(self, BLOCKCHAIN_HEADERS_SUB_ID) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-broadcast - pub fn blockchain_transaction_broadcast(&self, tx: BytesJson) -> RpcRes { - rpc_func!(self, "blockchain.transaction.broadcast", tx) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-estimatefee - /// It is recommended to set n_blocks as low as possible. - /// However, in some cases, n_blocks = 1 leads to an unreasonably high fee estimation. - /// https://github.com/KomodoPlatform/atomicDEX-API/issues/656#issuecomment-743759659 - pub fn estimate_fee(&self, mode: &Option, n_blocks: u32) -> UtxoRpcFut { - match mode { - Some(m) => { - Box::new(rpc_func!(self, "blockchain.estimatefee", n_blocks, m).map_to_mm_fut(UtxoRpcError::from)) - }, - None => Box::new(rpc_func!(self, "blockchain.estimatefee", n_blocks).map_to_mm_fut(UtxoRpcError::from)), - } - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-block-header - pub fn blockchain_block_header(&self, height: u64) -> RpcRes { - rpc_func!(self, "blockchain.block.header", height) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-block-headers - pub fn blockchain_block_headers(&self, start_height: u64, count: NonZeroU64) -> RpcRes { - rpc_func!(self, "blockchain.block.headers", start_height, count) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get-merkle - pub fn blockchain_transaction_get_merkle(&self, txid: H256Json, height: u64) -> RpcRes { - rpc_func!(self, "blockchain.transaction.get_merkle", txid, height) - } - - // get_tx_height_from_rpc is costly since it loops through history after requesting the whole history of the script pubkey - // This method should always be used if the block headers are saved to the DB - async fn get_tx_height_from_storage(&self, tx: &UtxoTx) -> Result> { - let tx_hash = tx.hash().reversed(); - let blockhash = self.get_verbose_transaction(&tx_hash.into()).compat().await?.blockhash; - Ok(self - .block_headers_storage() - .get_block_height_by_hash(blockhash.into()) - .await? - .ok_or_else(|| { - GetTxHeightError::HeightNotFound(format!( - "Transaction block header is not found in storage for {}", - self.0.coin_ticker - )) - })? - .try_into()?) - } - - // get_tx_height_from_storage is always preferred to be used instead of this, but if there is no headers in storage (storing headers is not enabled) - // this function can be used instead - async fn get_tx_height_from_rpc(&self, tx: &UtxoTx) -> Result { - for output in tx.outputs.clone() { - let script_pubkey_str = hex::encode(electrum_script_hash(&output.script_pubkey)); - if let Ok(history) = self.scripthash_get_history(script_pubkey_str.as_str()).compat().await { - if let Some(item) = history - .into_iter() - .find(|item| item.tx_hash.reversed() == H256Json(*tx.hash()) && item.height > 0) - { - return Ok(item.height as u64); - } - } - } - Err(GetTxHeightError::HeightNotFound(format!( - "Couldn't find height through electrum for {}", - self.coin_ticker - ))) - } - - async fn block_header_from_storage(&self, height: u64) -> Result> { - self.block_headers_storage() - .get_block_header(height) - .await? - .ok_or_else(|| { - GetBlockHeaderError::Internal(format!("Header not found in storage for {}", self.coin_ticker)).into() - }) - } - - async fn block_header_from_storage_or_rpc(&self, height: u64) -> Result> { - match self.block_header_from_storage(height).await { - Ok(h) => Ok(h), - Err(_) => Ok(deserialize( - self.blockchain_block_header(height).compat().await?.as_slice(), - )?), - } - } - - pub async fn get_confirmed_tx_info_from_rpc( - &self, - tx: &UtxoTx, - ) -> Result { - let height = self.get_tx_height_from_rpc(tx).await?; - - let merkle_branch = self - .blockchain_transaction_get_merkle(tx.hash().reversed().into(), height) - .compat() - .await?; - - let header = deserialize(self.blockchain_block_header(height).compat().await?.as_slice())?; - - Ok(ConfirmedTransactionInfo { - tx: tx.clone(), - header, - index: merkle_branch.pos as u64, - height, - }) - } - - pub async fn get_merkle_and_validated_header( - &self, - tx: &UtxoTx, - ) -> Result<(TxMerkleBranch, BlockHeader, u64), MmError> { - let height = self.get_tx_height_from_storage(tx).await?; - - let merkle_branch = self - .blockchain_transaction_get_merkle(tx.hash().reversed().into(), height) - .compat() - .await - .map_to_mm(|err| SPVError::UnableToGetMerkle { - coin: self.coin_ticker.clone(), - err: err.to_string(), - })?; - - let header = self.block_header_from_storage(height).await?; - - Ok((merkle_branch, header, height)) - } -} - -#[cfg_attr(test, mockable)] -impl ElectrumClient { - pub fn retrieve_headers_from( - &self, - server_address: &str, - from_height: u64, - to_height: u64, - ) -> UtxoRpcFut<(HashMap, Vec)> { - let coin_name = self.coin_ticker.clone(); - if from_height == 0 || to_height < from_height { - return Box::new(futures01::future::err( - UtxoRpcError::Internal("Invalid values for from/to parameters".to_string()).into(), - )); - } - let count: NonZeroU64 = match (to_height - from_height + 1).try_into() { - Ok(c) => c, - Err(e) => return Box::new(futures01::future::err(UtxoRpcError::Internal(e.to_string()).into())), - }; - Box::new( - self.get_block_headers_from(server_address, from_height, count) - .map_to_mm_fut(UtxoRpcError::from) - .and_then(move |headers| { - let (block_registry, block_headers) = { - if headers.count == 0 { - return MmError::err(UtxoRpcError::Internal("No headers available".to_string())); - } - let len = CompactInteger::from(headers.count); - let mut serialized = serialize(&len).take(); - serialized.extend(headers.hex.0.into_iter()); - drop_mutability!(serialized); - let mut reader = - Reader::new_with_coin_variant(serialized.as_slice(), coin_name.as_str().into()); - let maybe_block_headers = reader.read_list::(); - let block_headers = match maybe_block_headers { - Ok(headers) => headers, - Err(e) => return MmError::err(UtxoRpcError::InvalidResponse(format!("{:?}", e))), - }; - let mut block_registry: HashMap = HashMap::with_capacity(block_headers.len()); - let mut starting_height = from_height; - for block_header in &block_headers { - block_registry.insert(starting_height, block_header.clone()); - starting_height += 1; - } - (block_registry, block_headers) - }; - Ok((block_registry, block_headers)) - }), - ) - } - - pub(crate) fn get_servers_with_latest_block_count(&self) -> UtxoRpcFut<(Vec, u64)> { - let selfi = self.clone(); - let fut = async move { - let connections = selfi.connections.lock().await; - let futures = connections - .iter() - .map(|connection| { - let addr = connection.addr.clone(); - selfi - .get_block_count_from(&addr) - .map(|response| (addr, response)) - .compat() - }) - .collect::>(); - drop(connections); - - let responses = join_all(futures).await; - - // First, we use filter_map to get rid of any errors and collect the - // server addresses and block counts into two vectors - let (responding_servers, block_counts_from_all_servers): (Vec<_>, Vec<_>) = - responses.clone().into_iter().filter_map(|res| res.ok()).unzip(); - - // Next, we use max to find the maximum block count from all servers - if let Some(max_block_count) = block_counts_from_all_servers.clone().iter().max() { - // Then, we use filter and collect to get the servers that have the maximum block count - let servers_with_max_count: Vec<_> = responding_servers - .into_iter() - .zip(block_counts_from_all_servers) - .filter(|(_, count)| count == max_block_count) - .map(|(addr, _)| addr) - .collect(); - - // Finally, we return a tuple of servers with max count and the max count - return Ok((servers_with_max_count, *max_block_count)); - } - - Err(MmError::new(UtxoRpcError::Internal(format!( - "Couldn't get block count from any server for {}, responses: {:?}", - &selfi.coin_ticker, responses - )))) - }; - - Box::new(fut.boxed().compat()) - } -} - -// if mockable is placed before async_trait there is `munmap_chunk(): invalid pointer` error on async fn mocking attempt -#[async_trait] -#[cfg_attr(test, mockable)] -impl UtxoRpcClientOps for ElectrumClient { - fn list_unspent(&self, address: &Address, _decimals: u8) -> UtxoRpcFut> { - let mut output_scripts = vec![try_f!(output_script(address))]; - - // If the plain pubkey is available, fetch the UTXOs found in P2PK outputs as well (if any). - if let Some(pubkey) = address.pubkey() { - let p2pk_output_script = output_script_p2pk(pubkey); - output_scripts.push(p2pk_output_script); - } - - let this = self.clone(); - let fut = async move { - let hashes = output_scripts - .iter() - .map(|s| hex::encode(electrum_script_hash(s))) - .collect(); - let unspents = this.scripthash_list_unspent_batch(hashes).compat().await?; - - let unspents = unspents - .into_iter() - .zip(output_scripts) - .flat_map(|(unspents, output_script)| { - unspents - .into_iter() - .map(move |unspent| UnspentInfo::from_electrum(unspent, output_script.clone())) - }) - .collect(); - Ok(unspents) - }; - - Box::new(fut.boxed().compat()) - } - - fn list_unspent_group(&self, addresses: Vec
, _decimals: u8) -> UtxoRpcFut { - let output_scripts = try_f!(addresses - .iter() - .map(output_script) - .collect::, keys::Error>>()); - - let this = self.clone(); - let fut = async move { - let hashes = output_scripts - .iter() - .map(|s| hex::encode(electrum_script_hash(s))) - .collect(); - let unspents = this.scripthash_list_unspent_batch(hashes).compat().await?; - - let unspents: Vec> = unspents - .into_iter() - .zip(output_scripts) - .map(|(unspents, output_script)| { - unspents - .into_iter() - .map(|unspent| UnspentInfo::from_electrum(unspent, output_script.clone())) - .collect() - }) - .collect(); - - let unspent_map = addresses - .into_iter() - // `scripthash_list_unspent_batch` returns `ScriptHashUnspents` elements in the same order in which they were requested. - // So we can zip `addresses` and `unspents` into one iterator. - .zip(unspents) - .collect(); - Ok(unspent_map) - }; - Box::new(fut.boxed().compat()) - } - - fn send_transaction(&self, tx: &UtxoTx) -> UtxoRpcFut { - let bytes = if tx.has_witness() { - BytesJson::from(serialize_with_flags(tx, SERIALIZE_TRANSACTION_WITNESS)) - } else { - BytesJson::from(serialize(tx)) - }; - Box::new( - self.blockchain_transaction_broadcast(bytes) - .map_to_mm_fut(UtxoRpcError::from), - ) - } - - fn send_raw_transaction(&self, tx: BytesJson) -> UtxoRpcFut { - Box::new( - self.blockchain_transaction_broadcast(tx) - .map_to_mm_fut(UtxoRpcError::from), - ) - } - - fn blockchain_scripthash_subscribe(&self, scripthash: String) -> UtxoRpcFut { - Box::new(rpc_func!(self, BLOCKCHAIN_SCRIPTHASH_SUB_ID, scripthash).map_to_mm_fut(UtxoRpcError::from)) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get - /// returns transaction bytes by default - fn get_transaction_bytes(&self, txid: &H256Json) -> UtxoRpcFut { - let verbose = false; - Box::new(rpc_func!(self, "blockchain.transaction.get", txid, verbose).map_to_mm_fut(UtxoRpcError::from)) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get - /// returns verbose transaction by default - fn get_verbose_transaction(&self, txid: &H256Json) -> UtxoRpcFut { - let verbose = true; - Box::new(rpc_func!(self, "blockchain.transaction.get", txid, verbose).map_to_mm_fut(UtxoRpcError::from)) - } - - /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get - /// Returns verbose transactions in a batch. - fn get_verbose_transactions(&self, tx_ids: &[H256Json]) -> UtxoRpcFut> { - let verbose = true; - let requests = tx_ids - .iter() - .map(|txid| rpc_req!(self, "blockchain.transaction.get", txid, verbose)); - Box::new(self.batch_rpc(requests).map_to_mm_fut(UtxoRpcError::from)) - } - - fn get_block_count(&self) -> UtxoRpcFut { - Box::new( - self.blockchain_headers_subscribe() - .map(|r| r.block_height()) - .map_to_mm_fut(UtxoRpcError::from), - ) - } - - fn display_balance(&self, address: Address, decimals: u8) -> RpcRes { - let output_script = try_f!(output_script(&address).map_err(|err| JsonRpcError::new( - UtxoJsonRpcClientInfo::client_info(self), - rpc_req!(self, "blockchain.scripthash.get_balance").into(), - JsonRpcErrorType::Internal(err.to_string()) - ))); - let mut hashes = vec![hex::encode(electrum_script_hash(&output_script))]; - - // If the plain pubkey is available, fetch the balance found in P2PK output as well (if any). - if let Some(pubkey) = address.pubkey() { - let p2pk_output_script = output_script_p2pk(pubkey); - hashes.push(hex::encode(electrum_script_hash(&p2pk_output_script))); - } - - let this = self.clone(); - let fut = async move { - Ok(this - .scripthash_get_balances(hashes) - .compat() - .await? - .into_iter() - .fold(BigDecimal::from(0), |sum, electrum_balance| { - sum + electrum_balance.to_big_decimal(decimals) - })) - }; - Box::new(fut.boxed().compat()) - } - - fn display_balances(&self, addresses: Vec
, decimals: u8) -> UtxoRpcFut> { - let this = self.clone(); - let fut = async move { - let hashes = addresses - .iter() - .map(|address| { - let output_script = output_script(address)?; - let hash = electrum_script_hash(&output_script); - - Ok(hex::encode(hash)) - }) - .collect::, keys::Error>>()?; - - let electrum_balances = this.scripthash_get_balances(hashes).compat().await?; - let balances = electrum_balances - .into_iter() - // `scripthash_get_balances` returns `ElectrumBalance` elements in the same order in which they were requested. - // So we can zip `addresses` and the balances into one iterator. - .zip(addresses) - .map(|(electrum_balance, address)| (address, electrum_balance.to_big_decimal(decimals))) - .collect(); - Ok(balances) - }; - - Box::new(fut.boxed().compat()) - } - - fn estimate_fee_sat( - &self, - decimals: u8, - _fee_method: &EstimateFeeMethod, - mode: &Option, - n_blocks: u32, - ) -> UtxoRpcFut { - Box::new(self.estimate_fee(mode, n_blocks).map(move |fee| { - if fee > 0.00001 { - (fee * 10.0_f64.powf(decimals as f64)) as u64 - } else { - 1000 - } - })) - } - - fn get_relay_fee(&self) -> RpcRes { rpc_func!(self, "blockchain.relayfee") } - - fn find_output_spend( - &self, - tx_hash: H256, - script_pubkey: &[u8], - vout: usize, - _from_block: BlockHashOrHeight, - tx_hash_algo: TxHashAlgo, - ) -> Box, Error = String> + Send> { - let selfi = self.clone(); - let script_hash = hex::encode(electrum_script_hash(script_pubkey)); - let fut = async move { - let history = try_s!(selfi.scripthash_get_history(&script_hash).compat().await); - - if history.len() < 2 { - return Ok(None); - } - - for item in history.iter() { - let transaction = try_s!(selfi.get_transaction_bytes(&item.tx_hash).compat().await); - - let mut maybe_spend_tx: UtxoTx = - try_s!(deserialize(transaction.as_slice()).map_err(|e| ERRL!("{:?}", e))); - maybe_spend_tx.tx_hash_algo = tx_hash_algo; - drop_mutability!(maybe_spend_tx); - - for (index, input) in maybe_spend_tx.inputs.iter().enumerate() { - if input.previous_output.hash == tx_hash && input.previous_output.index == vout as u32 { - return Ok(Some(SpentOutputInfo { - input: input.clone(), - input_index: index, - spending_tx: maybe_spend_tx, - spent_in_block: BlockHashOrHeight::Height(item.height), - })); - } - } - } - Ok(None) - }; - Box::new(fut.boxed().compat()) - } - - fn get_median_time_past( - &self, - starting_block: u64, - count: NonZeroU64, - coin_variant: CoinVariant, - ) -> UtxoRpcFut { - let from = if starting_block <= count.get() { - 0 - } else { - starting_block - count.get() + 1 - }; - Box::new( - self.blockchain_block_headers(from, count) - .map_to_mm_fut(UtxoRpcError::from) - .and_then(|res| { - if res.count == 0 { - return MmError::err(UtxoRpcError::InvalidResponse("Server returned zero count".to_owned())); - } - let len = CompactInteger::from(res.count); - let mut serialized = serialize(&len).take(); - serialized.extend(res.hex.0.into_iter()); - let mut reader = Reader::new_with_coin_variant(serialized.as_slice(), coin_variant); - let headers = reader.read_list::()?; - 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()) - }), - ) - } - - async fn get_block_timestamp(&self, height: u64) -> Result> { - Ok(self.block_header_from_storage_or_rpc(height).await?.time as u64) - } -} - -#[cfg_attr(test, mockable)] -impl ElectrumClientImpl { - pub fn new( - coin_ticker: String, - event_handlers: Vec, - block_headers_storage: BlockHeaderStorage, - abortable_system: AbortableQueue, - negotiate_version: bool, - scripthash_notification_sender: ScripthashNotificationSender, - ) -> ElectrumClientImpl { - let protocol_version = OrdRange::new(1.2, 1.4).unwrap(); - ElectrumClientImpl { - coin_ticker, - connections: AsyncMutex::new(vec![]), - next_id: 0.into(), - event_handlers, - protocol_version, - get_balance_concurrent_map: ConcurrentRequestMap::new(), - list_unspent_concurrent_map: ConcurrentRequestMap::new(), - block_headers_storage, - abortable_system, - negotiate_version, - scripthash_notification_sender, - } - } - - #[cfg(test)] - pub fn with_protocol_version( - coin_ticker: String, - event_handlers: Vec, - protocol_version: OrdRange, - block_headers_storage: BlockHeaderStorage, - abortable_system: AbortableQueue, - scripthash_notification_sender: ScripthashNotificationSender, - ) -> ElectrumClientImpl { - ElectrumClientImpl { - protocol_version, - ..ElectrumClientImpl::new( - coin_ticker, - event_handlers, - block_headers_storage, - abortable_system, - false, - scripthash_notification_sender, - ) - } - } -} - -/// Helper function casting mpsc::Receiver as Stream. -fn rx_to_stream(rx: mpsc::Receiver>) -> impl Stream, Error = io::Error> { - rx.map_err(|_| panic!("errors not possible on rx")) -} - -async fn electrum_process_json( - raw_json: Json, - arc: &JsonRpcPendingRequestsShared, - scripthash_notification_sender: &ScripthashNotificationSender, -) { - // detect if we got standard JSONRPC response or subscription response as JSONRPC request - #[derive(Deserialize)] - #[serde(untagged)] - enum ElectrumRpcResponseEnum { - /// The subscription response as JSONRPC request. - /// - /// NOTE Because JsonRpcResponse uses default values for each of its field, - /// this variant has to stay at top in this enumeration to be properly deserialized - /// from serde. - SubscriptionNotification(JsonRpcRequest), - /// The standard JSONRPC single response. - SingleResponse(JsonRpcResponse), - /// The batch of standard JSONRPC responses. - BatchResponses(JsonRpcBatchResponse), - } - - let response: ElectrumRpcResponseEnum = match json::from_value(raw_json) { - Ok(res) => res, - Err(e) => { - error!("{}", e); - return; - }, - }; - - let response = match response { - ElectrumRpcResponseEnum::SingleResponse(single) => JsonRpcResponseEnum::Single(single), - ElectrumRpcResponseEnum::BatchResponses(batch) => JsonRpcResponseEnum::Batch(batch), - ElectrumRpcResponseEnum::SubscriptionNotification(req) => { - let id = match req.method.as_ref() { - BLOCKCHAIN_HEADERS_SUB_ID => BLOCKCHAIN_HEADERS_SUB_ID, - BLOCKCHAIN_SCRIPTHASH_SUB_ID => { - let scripthash = match req.params.first() { - Some(t) => t.as_str().unwrap_or_default(), - None => { - debug!("Notification must contain the scripthash value."); - return; - }, - }; - - if let Some(sender) = scripthash_notification_sender { - debug!("Sending scripthash message"); - if let Err(e) = sender.unbounded_send(ScripthashNotification::Triggered(scripthash.to_string())) - { - error!("Failed sending scripthash message. {e}"); - return; - }; - }; - BLOCKCHAIN_SCRIPTHASH_SUB_ID - }, - _ => { - error!("Couldn't get id of request {:?}", req); - return; - }, - }; - JsonRpcResponseEnum::Single(JsonRpcResponse { - id: id.into(), - jsonrpc: "2.0".into(), - result: req.params[0].clone(), - error: Json::Null, - }) - }, - }; - - // the corresponding sender may not exist, receiver may be dropped - // these situations are not considered as errors so we just silently skip them - let mut pending = arc.lock().await; - if let Some(tx) = pending.remove(&response.rpc_id()) { - tx.send(response).ok(); - } -} - -async fn electrum_process_chunk( - chunk: &[u8], - arc: &JsonRpcPendingRequestsShared, - scripthash_notification_sender: ScripthashNotificationSender, -) { - // we should split the received chunk because we can get several responses in 1 chunk. - 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.is_empty() { - let raw_json: Json = match json::from_slice(chunk) { - Ok(json) => json, - Err(e) => { - error!("{}", e); - return; - }, - }; - electrum_process_json(raw_json, arc, &scripthash_notification_sender).await - } - } -} - -fn increase_delay(delay: &AtomicU64) { - if delay.load(AtomicOrdering::Relaxed) < 60 { - delay.fetch_add(5, AtomicOrdering::Relaxed); - } -} - -macro_rules! try_loop { - ($e:expr, $addr: ident, $delay: ident) => { - match $e { - Ok(res) => res, - Err(e) => { - error!("{:?} error {:?}", $addr, e); - increase_delay(&$delay); - continue; - }, - } - }; -} - -/// The enum wrapping possible variants of underlying Streams -#[cfg(not(target_arch = "wasm32"))] -#[allow(clippy::large_enum_variant)] -enum ElectrumStream { - Tcp(TcpStream), - Tls(TlsStream), -} - -#[cfg(not(target_arch = "wasm32"))] -impl AsRef for ElectrumStream { - fn as_ref(&self) -> &TcpStream { - match self { - ElectrumStream::Tcp(stream) => stream, - ElectrumStream::Tls(stream) => stream.get_ref().0, - } - } -} - -#[cfg(not(target_arch = "wasm32"))] -impl AsyncRead for ElectrumStream { - fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { - match self.get_mut() { - ElectrumStream::Tcp(stream) => AsyncRead::poll_read(Pin::new(stream), cx, buf), - ElectrumStream::Tls(stream) => AsyncRead::poll_read(Pin::new(stream), cx, buf), - } - } -} - -#[cfg(not(target_arch = "wasm32"))] -impl AsyncWrite for ElectrumStream { - fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { - match self.get_mut() { - ElectrumStream::Tcp(stream) => AsyncWrite::poll_write(Pin::new(stream), cx, buf), - ElectrumStream::Tls(stream) => AsyncWrite::poll_write(Pin::new(stream), cx, buf), - } - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.get_mut() { - ElectrumStream::Tcp(stream) => AsyncWrite::poll_flush(Pin::new(stream), cx), - ElectrumStream::Tls(stream) => AsyncWrite::poll_flush(Pin::new(stream), cx), - } - } - - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.get_mut() { - ElectrumStream::Tcp(stream) => AsyncWrite::poll_shutdown(Pin::new(stream), cx), - ElectrumStream::Tls(stream) => AsyncWrite::poll_shutdown(Pin::new(stream), cx), - } - } -} - -const ELECTRUM_TIMEOUT: u64 = 60; - -async fn electrum_last_chunk_loop(last_chunk: Arc) { - loop { - 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 { - warn!( - "Didn't receive any data since {}. Shutting down the connection.", - last as i64 - ); - break; - } - } -} - -#[cfg(not(target_arch = "wasm32"))] -fn rustls_client_config(unsafe_conf: bool) -> Arc { - let mut cert_store = RootCertStore::empty(); - - cert_store.add_trust_anchors( - TLS_SERVER_ROOTS - .iter() - .map(|ta| OwnedTrustAnchor::from_subject_spki_name_constraints(ta.subject, ta.spki, ta.name_constraints)), - ); - - let mut tls_config = rustls::ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(cert_store) - .with_no_client_auth(); - - if unsafe_conf { - tls_config - .dangerous() - .set_certificate_verifier(Arc::new(NoCertificateVerification {})); - } - Arc::new(tls_config) -} - -#[cfg(not(target_arch = "wasm32"))] -lazy_static! { - static ref SAFE_TLS_CONFIG: Arc = rustls_client_config(false); - static ref UNSAFE_TLS_CONFIG: Arc = rustls_client_config(true); -} - -#[cfg(not(target_arch = "wasm32"))] -async fn connect_loop( - config: ElectrumConfig, - addr: String, - responses: JsonRpcPendingRequestsShared, - connection_tx: Arc>>>>, - event_handlers: Vec, - scripthash_notification_sender: ScripthashNotificationSender, - _spawner: Spawner, -) -> Result<(), ()> { - let delay = Arc::new(AtomicU64::new(0)); - - loop { - let current_delay = delay.load(AtomicOrdering::Relaxed); - if current_delay > 0 { - Timer::sleep(current_delay as f64).await; - }; - - let socket_addr = addr_to_socket_addr(&addr).map_err(|e| { - error!("{:?} error {:?}", addr, e); - })?; - - let connect_f = match config.clone() { - ElectrumConfig::TCP => Either::Left(TcpStream::connect(&socket_addr).map_ok(ElectrumStream::Tcp)), - ElectrumConfig::SSL { - dns_name, - skip_validation, - } => { - let tls_connector = if skip_validation { - TlsConnector::from(UNSAFE_TLS_CONFIG.clone()) - } else { - TlsConnector::from(SAFE_TLS_CONFIG.clone()) - }; - // The address should always be correct since we checked it beforehand in initializaiton. - let dns = server_name_from_domain(dns_name.as_str()).map_err(|e| { - error!("{:?} error {:?}", addr, e); - })?; - - Either::Right( - TcpStream::connect(&socket_addr) - .and_then(move |stream| tls_connector.connect(dns, stream).map_ok(ElectrumStream::Tls)), - ) - }, - }; - - let stream = try_loop!(connect_f.await, addr, delay); - try_loop!(stream.as_ref().set_nodelay(true), addr, delay); - info!("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 (read, mut write) = tokio::io::split(stream); - let recv_f = { - let delay = delay.clone(); - let addr = addr.clone(); - let responses = responses.clone(); - let scripthash_notification_sender = scripthash_notification_sender.clone(); - let event_handlers = event_handlers.clone(); - async move { - let mut buffer = String::with_capacity(1024); - let mut buf_reader = BufReader::new(read); - loop { - match buf_reader.read_line(&mut buffer).await { - Ok(c) => { - if c == 0 { - info!("EOF from {}", addr); - break; - } - // reset the delay if we've connected successfully and only if we received some data from connection - delay.store(0, AtomicOrdering::Relaxed); - }, - Err(e) => { - error!("Error on read {} from {}", e, addr); - break; - }, - }; - // measure the length of each incoming packet - event_handlers.on_incoming_response(buffer.as_bytes()); - last_chunk.store(now_ms(), AtomicOrdering::Relaxed); - - electrum_process_chunk(buffer.as_bytes(), &responses, scripthash_notification_sender.clone()).await; - buffer.clear(); - } - } - }; - let mut recv_f = Box::pin(recv_f).fuse(); - - let send_f = { - let addr = addr.clone(); - let mut rx = rx.compat(); - async move { - while let Some(Ok(bytes)) = rx.next().await { - if let Err(e) = write.write_all(&bytes).await { - error!("Write error {} to {}", e, addr); - } - } - } - }; - let mut send_f = Box::pin(send_f).fuse(); - macro_rules! reset_tx_and_continue { - () => { - info!("{} connection dropped", addr); - event_handlers.on_disconnected(addr.clone()).error_log(); - *connection_tx.lock().await = None; - increase_delay(&delay); - continue; - }; - } - - select! { - _last_chunk = last_chunk_f => { reset_tx_and_continue!(); }, - _recv = recv_f => { reset_tx_and_continue!(); }, - _send = send_f => { reset_tx_and_continue!(); }, - } - } -} - -#[cfg(target_arch = "wasm32")] -async fn connect_loop( - _config: ElectrumConfig, - addr: String, - responses: JsonRpcPendingRequestsShared, - connection_tx: Arc>>>>, - event_handlers: Vec, - scripthash_notification_sender: ScripthashNotificationSender, - spawner: Spawner, -) -> Result<(), ()> { - use std::sync::atomic::AtomicUsize; - - lazy_static! { - static ref CONN_IDX: Arc = Arc::new(AtomicUsize::new(0)); - } - - use mm2_net::wasm::wasm_ws::ws_transport; - - let delay = Arc::new(AtomicU64::new(0)); - loop { - let current_delay = delay.load(AtomicOrdering::Relaxed); - if current_delay > 0 { - Timer::sleep(current_delay as f64).await; - } - - let conn_idx = CONN_IDX.fetch_add(1, AtomicOrdering::Relaxed); - let (mut transport_tx, mut transport_rx) = - try_loop!(ws_transport(conn_idx, &addr, &spawner).await, addr, delay); - - info!("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_fut = electrum_last_chunk_loop(last_chunk.clone()).boxed().fuse(); - - let (outgoing_tx, outgoing_rx) = mpsc::channel(0); - *connection_tx.lock().await = Some(outgoing_tx); - - let incoming_fut = { - let delay = delay.clone(); - let addr = addr.clone(); - let responses = responses.clone(); - let scripthash_notification_sender = scripthash_notification_sender.clone(); - let event_handlers = event_handlers.clone(); - async move { - while let Some(incoming_res) = transport_rx.next().await { - last_chunk.store(now_ms(), AtomicOrdering::Relaxed); - match incoming_res { - Ok(incoming_json) => { - // reset the delay if we've connected successfully and only if we received some data from connection - delay.store(0, AtomicOrdering::Relaxed); - // measure the length of each incoming packet - let incoming_str = incoming_json.to_string(); - event_handlers.on_incoming_response(incoming_str.as_bytes()); - - electrum_process_json(incoming_json, &responses, &scripthash_notification_sender).await; - }, - Err(e) => { - error!("{} error: {:?}", addr, e); - }, - } - } - } - }; - let mut incoming_fut = Box::pin(incoming_fut).fuse(); - - let outgoing_fut = { - let addr = addr.clone(); - let mut outgoing_rx = rx_to_stream(outgoing_rx).compat(); - let event_handlers = event_handlers.clone(); - async move { - while let Some(Ok(data)) = outgoing_rx.next().await { - let raw_json: Json = match json::from_slice(&data) { - Ok(js) => js, - Err(e) => { - error!("Error {} deserializing the outgoing data: {:?}", e, data); - continue; - }, - }; - // measure the length of each sent packet - event_handlers.on_outgoing_request(&data); - - if let Err(e) = transport_tx.send(raw_json).await { - error!("Error sending to {}: {:?}", addr, e); - } - } - } - }; - let mut outgoing_fut = Box::pin(outgoing_fut).fuse(); - - macro_rules! reset_tx_and_continue { - () => { - info!("{} connection dropped", addr); - *connection_tx.lock().await = None; - event_handlers.on_disconnected(addr.clone()).error_log(); - increase_delay(&delay); - continue; - }; - } - - select! { - _last_chunk = last_chunk_fut => { reset_tx_and_continue!(); }, - _incoming = incoming_fut => { reset_tx_and_continue!(); }, - _outgoing = outgoing_fut => { reset_tx_and_continue!(); }, - } - } -} - -/// Builds up the electrum connection, spawns endless loop that attempts to reconnect to the server -/// in case of connection errors. -/// The function takes `abortable_system` that will be used to spawn Electrum's related futures. -fn electrum_connect( - addr: String, - config: ElectrumConfig, - event_handlers: Vec, - scripthash_notification_sender: &ScripthashNotificationSender, - abortable_system: AbortableQueue, -) -> ElectrumConnection { - let responses = Arc::new(AsyncMutex::new(JsonRpcPendingRequests::default())); - let tx = Arc::new(AsyncMutex::new(None)); - - let spawner = abortable_system.weak_spawner(); - let fut = connect_loop( - config.clone(), - addr.clone(), - responses.clone(), - tx.clone(), - event_handlers, - scripthash_notification_sender.clone(), - spawner.clone(), - ) - .then(|_| futures::future::ready(())); - - spawner.spawn(fut); - ElectrumConnection { - addr, - config, - tx, - responses, - protocol_version: AsyncMutex::new(None), - _abortable_system: abortable_system, - } -} - -/// # Important -/// `electrum_request` should always return [`JsonRpcErrorType::Transport`] error. -fn electrum_request( - mut req_json: String, - rpc_id: JsonRpcId, - tx: mpsc::Sender>, - responses: JsonRpcPendingRequestsShared, - timeout: u64, -) -> Box + Send + 'static> { - let send_fut = async move { - #[cfg(not(target_arch = "wasm"))] - { - // Electrum request and responses must end with \n - // https://electrumx.readthedocs.io/en/latest/protocol-basics.html#message-stream - req_json.push('\n'); - } - let (req_tx, resp_rx) = async_oneshot::channel(); - responses.lock().await.insert(rpc_id, req_tx); - tx.send(req_json.into_bytes()) - .compat() - .await - .map_err(|err| JsonRpcErrorType::Transport(err.to_string()))?; - let resps = resp_rx.await.map_err(|e| JsonRpcErrorType::Transport(e.to_string()))?; - Ok(resps) - }; - let send_fut = send_fut - .boxed() - .timeout(Duration::from_secs(timeout)) - .compat() - .then(move |res| res.map_err(|err| JsonRpcErrorType::Transport(err.to_string()))?); - Box::new(send_fut) -} - fn address_balance_from_unspent_map(address: &Address, unspent_map: &UnspentMap, decimals: u8) -> BigDecimal { let unspents = match unspent_map.get(address) { Some(unspents) => unspents, diff --git a/mm2src/coins/utxo/rpc_clients/electrum_rpc/client.rs b/mm2src/coins/utxo/rpc_clients/electrum_rpc/client.rs new file mode 100644 index 0000000000..ce0498cc31 --- /dev/null +++ b/mm2src/coins/utxo/rpc_clients/electrum_rpc/client.rs @@ -0,0 +1,1068 @@ +use super::super::{BlockHashOrHeight, EstimateFeeMethod, EstimateFeeMode, SpentOutputInfo, UnspentInfo, UnspentMap, + UtxoJsonRpcClientInfo, UtxoRpcClientOps, UtxoRpcError, UtxoRpcFut}; +use super::connection::{ElectrumConnection, ElectrumConnectionErr, ElectrumConnectionSettings}; +use super::connection_manager::ConnectionManager; +use super::constants::{BLOCKCHAIN_HEADERS_SUB_ID, BLOCKCHAIN_SCRIPTHASH_SUB_ID, ELECTRUM_REQUEST_TIMEOUT, + NO_FORCE_CONNECT_METHODS, SEND_TO_ALL_METHODS}; +use super::electrum_script_hash; +use super::event_handlers::{ElectrumConnectionManagerNotifier, ElectrumScriptHashNotificationBridge}; +use super::rpc_responses::*; + +use crate::utxo::rpc_clients::ConcurrentRequestMap; +use crate::utxo::utxo_block_header_storage::BlockHeaderStorage; +use crate::utxo::{output_script, output_script_p2pk, GetBlockHeaderError, GetConfirmedTxError, GetTxHeightError, + ScripthashNotification}; +use crate::RpcTransportEventHandler; +use crate::SharableRpcTransportEventHandler; +use chain::{BlockHeader, Transaction as UtxoTx, TxHashAlgo}; +use common::executor::abortable_queue::{AbortableQueue, WeakSpawner}; +use common::jsonrpc_client::{JsonRpcBatchClient, JsonRpcClient, JsonRpcError, JsonRpcErrorType, JsonRpcId, + JsonRpcMultiClient, JsonRpcRemoteAddr, JsonRpcRequest, JsonRpcRequestEnum, + JsonRpcResponseEnum, JsonRpcResponseFut, RpcRes}; +use common::log::warn; +use common::{median, OrdRange}; +use keys::hash::H256; +use keys::Address; +use mm2_err_handle::prelude::*; +use mm2_number::BigDecimal; +#[cfg(test)] use mocktopus::macros::*; +use rpc::v1::types::{Bytes as BytesJson, Transaction as RpcTransaction, H256 as H256Json}; +use serialization::{deserialize, serialize, serialize_with_flags, CoinVariant, CompactInteger, Reader, + SERIALIZE_TRANSACTION_WITNESS}; +use spv_validation::helpers_validation::SPVError; +use spv_validation::storage::BlockHeaderStorageOps; + +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::collections::HashSet; +use std::convert::TryInto; +use std::fmt::Debug; +use std::iter::FromIterator; +use std::num::NonZeroU64; +use std::ops::Deref; +use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering}; +use std::sync::Arc; + +use async_trait::async_trait; +use futures::channel::mpsc::UnboundedSender; +use futures::compat::Future01CompatExt; +use futures::future::{join_all, FutureExt, TryFutureExt}; +use futures::stream::FuturesUnordered; +use futures::StreamExt; +use futures01::Future; +use itertools::Itertools; +use serde_json::{self as json, Value as Json}; + +type ElectrumTxHistory = Vec; +type ElectrumScriptHash = String; +type ScriptHashUnspents = Vec; + +#[derive(Debug)] +pub struct ElectrumClientSettings { + pub client_name: String, + pub servers: Vec, + pub coin_ticker: String, + pub negotiate_version: bool, + pub spawn_ping: bool, + /// Minimum number of connections to keep alive at all times (best effort). + pub min_connected: usize, + /// Maximum number of connections to keep alive at any time. + pub max_connected: usize, +} + +#[derive(Debug)] +pub struct ElectrumClientImpl { + client_name: String, + coin_ticker: String, + pub connection_manager: ConnectionManager, + next_id: AtomicU64, + negotiate_version: bool, + protocol_version: OrdRange, + get_balance_concurrent_map: ConcurrentRequestMap, + list_unspent_concurrent_map: ConcurrentRequestMap>, + block_headers_storage: BlockHeaderStorage, + /// Event handlers that are triggered on (dis)connection & transport events. They are wrapped + /// in an `Arc` since they are shared outside `ElectrumClientImpl`. They are handed to each active + /// `ElectrumConnection` to notify them about the events. + event_handlers: Arc>>, + pub scripthash_notification_sender: Option>, + abortable_system: AbortableQueue, +} + +#[cfg_attr(test, mockable)] +impl ElectrumClientImpl { + /// Returns a new instance of `ElectrumClientImpl`. + /// + /// This doesn't initialize the connection manager contained within `ElectrumClientImpl`. + /// Use `try_new_arc` to create an Arc-wrapped instance with an initialized connection manager. + fn try_new( + client_settings: ElectrumClientSettings, + block_headers_storage: BlockHeaderStorage, + abortable_system: AbortableQueue, + mut event_handlers: Vec>, + scripthash_notification_sender: Option>, + ) -> Result { + // This is used for balance event streaming implementation for UTXOs. + // Will be used for sending scripthash messages to trigger re-connections, re-fetching the balances, etc. + if let Some(scripthash_notification_sender) = scripthash_notification_sender.clone() { + event_handlers.push(Box::new(ElectrumScriptHashNotificationBridge { + scripthash_notification_sender, + })); + } + + let connection_manager = ConnectionManager::try_new( + client_settings.servers, + client_settings.spawn_ping, + (client_settings.min_connected, client_settings.max_connected), + &abortable_system, + )?; + + event_handlers.push(Box::new(ElectrumConnectionManagerNotifier { + connection_manager: connection_manager.clone(), + })); + + Ok(ElectrumClientImpl { + client_name: client_settings.client_name, + coin_ticker: client_settings.coin_ticker, + connection_manager, + next_id: 0.into(), + negotiate_version: client_settings.negotiate_version, + protocol_version: OrdRange::new(1.2, 1.4).unwrap(), + get_balance_concurrent_map: ConcurrentRequestMap::new(), + list_unspent_concurrent_map: ConcurrentRequestMap::new(), + block_headers_storage, + abortable_system, + scripthash_notification_sender, + event_handlers: Arc::new(event_handlers), + }) + } + + /// Create a new Electrum client instance. + /// This function initializes the connection manager and starts the connection process. + pub fn try_new_arc( + client_settings: ElectrumClientSettings, + block_headers_storage: BlockHeaderStorage, + abortable_system: AbortableQueue, + event_handlers: Vec>, + scripthash_notification_sender: Option>, + ) -> Result, String> { + let client_impl = Arc::new(ElectrumClientImpl::try_new( + client_settings, + block_headers_storage, + abortable_system, + event_handlers, + scripthash_notification_sender, + )?); + // Initialize the connection manager. + client_impl + .connection_manager + .initialize(Arc::downgrade(&client_impl)) + .map_err(|e| e.to_string())?; + + Ok(client_impl) + } + + /// Remove an Electrum connection and stop corresponding spawned actor. + pub fn remove_server(&self, server_addr: &str) -> Result, String> { + self.connection_manager + .remove_connection(server_addr) + .map_err(|err| err.to_string()) + } + + /// Check if all connections have been removed. + pub fn is_connections_pool_empty(&self) -> bool { self.connection_manager.is_connections_pool_empty() } + + /// Get available protocol versions. + pub fn protocol_version(&self) -> &OrdRange { &self.protocol_version } + + pub fn coin_ticker(&self) -> &str { &self.coin_ticker } + + /// Whether to negotiate the protocol version. + pub fn negotiate_version(&self) -> bool { self.negotiate_version } + + /// Get the event handlers. + pub fn event_handlers(&self) -> Arc>> { self.event_handlers.clone() } + + /// Sends a list of addresses through the scripthash notification sender to subscribe to their scripthash notifications. + pub fn subscribe_addresses(&self, addresses: HashSet
) -> Result<(), String> { + if let Some(sender) = &self.scripthash_notification_sender { + sender + .unbounded_send(ScripthashNotification::SubscribeToAddresses(addresses)) + .map_err(|e| ERRL!("Failed sending scripthash message. {}", e))?; + } + + Ok(()) + } + + /// Get block headers storage. + pub fn block_headers_storage(&self) -> &BlockHeaderStorage { &self.block_headers_storage } + + pub fn weak_spawner(&self) -> WeakSpawner { self.abortable_system.weak_spawner() } + + #[cfg(test)] + pub fn with_protocol_version( + client_settings: ElectrumClientSettings, + block_headers_storage: BlockHeaderStorage, + abortable_system: AbortableQueue, + event_handlers: Vec>, + scripthash_notification_sender: Option>, + protocol_version: OrdRange, + ) -> Result, String> { + let client_impl = Arc::new(ElectrumClientImpl { + protocol_version, + ..ElectrumClientImpl::try_new( + client_settings, + block_headers_storage, + abortable_system, + event_handlers, + scripthash_notification_sender, + )? + }); + // Initialize the connection manager. + client_impl + .connection_manager + .initialize(Arc::downgrade(&client_impl)) + .map_err(|e| e.to_string())?; + + Ok(client_impl) + } +} + +#[derive(Clone, Debug)] +pub struct ElectrumClient(pub Arc); + +impl Deref for ElectrumClient { + type Target = ElectrumClientImpl; + fn deref(&self) -> &ElectrumClientImpl { &self.0 } +} + +impl UtxoJsonRpcClientInfo for ElectrumClient { + 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) -> u64 { self.next_id.fetch_add(1, AtomicOrdering::Relaxed) } + + fn client_info(&self) -> String { UtxoJsonRpcClientInfo::client_info(self) } + + fn transport(&self, request: JsonRpcRequestEnum) -> JsonRpcResponseFut { + Box::new(self.clone().electrum_request_multi(request).boxed().compat()) + } +} + +impl JsonRpcBatchClient for ElectrumClient {} + +impl JsonRpcMultiClient for ElectrumClient { + fn transport_exact(&self, to_addr: String, request: JsonRpcRequestEnum) -> JsonRpcResponseFut { + Box::new( + self.clone() + .electrum_request_to(to_addr.clone(), request) + .map_ok(|response| (JsonRpcRemoteAddr(to_addr), response)) + .boxed() + .compat(), + ) + } +} + +#[cfg_attr(test, mockable)] +impl ElectrumClient { + pub fn try_new( + client_settings: ElectrumClientSettings, + event_handlers: Vec>, + block_headers_storage: BlockHeaderStorage, + abortable_system: AbortableQueue, + scripthash_notification_sender: Option>, + ) -> Result { + let client = ElectrumClient(ElectrumClientImpl::try_new_arc( + client_settings, + block_headers_storage, + abortable_system, + event_handlers, + scripthash_notification_sender, + )?); + + Ok(client) + } + + /// Sends a JSONRPC request to all the connected servers. + /// + /// This method will block until a response is received from at least one server. + async fn electrum_request_multi( + self, + request: JsonRpcRequestEnum, + ) -> Result<(JsonRpcRemoteAddr, JsonRpcResponseEnum), JsonRpcErrorType> { + // Whether to send the request to all active connections or not. + let send_to_all = matches!(request, JsonRpcRequestEnum::Single(ref req) if SEND_TO_ALL_METHODS.contains(&req.method.as_str())); + // Request id and serialized request. + let req_id = request.rpc_id(); + let request = json::to_string(&request).map_err(|e| JsonRpcErrorType::InvalidRequest(e.to_string()))?; + let request = (req_id, request); + // Use the active connections for this request. + let connections = self.connection_manager.get_active_connections(); + // Maximum number of connections to establish or use in request concurrently. Could be up to connections.len(). + let concurrency = if send_to_all { connections.len() } else { 1 }; + match self + .send_request_using(&request, connections, send_to_all, concurrency) + .await + { + Ok(response) => Ok(response), + // If we failed the request using only the active connections, try again using all connections. + Err(_) if !send_to_all => { + warn!( + "[coin={}] Failed to send the request using active connections, trying all connections.", + self.coin_ticker() + ); + let connections = self.connection_manager.get_all_connections(); + // At this point we should have all the connections disconnected since all + // the active connections failed (and we disconnected them in the process). + // So use a higher concurrency to speed up the response time. + // + // Note that a side effect of this is that we might break the `max_connected` threshold for + // a short time since the connection manager's background task will be trying to establish + // connections at the same time. This is not as bad though since the manager's background task + // tries connections sequentially and we are expected for finish much quicker due to parallelizing. + let concurrency = self.connection_manager.config().max_connected; + match self.send_request_using(&request, connections, false, concurrency).await { + Ok(response) => Ok(response), + Err(err_vec) => Err(JsonRpcErrorType::Internal(format!("All servers errored: {err_vec:?}"))), + } + }, + Err(e) => Err(JsonRpcErrorType::Internal(format!("All servers errored: {e:?}"))), + } + } + + /// Sends a JSONRPC request to a specific electrum server. + /// + /// This will try to wake up the server connection if it's not connected. + async fn electrum_request_to( + self, + to_addr: String, + request: JsonRpcRequestEnum, + ) -> Result { + // Whether to force the connection to be established (if not) before sending the request. + let force_connect = !matches!(request, JsonRpcRequestEnum::Single(ref req) if NO_FORCE_CONNECT_METHODS.contains(&req.method.as_str())); + let json = json::to_string(&request).map_err(|err| JsonRpcErrorType::InvalidRequest(err.to_string()))?; + + let connection = self + .connection_manager + .get_connection_by_address(&to_addr, force_connect) + .await + .map_err(|err| JsonRpcErrorType::Internal(err.to_string()))?; + + let response = connection + .electrum_request(json, request.rpc_id(), ELECTRUM_REQUEST_TIMEOUT) + .await; + // If the request was not forcefully connected, we shouldn't inform the connection manager that it's + // not needed anymore, as we didn't force spawn it in the first place. + // This fixes dropping the connection after the version check request, as we don't mark the connection + // maintained till after the version is checked. + if force_connect { + // Inform the connection manager that the connection was queried and no longer needed now. + self.connection_manager.not_needed(&to_addr); + } + + response + } + + /// Sends a JSONRPC request to all the given connections in parallel and returns + /// the first successful response if there is any, or a vector of errors otherwise. + /// + /// If `send_to_all` is set to `true`, we won't return on first successful response but + /// wait for all responses to come back first. + async fn send_request_using( + &self, + request: &(JsonRpcId, String), + connections: Vec>, + send_to_all: bool, + max_concurrency: usize, + ) -> Result<(JsonRpcRemoteAddr, JsonRpcResponseEnum), Vec<(JsonRpcRemoteAddr, JsonRpcErrorType)>> { + let max_concurrency = max_concurrency.max(1); + // Create the request + let chunked_requests = connections.chunks(max_concurrency).map(|chunk| { + FuturesUnordered::from_iter(chunk.iter().map(|connection| { + let client = self.clone(); + let req_id = request.0; + let req_json = request.1.clone(); + async move { + let connection_is_established = connection + // We first make sure that the connection loop is established before sending the request. + .establish_connection_loop(client) + .await + .map_err(|e| JsonRpcErrorType::Transport(format!("Failed to establish connection: {e:?}"))); + let response = match connection_is_established { + Ok(_) => { + // Perform the request. + connection + .electrum_request(req_json, req_id, ELECTRUM_REQUEST_TIMEOUT) + .await + }, + Err(e) => Err(e), + }; + (response, connection.clone()) + } + })) + }); + let client = self.clone(); + let mut final_response = None; + let mut errors = Vec::new(); + // Iterate over the request chunks sequentially. + for mut requests in chunked_requests { + // For each chunk, iterate over the requests in parallel. + while let Some((response, connection)) = requests.next().await { + let address = JsonRpcRemoteAddr(connection.address().to_string()); + match response { + Ok(response) => { + if final_response.is_none() { + final_response = Some((address, response)); + } + client.connection_manager.not_needed(connection.address()); + if !send_to_all && final_response.is_some() { + return Ok(final_response.unwrap()); + } + }, + Err(e) => { + warn!( + "[coin={}], Error while sending request to {address:?}: {e:?}", + client.coin_ticker() + ); + connection.disconnect(Some(ElectrumConnectionErr::Temporary(format!( + "Forcefully disconnected for erroring: {e:?}." + )))); + client.event_handlers.on_disconnected(connection.address()).ok(); + errors.push((address, e)) + }, + } + } + } + final_response.ok_or(errors) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#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, version: &OrdRange) -> RpcRes { + let protocol_version: Vec = version.flatten().into_iter().map(|v| format!("{}", v)).collect(); + rpc_func_from!( + self, + server_address, + "server.version", + &self.client_name, + protocol_version + ) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-headers-subscribe + pub fn get_block_count_from(&self, server_address: &str) -> RpcRes { + Box::new( + rpc_func_from!(self, server_address, BLOCKCHAIN_HEADERS_SUB_ID) + .map(|r: ElectrumBlockHeader| r.block_height()), + ) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-block-headers + pub fn get_block_headers_from( + &self, + server_address: &str, + start_height: u64, + count: NonZeroU64, + ) -> RpcRes { + rpc_func_from!(self, server_address, "blockchain.block.headers", start_height, count) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-listunspent + /// It can return duplicates sometimes: https://github.com/artemii235/SuperNET/issues/269 + /// We should remove them to build valid transactions + pub fn scripthash_list_unspent(&self, hash: &str) -> RpcRes> { + let request_fut = 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, unspent.tx_pos)) { + Entry::Occupied(_) => false, + Entry::Vacant(e) => { + e.insert(true); + true + }, + }) + .collect(); + Ok(unspents) + }, + )); + let arc = self.clone(); + let hash = hash.to_owned(); + let fut = async move { arc.list_unspent_concurrent_map.wrap_request(hash, request_fut).await }; + Box::new(fut.boxed().compat()) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-listunspent + /// It can return duplicates sometimes: https://github.com/artemii235/SuperNET/issues/269 + /// We should remove them to build valid transactions. + /// Please note the function returns `ScriptHashUnspents` elements in the same order in which they were requested. + pub fn scripthash_list_unspent_batch(&self, hashes: Vec) -> RpcRes> { + let requests = hashes + .iter() + .map(|hash| rpc_req!(self, "blockchain.scripthash.listunspent", hash)); + Box::new(self.batch_rpc(requests).map(move |unspents: Vec| { + unspents + .into_iter() + .map(|hash_unspents| { + hash_unspents + .into_iter() + .unique_by(|unspent| (unspent.tx_hash, unspent.tx_pos)) + .collect::>() + }) + .collect() + })) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-get-history + pub fn scripthash_get_history(&self, hash: &str) -> RpcRes { + rpc_func!(self, "blockchain.scripthash.get_history", hash) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-get-history + /// Requests history of the `hashes` in a batch and returns them in the same order they were requested. + pub fn scripthash_get_history_batch(&self, hashes: I) -> RpcRes> + where + I: IntoIterator, + { + let requests = hashes + .into_iter() + .map(|hash| rpc_req!(self, "blockchain.scripthash.get_history", hash)); + self.batch_rpc(requests) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-gethistory + pub fn scripthash_get_balance(&self, hash: &str) -> RpcRes { + let arc = self.clone(); + let hash = hash.to_owned(); + let fut = async move { + let request = rpc_func!(arc, "blockchain.scripthash.get_balance", &hash); + arc.get_balance_concurrent_map.wrap_request(hash, request).await + }; + Box::new(fut.boxed().compat()) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-scripthash-gethistory + /// Requests balances in a batch and returns them in the same order they were requested. + pub fn scripthash_get_balances(&self, hashes: I) -> RpcRes> + where + I: IntoIterator, + { + let requests = hashes + .into_iter() + .map(|hash| rpc_req!(self, "blockchain.scripthash.get_balance", &hash)); + self.batch_rpc(requests) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-headers-subscribe + pub fn blockchain_headers_subscribe(&self) -> RpcRes { + rpc_func!(self, BLOCKCHAIN_HEADERS_SUB_ID) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-broadcast + pub fn blockchain_transaction_broadcast(&self, tx: BytesJson) -> RpcRes { + rpc_func!(self, "blockchain.transaction.broadcast", tx) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-estimatefee + /// It is recommended to set n_blocks as low as possible. + /// However, in some cases, n_blocks = 1 leads to an unreasonably high fee estimation. + /// https://github.com/KomodoPlatform/atomicDEX-API/issues/656#issuecomment-743759659 + pub fn estimate_fee(&self, mode: &Option, n_blocks: u32) -> UtxoRpcFut { + match mode { + Some(m) => { + Box::new(rpc_func!(self, "blockchain.estimatefee", n_blocks, m).map_to_mm_fut(UtxoRpcError::from)) + }, + None => Box::new(rpc_func!(self, "blockchain.estimatefee", n_blocks).map_to_mm_fut(UtxoRpcError::from)), + } + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-block-header + pub fn blockchain_block_header(&self, height: u64) -> RpcRes { + rpc_func!(self, "blockchain.block.header", height) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-block-headers + pub fn blockchain_block_headers(&self, start_height: u64, count: NonZeroU64) -> RpcRes { + rpc_func!(self, "blockchain.block.headers", start_height, count) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get-merkle + pub fn blockchain_transaction_get_merkle(&self, txid: H256Json, height: u64) -> RpcRes { + rpc_func!(self, "blockchain.transaction.get_merkle", txid, height) + } + + // get_tx_height_from_rpc is costly since it loops through history after requesting the whole history of the script pubkey + // This method should always be used if the block headers are saved to the DB + async fn get_tx_height_from_storage(&self, tx: &UtxoTx) -> Result> { + let tx_hash = tx.hash().reversed(); + let blockhash = self.get_verbose_transaction(&tx_hash.into()).compat().await?.blockhash; + Ok(self + .block_headers_storage() + .get_block_height_by_hash(blockhash.into()) + .await? + .ok_or_else(|| { + GetTxHeightError::HeightNotFound(format!( + "Transaction block header is not found in storage for {}", + self.coin_ticker() + )) + })? + .try_into()?) + } + + // get_tx_height_from_storage is always preferred to be used instead of this, but if there is no headers in storage (storing headers is not enabled) + // this function can be used instead + async fn get_tx_height_from_rpc(&self, tx: &UtxoTx) -> Result { + let selfi = self; + for output in tx.outputs.clone() { + let script_pubkey_str = hex::encode(electrum_script_hash(&output.script_pubkey)); + if let Ok(history) = selfi.scripthash_get_history(script_pubkey_str.as_str()).compat().await { + if let Some(item) = history + .into_iter() + .find(|item| item.tx_hash.reversed() == H256Json(*tx.hash()) && item.height > 0) + { + return Ok(item.height as u64); + } + } + } + Err(GetTxHeightError::HeightNotFound(format!( + "Couldn't find height through electrum for {}", + selfi.coin_ticker + ))) + } + + async fn block_header_from_storage(&self, height: u64) -> Result> { + self.block_headers_storage() + .get_block_header(height) + .await? + .ok_or_else(|| { + GetBlockHeaderError::Internal(format!("Header not found in storage for {}", self.coin_ticker)).into() + }) + } + + async fn block_header_from_storage_or_rpc(&self, height: u64) -> Result> { + match self.block_header_from_storage(height).await { + Ok(h) => Ok(h), + Err(_) => Ok(deserialize( + self.blockchain_block_header(height).compat().await?.as_slice(), + )?), + } + } + + pub async fn get_confirmed_tx_info_from_rpc( + &self, + tx: &UtxoTx, + ) -> Result { + let height = self.get_tx_height_from_rpc(tx).await?; + + let merkle_branch = self + .blockchain_transaction_get_merkle(tx.hash().reversed().into(), height) + .compat() + .await?; + + let header = deserialize(self.blockchain_block_header(height).compat().await?.as_slice())?; + + Ok(ConfirmedTransactionInfo { + tx: tx.clone(), + header, + index: merkle_branch.pos as u64, + height, + }) + } + + pub async fn get_merkle_and_validated_header( + &self, + tx: &UtxoTx, + ) -> Result<(TxMerkleBranch, BlockHeader, u64), MmError> { + let height = self.get_tx_height_from_storage(tx).await?; + + let merkle_branch = self + .blockchain_transaction_get_merkle(tx.hash().reversed().into(), height) + .compat() + .await + .map_to_mm(|err| SPVError::UnableToGetMerkle { + coin: self.coin_ticker.clone(), + err: err.to_string(), + })?; + + let header = self.block_header_from_storage(height).await?; + + Ok((merkle_branch, header, height)) + } + + pub fn retrieve_headers_from( + &self, + server_address: &str, + from_height: u64, + to_height: u64, + ) -> UtxoRpcFut<(HashMap, Vec)> { + let coin_name = self.coin_ticker.clone(); + if from_height == 0 || to_height < from_height { + return Box::new(futures01::future::err( + UtxoRpcError::Internal("Invalid values for from/to parameters".to_string()).into(), + )); + } + let count: NonZeroU64 = match (to_height - from_height + 1).try_into() { + Ok(c) => c, + Err(e) => return Box::new(futures01::future::err(UtxoRpcError::Internal(e.to_string()).into())), + }; + Box::new( + self.get_block_headers_from(server_address, from_height, count) + .map_to_mm_fut(UtxoRpcError::from) + .and_then(move |headers| { + let (block_registry, block_headers) = { + if headers.count == 0 { + return MmError::err(UtxoRpcError::Internal("No headers available".to_string())); + } + let len = CompactInteger::from(headers.count); + let mut serialized = serialize(&len).take(); + serialized.extend(headers.hex.0.into_iter()); + drop_mutability!(serialized); + let mut reader = + Reader::new_with_coin_variant(serialized.as_slice(), coin_name.as_str().into()); + let maybe_block_headers = reader.read_list::(); + let block_headers = match maybe_block_headers { + Ok(headers) => headers, + Err(e) => return MmError::err(UtxoRpcError::InvalidResponse(format!("{:?}", e))), + }; + let mut block_registry: HashMap = HashMap::new(); + let mut starting_height = from_height; + for block_header in &block_headers { + block_registry.insert(starting_height, block_header.clone()); + starting_height += 1; + } + (block_registry, block_headers) + }; + Ok((block_registry, block_headers)) + }), + ) + } + + pub(crate) fn get_servers_with_latest_block_count(&self) -> UtxoRpcFut<(Vec, u64)> { + let selfi = self.clone(); + let fut = async move { + let addresses = selfi.connection_manager.get_all_server_addresses(); + let futures = addresses + .into_iter() + .map(|address| { + selfi + .get_block_count_from(&address) + .map(|response| (address, response)) + .compat() + }) + .collect::>(); + + let responses = join_all(futures).await; + + // First, we use filter_map to get rid of any errors and collect the + // server addresses and block counts into two vectors + let (responding_servers, block_counts_from_all_servers): (Vec<_>, Vec<_>) = + responses.clone().into_iter().filter_map(|res| res.ok()).unzip(); + + // Next, we use max to find the maximum block count from all servers + if let Some(max_block_count) = block_counts_from_all_servers.clone().iter().max() { + // Then, we use filter and collect to get the servers that have the maximum block count + let servers_with_max_count: Vec<_> = responding_servers + .into_iter() + .zip(block_counts_from_all_servers) + .filter(|(_, count)| count == max_block_count) + .map(|(addr, _)| addr) + .collect(); + + // Finally, we return a tuple of servers with max count and the max count + return Ok((servers_with_max_count, *max_block_count)); + } + + Err(MmError::new(UtxoRpcError::Internal(format!( + "Couldn't get block count from any server for {}, responses: {:?}", + &selfi.coin_ticker, responses + )))) + }; + + Box::new(fut.boxed().compat()) + } +} + +// if mockable is placed before async_trait there is `munmap_chunk(): invalid pointer` error on async fn mocking attempt +#[async_trait] +#[cfg_attr(test, mockable)] +impl UtxoRpcClientOps for ElectrumClient { + fn list_unspent(&self, address: &Address, _decimals: u8) -> UtxoRpcFut> { + let mut output_scripts = vec![try_f!(output_script(address))]; + + // If the plain pubkey is available, fetch the UTXOs found in P2PK outputs as well (if any). + if let Some(pubkey) = address.pubkey() { + let p2pk_output_script = output_script_p2pk(pubkey); + output_scripts.push(p2pk_output_script); + } + + let this = self.clone(); + let fut = async move { + let hashes = output_scripts + .iter() + .map(|s| hex::encode(electrum_script_hash(s))) + .collect(); + let unspents = this.scripthash_list_unspent_batch(hashes).compat().await?; + + let unspents = unspents + .into_iter() + .zip(output_scripts) + .flat_map(|(unspents, output_script)| { + unspents + .into_iter() + .map(move |unspent| UnspentInfo::from_electrum(unspent, output_script.clone())) + }) + .collect(); + Ok(unspents) + }; + + Box::new(fut.boxed().compat()) + } + + fn list_unspent_group(&self, addresses: Vec
, _decimals: u8) -> UtxoRpcFut { + let output_scripts = try_f!(addresses + .iter() + .map(output_script) + .collect::, keys::Error>>()); + + let this = self.clone(); + let fut = async move { + let hashes = output_scripts + .iter() + .map(|s| hex::encode(electrum_script_hash(s))) + .collect(); + let unspents = this.scripthash_list_unspent_batch(hashes).compat().await?; + + let unspents: Vec> = unspents + .into_iter() + .zip(output_scripts) + .map(|(unspents, output_script)| { + unspents + .into_iter() + .map(|unspent| UnspentInfo::from_electrum(unspent, output_script.clone())) + .collect() + }) + .collect(); + + let unspent_map = addresses + .into_iter() + // `scripthash_list_unspent_batch` returns `ScriptHashUnspents` elements in the same order in which they were requested. + // So we can zip `addresses` and `unspents` into one iterator. + .zip(unspents) + .collect(); + Ok(unspent_map) + }; + Box::new(fut.boxed().compat()) + } + + fn send_transaction(&self, tx: &UtxoTx) -> UtxoRpcFut { + let bytes = if tx.has_witness() { + BytesJson::from(serialize_with_flags(tx, SERIALIZE_TRANSACTION_WITNESS)) + } else { + BytesJson::from(serialize(tx)) + }; + Box::new( + self.blockchain_transaction_broadcast(bytes) + .map_to_mm_fut(UtxoRpcError::from), + ) + } + + fn send_raw_transaction(&self, tx: BytesJson) -> UtxoRpcFut { + Box::new( + self.blockchain_transaction_broadcast(tx) + .map_to_mm_fut(UtxoRpcError::from), + ) + } + + fn blockchain_scripthash_subscribe_using(&self, server_address: &str, scripthash: String) -> UtxoRpcFut { + Box::new( + rpc_func_from!(self, server_address, BLOCKCHAIN_SCRIPTHASH_SUB_ID, scripthash) + .map_to_mm_fut(UtxoRpcError::from), + ) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get + /// returns transaction bytes by default + fn get_transaction_bytes(&self, txid: &H256Json) -> UtxoRpcFut { + let verbose = false; + Box::new(rpc_func!(self, "blockchain.transaction.get", txid, verbose).map_to_mm_fut(UtxoRpcError::from)) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get + /// returns verbose transaction by default + fn get_verbose_transaction(&self, txid: &H256Json) -> UtxoRpcFut { + let verbose = true; + Box::new(rpc_func!(self, "blockchain.transaction.get", txid, verbose).map_to_mm_fut(UtxoRpcError::from)) + } + + /// https://electrumx.readthedocs.io/en/latest/protocol-methods.html#blockchain-transaction-get + /// Returns verbose transactions in a batch. + fn get_verbose_transactions(&self, tx_ids: &[H256Json]) -> UtxoRpcFut> { + let verbose = true; + let requests = tx_ids + .iter() + .map(|txid| rpc_req!(self, "blockchain.transaction.get", txid, verbose)); + Box::new(self.batch_rpc(requests).map_to_mm_fut(UtxoRpcError::from)) + } + + fn get_block_count(&self) -> UtxoRpcFut { + Box::new( + self.blockchain_headers_subscribe() + .map(|r| r.block_height()) + .map_to_mm_fut(UtxoRpcError::from), + ) + } + + fn display_balance(&self, address: Address, decimals: u8) -> RpcRes { + let output_script = try_f!(output_script(&address).map_err(|err| JsonRpcError::new( + UtxoJsonRpcClientInfo::client_info(self), + rpc_req!(self, "blockchain.scripthash.get_balance").into(), + JsonRpcErrorType::Internal(err.to_string()) + ))); + let mut hashes = vec![hex::encode(electrum_script_hash(&output_script))]; + + // If the plain pubkey is available, fetch the balance found in P2PK output as well (if any). + if let Some(pubkey) = address.pubkey() { + let p2pk_output_script = output_script_p2pk(pubkey); + hashes.push(hex::encode(electrum_script_hash(&p2pk_output_script))); + } + + let this = self.clone(); + let fut = async move { + Ok(this + .scripthash_get_balances(hashes) + .compat() + .await? + .into_iter() + .fold(BigDecimal::from(0), |sum, electrum_balance| { + sum + electrum_balance.to_big_decimal(decimals) + })) + }; + Box::new(fut.boxed().compat()) + } + + fn display_balances(&self, addresses: Vec
, decimals: u8) -> UtxoRpcFut> { + let this = self.clone(); + let fut = async move { + let hashes = addresses + .iter() + .map(|address| { + let output_script = output_script(address)?; + let hash = electrum_script_hash(&output_script); + + Ok(hex::encode(hash)) + }) + .collect::, keys::Error>>()?; + + let electrum_balances = this.scripthash_get_balances(hashes).compat().await?; + let balances = electrum_balances + .into_iter() + // `scripthash_get_balances` returns `ElectrumBalance` elements in the same order in which they were requested. + // So we can zip `addresses` and the balances into one iterator. + .zip(addresses) + .map(|(electrum_balance, address)| (address, electrum_balance.to_big_decimal(decimals))) + .collect(); + Ok(balances) + }; + + Box::new(fut.boxed().compat()) + } + + fn estimate_fee_sat( + &self, + decimals: u8, + _fee_method: &EstimateFeeMethod, + mode: &Option, + n_blocks: u32, + ) -> UtxoRpcFut { + Box::new(self.estimate_fee(mode, n_blocks).map(move |fee| { + if fee > 0.00001 { + (fee * 10.0_f64.powf(decimals as f64)) as u64 + } else { + 1000 + } + })) + } + + fn get_relay_fee(&self) -> RpcRes { rpc_func!(self, "blockchain.relayfee") } + + fn find_output_spend( + &self, + tx_hash: H256, + script_pubkey: &[u8], + vout: usize, + _from_block: BlockHashOrHeight, + tx_hash_algo: TxHashAlgo, + ) -> Box, Error = String> + Send> { + let selfi = self.clone(); + let script_hash = hex::encode(electrum_script_hash(script_pubkey)); + let fut = async move { + let history = try_s!(selfi.scripthash_get_history(&script_hash).compat().await); + + if history.len() < 2 { + return Ok(None); + } + + for item in history.iter() { + let transaction = try_s!(selfi.get_transaction_bytes(&item.tx_hash).compat().await); + + let mut maybe_spend_tx: UtxoTx = + try_s!(deserialize(transaction.as_slice()).map_err(|e| ERRL!("{:?}", e))); + maybe_spend_tx.tx_hash_algo = tx_hash_algo; + drop_mutability!(maybe_spend_tx); + + for (index, input) in maybe_spend_tx.inputs.iter().enumerate() { + if input.previous_output.hash == tx_hash && input.previous_output.index == vout as u32 { + return Ok(Some(SpentOutputInfo { + input: input.clone(), + input_index: index, + spending_tx: maybe_spend_tx, + spent_in_block: BlockHashOrHeight::Height(item.height), + })); + } + } + } + Ok(None) + }; + Box::new(fut.boxed().compat()) + } + + fn get_median_time_past( + &self, + starting_block: u64, + count: NonZeroU64, + coin_variant: CoinVariant, + ) -> UtxoRpcFut { + let from = if starting_block <= count.get() { + 0 + } else { + starting_block - count.get() + 1 + }; + Box::new( + self.blockchain_block_headers(from, count) + .map_to_mm_fut(UtxoRpcError::from) + .and_then(|res| { + if res.count == 0 { + return MmError::err(UtxoRpcError::InvalidResponse("Server returned zero count".to_owned())); + } + let len = CompactInteger::from(res.count); + let mut serialized = serialize(&len).take(); + serialized.extend(res.hex.0.into_iter()); + let mut reader = Reader::new_with_coin_variant(serialized.as_slice(), coin_variant); + let headers = reader.read_list::()?; + 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()) + }), + ) + } + + async fn get_block_timestamp(&self, height: u64) -> Result> { + Ok(self.block_header_from_storage_or_rpc(height).await?.time as u64) + } +} diff --git a/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection.rs b/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection.rs new file mode 100644 index 0000000000..ca7a4bac60 --- /dev/null +++ b/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection.rs @@ -0,0 +1,734 @@ +use super::client::ElectrumClient; +use super::constants::{BLOCKCHAIN_HEADERS_SUB_ID, BLOCKCHAIN_SCRIPTHASH_SUB_ID, CUTOFF_TIMEOUT, + DEFAULT_CONNECTION_ESTABLISHMENT_TIMEOUT}; + +use crate::{RpcTransportEventHandler, SharableRpcTransportEventHandler}; +use common::custom_futures::timeout::FutureTimerExt; +use common::executor::{abortable_queue::AbortableQueue, abortable_queue::WeakSpawner, AbortableSystem, SpawnFuture, + Timer}; +use common::expirable_map::ExpirableMap; +use common::jsonrpc_client::{JsonRpcBatchResponse, JsonRpcErrorType, JsonRpcId, JsonRpcRequest, JsonRpcResponse, + JsonRpcResponseEnum}; +use common::log::{error, info}; +use common::{now_float, now_ms}; +use mm2_rpc::data::legacy::ElectrumProtocol; + +use std::io; +use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering}; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use futures::channel::oneshot as async_oneshot; +use futures::compat::{Future01CompatExt, Stream01CompatExt}; +use futures::future::FutureExt; +use futures::lock::Mutex as AsyncMutex; +use futures::select; +use futures::stream::StreamExt; +use futures01::sync::mpsc; +use futures01::{Sink, Stream}; +use http::Uri; +use instant::Instant; +use serde::Serialize; + +cfg_native! { + use super::tcp_stream::*; + + use std::convert::TryFrom; + use std::net::ToSocketAddrs; + use futures::future::{Either, TryFutureExt}; + use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, WriteHalf, ReadHalf}; + use tokio::net::TcpStream; + use tokio_rustls::{TlsConnector}; + use rustls::{ServerName}; +} + +cfg_wasm32! { + use mm2_net::wasm::wasm_ws::{ws_transport,WsOutgoingSender,WsIncomingReceiver}; + + use std::sync::atomic::AtomicUsize; +} + +pub type JsonRpcPendingRequests = ExpirableMap>; + +macro_rules! disconnect_and_return { + ($typ:tt, $err:expr, $conn:expr, $handlers:expr) => {{ + let err = ElectrumConnectionErr::$typ(format!("{:?}", $err)); + disconnect_and_return!(err, $conn, $handlers); + }}; + ($err:expr, $conn:expr, $handlers:expr) => {{ + // Inform the event handlers of the disconnection. + $handlers.on_disconnected(&$conn.address()).ok(); + // Disconnect the connection. + $conn.disconnect(Some($err.clone())); + return Err($err); + }}; +} + +macro_rules! disconnect_and_return_if_err { + ($ex:expr, $typ:tt, $conn:expr, $handlers:expr) => {{ + match $ex { + Ok(res) => res, + Err(e) => { + disconnect_and_return!($typ, e, $conn, $handlers); + }, + } + }}; + ($ex:expr, $conn:expr, $handlers:expr) => {{ + match $ex { + Ok(res) => res, + Err(e) => { + disconnect_and_return!(e, $conn, $handlers); + }, + } + }}; +} + +macro_rules! wrap_timeout { + ($call:expr, $timeout:expr, $conn:expr, $handlers:expr) => {{ + let now = Instant::now(); + let res = match $call.timeout_secs($timeout).await { + Ok(res) => res, + Err(_) => { + disconnect_and_return!( + ElectrumConnectionErr::Timeout(stringify!($call), $timeout), + $conn, + $handlers + ); + }, + }; + // Remaining timeout after executing `$call`. + let timeout = ($timeout - now.elapsed().as_secs_f64()).max(0.0); + (timeout, res) + }}; +} + +/// Helper function casting mpsc::Receiver as Stream. +fn rx_to_stream(rx: mpsc::Receiver>) -> impl Stream, Error = io::Error> { + rx.map_err(|_| panic!("errors not possible on rx")) +} + +#[cfg(not(target_arch = "wasm32"))] +/// Helper function to parse a a string DNS name into a ServerName. +fn server_name_from_domain(dns_name: &str) -> Result { + match ServerName::try_from(dns_name) { + // The `ServerName` must be `DnsName` variant, SSL works with domain names and not IPs. + Ok(dns_name) if matches!(dns_name, ServerName::DnsName(_)) => Ok(dns_name), + _ => ERR!("Couldn't parse DNS name from '{}'", dns_name), + } +} + +/// Electrum request RPC representation +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ElectrumConnectionSettings { + pub url: String, + #[serde(default)] + pub protocol: ElectrumProtocol, + #[serde(default)] + pub disable_cert_verification: bool, + pub timeout_sec: Option, +} + +/// Possible connection errors when connection to an Electrum server. +#[derive(Clone, Debug)] +pub enum ElectrumConnectionErr { + /// Couldn't connect to the server within the provided timeout. + /// The first argument is the call (stringified) that timed out. + /// The second argument is the time limit it had to finish within, in seconds. + Timeout(&'static str, f64), + /// A temporary error that might be resolved later on. + Temporary(String), + /// An error that can't be resolved by retrying. + Irrecoverable(String), + /// The server's version doesn't match the client's version. + VersionMismatch(String), +} + +impl ElectrumConnectionErr { + pub fn is_recoverable(&self) -> bool { + match self { + ElectrumConnectionErr::Irrecoverable(_) | ElectrumConnectionErr::VersionMismatch(_) => false, + ElectrumConnectionErr::Timeout(_, _) | ElectrumConnectionErr::Temporary(_) => true, + } + } +} + +/// Represents the active Electrum connection to selected address +#[derive(Debug)] +pub struct ElectrumConnection { + /// The client connected to this SocketAddr + settings: ElectrumConnectionSettings, + /// The Sender forwarding requests to writing part of underlying stream + tx: Mutex>>>, + /// A lock to prevent multiple connection establishments happening concurrently. + establishing_connection: AsyncMutex<()>, + /// Responses are stored here + responses: Mutex, + /// Selected protocol version. The value is initialized after the server.version RPC call. + protocol_version: Mutex>, + /// Why was the connection disconnected the last time? + last_error: Mutex>, + /// An abortable system for connection specific tasks to run on. + abortable_system: AbortableQueue, +} + +impl ElectrumConnection { + pub fn new(settings: ElectrumConnectionSettings, abortable_system: AbortableQueue) -> Self { + ElectrumConnection { + settings, + tx: Mutex::new(None), + establishing_connection: AsyncMutex::new(()), + responses: Mutex::new(JsonRpcPendingRequests::new()), + protocol_version: Mutex::new(None), + last_error: Mutex::new(None), + abortable_system, + } + } + + pub fn address(&self) -> &str { &self.settings.url } + + fn weak_spawner(&self) -> WeakSpawner { self.abortable_system.weak_spawner() } + + fn is_connected(&self) -> bool { self.tx.lock().unwrap().is_some() } + + fn set_protocol_version(&self, version: f32) { + let mut protocol_version = self.protocol_version.lock().unwrap(); + if protocol_version.is_none() { + *protocol_version = Some(version); + } + } + + fn clear_protocol_version(&self) { self.protocol_version.lock().unwrap().take(); } + + fn set_last_error(&self, reason: ElectrumConnectionErr) { + let mut last_error = self.last_error.lock().unwrap(); + if last_error.is_none() { + *last_error = Some(reason); + } + } + + fn clear_last_error(&self) { self.last_error.lock().unwrap().take(); } + + fn last_error(&self) -> Option { self.last_error.lock().unwrap().clone() } + + /// Connects to the electrum server by setting the `tx` sender channel. + /// + /// # Safety: + /// For this to be atomic, the caller must have acquired the lock to `establishing_connection`. + fn connect(&self, tx: mpsc::Sender>) { + self.tx.lock().unwrap().replace(tx); + self.clear_last_error(); + } + + /// Disconnect and clear the connection state. + pub fn disconnect(&self, reason: Option) { + self.tx.lock().unwrap().take(); + self.responses.lock().unwrap().clear(); + self.clear_protocol_version(); + if let Some(reason) = reason { + self.set_last_error(reason); + } + self.abortable_system.abort_all_and_reset().ok(); + } + + /// Sends a request to the electrum server and waits for the response. + /// + /// ## Important: This should always return [`JsonRpcErrorType::Transport`] error. + pub async fn electrum_request( + &self, + mut req_json: String, + rpc_id: JsonRpcId, + timeout: f64, + ) -> Result { + #[cfg(not(target_arch = "wasm"))] + { + // Electrum request and responses must end with \n + // https://electrumx.readthedocs.io/en/latest/protocol-basics.html#message-stream + req_json.push('\n'); + } + + // Create a oneshot channel to receive the response in. + let (req_tx, res_rx) = async_oneshot::channel(); + self.responses + .lock() + .unwrap() + .insert(rpc_id, req_tx, Duration::from_secs_f64(timeout)); + let tx = self + .tx + .lock() + .unwrap() + // Clone to not to hold the lock while sending the request. + .clone() + .ok_or_else(|| JsonRpcErrorType::Transport("Connection is not established".to_string()))?; + + // Send the request to the electrum server. + tx.send(req_json.into_bytes()) + .compat() + .await + .map_err(|e| JsonRpcErrorType::Transport(e.to_string()))?; + + // Wait for the response to be processed and sent back to us. + res_rx + .timeout_secs(timeout) + .await + .map_err(|e| JsonRpcErrorType::Transport(e.to_string()))? + .map_err(|_e| JsonRpcErrorType::Transport("The sender didn't send".to_string())) + } + + /// Process an incoming JSONRPC response from the electrum server. + fn process_electrum_response(&self, bytes: &[u8], event_handlers: &Vec>) { + // Inform the event handlers. + event_handlers.on_incoming_response(bytes); + + // detect if we got standard JSONRPC response or subscription response as JSONRPC request + #[derive(Deserialize)] + #[serde(untagged)] + enum ElectrumRpcResponseEnum { + /// The subscription response as JSONRPC request. + /// + /// NOTE Because JsonRpcResponse uses default values for each of its field, + /// this variant has to stay at top in this enumeration to be properly deserialized + /// from serde. + SubscriptionNotification(JsonRpcRequest), + /// The standard JSONRPC single response. + SingleResponse(JsonRpcResponse), + /// The batch of standard JSONRPC responses. + BatchResponses(JsonRpcBatchResponse), + } + + let response: ElectrumRpcResponseEnum = match serde_json::from_slice(bytes) { + Ok(res) => res, + Err(e) => { + error!("{}", e); + return; + }, + }; + + let response = match response { + ElectrumRpcResponseEnum::SingleResponse(single) => JsonRpcResponseEnum::Single(single), + ElectrumRpcResponseEnum::BatchResponses(batch) => JsonRpcResponseEnum::Batch(batch), + ElectrumRpcResponseEnum::SubscriptionNotification(req) => { + match req.method.as_str() { + // NOTE: Sending a script hash notification is handled in it's own event handler. + BLOCKCHAIN_SCRIPTHASH_SUB_ID | BLOCKCHAIN_HEADERS_SUB_ID => {}, + _ => { + error!("Unexpected notification method: {}", req.method); + }, + } + return; + }, + }; + + // the corresponding sender may not exist, receiver may be dropped + // these situations are not considered as errors so we just silently skip them + let pending = self.responses.lock().unwrap().remove(&response.rpc_id()); + if let Some(tx) = pending { + tx.send(response).ok(); + } + } + + /// Process a bulk response from the electrum server. + /// + /// A bulk response is a response that contains multiple JSONRPC responses. + fn process_electrum_bulk_response( + &self, + bulk_response: &[u8], + event_handlers: &Vec>, + ) { + // We should split the received response because we can get several responses in bulk. + let responses = bulk_response.split(|item| *item == b'\n'); + + for response in responses { + // `split` returns empty slice if it ends with separator which is our case. + if !response.is_empty() { + self.process_electrum_response(response, event_handlers) + } + } + } +} + +// Connection loop establishment methods. +impl ElectrumConnection { + /// Tries to establish a connection to the server. + /// + /// Returns the tokio stream with the server and the remaining timeout + /// left from the input timeout. + #[cfg(not(target_arch = "wasm32"))] + async fn establish_connection(connection: &ElectrumConnection) -> Result { + let address = connection.address(); + + let socket_addr = match address.to_socket_addrs() { + Err(e) if matches!(e.kind(), std::io::ErrorKind::InvalidInput) => { + return Err(ElectrumConnectionErr::Irrecoverable(format!( + "Invalid address format: {e:?}" + ))); + }, + Err(e) => { + return Err(ElectrumConnectionErr::Temporary(format!( + "Resolve error in address: {e:?}" + ))); + }, + Ok(mut addr) => match addr.next() { + None => { + return Err(ElectrumConnectionErr::Temporary("Address resolved to None".to_string())); + }, + Some(addr) => addr, + }, + }; + + let connect_f = match connection.settings.protocol { + ElectrumProtocol::TCP => Either::Left(TcpStream::connect(&socket_addr).map_ok(ElectrumStream::Tcp)), + ElectrumProtocol::SSL => { + let uri: Uri = match address.parse() { + Ok(uri) => uri, + Err(e) => { + return Err(ElectrumConnectionErr::Irrecoverable(format!("URL parse error: {e:?}"))); + }, + }; + + let Some(dns_name) = uri.host().map(String::from) else { + return Err(ElectrumConnectionErr::Irrecoverable( + "Couldn't retrieve host from address".to_string(), + )); + }; + + let Ok(dns) = server_name_from_domain(dns_name.as_str()) else { + return Err(ElectrumConnectionErr::Irrecoverable( + "Address isn't a valid domain name".to_string(), + )); + }; + + let tls_connector = if connection.settings.disable_cert_verification { + TlsConnector::from(UNSAFE_TLS_CONFIG.clone()) + } else { + TlsConnector::from(SAFE_TLS_CONFIG.clone()) + }; + + Either::Right( + TcpStream::connect(&socket_addr) + .and_then(move |stream| tls_connector.connect(dns, stream).map_ok(ElectrumStream::Tls)), + ) + }, + ElectrumProtocol::WS | ElectrumProtocol::WSS => { + return Err(ElectrumConnectionErr::Irrecoverable( + "Incorrect protocol for native connection ('WS'/'WSS'). Use 'TCP' or 'SSL' instead.".to_string(), + )); + }, + }; + + // Try to connect to the server. + let stream = match connect_f.await { + Ok(stream) => stream, + Err(e) => { + return Err(ElectrumConnectionErr::Temporary(format!( + "Couldn't connect to the electrum server: {e:?}" + ))) + }, + }; + if let Err(e) = stream.as_ref().set_nodelay(true) { + return Err(ElectrumConnectionErr::Temporary(format!( + "Setting TCP_NODELAY failed: {e:?}" + ))); + }; + + Ok(stream) + } + + #[cfg(target_arch = "wasm32")] + async fn establish_connection( + connection: &ElectrumConnection, + ) -> Result<(WsIncomingReceiver, WsOutgoingSender), ElectrumConnectionErr> { + lazy_static! { + static ref CONN_IDX: Arc = Arc::new(AtomicUsize::new(0)); + } + + let address = connection.address(); + let uri: Uri = match address.parse() { + Ok(uri) => uri, + Err(e) => { + return Err(ElectrumConnectionErr::Irrecoverable(format!( + "Failed to parse the address: {e:?}" + ))); + }, + }; + if uri.scheme().is_some() { + return Err(ElectrumConnectionErr::Irrecoverable( + "There has not to be a scheme in the url. 'ws://' scheme is used by default. Consider using 'protocol: \"WSS\"' in the electrum request to switch to the 'wss://' scheme.".to_string(), + ) + ); + } + + let protocol_prefixed_address = match connection.settings.protocol { + ElectrumProtocol::WS => { + format!("ws://{address}") + }, + ElectrumProtocol::WSS => { + format!("wss://{address}") + }, + ElectrumProtocol::TCP | ElectrumProtocol::SSL => { + return Err(ElectrumConnectionErr::Irrecoverable( + "'TCP' and 'SSL' are not supported in a browser. Please use 'WS' or 'WSS' protocols".to_string(), + )); + }, + }; + + let spawner = connection.weak_spawner(); + let connect_f = ws_transport( + CONN_IDX.fetch_add(1, AtomicOrdering::Relaxed), + &protocol_prefixed_address, + &spawner, + ); + + // Try to connect to the server. + let (transport_tx, transport_rx) = match connect_f.await { + Ok(stream) => stream, + Err(e) => { + return Err(ElectrumConnectionErr::Temporary(format!( + "Couldn't connect to the electrum server: {e:?}" + ))) + }, + }; + + Ok((transport_rx, transport_tx)) + } + + /// Waits until `last_response` time is too old in the past then returns a temporary error. + async fn timeout_loop(last_response: Arc) -> ElectrumConnectionErr { + loop { + Timer::sleep(CUTOFF_TIMEOUT).await; + let last_sec = (last_response.load(AtomicOrdering::Relaxed) / 1000) as f64; + if now_float() - last_sec > CUTOFF_TIMEOUT { + break ElectrumConnectionErr::Temporary(format!( + "Server didn't respond for too long ({}s).", + now_float() - last_sec + )); + } + } + } + + /// Runs the send loop that sends outgoing requests to the server. + /// + /// This runs until the sender is disconnected. + async fn send_loop( + address: String, + event_handlers: Arc>>, + #[cfg(not(target_arch = "wasm32"))] mut write: WriteHalf, + #[cfg(target_arch = "wasm32")] mut write: WsOutgoingSender, + rx: mpsc::Receiver>, + ) -> ElectrumConnectionErr { + let mut rx = rx_to_stream(rx).compat(); + while let Some(Ok(bytes)) = rx.next().await { + // NOTE: We shouldn't really notify on going request yet since we don't know + // if sending will error. We do that early though to avoid cloning the bytes on wasm. + event_handlers.on_outgoing_request(&bytes); + + #[cfg(not(target_arch = "wasm32"))] + let send_result = write.write_all(&bytes).await; + #[cfg(target_arch = "wasm32")] + let send_result = write.send(bytes).await; + + if let Err(e) = send_result { + error!("Write error {e} to {address}"); + } + } + ElectrumConnectionErr::Temporary("Sender disconnected".to_string()) + } + + /// Runs the receive loop that reads incoming responses from the server. + /// + /// This runs until the electrum server sends an empty response (signaling disconnection), + /// or if we encounter an error while reading from the stream. + #[cfg(not(target_arch = "wasm32"))] + async fn recv_loop( + connection: Arc, + event_handlers: Arc>>, + read: ReadHalf, + last_response: Arc, + ) -> ElectrumConnectionErr { + let mut buffer = String::with_capacity(1024); + let mut buf_reader = BufReader::new(read); + loop { + match buf_reader.read_line(&mut buffer).await { + Ok(c) => { + if c == 0 { + break ElectrumConnectionErr::Temporary("EOF".to_string()); + } + }, + Err(e) => { + break ElectrumConnectionErr::Temporary(format!("Error on read {e:?}")); + }, + }; + + last_response.store(now_ms(), AtomicOrdering::Relaxed); + connection.process_electrum_bulk_response(buffer.as_bytes(), &event_handlers); + buffer.clear(); + } + } + + #[cfg(target_arch = "wasm32")] + async fn recv_loop( + connection: Arc, + event_handlers: Arc>>, + mut read: WsIncomingReceiver, + last_response: Arc, + ) -> ElectrumConnectionErr { + let address = connection.address(); + while let Some(response) = read.next().await { + match response { + Ok(bytes) => { + last_response.store(now_ms(), AtomicOrdering::Relaxed); + connection.process_electrum_response(&bytes, &event_handlers); + }, + Err(e) => { + error!("{address} error: {e:?}"); + }, + } + } + ElectrumConnectionErr::Temporary("Receiver disconnected".to_string()) + } + + /// Checks the server version against the range of accepted versions and disconnects the server + /// if the version is not supported. + async fn check_server_version( + connection: &ElectrumConnection, + client: &ElectrumClient, + ) -> Result<(), ElectrumConnectionErr> { + let address = connection.address(); + + // Don't query for the version if the client doesn't care about it, as querying for the version might + // fail with the protocol range we will provide. + if !client.negotiate_version() { + return Ok(()); + } + + match client.server_version(address, client.protocol_version()).compat().await { + Ok(version_str) => match version_str.protocol_version.parse::() { + Ok(version_f32) => { + connection.set_protocol_version(version_f32); + Ok(()) + }, + Err(e) => Err(ElectrumConnectionErr::Temporary(format!( + "Failed to parse electrum server version {e:?}" + ))), + }, + // If the version we provided isn't supported by the server, it returns a JSONRPC response error. + Err(e) if matches!(e.error, JsonRpcErrorType::Response(..)) => { + Err(ElectrumConnectionErr::VersionMismatch(format!("{e:?}"))) + }, + Err(e) => Err(ElectrumConnectionErr::Temporary(format!( + "Failed to get electrum server version {e:?}" + ))), + } + } + + /// Starts the connection loop that keeps an active connection to the electrum server. + /// If this connection is already connected, nothing is performed and `Ok(())` is returned. + /// + /// This will first try to connect to the server and use that connection to query its version. + /// If version checks succeed, the connection will be kept alive, otherwise, it will be terminated. + pub async fn establish_connection_loop( + self: &Arc, + client: ElectrumClient, + ) -> Result<(), ElectrumConnectionErr> { + let connection = self.clone(); + let address = connection.address().to_string(); + let event_handlers = client.event_handlers(); + // This is the timeout for connection establishment and version querying (i.e. the whole method). + // The caller is guaranteed that the method will return within this time. + let timeout = connection + .settings + .timeout_sec + .unwrap_or(DEFAULT_CONNECTION_ESTABLISHMENT_TIMEOUT); + + // Locking `establishing_connection` will prevent other threads from establishing a connection concurrently. + let (timeout, _establishing_connection) = wrap_timeout!( + connection.establishing_connection.lock(), + timeout, + connection, + event_handlers + ); + + // Check if we are already connected. + if connection.is_connected() { + return Ok(()); + } + + // Check why we errored the last time, don't try to reconnect if it was an irrecoverable error. + if let Some(last_error) = connection.last_error() { + if !last_error.is_recoverable() { + return Err(last_error); + } + } + + let (timeout, stream_res) = wrap_timeout!( + Self::establish_connection(&connection).boxed(), + timeout, + connection, + event_handlers + ); + let stream = disconnect_and_return_if_err!(stream_res, connection, event_handlers); + + let (connection_ready_signal, wait_for_connection_ready) = async_oneshot::channel(); + let connection_loop = { + // Branch 1: Disconnect after not receiving responses for too long. + let last_response = Arc::new(AtomicU64::new(now_ms())); + let timeout_branch = Self::timeout_loop(last_response.clone()).boxed(); + + // Branch 2: Read incoming responses from the server. + #[cfg(not(target_arch = "wasm32"))] + let (read, write) = tokio::io::split(stream); + #[cfg(target_arch = "wasm32")] + let (read, write) = stream; + let recv_branch = Self::recv_loop(connection.clone(), event_handlers.clone(), read, last_response).boxed(); + + // Branch 3: Send outgoing requests to the server. + let (tx, rx) = mpsc::channel(0); + let send_branch = Self::send_loop(address.clone(), event_handlers.clone(), write, rx).boxed(); + + let connection = connection.clone(); + let event_handlers = event_handlers.clone(); + async move { + connection.connect(tx); + // Signal that the connection is up and ready so to start the version querying. + connection_ready_signal.send(()).ok(); + event_handlers.on_connected(&address).ok(); + let via = match connection.settings.protocol { + ElectrumProtocol::TCP => "via TCP", + ElectrumProtocol::SSL if connection.settings.disable_cert_verification => { + "via SSL *with disabled certificate verification*" + }, + ElectrumProtocol::SSL => "via SSL", + ElectrumProtocol::WS => "via WS", + ElectrumProtocol::WSS => "via WSS", + }; + info!("{address} is now connected {via}."); + + let err = select! { + e = timeout_branch.fuse() => e, + e = recv_branch.fuse() => e, + e = send_branch.fuse() => e, + }; + + error!("{address} connection dropped due to: {err:?}"); + event_handlers.on_disconnected(&address).ok(); + connection.disconnect(Some(err)); + } + }; + // Start the connection loop on a weak spawner. + connection.weak_spawner().spawn(connection_loop); + + // Wait for the connection to be ready before querying the version. + let (timeout, connection_ready_res) = + wrap_timeout!(wait_for_connection_ready, timeout, connection, event_handlers); + disconnect_and_return_if_err!(connection_ready_res, Temporary, connection, event_handlers); + + let (_, version_res) = wrap_timeout!( + Self::check_server_version(&connection, &client).boxed(), + timeout, + connection, + event_handlers + ); + disconnect_and_return_if_err!(version_res, connection, event_handlers); + + Ok(()) + } +} diff --git a/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection_manager/connection_context.rs b/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection_manager/connection_context.rs new file mode 100644 index 0000000000..17f3495b85 --- /dev/null +++ b/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection_manager/connection_context.rs @@ -0,0 +1,91 @@ +use std::collections::HashSet; +use std::mem; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{Arc, Mutex}; + +use super::super::connection::ElectrumConnection; +use super::super::constants::FIRST_SUSPEND_TIME; + +use common::now_ms; +use keys::Address; + +#[derive(Debug)] +struct SuspendTimer { + /// When was the connection last disconnected. + disconnected_at: AtomicU64, + /// How long to suspend the server the next time it disconnects (in milliseconds). + next_suspend_time: AtomicU64, +} + +impl SuspendTimer { + /// Creates a new suspend timer. + fn new() -> Self { + SuspendTimer { + disconnected_at: AtomicU64::new(0), + next_suspend_time: AtomicU64::new(FIRST_SUSPEND_TIME), + } + } + + /// Resets the suspend time and disconnection time. + fn reset(&self) { + self.disconnected_at.store(0, Ordering::SeqCst); + self.next_suspend_time.store(FIRST_SUSPEND_TIME, Ordering::SeqCst); + } + + /// Doubles the suspend time and sets the disconnection time to `now`. + fn double(&self) { + // The max suspend time, 12h. + const MAX_SUSPEND_TIME: u64 = 12 * 60 * 60; + self.disconnected_at.store(now_ms(), Ordering::SeqCst); + let mut next_suspend_time = self.next_suspend_time.load(Ordering::SeqCst); + next_suspend_time = (next_suspend_time * 2).min(MAX_SUSPEND_TIME); + self.next_suspend_time.store(next_suspend_time, Ordering::SeqCst); + } + + /// Returns the time until when the server should be suspended in milliseconds. + fn get_suspend_until(&self) -> u64 { + self.disconnected_at.load(Ordering::SeqCst) + self.next_suspend_time.load(Ordering::SeqCst) * 1000 + } +} + +/// A struct that encapsulates an Electrum connection and its information. +#[derive(Debug)] +pub struct ConnectionContext { + /// The electrum connection. + pub connection: Arc, + /// The list of addresses subscribed to the connection. + subs: Mutex>, + /// The timer deciding when the connection is ready to be used again. + suspend_timer: SuspendTimer, + /// The ID of this connection which also serves as a priority (lower is better). + pub id: u32, +} + +impl ConnectionContext { + /// Creates a new connection context. + pub(super) fn new(connection: ElectrumConnection, id: u32) -> Self { + ConnectionContext { + connection: Arc::new(connection), + subs: Mutex::new(HashSet::new()), + suspend_timer: SuspendTimer::new(), + id, + } + } + + /// Resets the suspend time. + pub(super) fn connected(&self) { self.suspend_timer.reset(); } + + /// Inform the connection context that the connection has been disconnected. + /// + /// Doubles the suspend time and clears the subs list and returns it. + pub(super) fn disconnected(&self) -> HashSet
{ + self.suspend_timer.double(); + mem::take(&mut self.subs.lock().unwrap()) + } + + /// Returns the time the server should be suspended until (when to take it up) in milliseconds. + pub(super) fn suspended_till(&self) -> u64 { self.suspend_timer.get_suspend_until() } + + /// Adds a subscription to the connection context. + pub(super) fn add_sub(&self, address: Address) { self.subs.lock().unwrap().insert(address); } +} diff --git a/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection_manager/manager.rs b/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection_manager/manager.rs new file mode 100644 index 0000000000..b06628fd60 --- /dev/null +++ b/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection_manager/manager.rs @@ -0,0 +1,528 @@ +use std::collections::{BTreeMap, HashMap}; +use std::sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, Weak}; + +use super::super::client::{ElectrumClient, ElectrumClientImpl}; +use super::super::connection::{ElectrumConnection, ElectrumConnectionErr, ElectrumConnectionSettings}; +use super::super::constants::{BACKGROUND_TASK_WAIT_TIMEOUT, PING_INTERVAL}; +use super::connection_context::ConnectionContext; + +use crate::utxo::rpc_clients::UtxoRpcClientOps; +use common::executor::abortable_queue::AbortableQueue; +use common::executor::{AbortableSystem, SpawnFuture, Timer}; +use common::log::{debug, error}; +use common::notifier::{Notifiee, Notifier}; +use common::now_ms; +use keys::Address; + +use futures::compat::Future01CompatExt; +use futures::FutureExt; + +/// A macro to unwrap an option and *execute* some code if the option is None. +macro_rules! unwrap_or_else { + ($option:expr, $($action:tt)*) => {{ + match $option { + Some(some_val) => some_val, + None => { $($action)* } + } + }}; +} + +macro_rules! unwrap_or_continue { + ($option:expr) => { + unwrap_or_else!($option, continue) + }; +} + +macro_rules! unwrap_or_return { + ($option:expr, $ret:expr) => { + unwrap_or_else!($option, return $ret) + }; + ($option:expr) => { + unwrap_or_else!($option, return) + }; +} + +/// The ID of a connection (and also its priority, lower is better). +type ID = u32; + +#[derive(Debug, Display)] +pub enum ConnectionManagerErr { + #[display(fmt = "Unknown server address")] + UnknownAddress, + #[display(fmt = "Failed to connect to the server due to {:?}", _0)] + ConnectingError(ElectrumConnectionErr), + #[display(fmt = "No client found, connection manager isn't initialized properly")] + NoClient, + #[display(fmt = "Connection manager is already initialized")] + AlreadyInitialized, +} + +/// The configuration parameter for a connection manager. +#[derive(Debug)] +pub struct ManagerConfig { + /// A flag to spawn a ping loop task for active connections. + pub spawn_ping: bool, + /// The minimum number of connections that should be connected at all times. + pub min_connected: usize, + /// The maximum number of connections that can be connected at any given time. + pub max_connected: usize, +} + +#[derive(Debug)] +/// A connection manager that maintains a set of connections to electrum servers and +/// handles reconnecting, address subscription distribution, etc... +struct ConnectionManagerImpl { + /// The configuration for the connection manager. + config: ManagerConfig, + /// The set of addresses that are currently connected. + /// + /// This set's size should satisfy: `min_connected <= maintained_connections.len() <= max_connected`. + /// + /// It is actually represented as a sorted map from connection ID (u32, also represents connection priority) + /// to address so we can easily/cheaply pop low priority connections and add high priority ones. + maintained_connections: RwLock>, + /// A map for server addresses to their corresponding connections. + connections: RwLock>, + /// A weak reference to the electrum client that owns this connection manager. + /// It is used to send electrum requests during connection establishment (version querying). + // TODO: This field might not be necessary if [`ElectrumConnection`] object be used to send + // electrum requests on its own, i.e. implement [`JsonRpcClient`] & [`UtxoRpcClientOps`]. + electrum_client: RwLock>>, + /// A notification sender to notify the background task when we have less than `min_connected` connections. + below_min_connected_notifier: Notifier, + /// A notification receiver to be used by the background task to receive notifications of when + /// we have less than `min_connected` maintained connections. + /// + /// Wrapped inside a Mutex>, +} + +#[derive(Clone, Debug)] +pub struct ConnectionManager(Arc); + +// Public interface. +impl ConnectionManager { + pub fn try_new( + servers: Vec, + spawn_ping: bool, + (min_connected, max_connected): (usize, usize), + abortable_system: &AbortableQueue, + ) -> Result { + let mut connections = HashMap::with_capacity(servers.len()); + // Priority is assumed to be the order of the servers in the list as they appear. + for (priority, connection_settings) in servers.into_iter().enumerate() { + let subsystem = abortable_system.create_subsystem().map_err(|e| { + ERRL!( + "Failed to create abortable subsystem for connection: {}, error: {:?}", + connection_settings.url, + e + ) + })?; + let connection = ElectrumConnection::new(connection_settings, subsystem); + connections.insert( + connection.address().to_string(), + ConnectionContext::new(connection, priority as u32), + ); + } + + if min_connected == 0 { + return Err(ERRL!("min_connected should be greater than 0")); + } + if min_connected > max_connected { + return Err(ERRL!( + "min_connected ({}) must be <= max_connected ({})", + min_connected, + max_connected + )); + } + + let (notifier, notifiee) = Notifier::new(); + Ok(ConnectionManager(Arc::new(ConnectionManagerImpl { + config: ManagerConfig { + spawn_ping, + min_connected, + max_connected, + }, + connections: RwLock::new(connections), + maintained_connections: RwLock::new(BTreeMap::new()), + electrum_client: RwLock::new(None), + below_min_connected_notifier: notifier, + below_min_connected_notifiee: Mutex::new(Some(notifiee)), + }))) + } + + /// Initializes the connection manager by connecting the electrum connections. + /// This must be called and only be called once to have a functioning connection manager. + pub fn initialize(&self, weak_client: Weak) -> Result<(), ConnectionManagerErr> { + // Disallow reusing the same manager with another client. + if self.weak_client().read().unwrap().is_some() { + return Err(ConnectionManagerErr::AlreadyInitialized); + } + + let electrum_client = unwrap_or_return!(weak_client.upgrade(), Err(ConnectionManagerErr::NoClient)); + + // Store the (weak) electrum client. + *self.weak_client().write().unwrap() = Some(weak_client); + + // Use the client's spawner to spawn the connection manager's background task. + electrum_client.weak_spawner().spawn(self.clone().background_task()); + + if self.config().spawn_ping { + // Use the client's spawner to spawn the connection manager's ping task. + electrum_client.weak_spawner().spawn(self.clone().ping_task()); + } + + Ok(()) + } + + /// Returns all the server addresses. + pub fn get_all_server_addresses(&self) -> Vec { self.read_connections().keys().cloned().collect() } + + /// Returns all the connections. + pub fn get_all_connections(&self) -> Vec> { + self.read_connections() + .values() + .map(|conn_ctx| conn_ctx.connection.clone()) + .collect() + } + + /// Retrieve a specific electrum connection by its address. + /// The connection will be forcibly established if it's disconnected. + pub async fn get_connection_by_address( + &self, + server_address: &str, + force_connect: bool, + ) -> Result, ConnectionManagerErr> { + let connection = self + .get_connection(server_address) + .ok_or(ConnectionManagerErr::UnknownAddress)?; + + if force_connect { + let client = unwrap_or_return!(self.get_client(), Err(ConnectionManagerErr::NoClient)); + // Make sure the connection is connected. + connection + .establish_connection_loop(client) + .await + .map_err(ConnectionManagerErr::ConnectingError)?; + } + + Ok(connection) + } + + /// Returns a list of active/maintained connections. + pub fn get_active_connections(&self) -> Vec> { + self.read_maintained_connections() + .iter() + .filter_map(|(_id, address)| self.get_connection(address)) + .collect() + } + + /// Returns a boolean `true` if the connection pool is empty, `false` otherwise. + pub fn is_connections_pool_empty(&self) -> bool { self.read_connections().is_empty() } + + /// Subscribe the list of addresses to our active connections. + /// + /// There is a bit of indirection here. We register the abandoned addresses on `on_disconnected` with + /// the client to queue them for `utxo_balance_events` which in turn calls this method back to re-subscribe + /// the abandoned addresses. We could have instead directly re-subscribed the addresses here in the connection + /// manager without sending them to `utxo_balance_events`. However, we don't do that so that `utxo_balance_events` + /// knows about all the added addresses. If it doesn't know about them, it won't be able to retrieve the triggered + /// address when its script hash is notified. + pub async fn add_subscriptions(&self, addresses: &HashMap) { + for (scripthash, address) in addresses.iter() { + // For a single address/scripthash, keep trying to subscribe it until we succeed. + 'single_address_sub: loop { + let client = unwrap_or_return!(self.get_client()); + let connections = self.get_active_connections(); + if connections.is_empty() { + // If there are no active connections, wait for a connection to be established. + Timer::sleep(1.).await; + continue; + } + // Try to subscribe the address to any connection we have. + for connection in connections { + if client + .blockchain_scripthash_subscribe_using(connection.address(), scripthash.clone()) + .compat() + .await + .is_ok() + { + let all_connections = self.read_connections(); + let connection_ctx = unwrap_or_continue!(all_connections.get(connection.address())); + connection_ctx.add_sub(address.clone()); + break 'single_address_sub; + } + } + } + } + } + + /// Handles the connection event. + pub fn on_connected(&self, server_address: &str) { + let all_connections = self.read_connections(); + let connection_ctx = unwrap_or_return!(all_connections.get(server_address)); + + // Reset the suspend time & disconnection time. + connection_ctx.connected(); + } + + /// Handles the disconnection event from an Electrum server. + pub fn on_disconnected(&self, server_address: &str) { + debug!("Electrum server disconnected: {}", server_address); + let all_connections = self.read_connections(); + let connection_ctx = unwrap_or_return!(all_connections.get(server_address)); + + self.unmaintain(connection_ctx.id); + + let abandoned_subs = connection_ctx.disconnected(); + // Re-subscribe the abandoned addresses using the client. + let client = unwrap_or_return!(self.get_client()); + client.subscribe_addresses(abandoned_subs).ok(); + } + + /// A method that should be called after using a specific server for some request. + /// + /// Instead of disconnecting the connection right away, this method will only disconnect it + /// if it's not in the maintained connections set. + pub fn not_needed(&self, server_address: &str) { + let (id, connection) = { + let all_connections = self.read_connections(); + let connection_ctx = unwrap_or_return!(all_connections.get(server_address)); + (connection_ctx.id, connection_ctx.connection.clone()) + }; + if !self.read_maintained_connections().contains_key(&id) { + connection.disconnect(Some(ElectrumConnectionErr::Temporary("Not needed anymore".to_string()))); + self.on_disconnected(connection.address()); + } + } + + /// Remove a connection from the connection manager by its address. + // TODO(feat): Add the ability to add a connection during runtime. + pub fn remove_connection(&self, server_address: &str) -> Result, ConnectionManagerErr> { + let connection = self + .get_connection(server_address) + .ok_or(ConnectionManagerErr::UnknownAddress)?; + // Make sure this connection is disconnected. + connection.disconnect(Some(ElectrumConnectionErr::Irrecoverable( + "Forcefully disconnected & removed".to_string(), + ))); + // Run the on-disconnection hook, this will also make sure the connection is removed from the maintained set. + self.on_disconnected(connection.address()); + // Remove the connection from the manager. + self.write_connections().remove(server_address); + Ok(connection) + } +} + +// Background tasks. +impl ConnectionManager { + /// A forever-lived task that pings active/maintained connections periodically. + async fn ping_task(self) { + loop { + let client = unwrap_or_return!(self.get_client()); + // This will ping all the active/maintained connections, which will keep these connections alive. + client.server_ping().compat().await.ok(); + Timer::sleep(PING_INTERVAL).await; + } + } + + /// A forever-lived task that does the house keeping tasks of the connection manager: + /// - Maintaining the right number of active connections. + /// - Establishing new connections if needed. + /// - Replacing low priority connections with high priority ones periodically. + /// - etc... + async fn background_task(self) { + // Take out the min_connected notifiee from the manager. + let mut min_connected_notification = unwrap_or_return!(self.extract_below_min_connected_notifiee()); + // A flag to indicate whether to log connection establishment errors or not. We should not log them if we + // are in panic mode (i.e. we are below the `min_connected` threshold) as this will flood the error log. + let mut log_errors = true; + loop { + // Get the candidate connections that we will consider maintaining. + let (candidate_connections, will_never_get_min_connected) = self.get_candidate_connections(); + // Establish the connections to the selected candidates and alter the maintained connections set accordingly. + self.establish_best_connections(candidate_connections, log_errors).await; + // Only sleep if we successfully acquired the minimum number of connections, + // or if we know we can never maintain `min_connected` connections; there is no point of infinite non-wait looping then. + if self.read_maintained_connections().len() >= self.config().min_connected || will_never_get_min_connected { + // Wait for a timeout or a below `min_connected` notification before doing another round of house keeping. + futures::select! { + _ = Timer::sleep(BACKGROUND_TASK_WAIT_TIMEOUT).fuse() => (), + _ = min_connected_notification.wait().fuse() => (), + } + log_errors = true; + } else { + // Never sleeping can result in busy waiting, which is problematic as it might not + // give a chance to other tasks to make progress, especially in single threaded environments. + // Yield the execution to the executor to give a chance to other tasks to run. + // TODO: `yield` keyword is not supported in the current rust version, using a short sleep for now. + Timer::sleep(1.).await; + log_errors = false; + } + } + } + + /// Returns a list of candidate connections that aren't maintained and could be considered for maintaining. + /// + /// Also returns a flag indicating whether covering `min_connected` connections is even possible: not possible when + /// `min_connected` is greater than the number of connections we have. + fn get_candidate_connections(&self) -> (Vec<(Arc, u32)>, bool) { + let all_connections = self.read_connections(); + let maintained_connections = self.read_maintained_connections(); + // The number of connections we need to add as maintained to reach the `min_connected` threshold. + let connections_needed = self.config().min_connected.saturating_sub(maintained_connections.len()); + // The connections that we can consider (all connections - candidate connections). + let all_candidate_connections: Vec<_> = all_connections + .iter() + .filter_map(|(_, conn_ctx)| { + (!maintained_connections.contains_key(&conn_ctx.id)).then(|| (conn_ctx.connection.clone(), conn_ctx.id)) + }) + .collect(); + // The candidate connections from above, but further filtered by whether they are suspended or not. + let non_suspended_candidate_connections: Vec<_> = all_candidate_connections + .iter() + .filter(|(connection, _)| { + all_connections + .get(connection.address()) + .map_or(false, |conn_ctx| now_ms() > conn_ctx.suspended_till()) + }) + .cloned() + .collect(); + // Decide which candidate connections to consider (all or only non-suspended). + if connections_needed > non_suspended_candidate_connections.len() { + if connections_needed > all_candidate_connections.len() { + // Not enough connections to cover the `min_connected` threshold. + // This means we will never be able to maintain `min_connected` active connections. + (all_candidate_connections, true) + } else { + // If we consider all candidate connection (but some are suspended), we can cover the needed connections. + // We will consider the suspended ones since if we don't we will stay below `min_connected` threshold. + (all_candidate_connections, false) + } + } else { + // Non suspended candidates are enough to cover the needed connections. + (non_suspended_candidate_connections, false) + } + } + + /// Establishes the best connections (based on priority) using the candidate connections + /// till we can't establish no more (hit the `max_connected` threshold). + async fn establish_best_connections( + &self, + mut candidate_connections: Vec<(Arc, u32)>, + log_errors: bool, + ) { + let client = unwrap_or_return!(self.get_client()); + // Sort the candidate connections by their priority/ID. + candidate_connections.sort_by_key(|(_, priority)| *priority); + for (connection, connection_id) in candidate_connections { + let address = connection.address().to_string(); + let (maintained_connections_size, lowest_priority_connection_id) = { + let maintained_connections = self.read_maintained_connections(); + let maintained_connections_size = maintained_connections.len(); + let lowest_priority_connection_id = *maintained_connections.keys().next_back().unwrap_or(&u32::MAX); + (maintained_connections_size, lowest_priority_connection_id) + }; + + // We can only try to add the connection if: + // 1- We haven't reached the `max_connected` threshold. + // 2- We have reached the `max_connected` threshold but the connection has a higher priority than the lowest priority connection. + if maintained_connections_size < self.config().max_connected + || connection_id < lowest_priority_connection_id + { + // Now that we know the connection is good to be inserted, try to establish it. + if let Err(e) = connection.establish_connection_loop(client.clone()).await { + if log_errors { + error!("Failed to establish connection to {address} due to error: {e:?}"); + } + // Remove the connection if it's not recoverable. + if !e.is_recoverable() { + self.remove_connection(&address).ok(); + } + continue; + } + self.maintain(connection_id, address); + } else { + // If any of the two conditions on the `if` statement above are not met, there is nothing to do. + // At this point we have already collected `max_connected` connections and also the current connection + // in the candidate list has a lower priority than the lowest priority maintained connection, and the next + // candidate connections as well since they are sorted by priority. + break; + } + } + } +} + +// Abstractions over the accesses of the inner fields of the connection manager. +impl ConnectionManager { + #[inline] + pub fn config(&self) -> &ManagerConfig { &self.0.config } + + #[inline] + fn read_connections(&self) -> RwLockReadGuard> { + self.0.connections.read().unwrap() + } + + #[inline] + fn write_connections(&self) -> RwLockWriteGuard> { + self.0.connections.write().unwrap() + } + + #[inline] + fn get_connection(&self, server_address: &str) -> Option> { + self.read_connections() + .get(server_address) + .map(|connection_ctx| connection_ctx.connection.clone()) + } + + #[inline] + fn read_maintained_connections(&self) -> RwLockReadGuard> { + self.0.maintained_connections.read().unwrap() + } + + #[inline] + fn maintain(&self, id: ID, server_address: String) { + let mut maintained_connections = self.0.maintained_connections.write().unwrap(); + maintained_connections.insert(id, server_address); + // If we have reached the `max_connected` threshold then remove the lowest priority connection. + if maintained_connections.len() > self.config().max_connected { + let lowest_priority_connection_id = *maintained_connections.keys().next_back().unwrap_or(&u32::MAX); + maintained_connections.remove(&lowest_priority_connection_id); + } + } + + #[inline] + fn unmaintain(&self, id: ID) { + // To avoid write locking the maintained connections, just make sure the connection is actually maintained first. + let is_maintained = self.read_maintained_connections().contains_key(&id); + if is_maintained { + // If the connection was maintained, remove it from the maintained connections. + let mut maintained_connections = self.0.maintained_connections.write().unwrap(); + maintained_connections.remove(&id); + // And notify the background task if we fell below the `min_connected` threshold. + if maintained_connections.len() < self.config().min_connected { + self.notify_below_min_connected() + } + } + } + + #[inline] + fn notify_below_min_connected(&self) { self.0.below_min_connected_notifier.notify().ok(); } + + #[inline] + fn extract_below_min_connected_notifiee(&self) -> Option { + self.0.below_min_connected_notifiee.lock().unwrap().take() + } + + #[inline] + fn weak_client(&self) -> &RwLock>> { &self.0.electrum_client } + + #[inline] + fn get_client(&self) -> Option { + self.weak_client() + .read() + .unwrap() + .as_ref() // None here = client was never initialized. + .and_then(|weak| weak.upgrade().map(ElectrumClient)) // None here = client was dropped. + } +} diff --git a/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection_manager/mod.rs b/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection_manager/mod.rs new file mode 100644 index 0000000000..b301ad3186 --- /dev/null +++ b/mm2src/coins/utxo/rpc_clients/electrum_rpc/connection_manager/mod.rs @@ -0,0 +1,4 @@ +mod connection_context; +mod manager; + +pub use manager::ConnectionManager; diff --git a/mm2src/coins/utxo/rpc_clients/electrum_rpc/constants.rs b/mm2src/coins/utxo/rpc_clients/electrum_rpc/constants.rs new file mode 100644 index 0000000000..f4d1a0efa5 --- /dev/null +++ b/mm2src/coins/utxo/rpc_clients/electrum_rpc/constants.rs @@ -0,0 +1,32 @@ +/// The timeout for the electrum server to respond to a request. +pub const ELECTRUM_REQUEST_TIMEOUT: f64 = 20.; +/// The default (can be overridden) maximum timeout to establish a connection with the electrum server. +/// This included connecting to the server and querying the server version. +pub const DEFAULT_CONNECTION_ESTABLISHMENT_TIMEOUT: f64 = 20.; +/// Wait this long before pinging again. +pub const PING_INTERVAL: f64 = 30.; +/// Used to cutoff the server connection after not receiving any response for that long. +/// This only makes sense if we have sent a request to the server. So we need to keep `PING_INTERVAL` +/// lower than this value, otherwise we might disconnect servers that are perfectly responsive but just +/// haven't received any requests from us for a while. +pub const CUTOFF_TIMEOUT: f64 = 60.; +/// Initial server suspension time. +pub const FIRST_SUSPEND_TIME: u64 = 10; +/// The timeout used by the background task of the connection manager to re-check the manager's health. +pub const BACKGROUND_TASK_WAIT_TIMEOUT: f64 = (5 * 60) as f64; +/// Electrum methods that should not be sent without forcing the connection to be established first. +pub const NO_FORCE_CONNECT_METHODS: &[&str] = &[ + // The server should already be connected if we are querying for its version, don't force connect. + "server.version", +]; +/// Electrum methods that should be sent to all connections even after receiving a response from a subset of them. +/// Note that this is only applicable to active/maintained connections. If an electrum request fails by all maintained +/// connections, a fallback using all connections will *NOT* be attempted. +pub const SEND_TO_ALL_METHODS: &[&str] = &[ + // A ping should be sent to all connections even if we got a response from one of them early. + "server.ping", +]; +/// Electrum RPC method for headers subscription. +pub const BLOCKCHAIN_HEADERS_SUB_ID: &str = "blockchain.headers.subscribe"; +/// Electrum RPC method for script/address subscription. +pub const BLOCKCHAIN_SCRIPTHASH_SUB_ID: &str = "blockchain.scripthash.subscribe"; diff --git a/mm2src/coins/utxo/rpc_clients/electrum_rpc/event_handlers.rs b/mm2src/coins/utxo/rpc_clients/electrum_rpc/event_handlers.rs new file mode 100644 index 0000000000..27bd74b4d9 --- /dev/null +++ b/mm2src/coins/utxo/rpc_clients/electrum_rpc/event_handlers.rs @@ -0,0 +1,74 @@ +use super::connection_manager::ConnectionManager; +use super::constants::BLOCKCHAIN_SCRIPTHASH_SUB_ID; + +use crate::utxo::ScripthashNotification; +use crate::RpcTransportEventHandler; +use common::jsonrpc_client::JsonRpcRequest; +use common::log::{error, warn}; + +use futures::channel::mpsc::UnboundedSender; +use serde_json::{self as json, Value as Json}; + +/// An `RpcTransportEventHandler` that forwards `ScripthashNotification`s to trigger balance updates. +/// +/// This handler hooks in `on_incoming_response` and looks for an electrum script hash notification to forward it. +pub struct ElectrumScriptHashNotificationBridge { + pub scripthash_notification_sender: UnboundedSender, +} + +impl RpcTransportEventHandler for ElectrumScriptHashNotificationBridge { + fn debug_info(&self) -> String { "ElectrumScriptHashNotificationBridge".into() } + + fn on_incoming_response(&self, data: &[u8]) { + if let Ok(raw_json) = json::from_slice::(data) { + // Try to parse the notification. A notification is sent as a JSON-RPC request. + if let Ok(notification) = json::from_value::(raw_json) { + // Only care about `BLOCKCHAIN_SCRIPTHASH_SUB_ID` notifications. + if notification.method.as_str() == BLOCKCHAIN_SCRIPTHASH_SUB_ID { + if let Some(scripthash) = notification.params.first().and_then(|s| s.as_str()) { + if let Err(e) = self + .scripthash_notification_sender + .unbounded_send(ScripthashNotification::Triggered(scripthash.to_string())) + { + error!("Failed sending script hash message. {e:?}"); + } + } else { + warn!("Notification must contain the script hash value, got: {notification:?}"); + } + }; + } + } + } + + fn on_connected(&self, _address: &str) -> Result<(), String> { Ok(()) } + + fn on_disconnected(&self, _address: &str) -> Result<(), String> { Ok(()) } + + fn on_outgoing_request(&self, _data: &[u8]) {} +} + +/// An `RpcTransportEventHandler` that notifies the `ConnectionManager` upon connections and disconnections. +/// +/// When a connection is connected or disconnected, this event handler will notify the `ConnectionManager` +/// to handle the the event. +pub struct ElectrumConnectionManagerNotifier { + pub connection_manager: ConnectionManager, +} + +impl RpcTransportEventHandler for ElectrumConnectionManagerNotifier { + fn debug_info(&self) -> String { "ElectrumConnectionManagerNotifier".into() } + + fn on_connected(&self, address: &str) -> Result<(), String> { + self.connection_manager.on_connected(address); + Ok(()) + } + + fn on_disconnected(&self, address: &str) -> Result<(), String> { + self.connection_manager.on_disconnected(address); + Ok(()) + } + + fn on_incoming_response(&self, _data: &[u8]) {} + + fn on_outgoing_request(&self, _data: &[u8]) {} +} diff --git a/mm2src/coins/utxo/rpc_clients/electrum_rpc/mod.rs b/mm2src/coins/utxo/rpc_clients/electrum_rpc/mod.rs new file mode 100644 index 0000000000..bf78308be2 --- /dev/null +++ b/mm2src/coins/utxo/rpc_clients/electrum_rpc/mod.rs @@ -0,0 +1,20 @@ +use sha2::{Digest, Sha256}; + +mod client; +mod connection; +mod connection_manager; +mod constants; +mod event_handlers; +mod rpc_responses; +#[cfg(not(target_arch = "wasm32"))] mod tcp_stream; + +pub use client::{ElectrumClient, ElectrumClientImpl, ElectrumClientSettings}; +pub use connection::ElectrumConnectionSettings; +pub use rpc_responses::*; + +#[inline] +pub fn electrum_script_hash(script: &[u8]) -> Vec { + let mut sha = Sha256::new(); + sha.update(script); + sha.finalize().iter().rev().copied().collect() +} diff --git a/mm2src/coins/utxo/rpc_clients/electrum_rpc/rpc_responses.rs b/mm2src/coins/utxo/rpc_clients/electrum_rpc/rpc_responses.rs new file mode 100644 index 0000000000..75daac6f35 --- /dev/null +++ b/mm2src/coins/utxo/rpc_clients/electrum_rpc/rpc_responses.rs @@ -0,0 +1,168 @@ +use chain::{BlockHeader, BlockHeaderBits, BlockHeaderNonce, Transaction as UtxoTx}; +use mm2_number::{BigDecimal, BigInt}; +use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json}; +use serialization::serialize; + +#[derive(Debug, Deserialize)] +pub struct ElectrumTxHistoryItem { + pub height: i64, + pub tx_hash: H256Json, + pub fee: Option, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct ElectrumUnspent { + pub height: Option, + pub tx_hash: H256Json, + pub tx_pos: u32, + pub value: u64, +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(untagged)] +pub enum ElectrumNonce { + Number(u64), + Hash(H256Json), +} + +#[allow(clippy::from_over_into)] +impl Into for ElectrumNonce { + fn into(self) -> BlockHeaderNonce { + match self { + ElectrumNonce::Number(n) => BlockHeaderNonce::U32(n as u32), + ElectrumNonce::Hash(h) => BlockHeaderNonce::H256(h.into()), + } + } +} + +#[derive(Debug, Deserialize)] +pub struct ElectrumBlockHeadersRes { + pub count: u64, + pub hex: BytesJson, + #[allow(dead_code)] + max: u64, +} + +/// The block header compatible with Electrum 1.2 +#[derive(Clone, Debug, Deserialize)] +pub struct ElectrumBlockHeaderV12 { + pub bits: u64, + pub block_height: u64, + pub merkle_root: H256Json, + pub nonce: ElectrumNonce, + pub prev_block_hash: H256Json, + pub timestamp: u64, + pub version: u64, +} + +impl ElectrumBlockHeaderV12 { + fn as_block_header(&self) -> BlockHeader { + BlockHeader { + version: self.version as u32, + previous_header_hash: self.prev_block_hash.into(), + merkle_root_hash: self.merkle_root.into(), + claim_trie_root: None, + hash_final_sapling_root: None, + time: self.timestamp as u32, + bits: BlockHeaderBits::U32(self.bits as u32), + nonce: self.nonce.clone().into(), + solution: None, + aux_pow: None, + prog_pow: None, + mtp_pow: None, + is_verus: false, + hash_state_root: None, + hash_utxo_root: None, + prevout_stake: None, + vch_block_sig_dlgt: None, + n_height: None, + n_nonce_u64: None, + mix_hash: None, + } + } + + #[inline] + pub fn as_hex(&self) -> String { + let block_header = self.as_block_header(); + let serialized = serialize(&block_header); + hex::encode(serialized) + } + + #[inline] + pub fn hash(&self) -> H256Json { + let block_header = self.as_block_header(); + BlockHeader::hash(&block_header).into() + } +} + +/// The block header compatible with Electrum 1.4 +#[derive(Clone, Debug, Deserialize)] +pub struct ElectrumBlockHeaderV14 { + pub height: u64, + pub hex: BytesJson, +} + +impl ElectrumBlockHeaderV14 { + pub fn hash(&self) -> H256Json { self.hex.clone().into_vec()[..].into() } +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(untagged)] +pub enum ElectrumBlockHeader { + V12(ElectrumBlockHeaderV12), + V14(ElectrumBlockHeaderV14), +} + +impl ElectrumBlockHeader { + pub fn block_height(&self) -> u64 { + match self { + ElectrumBlockHeader::V12(h) => h.block_height, + ElectrumBlockHeader::V14(h) => h.height, + } + } + + pub fn block_hash(&self) -> H256Json { + match self { + ElectrumBlockHeader::V12(h) => h.hash(), + ElectrumBlockHeader::V14(h) => h.hash(), + } + } +} + +/// The merkle branch of a confirmed transaction +#[derive(Clone, Debug, Deserialize)] +pub struct TxMerkleBranch { + pub merkle: Vec, + pub block_height: u64, + pub pos: usize, +} + +#[derive(Clone)] +pub struct ConfirmedTransactionInfo { + pub tx: UtxoTx, + pub header: BlockHeader, + pub index: u64, + pub height: u64, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct ElectrumBalance { + pub(crate) confirmed: i128, + pub(crate) unconfirmed: i128, +} + +impl ElectrumBalance { + #[inline] + pub fn to_big_decimal(&self, decimals: u8) -> BigDecimal { + let balance_sat = BigInt::from(self.confirmed) + BigInt::from(self.unconfirmed); + BigDecimal::from(balance_sat) / BigDecimal::from(10u64.pow(decimals as u32)) + } +} + +#[derive(Debug, Deserialize, Serialize)] +/// Deserializable Electrum protocol version representation for RPC +/// https://electrumx-spesmilo.readthedocs.io/en/latest/protocol-methods.html#server.version +pub struct ElectrumProtocolVersion { + pub server_software_version: String, + pub protocol_version: String, +} diff --git a/mm2src/coins/utxo/rpc_clients/electrum_rpc/tcp_stream.rs b/mm2src/coins/utxo/rpc_clients/electrum_rpc/tcp_stream.rs new file mode 100644 index 0000000000..b50b7f5c85 --- /dev/null +++ b/mm2src/coins/utxo/rpc_clients/electrum_rpc/tcp_stream.rs @@ -0,0 +1,105 @@ +use std::io; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; +use std::time::SystemTime; + +use futures::io::Error; +use rustls::client::ServerCertVerified; +use rustls::{Certificate, ClientConfig, OwnedTrustAnchor, RootCertStore, ServerName}; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; +use tokio::net::TcpStream; +use tokio_rustls::client::TlsStream; +use webpki_roots::TLS_SERVER_ROOTS; + +/// The enum wrapping possible variants of underlying Streams +#[allow(clippy::large_enum_variant)] +pub enum ElectrumStream { + Tcp(TcpStream), + Tls(TlsStream), +} + +impl AsRef for ElectrumStream { + fn as_ref(&self) -> &TcpStream { + match self { + ElectrumStream::Tcp(stream) => stream, + ElectrumStream::Tls(stream) => stream.get_ref().0, + } + } +} + +impl AsyncRead for ElectrumStream { + fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { + match self.get_mut() { + ElectrumStream::Tcp(stream) => AsyncRead::poll_read(Pin::new(stream), cx, buf), + ElectrumStream::Tls(stream) => AsyncRead::poll_read(Pin::new(stream), cx, buf), + } + } +} + +impl AsyncWrite for ElectrumStream { + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { + match self.get_mut() { + ElectrumStream::Tcp(stream) => AsyncWrite::poll_write(Pin::new(stream), cx, buf), + ElectrumStream::Tls(stream) => AsyncWrite::poll_write(Pin::new(stream), cx, buf), + } + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.get_mut() { + ElectrumStream::Tcp(stream) => AsyncWrite::poll_flush(Pin::new(stream), cx), + ElectrumStream::Tls(stream) => AsyncWrite::poll_flush(Pin::new(stream), cx), + } + } + + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.get_mut() { + ElectrumStream::Tcp(stream) => AsyncWrite::poll_shutdown(Pin::new(stream), cx), + ElectrumStream::Tls(stream) => AsyncWrite::poll_shutdown(Pin::new(stream), cx), + } + } +} + +/// Skips the server certificate verification on TLS connection +pub struct NoCertificateVerification {} + +impl rustls::client::ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _: &Certificate, + _: &[Certificate], + _: &ServerName, + _: &mut dyn Iterator, + _: &[u8], + _: SystemTime, + ) -> Result { + Ok(rustls::client::ServerCertVerified::assertion()) + } +} + +fn rustls_client_config(unsafe_conf: bool) -> Arc { + let mut cert_store = RootCertStore::empty(); + + cert_store.add_trust_anchors( + TLS_SERVER_ROOTS + .iter() + .map(|ta| OwnedTrustAnchor::from_subject_spki_name_constraints(ta.subject, ta.spki, ta.name_constraints)), + ); + + let mut tls_config = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(cert_store) + .with_no_client_auth(); + + if unsafe_conf { + tls_config + .dangerous() + .set_certificate_verifier(Arc::new(NoCertificateVerification {})); + } + Arc::new(tls_config) +} + +lazy_static! { + pub static ref SAFE_TLS_CONFIG: Arc = rustls_client_config(false); + pub static ref UNSAFE_TLS_CONFIG: Arc = rustls_client_config(true); +} diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index c559449c22..cbc7780a34 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -55,6 +55,7 @@ use serde_json::Value as Json; use serialization::{deserialize, serialize, Deserializable, Error as SerError, Reader}; use serialization_derive::Deserializable; use std::convert::TryInto; +use std::num::TryFromIntError; use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering}; use std::sync::Arc; use utxo_signer::with_key_pair::{p2pkh_spend, p2sh_spend, sign_tx, UtxoSignWithKeyPairError}; @@ -1188,7 +1189,7 @@ impl MarketCoinOps for SlpToken { self.platform_coin.wait_for_confirmations(input) } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { utxo_common::wait_for_output_spend( self.clone(), args.tx_bytes, @@ -1197,6 +1198,7 @@ impl MarketCoinOps for SlpToken { args.wait_until, args.check_every, ) + .await } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { @@ -1216,63 +1218,56 @@ impl MarketCoinOps for SlpToken { #[async_trait] impl SwapOps for SlpToken { - fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut { - let coin = self.clone(); - let fee_pubkey = try_tx_fus!(Public::from_slice(fee_addr)); + async fn send_taker_fee( + &self, + fee_addr: &[u8], + dex_fee: DexFee, + _uuid: &[u8], + _expire_at: u64, + ) -> TransactionResult { + let fee_pubkey = try_tx_s!(Public::from_slice(fee_addr)); let script_pubkey = ScriptBuilder::build_p2pkh(&fee_pubkey.address_hash().into()).into(); - let amount = try_tx_fus!(dex_fee.fee_uamount(self.decimals())); + let amount = try_tx_s!(dex_fee.fee_uamount(self.decimals())); + let slp_out = SlpOutput { amount, script_pubkey }; + let (preimage, recently_spent) = try_tx_s!(self.generate_slp_tx_preimage(vec![slp_out]).await); - let fut = async move { - let slp_out = SlpOutput { amount, script_pubkey }; - let (preimage, recently_spent) = try_tx_s!(coin.generate_slp_tx_preimage(vec![slp_out]).await); - generate_and_send_tx( - &coin, - preimage.available_bch_inputs, - Some(preimage.slp_inputs.into_iter().map(|slp| slp.bch_unspent).collect()), - FeePolicy::SendExact, - recently_spent, - preimage.outputs, - ) - .await - }; - Box::new(fut.boxed().compat().map(|tx| tx.into())) + generate_and_send_tx( + self, + preimage.available_bch_inputs, + Some(preimage.slp_inputs.into_iter().map(|slp| slp.bch_unspent).collect()), + FeePolicy::SendExact, + recently_spent, + preimage.outputs, + ) + .await + .map(|tx| tx.into()) } - fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { - let taker_pub = try_tx_fus!(Public::from_slice(maker_payment_args.other_pubkey)); - let amount = try_tx_fus!(sat_from_big_decimal(&maker_payment_args.amount, self.decimals())); + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + let taker_pub = try_tx_s!(Public::from_slice(maker_payment_args.other_pubkey)); + let amount = try_tx_s!(sat_from_big_decimal(&maker_payment_args.amount, self.decimals())); let secret_hash = maker_payment_args.secret_hash.to_owned(); let maker_htlc_keypair = self.derive_htlc_key_pair(maker_payment_args.swap_unique_data); - let time_lock = try_tx_fus!(maker_payment_args.time_lock.try_into()); + let time_lock = try_tx_s!(maker_payment_args.time_lock.try_into()); - let coin = self.clone(); - let fut = async move { - let tx = try_tx_s!( - coin.send_htlc(maker_htlc_keypair.public(), &taker_pub, time_lock, &secret_hash, amount) - .await - ); - Ok(tx.into()) - }; - Box::new(fut.boxed().compat()) + let tx = try_tx_s!( + self.send_htlc(maker_htlc_keypair.public(), &taker_pub, time_lock, &secret_hash, amount) + .await + ); + Ok(tx.into()) } - fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { - let maker_pub = try_tx_fus!(Public::from_slice(taker_payment_args.other_pubkey)); - let amount = try_tx_fus!(sat_from_big_decimal(&taker_payment_args.amount, self.decimals())); + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { + let maker_pub = try_tx_s!(Public::from_slice(taker_payment_args.other_pubkey)); + let amount = try_tx_s!(sat_from_big_decimal(&taker_payment_args.amount, self.decimals())); let secret_hash = taker_payment_args.secret_hash.to_owned(); let taker_htlc_keypair = self.derive_htlc_key_pair(taker_payment_args.swap_unique_data); - let time_lock = try_tx_fus!(taker_payment_args.time_lock.try_into()); + let time_lock = try_tx_s!(taker_payment_args.time_lock.try_into()); - let coin = self.clone(); - let fut = async move { - let tx = try_tx_s!( - coin.send_htlc(taker_htlc_keypair.public(), &maker_pub, time_lock, &secret_hash, amount) - .await - ); - Ok(tx.into()) - }; - Box::new(fut.boxed().compat()) + self.send_htlc(taker_htlc_keypair.public(), &maker_pub, time_lock, &secret_hash, amount) + .await + .map(|tx| tx.into()) } async fn send_maker_spends_taker_payment( @@ -1343,24 +1338,27 @@ impl SwapOps for SlpToken { Ok(tx.into()) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { let tx = match validate_fee_args.fee_tx { TransactionEnum::UtxoTx(tx) => tx.clone(), - _ => panic!(), + fee_tx => { + return MmError::err(ValidatePaymentError::InternalError(format!( + "Invalid fee tx type. fee tx: {:?}", + fee_tx + ))) + }, }; - let coin = self.clone(); - let expected_sender = validate_fee_args.expected_sender.to_owned(); - let fee_addr = validate_fee_args.fee_addr.to_owned(); - let amount = validate_fee_args.dex_fee.fee_amount(); - let min_block_number = validate_fee_args.min_block_number; - let fut = async move { - coin.validate_dex_fee(tx, &expected_sender, &fee_addr, amount.into(), min_block_number) - .await - .map_err(|e| MmError::new(ValidatePaymentError::WrongPaymentTx(e.into_inner().to_string())))?; - Ok(()) - }; - Box::new(fut.boxed().compat()) + let amount = validate_fee_args.dex_fee.fee_amount(); + self.validate_dex_fee( + tx, + validate_fee_args.expected_sender, + validate_fee_args.fee_addr, + amount.into(), + validate_fee_args.min_block_number, + ) + .await + .map_err(|e| MmError::new(ValidatePaymentError::WrongPaymentTx(e.into_inner().to_string()))) } async fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentResult<()> { @@ -1372,17 +1370,23 @@ impl SwapOps for SlpToken { } #[inline] - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, - if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { + if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, + ) -> Result, String> { + let time_lock = if_my_payment_sent_args + .time_lock + .try_into() + .map_err(|e: TryFromIntError| e.to_string())?; utxo_common::check_if_my_payment_sent( self.platform_coin.clone(), - try_fus!(if_my_payment_sent_args.time_lock.try_into()), + time_lock, if_my_payment_sent_args.other_pub, if_my_payment_sent_args.secret_hash, if_my_payment_sent_args.swap_unique_data, ) + .compat() + .await } #[inline] @@ -1945,6 +1949,7 @@ mod slp_tests { use crate::utxo::GetUtxoListOps; use crate::{utxo::bch::tbch_coin_for_test, TransactionErr}; use common::block_on; + use common::block_on_f01; use mocktopus::mocking::{MockResult, Mockable}; use std::mem::discriminant; @@ -2206,11 +2211,11 @@ mod slp_tests { ]; let tx_bytes_str = hex::encode(tx_bytes); - let err = fusd.send_raw_tx(&tx_bytes_str).wait().unwrap_err(); + let err = block_on_f01(fusd.send_raw_tx(&tx_bytes_str)).unwrap_err(); println!("{:?}", err); assert!(err.contains("is not valid with reason outputs greater than inputs")); - let err2 = fusd.send_raw_tx_bytes(tx_bytes).wait().unwrap_err(); + let err2 = block_on_f01(fusd.send_raw_tx_bytes(tx_bytes)).unwrap_err(); println!("{:?}", err2); assert!(err2.contains("is not valid with reason outputs greater than inputs")); assert_eq!(err, err2); diff --git a/mm2src/coins/utxo/utxo_balance_events.rs b/mm2src/coins/utxo/utxo_balance_events.rs index 2d97ef5cc9..ec1de7aa40 100644 --- a/mm2src/coins/utxo/utxo_balance_events.rs +++ b/mm2src/coins/utxo/utxo_balance_events.rs @@ -1,20 +1,20 @@ +use super::utxo_standard::UtxoStandardCoin; +use crate::utxo::rpc_clients::UtxoRpcClientEnum; +use crate::{utxo::{output_script, + rpc_clients::electrum_script_hash, + utxo_common::{address_balance, address_to_scripthash}, + ScripthashNotification, UtxoCoinFields}, + CoinWithDerivationMethod, MarketCoinOps, MmCoin}; use async_trait::async_trait; -use common::{executor::{AbortSettings, SpawnAbortable, Timer}, - log, Future01CompatExt}; +use common::{executor::{AbortSettings, SpawnAbortable}, + log}; use futures::channel::oneshot::{self, Receiver, Sender}; use futures_util::StreamExt; use keys::Address; use mm2_core::mm_ctx::MmArc; use mm2_event_stream::{behaviour::{EventBehaviour, EventInitStatus}, ErrorEventName, Event, EventName, EventStreamConfiguration}; -use std::collections::{BTreeMap, HashSet}; - -use super::utxo_standard::UtxoStandardCoin; -use crate::{utxo::{output_script, - rpc_clients::electrum_script_hash, - utxo_common::{address_balance, address_to_scripthash}, - ScripthashNotification, UtxoCoinFields}, - CoinWithDerivationMethod, MarketCoinOps, MmCoin}; +use std::collections::{BTreeMap, HashMap, HashSet}; macro_rules! try_or_continue { ($exp:expr) => { @@ -41,37 +41,29 @@ impl EventBehaviour for UtxoStandardCoin { utxo: &UtxoCoinFields, addresses: HashSet
, ) -> Result, String> { - const LOOP_INTERVAL: f64 = 0.5; - - let mut scripthash_to_address_map: BTreeMap = BTreeMap::new(); - for address in addresses { - let scripthash = address_to_scripthash(&address).map_err(|e| e.to_string())?; - - scripthash_to_address_map.insert(scripthash.clone(), address); - - let mut attempt = 0; - while let Err(e) = utxo - .rpc_client - .blockchain_scripthash_subscribe(scripthash.clone()) - .compat() - .await - { - if attempt == 5 { - return Err(e.to_string()); - } - - log::error!( - "Failed to subscribe {} scripthash ({attempt}/5 attempt). Error: {}", - scripthash, - e.to_string() - ); - - attempt += 1; - Timer::sleep(LOOP_INTERVAL).await; - } + match utxo.rpc_client.clone() { + UtxoRpcClientEnum::Electrum(client) => { + // Collect the scrpithash for every address into a map. + let scripthash_to_address_map = addresses + .into_iter() + .map(|address| { + let scripthash = address_to_scripthash(&address).map_err(|e| e.to_string())?; + Ok((scripthash, address)) + }) + .collect::, String>>()?; + // Add these subscriptions to the connection manager. It will choose whatever connections + // it sees fit to subscribe each of these addresses to. + client + .connection_manager + .add_subscriptions(&scripthash_to_address_map) + .await; + // Convert the hashmap back to btreemap. + Ok(scripthash_to_address_map.into_iter().map(|(k, v)| (k, v)).collect()) + }, + UtxoRpcClientEnum::Native(_) => { + Err("Balance streaming is currently not supported for native client.".to_owned()) + }, } - - Ok(scripthash_to_address_map) } let ctx = match MmArc::from_weak(&self.as_ref().ctx) { @@ -115,24 +107,6 @@ impl EventBehaviour for UtxoStandardCoin { }, }; - continue; - }, - ScripthashNotification::RefreshSubscriptions => { - let my_addresses = try_or_continue!(self.all_addresses().await); - match subscribe_to_addresses(self.as_ref(), my_addresses).await { - Ok(map) => scripthash_to_address_map = map, - Err(e) => { - log::error!("{e}"); - - ctx.stream_channel_controller - .broadcast(Event::new( - format!("{}:{}", Self::error_event_name(), self.ticker()), - json!({ "error": e }).to_string(), - )) - .await; - }, - }; - continue; }, }; diff --git a/mm2src/coins/utxo/utxo_builder/utxo_arc_builder.rs b/mm2src/coins/utxo/utxo_builder/utxo_arc_builder.rs index 60b4d75ff0..f8e16a6089 100644 --- a/mm2src/coins/utxo/utxo_builder/utxo_arc_builder.rs +++ b/mm2src/coins/utxo/utxo_builder/utxo_arc_builder.rs @@ -260,14 +260,14 @@ pub(crate) async fn block_header_utxo_loop( ) { macro_rules! remove_server_and_break_if_no_servers_left { ($client:expr, $server_address:expr, $ticker:expr, $sync_status_loop_handle:expr) => { - if let Err(e) = $client.remove_server($server_address).await { + if let Err(e) = $client.remove_server($server_address) { let msg = format!("Error {} on removing server {}!", e, $server_address); // Todo: Permanent error notification should lead to deactivation of coin after applying some fail-safe measures if there are on-going swaps $sync_status_loop_handle.notify_on_permanent_error(msg); break; } - if $client.is_connections_pool_empty().await { + if $client.is_connections_pool_empty() { // Todo: Permanent error notification should lead to deactivation of coin after applying some fail-safe measures if there are on-going swaps let msg = format!("All servers are removed for {}!", $ticker); $sync_status_loop_handle.notify_on_permanent_error(msg); @@ -294,14 +294,14 @@ pub(crate) async fn block_header_utxo_loop( }; let mut args = BlockHeaderUtxoLoopExtraArgs::default(); while let Some(client) = weak.upgrade() { - let client = &ElectrumClient(client); + let client = ElectrumClient(client); let ticker = client.coin_name(); let storage = client.block_headers_storage(); let last_height_in_storage = match storage.get_last_block_height().await { Ok(Some(height)) => height, Ok(None) => { - if let Err(err) = validate_and_store_starting_header(client, ticker, storage, &spv_conf).await { + if let Err(err) = validate_and_store_starting_header(&client, ticker, storage, &spv_conf).await { sync_status_loop_handle.notify_on_permanent_error(err); break; } @@ -372,7 +372,7 @@ pub(crate) async fn block_header_utxo_loop( }; let (block_registry, block_headers) = match try_to_retrieve_headers_until_success( &mut args, - client, + &client, server_address, last_height_in_storage + 1, retrieve_to, @@ -411,7 +411,7 @@ pub(crate) async fn block_header_utxo_loop( } = &err { match resolve_possible_chain_reorg( - client, + &client, server_address, &mut args, last_height_in_storage, diff --git a/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs b/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs index 446cadf2bb..15a699c2f1 100644 --- a/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs +++ b/mm2src/coins/utxo/utxo_builder/utxo_coin_builder.rs @@ -1,41 +1,37 @@ use crate::hd_wallet::{load_hd_accounts_from_storage, HDAccountsMutex, HDWallet, HDWalletCoinStorage, HDWalletStorageError, DEFAULT_GAP_LIMIT}; -use crate::utxo::rpc_clients::{ElectrumClient, ElectrumClientImpl, ElectrumRpcRequest, EstimateFeeMethod, +use crate::utxo::rpc_clients::{ElectrumClient, ElectrumClientSettings, ElectrumConnectionSettings, EstimateFeeMethod, UtxoRpcClientEnum}; use crate::utxo::tx_cache::{UtxoVerboseCacheOps, UtxoVerboseCacheShared}; use crate::utxo::utxo_block_header_storage::BlockHeaderStorage; use crate::utxo::utxo_builder::utxo_conf_builder::{UtxoConfBuilder, UtxoConfError}; -use crate::utxo::{output_script, ElectrumBuilderArgs, ElectrumProtoVerifier, ElectrumProtoVerifierEvent, - RecentlySpentOutPoints, ScripthashNotification, ScripthashNotificationSender, TxFee, UtxoCoinConf, - UtxoCoinFields, UtxoHDWallet, UtxoRpcMode, UtxoSyncStatus, UtxoSyncStatusLoopHandle, - UTXO_DUST_AMOUNT}; +use crate::utxo::{output_script, ElectrumBuilderArgs, RecentlySpentOutPoints, ScripthashNotification, + ScripthashNotificationSender, TxFee, UtxoCoinConf, UtxoCoinFields, UtxoHDWallet, UtxoRpcMode, + UtxoSyncStatus, UtxoSyncStatusLoopHandle, UTXO_DUST_AMOUNT}; use crate::{BlockchainNetwork, CoinTransportMetrics, DerivationMethod, HistorySyncState, IguanaPrivKey, - PrivKeyBuildPolicy, PrivKeyPolicy, PrivKeyPolicyNotAllowed, RpcClientType, UtxoActivationParams}; + PrivKeyBuildPolicy, PrivKeyPolicy, PrivKeyPolicyNotAllowed, RpcClientType, + SharableRpcTransportEventHandler, UtxoActivationParams}; use async_trait::async_trait; use chain::TxHashAlgo; -use common::custom_futures::repeatable::{Ready, Retry}; -use common::executor::{abortable_queue::AbortableQueue, AbortSettings, AbortableSystem, AbortedError, SpawnAbortable, - Timer}; -use common::log::{error, info, LogOnError}; -use common::{now_sec, small_rng}; +use common::executor::{abortable_queue::AbortableQueue, AbortableSystem, AbortedError}; +use common::now_sec; use crypto::{Bip32DerPathError, CryptoCtx, CryptoCtxError, GlobalHDAccountArc, HwWalletType, StandardHDPathError}; use derive_more::Display; -use futures::channel::mpsc::{channel, unbounded, Receiver as AsyncReceiver, UnboundedReceiver, UnboundedSender}; +use futures::channel::mpsc::{channel, Receiver as AsyncReceiver, UnboundedReceiver, UnboundedSender}; use futures::compat::Future01CompatExt; use futures::lock::Mutex as AsyncMutex; -use futures::StreamExt; use keys::bytes::Bytes; pub use keys::{Address, AddressBuilder, AddressFormat as UtxoAddressFormat, AddressHashEnum, AddressScriptType, KeyPair, Private, Public, Secret}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use primitives::hash::H160; -use rand::seq::SliceRandom; use serde_json::{self as json, Value as Json}; use spv_validation::conf::SPVConf; use spv_validation::helpers_validation::SPVError; use spv_validation::storage::{BlockHeaderStorageError, BlockHeaderStorageOps}; -use std::sync::{Arc, Mutex, Weak}; +use std::sync::Arc; +use std::sync::Mutex; cfg_native! { use crate::utxo::coin_daemon_data_dir; @@ -60,16 +56,6 @@ pub enum UtxoCoinBuildError { ErrorDetectingFeeMethod(String), ErrorDetectingDecimals(String), InvalidBlockchainNetwork(String), - #[display( - fmt = "Failed to connect to at least 1 of {:?} in {} seconds.", - electrum_servers, - seconds - )] - FailedToConnectToElectrums { - electrum_servers: Vec, - seconds: u64, - }, - ElectrumProtocolVersionCheckError(String), #[display(fmt = "Can not detect the user home directory")] CantDetectUserHome, #[display(fmt = "Private key policy is not allowed: {}", _0)] @@ -82,8 +68,6 @@ pub enum UtxoCoinBuildError { )] CoinDoesntSupportTrezor, BlockHeaderStorageError(BlockHeaderStorageError), - #[display(fmt = "Error {} on getting the height of the latest block from rpc!", _0)] - CantGetBlockCount(String), #[display(fmt = "Internal error: {}", _0)] Internal(String), #[display(fmt = "SPV params verificaiton failed. Error: {_0}")] @@ -252,10 +236,7 @@ fn get_scripthash_notification_handlers( Arc>>, )> { if ctx.event_stream_configuration.is_some() { - let (sender, receiver): ( - UnboundedSender, - UnboundedReceiver, - ) = futures::channel::mpsc::unbounded(); + let (sender, receiver) = futures::channel::mpsc::unbounded(); Some((sender, Arc::new(AsyncMutex::new(receiver)))) } else { None @@ -565,12 +546,17 @@ pub trait UtxoCoinBuilderCommonOps { Ok(UtxoRpcClientEnum::Native(native)) } }, - UtxoRpcMode::Electrum { servers } => { + UtxoRpcMode::Electrum { + servers, + min_connected, + max_connected, + } => { let electrum = self .electrum_client( abortable_system, ElectrumBuilderArgs::default(), servers, + (min_connected, max_connected), scripthash_notification_sender, ) .await?; @@ -585,21 +571,19 @@ pub trait UtxoCoinBuilderCommonOps { &self, abortable_system: AbortableQueue, args: ElectrumBuilderArgs, - mut servers: Vec, + servers: Vec, + (min_connected, max_connected): (Option, Option), scripthash_notification_sender: ScripthashNotificationSender, ) -> UtxoCoinBuildResult { - let (on_event_tx, on_event_rx) = unbounded(); - let ticker = self.ticker().to_owned(); + let coin_ticker = self.ticker().to_owned(); let ctx = self.ctx(); - let mut event_handlers = vec![]; + let mut event_handlers: Vec> = vec![]; if args.collect_metrics { - event_handlers.push( - CoinTransportMetrics::new(ctx.metrics.weak(), ticker.clone(), RpcClientType::Electrum).into_shared(), - ); - } - - if args.negotiate_version { - event_handlers.push(ElectrumProtoVerifier { on_event_tx }.into_shared()); + event_handlers.push(Box::new(CoinTransportMetrics::new( + ctx.metrics.weak(), + coin_ticker.clone(), + RpcClientType::Electrum, + ))); } let storage_ticker = self.ticker().replace('-', "_"); @@ -609,56 +593,27 @@ pub trait UtxoCoinBuilderCommonOps { block_headers_storage.init().await?; } - let mut rng = small_rng(); - servers.as_mut_slice().shuffle(&mut rng); + let gui = ctx.gui().unwrap_or("UNKNOWN").to_string(); + let mm_version = ctx.mm_version().to_string(); + let (min_connected, max_connected) = (min_connected.unwrap_or(1), max_connected.unwrap_or(servers.len())); + let client_settings = ElectrumClientSettings { + client_name: format!("{} GUI/MM2 {}", gui, mm_version), + servers: servers.clone(), + coin_ticker, + spawn_ping: args.spawn_ping, + negotiate_version: args.negotiate_version, + min_connected, + max_connected, + }; - let client = ElectrumClientImpl::new( - ticker, + ElectrumClient::try_new( + client_settings, event_handlers, block_headers_storage, abortable_system, - args.negotiate_version, scripthash_notification_sender, - ); - for server in servers.iter() { - match client.add_server(server).await { - Ok(_) => (), - Err(e) => error!("Error {:?} connecting to {:?}. Address won't be used", e, server), - }; - } - - let mut attempts = 0i32; - while !client.is_connected().await { - if attempts >= 10 { - return MmError::err(UtxoCoinBuildError::FailedToConnectToElectrums { - electrum_servers: servers.clone(), - seconds: 5, - }); - } - - Timer::sleep(0.5).await; - attempts += 1; - } - - let client = Arc::new(client); - - let spawner = client.spawner(); - if args.negotiate_version { - let weak_client = Arc::downgrade(&client); - let client_name = format!("{} GUI/MM2 {}", ctx.gui().unwrap_or("UNKNOWN"), ctx.mm_version()); - spawn_electrum_version_loop(&spawner, weak_client, on_event_rx, client_name); - - wait_for_protocol_version_checked(&client) - .await - .map_to_mm(UtxoCoinBuildError::ElectrumProtocolVersionCheckError)?; - } - - if args.spawn_ping { - let weak_client = Arc::downgrade(&client); - spawn_electrum_ping_loop(&spawner, weak_client, servers); - } - - Ok(ElectrumClient(client)) + ) + .map_to_mm(UtxoCoinBuildError::Internal) } #[cfg(not(target_arch = "wasm32"))] @@ -869,140 +824,3 @@ fn read_native_mode_conf( ))); Ok((rpc_port, rpc_user.clone(), rpc_password.clone())) } - -/// Ping the electrum servers every 30 seconds to prevent them from disconnecting us. -/// According to docs server can do it if there are no messages in ~10 minutes. -/// https://electrumx.readthedocs.io/en/latest/protocol-methods.html?highlight=keep#server-ping -/// Weak reference will allow to stop the thread if client is dropped. -fn spawn_electrum_ping_loop( - spawner: &Spawner, - weak_client: Weak, - servers: Vec, -) { - let msg_on_stopped = format!("Electrum servers {servers:?} ping loop stopped"); - let fut = async move { - loop { - if let Some(client) = weak_client.upgrade() { - if let Err(e) = ElectrumClient(client).server_ping().compat().await { - error!("Electrum servers {:?} ping error: {}", servers, e); - } - } else { - break; - } - Timer::sleep(30.).await - } - }; - - let settings = AbortSettings::info_on_any_stop(msg_on_stopped); - spawner.spawn_with_settings(fut, settings); -} - -/// Follow the `on_connect_rx` stream and verify the protocol version of each connected electrum server. -/// https://electrumx.readthedocs.io/en/latest/protocol-methods.html?highlight=keep#server-version -/// Weak reference will allow to stop the thread if client is dropped. -fn spawn_electrum_version_loop( - spawner: &Spawner, - weak_client: Weak, - mut on_event_rx: UnboundedReceiver, - client_name: String, -) { - let fut = async move { - while let Some(event) = on_event_rx.next().await { - match event { - ElectrumProtoVerifierEvent::Connected(electrum_addr) => { - check_electrum_server_version(weak_client.clone(), client_name.clone(), electrum_addr).await - }, - ElectrumProtoVerifierEvent::Disconnected(electrum_addr) => { - if let Some(client) = weak_client.upgrade() { - client.reset_protocol_version(&electrum_addr).await.error_log(); - } - }, - } - } - }; - let settings = AbortSettings::info_on_any_stop("Electrum server.version loop stopped".to_string()); - spawner.spawn_with_settings(fut, settings); -} - -async fn check_electrum_server_version( - weak_client: Weak, - client_name: String, - electrum_addr: String, -) { - // client.remove_server() is called too often - async fn remove_server(client: ElectrumClient, electrum_addr: &str) { - if let Err(e) = client.remove_server(electrum_addr).await { - error!("Error on remove server: {}", e); - } - } - - if let Some(c) = weak_client.upgrade() { - let client = ElectrumClient(c); - let available_protocols = client.protocol_version(); - let version = match client - .server_version(&electrum_addr, &client_name, available_protocols) - .compat() - .await - { - Ok(version) => version, - Err(e) => { - error!("Electrum {} server.version error: {:?}", electrum_addr, e); - if !e.error.is_transport() { - remove_server(client, &electrum_addr).await; - }; - return; - }, - }; - - // check if the version is allowed - let actual_version = match version.protocol_version.parse::() { - Ok(v) => v, - Err(e) => { - error!("Error on parse protocol_version: {:?}", e); - remove_server(client, &electrum_addr).await; - return; - }, - }; - - if !available_protocols.contains(&actual_version) { - error!( - "Received unsupported protocol version {:?} from {:?}. Remove the connection", - actual_version, electrum_addr - ); - remove_server(client, &electrum_addr).await; - return; - } - - match client.set_protocol_version(&electrum_addr, actual_version).await { - Ok(()) => info!( - "Use protocol version {:?} for Electrum {:?}", - actual_version, electrum_addr - ), - Err(e) => error!("Error on set protocol_version: {}", e), - }; - } -} - -/// Wait until the protocol version of at least one client's Electrum is checked. -async fn wait_for_protocol_version_checked(client: &ElectrumClientImpl) -> Result<(), String> { - repeatable!(async { - if client.count_connections().await == 0 { - // All of the connections were removed because of server.version checking - return Ready(ERR!( - "There are no Electrums with the required protocol version {:?}", - client.protocol_version() - )); - } - - if client.is_protocol_version_checked().await { - return Ready(Ok(())); - } - Retry(()) - }) - .repeat_every_secs(0.5) - .attempts(10) - .await - .map_err(|_exceed| ERRL!("Failed protocol version verifying of at least 1 of Electrums in 5 seconds.")) - // Flatten `Result< Result<(), String>, String >` - .flatten() -} diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index be54d53fe1..70c8522b58 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -15,7 +15,7 @@ use crate::watcher_common::validate_watcher_reward; use crate::{scan_for_new_addresses_impl, CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, DexFee, GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, RawTransactionError, RawTransactionRequest, RawTransactionRes, RawTransactionResult, - RefundFundingSecretArgs, RefundMakerPaymentArgs, RefundPaymentArgs, RewardTarget, + RefundFundingSecretArgs, RefundMakerPaymentSecretArgs, RefundPaymentArgs, RewardTarget, SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignRawTransactionEnum, SignRawTransactionRequest, SignUtxoTransactionParams, SignatureError, SignatureResult, SpendMakerPaymentArgs, SpendPaymentArgs, SwapOps, @@ -2085,7 +2085,7 @@ pub fn watcher_validate_taker_fee( if tx_confirmed_before_block { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "{}: Fee tx {:?} confirmed before min_block {}", - EARLY_CONFIRMATION_ERR_LOG, taker_fee_tx, min_block_number + EARLY_CONFIRMATION_ERR_LOG, tx_from_rpc, min_block_number ))); } @@ -2871,13 +2871,12 @@ pub async fn wait_for_output_spend_impl( wait_until: u64, check_every: f64, ) -> MmResult { + let script_pubkey = &tx + .outputs + .get(output_index) + .or_mm_err(|| WaitForOutputSpendErr::NoOutputWithIndex(output_index))? + .script_pubkey; loop { - let script_pubkey = &tx - .outputs - .get(output_index) - .or_mm_err(|| WaitForOutputSpendErr::NoOutputWithIndex(output_index))? - .script_pubkey; - match coin .rpc_client .find_output_spend( @@ -2905,24 +2904,21 @@ pub async fn wait_for_output_spend_impl( } } -pub fn wait_for_output_spend + Send + Sync + 'static>( +pub async fn wait_for_output_spend + Send + Sync + 'static>( coin: T, tx_bytes: &[u8], output_index: usize, from_block: u64, wait_until: u64, check_every: f64, -) -> TransactionFut { - let mut tx: UtxoTx = try_tx_fus!(deserialize(tx_bytes).map_err(|e| ERRL!("{:?}", e))); +) -> TransactionResult { + let mut tx: UtxoTx = try_tx_s!(deserialize(tx_bytes).map_err(|e| ERRL!("{:?}", e))); tx.tx_hash_algo = coin.as_ref().tx_hash_algo; - let fut = async move { - wait_for_output_spend_impl(coin.as_ref(), &tx, output_index, from_block, wait_until, check_every) - .await - .map(|tx| tx.into()) - .map_err(|e| TransactionErr::Plain(format!("{:?}", e))) - }; - Box::new(fut.boxed().compat()) + wait_for_output_spend_impl(coin.as_ref(), &tx, output_index, from_block, wait_until, check_every) + .await + .map(|tx| tx.into()) + .map_err(|e| TransactionErr::Plain(format!("{:?}", e))) } pub fn tx_enum_from_bytes(coin: &UtxoCoinFields, bytes: &[u8]) -> Result> { @@ -4795,7 +4791,7 @@ where outputs, } = try_tx_s!(generate_swap_payment_outputs( &coin, - try_tx_s!(args.time_lock.try_into()), + try_tx_s!(args.funding_time_lock.try_into()), taker_htlc_key_pair.public_slice(), args.maker_pub, total_amount, @@ -4831,7 +4827,7 @@ where .push_opcode(Opcode::OP_0) .push_opcode(Opcode::OP_0) .into_script(); - let time_lock = try_tx_s!(args.time_lock.try_into()); + let time_lock = try_tx_s!(args.funding_time_lock.try_into()); let redeem_script = swap_proto_v2_scripts::taker_funding_script( time_lock, @@ -4886,14 +4882,14 @@ where let expected_amount_sat = sat_from_big_decimal(&total_expected_amount, coin.as_ref().decimals)?; let time_lock = args - .time_lock + .funding_time_lock .try_into() - .map_to_mm(|e: TryFromIntError| ValidateSwapV2TxError::LocktimeOverflow(e.to_string()))?; + .map_to_mm(|e: TryFromIntError| ValidateSwapV2TxError::Overflow(e.to_string()))?; let redeem_script = swap_proto_v2_scripts::taker_funding_script( time_lock, args.taker_secret_hash, - args.other_pub, + args.taker_pub, maker_htlc_key_pair.public(), ); let expected_output = TransactionOutput { @@ -4998,11 +4994,7 @@ where valid_addresses.insert(valid_address); } if let UtxoRpcClientEnum::Electrum(electrum_client) = &coin.as_ref().rpc_client { - if let Some(sender) = &electrum_client.scripthash_notification_sender { - sender - .unbounded_send(ScripthashNotification::SubscribeToAddresses(valid_addresses)) - .map_err(|e| ERRL!("Failed sending scripthash message. {}", e))?; - } + electrum_client.subscribe_addresses(valid_addresses)?; }; Ok(()) @@ -5069,7 +5061,7 @@ pub async fn spend_maker_payment_v2( /// Common implementation of maker payment v2 reclaim for UTXO coins using immediate refund path with secret reveal. pub async fn refund_maker_payment_v2_secret( coin: T, - args: RefundMakerPaymentArgs<'_, T>, + args: RefundMakerPaymentSecretArgs<'_, T>, ) -> Result where T: UtxoCommonOps + SwapOps, diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 75dfb5391a..f5a02f5095 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -22,13 +22,14 @@ use crate::utxo::utxo_builder::{UtxoArcBuilder, UtxoCoinBuilder}; use crate::utxo::utxo_hd_wallet::{UtxoHDAccount, UtxoHDAddress}; use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; -use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, CoinWithPrivKeyPolicy, - ConfirmPaymentInput, DexFee, FundingTxSpend, GenPreimageResult, GenTakerFundingSpendArgs, - GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, IguanaBalanceOps, IguanaPrivKey, MakerCoinSwapOpsV2, - MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, - PaymentInstructionsErr, PrivKeyBuildPolicy, RawTransactionRequest, RawTransactionResult, RefundError, - RefundFundingSecretArgs, RefundMakerPaymentArgs, RefundPaymentArgs, RefundResult, - SearchForFundingSpendErr, SearchForSwapTxSpendInput, SendMakerPaymentArgs, +use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinBalanceMap, CoinWithDerivationMethod, + CoinWithPrivKeyPolicy, CommonSwapOpsV2, ConfirmPaymentInput, DexFee, FundingTxSpend, GenPreimageResult, + GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, IguanaBalanceOps, + IguanaPrivKey, MakerCoinSwapOpsV2, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, + PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, + RawTransactionRequest, RawTransactionResult, RefundError, RefundFundingSecretArgs, + RefundMakerPaymentSecretArgs, RefundMakerPaymentTimelockArgs, RefundPaymentArgs, RefundResult, + RefundTakerPaymentArgs, SearchForFundingSpendErr, SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignRawTransactionRequest, SignatureResult, SpendMakerPaymentArgs, SpendPaymentArgs, SwapOps, SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, TakerSwapMakerCoin, ToBytes, TradePreimageValue, TransactionFut, TransactionResult, @@ -36,7 +37,7 @@ use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDeriva ValidateMakerPaymentArgs, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateSwapV2TxResult, ValidateTakerFundingArgs, ValidateTakerFundingSpendPreimageResult, ValidateTakerPaymentSpendPreimageResult, - ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WaitForTakerPaymentSpendError, + ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WaitForPaymentSpendError, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut}; use common::executor::{AbortableSystem, AbortedError}; @@ -302,18 +303,30 @@ impl UtxoStandardOps for UtxoStandardCoin { #[async_trait] impl SwapOps for UtxoStandardCoin { #[inline] - fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8], _expire_at: u64) -> TransactionFut { + async fn send_taker_fee( + &self, + fee_addr: &[u8], + dex_fee: DexFee, + _uuid: &[u8], + _expire_at: u64, + ) -> TransactionResult { utxo_common::send_taker_fee(self.clone(), fee_addr, dex_fee) + .compat() + .await } #[inline] - fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { utxo_common::send_maker_payment(self.clone(), maker_payment_args) + .compat() + .await } #[inline] - fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { utxo_common::send_taker_payment(self.clone(), taker_payment_args) + .compat() + .await } #[inline] @@ -342,10 +355,15 @@ impl SwapOps for UtxoStandardCoin { utxo_common::send_maker_refunds_payment(self.clone(), maker_refunds_payment_args).await } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { let tx = match validate_fee_args.fee_tx { TransactionEnum::UtxoTx(tx) => tx.clone(), - _ => panic!(), + fee_tx => { + return MmError::err(ValidatePaymentError::InternalError(format!( + "Invalid fee tx type. fee tx: {:?}", + fee_tx + ))) + }, }; utxo_common::validate_fee( self.clone(), @@ -356,6 +374,8 @@ impl SwapOps for UtxoStandardCoin { validate_fee_args.min_block_number, validate_fee_args.fee_addr, ) + .compat() + .await } #[inline] @@ -369,17 +389,23 @@ impl SwapOps for UtxoStandardCoin { } #[inline] - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, - if_my_payment_sent_args: CheckIfMyPaymentSentArgs, - ) -> Box, Error = String> + Send> { + if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, + ) -> Result, String> { + let time_lock = if_my_payment_sent_args + .time_lock + .try_into() + .map_err(|e: TryFromIntError| e.to_string())?; utxo_common::check_if_my_payment_sent( self.clone(), - try_fus!(if_my_payment_sent_args.time_lock.try_into()), + time_lock, if_my_payment_sent_args.other_pub, if_my_payment_sent_args.secret_hash, if_my_payment_sent_args.swap_unique_data, ) + .compat() + .await } #[inline] @@ -414,13 +440,10 @@ impl SwapOps for UtxoStandardCoin { } #[inline] - fn can_refund_htlc(&self, locktime: u64) -> Box + Send + '_> { - Box::new( - utxo_common::can_refund_htlc(self, locktime) - .boxed() - .map_err(|e| ERRL!("{}", e)) - .compat(), - ) + async fn can_refund_htlc(&self, locktime: u64) -> Result { + utxo_common::can_refund_htlc(self, locktime) + .await + .map_err(|e| ERRL!("{}", e)) } fn is_auto_refundable(&self) -> bool { false } @@ -637,13 +660,25 @@ impl MakerCoinSwapOpsV2 for UtxoStandardCoin { .await } - async fn refund_maker_payment_v2_timelock(&self, args: RefundPaymentArgs<'_>) -> Result { + async fn refund_maker_payment_v2_timelock( + &self, + args: RefundMakerPaymentTimelockArgs<'_>, + ) -> Result { + let args = RefundPaymentArgs { + payment_tx: args.payment_tx, + time_lock: args.time_lock, + other_pubkey: args.taker_pub, + tx_type_with_secret_hash: args.tx_type_with_secret_hash, + swap_contract_address: &None, + swap_unique_data: args.swap_unique_data, + watcher_reward: args.watcher_reward, + }; utxo_common::refund_htlc_payment(self.clone(), args).await } async fn refund_maker_payment_v2_secret( &self, - args: RefundMakerPaymentArgs<'_, Self>, + args: RefundMakerPaymentSecretArgs<'_, Self>, ) -> Result { utxo_common::refund_maker_payment_v2_secret(self.clone(), args).await } @@ -663,7 +698,19 @@ impl TakerCoinSwapOpsV2 for UtxoStandardCoin { utxo_common::validate_taker_funding(self, args).await } - async fn refund_taker_funding_timelock(&self, args: RefundPaymentArgs<'_>) -> Result { + async fn refund_taker_funding_timelock( + &self, + args: RefundTakerPaymentArgs<'_>, + ) -> Result { + let args = RefundPaymentArgs { + payment_tx: args.payment_tx, + time_lock: args.time_lock, + other_pubkey: args.maker_pub, + tx_type_with_secret_hash: args.tx_type_with_secret_hash, + swap_contract_address: &None, + swap_unique_data: args.swap_unique_data, + watcher_reward: args.watcher_reward, + }; utxo_common::refund_htlc_payment(self.clone(), args).await } @@ -775,7 +822,19 @@ impl TakerCoinSwapOpsV2 for UtxoStandardCoin { utxo_common::sign_and_send_taker_funding_spend(self, preimage, args, &htlc_keypair).await } - async fn refund_combined_taker_payment(&self, args: RefundPaymentArgs<'_>) -> Result { + async fn refund_combined_taker_payment( + &self, + args: RefundTakerPaymentArgs<'_>, + ) -> Result { + let args = RefundPaymentArgs { + payment_tx: args.payment_tx, + time_lock: args.time_lock, + other_pubkey: args.maker_pub, + tx_type_with_secret_hash: args.tx_type_with_secret_hash, + swap_contract_address: &None, + swap_unique_data: args.swap_unique_data, + watcher_reward: args.watcher_reward, + }; utxo_common::refund_htlc_payment(self.clone(), args).await } @@ -812,7 +871,7 @@ impl TakerCoinSwapOpsV2 for UtxoStandardCoin { taker_payment: &Self::Tx, from_block: u64, wait_until: u64, - ) -> MmResult { + ) -> MmResult { let res = utxo_common::wait_for_output_spend_impl( self.as_ref(), taker_payment, @@ -824,10 +883,17 @@ impl TakerCoinSwapOpsV2 for UtxoStandardCoin { .await?; Ok(res) } +} +impl CommonSwapOpsV2 for UtxoStandardCoin { fn derive_htlc_pubkey_v2(&self, swap_unique_data: &[u8]) -> Self::Pubkey { *self.derive_htlc_key_pair(swap_unique_data).public() } + + #[inline(always)] + fn derive_htlc_pubkey_v2_bytes(&self, swap_unique_data: &[u8]) -> Vec { + self.derive_htlc_pubkey_v2(swap_unique_data).to_bytes() + } } #[async_trait] @@ -878,7 +944,7 @@ impl MarketCoinOps for UtxoStandardCoin { utxo_common::wait_for_confirmations(&self.utxo_arc, input) } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { utxo_common::wait_for_output_spend( self.clone(), args.tx_bytes, @@ -887,6 +953,7 @@ impl MarketCoinOps for UtxoStandardCoin { args.wait_until, args.check_every, ) + .await } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { @@ -1068,9 +1135,12 @@ impl CoinWithDerivationMethod for UtxoStandardCoin { #[async_trait] impl IguanaBalanceOps for UtxoStandardCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; - async fn iguana_balances(&self) -> BalanceResult { self.my_balance().compat().await } + async fn iguana_balances(&self) -> BalanceResult { + let balance = self.my_balance().compat().await?; + Ok(HashMap::from([(self.ticker().to_string(), balance)])) + } } #[async_trait] @@ -1108,7 +1178,7 @@ impl HDCoinWithdrawOps for UtxoStandardCoin {} #[async_trait] impl GetNewAddressRpcOps for UtxoStandardCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn get_new_address_rpc_without_conf( &self, @@ -1132,7 +1202,7 @@ impl GetNewAddressRpcOps for UtxoStandardCoin { #[async_trait] impl HDWalletBalanceOps for UtxoStandardCoin { type HDAddressScanner = UtxoAddressScanner; - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn produce_hd_address_scanner(&self) -> BalanceResult { utxo_common::produce_hd_address_scanner(self).await @@ -1169,14 +1239,21 @@ impl HDWalletBalanceOps for UtxoStandardCoin { } async fn known_address_balance(&self, address: &Address) -> BalanceResult { - utxo_common::address_balance(self, address).await + let balance = utxo_common::address_balance(self, address).await?; + Ok(HashMap::from([(self.ticker().to_string(), balance)])) } async fn known_addresses_balances( &self, addresses: Vec
, ) -> BalanceResult> { - utxo_common::addresses_balances(self, addresses).await + let ticker = self.ticker().to_string(); + let balances = utxo_common::addresses_balances(self, addresses).await?; + + balances + .into_iter() + .map(|(address, balance)| Ok((address, HashMap::from([(ticker.clone(), balance)])))) + .collect() } async fn prepare_addresses_for_balance_stream_if_enabled( @@ -1189,7 +1266,7 @@ impl HDWalletBalanceOps for UtxoStandardCoin { #[async_trait] impl AccountBalanceRpcOps for UtxoStandardCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn account_balance_rpc( &self, @@ -1201,7 +1278,7 @@ impl AccountBalanceRpcOps for UtxoStandardCoin { #[async_trait] impl InitAccountBalanceRpcOps for UtxoStandardCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn init_account_balance_rpc( &self, @@ -1213,7 +1290,7 @@ impl InitAccountBalanceRpcOps for UtxoStandardCoin { #[async_trait] impl InitScanAddressesRpcOps for UtxoStandardCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn init_scan_for_new_addresses_rpc( &self, @@ -1225,7 +1302,7 @@ impl InitScanAddressesRpcOps for UtxoStandardCoin { #[async_trait] impl InitCreateAccountRpcOps for UtxoStandardCoin { - type BalanceObject = CoinBalance; + type BalanceObject = CoinBalanceMap; async fn init_create_account_rpc( &self, diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index 868f558f1f..bcd7cc991f 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -13,9 +13,9 @@ use crate::rpc_command::init_scan_for_new_addresses::{InitScanAddressesRpcOps, S use crate::utxo::qtum::{qtum_coin_with_priv_key, QtumCoin, QtumDelegationOps, QtumDelegationRequest}; #[cfg(not(target_arch = "wasm32"))] use crate::utxo::rpc_clients::{BlockHashOrHeight, NativeUnspent}; -use crate::utxo::rpc_clients::{ElectrumBalance, ElectrumClient, ElectrumClientImpl, GetAddressInfoRes, - ListSinceBlockRes, NativeClient, NativeClientImpl, NetworkInfo, UtxoRpcClientOps, - ValidateAddressRes, VerboseBlock}; +use crate::utxo::rpc_clients::{ElectrumBalance, ElectrumClient, ElectrumClientImpl, ElectrumClientSettings, + GetAddressInfoRes, ListSinceBlockRes, NativeClient, NativeClientImpl, NetworkInfo, + UtxoRpcClientOps, ValidateAddressRes, VerboseBlock}; use crate::utxo::spv::SimplePaymentVerification; #[cfg(not(target_arch = "wasm32"))] use crate::utxo::utxo_block_header_storage::{BlockHeaderStorage, SqliteBlockHeadersStorage}; @@ -27,20 +27,19 @@ use crate::utxo::utxo_common_tests::{self, utxo_coin_fields_for_test, utxo_coin_ use crate::utxo::utxo_hd_wallet::UtxoHDAccount; use crate::utxo::utxo_standard::{utxo_standard_coin_with_priv_key, UtxoStandardCoin}; use crate::utxo::utxo_tx_history_v2::{UtxoTxDetailsParams, UtxoTxHistoryOps}; -use crate::{BlockHeightAndTime, CoinBalance, ConfirmPaymentInput, DexFee, IguanaPrivKey, PrivKeyBuildPolicy, - SearchForSwapTxSpendInput, SpendPaymentArgs, StakingInfosDetails, SwapOps, TradePreimageValue, - TxFeeDetails, TxMarshalingErr, ValidateFeeArgs, INVALID_SENDER_ERR_LOG}; +use crate::{BlockHeightAndTime, CoinBalance, CoinBalanceMap, ConfirmPaymentInput, DexFee, IguanaPrivKey, + PrivKeyBuildPolicy, SearchForSwapTxSpendInput, SpendPaymentArgs, StakingInfosDetails, SwapOps, + TradePreimageValue, TxFeeDetails, TxMarshalingErr, ValidateFeeArgs, INVALID_SENDER_ERR_LOG}; #[cfg(not(target_arch = "wasm32"))] use crate::{WaitForHTLCTxSpendArgs, WithdrawFee}; use chain::{BlockHeader, BlockHeaderBits, OutPoint}; use common::executor::Timer; -use common::{block_on, wait_until_sec, OrdRange, PagingOptionsEnum, DEX_FEE_ADDR_RAW_PUBKEY}; +use common::{block_on, block_on_f01, wait_until_sec, OrdRange, PagingOptionsEnum, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::{privkey::key_pair_from_seed, Bip44Chain, HDPathToAccount, RpcDerivationPath, Secp256k1Secret}; #[cfg(not(target_arch = "wasm32"))] use db_common::sqlite::rusqlite::Connection; use futures::channel::mpsc::channel; -use futures::future::join_all; -use futures::TryFutureExt; +use futures::future::{join_all, Either, FutureExt, TryFutureExt}; use keys::prefixes::*; use mm2_core::mm_ctx::MmCtxBuilder; use mm2_number::bigdecimal::{BigDecimal, Signed}; @@ -86,7 +85,7 @@ pub fn electrum_client_for_test(servers: &[&str]) -> ElectrumClient { let servers = servers.into_iter().map(|s| json::from_value(s).unwrap()).collect(); let abortable_system = AbortableQueue::default(); - block_on(builder.electrum_client(abortable_system, args, servers, None)).unwrap() + block_on(builder.electrum_client(abortable_system, args, servers, (None, None), None)).unwrap() } /// Returned client won't work by default, requires some mocks to be usable @@ -438,18 +437,16 @@ fn test_wait_for_payment_spend_timeout_native() { let wait_until = now_sec() - 1; let from_block = 1000; - assert!(coin - .wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { - tx_bytes: &transaction, - secret_hash: &[], - wait_until, - from_block, - swap_contract_address: &None, - check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, - watcher_reward: false - }) - .wait() - .is_err()); + assert!(block_on(coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { + tx_bytes: &transaction, + secret_hash: &[], + wait_until, + from_block, + swap_contract_address: &None, + check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, + watcher_reward: false + })) + .is_err()); assert!(unsafe { OUTPUT_SPEND_CALLED }); } @@ -471,33 +468,40 @@ fn test_wait_for_payment_spend_timeout_electrum() { }; let abortable_system = AbortableQueue::default(); - let client = ElectrumClientImpl::new( - TEST_COIN_NAME.into(), + let client_settings = ElectrumClientSettings { + client_name: "test".to_string(), + servers: vec![], + coin_ticker: TEST_COIN_NAME.into(), + spawn_ping: true, + negotiate_version: true, + min_connected: 1, + max_connected: 1, + }; + let client = ElectrumClient::try_new( + client_settings, Default::default(), block_headers_storage, abortable_system, - true, None, - ); - let client = UtxoRpcClientEnum::Electrum(ElectrumClient(Arc::new(client))); + ) + .expect("Expected electrum_client_impl constructed without a problem"); + let client = UtxoRpcClientEnum::Electrum(client); let coin = utxo_coin_for_test(client, None, false); let transaction = hex::decode("01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f00000000494830450221008b9d1dc26ba6a9cb62127b02742fa9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac000247304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee0121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635711000000") .unwrap(); let wait_until = now_sec() - 1; let from_block = 1000; - assert!(coin - .wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { - tx_bytes: &transaction, - secret_hash: &[], - wait_until, - from_block, - swap_contract_address: &None, - check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, - watcher_reward: false - }) - .wait() - .is_err()); + assert!(block_on(coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { + tx_bytes: &transaction, + secret_hash: &[], + wait_until, + from_block, + swap_contract_address: &None, + check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, + watcher_reward: false + })) + .is_err()); assert!(unsafe { OUTPUT_SPEND_CALLED }); } @@ -575,19 +579,22 @@ fn test_search_for_swap_tx_spend_electrum_was_refunded() { #[cfg(not(target_arch = "wasm32"))] fn test_withdraw_impl_set_fixed_fee() { UtxoStandardCoin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); let client = NativeClient(Arc::new(NativeClientImpl::default())); @@ -613,7 +620,7 @@ fn test_withdraw_impl_set_fixed_fee() { } .into(), ); - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); assert_eq!(expected, tx_details.fee_details); } @@ -621,19 +628,22 @@ fn test_withdraw_impl_set_fixed_fee() { #[cfg(not(target_arch = "wasm32"))] fn test_withdraw_impl_sat_per_kb_fee() { UtxoStandardCoin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); let client = NativeClient(Arc::new(NativeClientImpl::default())); @@ -662,7 +672,7 @@ fn test_withdraw_impl_sat_per_kb_fee() { } .into(), ); - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); assert_eq!(expected, tx_details.fee_details); } @@ -670,19 +680,22 @@ fn test_withdraw_impl_sat_per_kb_fee() { #[cfg(not(target_arch = "wasm32"))] fn test_withdraw_impl_sat_per_kb_fee_amount_equal_to_max() { UtxoStandardCoin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); let client = NativeClient(Arc::new(NativeClientImpl::default())); @@ -701,7 +714,7 @@ fn test_withdraw_impl_sat_per_kb_fee_amount_equal_to_max() { memo: None, ibc_source_channel: None, }; - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).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 @@ -721,19 +734,22 @@ fn test_withdraw_impl_sat_per_kb_fee_amount_equal_to_max() { #[cfg(not(target_arch = "wasm32"))] fn test_withdraw_impl_sat_per_kb_fee_amount_equal_to_max_dust_included_to_fee() { UtxoStandardCoin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); let client = NativeClient(Arc::new(NativeClientImpl::default())); @@ -752,7 +768,7 @@ fn test_withdraw_impl_sat_per_kb_fee_amount_equal_to_max_dust_included_to_fee() memo: None, ibc_source_channel: None, }; - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).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 @@ -772,19 +788,22 @@ fn test_withdraw_impl_sat_per_kb_fee_amount_equal_to_max_dust_included_to_fee() #[cfg(not(target_arch = "wasm32"))] fn test_withdraw_impl_sat_per_kb_fee_amount_over_max() { UtxoStandardCoin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); let client = NativeClient(Arc::new(NativeClientImpl::default())); @@ -803,26 +822,29 @@ fn test_withdraw_impl_sat_per_kb_fee_amount_over_max() { memo: None, ibc_source_channel: None, }; - coin.withdraw(withdraw_req).wait().unwrap_err(); + block_on_f01(coin.withdraw(withdraw_req)).unwrap_err(); } #[test] #[cfg(not(target_arch = "wasm32"))] fn test_withdraw_impl_sat_per_kb_fee_max() { UtxoStandardCoin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); let client = NativeClient(Arc::new(NativeClientImpl::default())); @@ -851,7 +873,7 @@ fn test_withdraw_impl_sat_per_kb_fee_max() { } .into(), ); - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); assert_eq!(expected, tx_details.fee_details); } @@ -866,20 +888,23 @@ fn test_withdraw_kmd_rewards_impl( let verbose: RpcTransaction = json::from_str(verbose_serialized).unwrap(); let unspent_height = verbose.height; UtxoStandardCoin::get_unspent_ordered_list.mock_safe(move |coin: &UtxoStandardCoin, _| { - let tx: UtxoTx = tx_hex.into(); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: tx.hash(), - index: 0, - }, - value: tx.outputs[0].value, - height: unspent_height, - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let tx: UtxoTx = tx_hex.into(); + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: tx.hash(), + index: 0, + }, + value: tx.outputs[0].value, + height: unspent_height, + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); UtxoStandardCoin::get_current_mtp .mock_safe(move |_fields| MockResult::Return(Box::pin(futures::future::ok(current_mtp)))); @@ -909,7 +934,7 @@ fn test_withdraw_kmd_rewards_impl( coin: Some("KMD".into()), amount: "0.00001".parse().unwrap(), }); - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); assert_eq!(tx_details.fee_details, Some(expected_fee)); let expected_rewards = expected_rewards.map(|amount| KmdRewardsDetails { @@ -958,20 +983,23 @@ fn test_withdraw_rick_rewards_none() { const TX_HEX: &str = "0400008085202f8901df8119c507aa61d32332cd246dbfeb3818a4f96e76492454c1fbba5aa097977e000000004847304402205a7e229ea6929c97fd6dde254c19e4eb890a90353249721701ae7a1c477d99c402206a8b7c5bf42b5095585731d6b4c589ce557f63c20aed69ff242eca22ecfcdc7a01feffffff02d04d1bffbc050000232102afdbba3e3c90db5f0f4064118f79cf308f926c68afd64ea7afc930975663e4c4ac402dd913000000001976a9143e17014eca06281ee600adffa34b4afb0922a22288ac2bdab86035a00e000000000000000000000000"; UtxoStandardCoin::get_unspent_ordered_list.mock_safe(move |coin, _| { - let tx: UtxoTx = TX_HEX.into(); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: tx.hash(), - index: 0, - }, - value: tx.outputs[0].value, - height: Some(1431628), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let tx: UtxoTx = TX_HEX.into(); + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: tx.hash(), + index: 0, + }, + value: tx.outputs[0].value, + height: Some(1431628), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); let withdraw_req = WithdrawRequest { @@ -988,7 +1016,7 @@ fn test_withdraw_rick_rewards_none() { coin: Some(TEST_COIN_NAME.into()), amount: "0.00001".parse().unwrap(), }); - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); assert_eq!(tx_details.fee_details, Some(expected_fee)); assert_eq!(tx_details.kmd_rewards, None); } @@ -1066,11 +1094,11 @@ fn test_electrum_rpc_client_error() { let client = electrum_client_for_test(&["electrum1.cipig.net:10060"]); let empty_hash = H256Json::default(); - let err = client.get_verbose_transaction(&empty_hash).wait().unwrap_err(); + let err = block_on_f01(client.get_verbose_transaction(&empty_hash)).unwrap_err(); // use the static string instead because the actual error message cannot be obtain // by serde_json serialization - let expected = r#"JsonRpcError { client_info: "coin: DOC", request: JsonRpcRequest { jsonrpc: "2.0", id: "1", method: "blockchain.transaction.get", params: [String("0000000000000000000000000000000000000000000000000000000000000000"), Bool(true)] }, error: Response(electrum1.cipig.net:10060, Object({"code": Number(2), "message": String("daemon error: DaemonError({'code': -5, 'message': 'No such mempool or blockchain transaction. Use gettransaction for wallet transactions.'})")})) }"#; + let expected = r#"method: "blockchain.transaction.get", params: [String("0000000000000000000000000000000000000000000000000000000000000000"), Bool(true)] }, error: Response(electrum1.cipig.net:10060, Object({"code": Number(2), "message": String("daemon error: DaemonError({'code': -5, 'message': 'No such mempool or blockchain transaction. Use gettransaction for wallet transactions.'})")})) }"#; let actual = format!("{}", err); assert!(actual.contains(expected)); @@ -1305,10 +1333,7 @@ fn test_get_median_time_past_from_electrum_kmd() { "electrum3.cipig.net:10001", ]); - let mtp = client - .get_median_time_past(1773390, KMD_MTP_BLOCK_COUNT, CoinVariant::Standard) - .wait() - .unwrap(); + let mtp = block_on_f01(client.get_median_time_past(1773390, KMD_MTP_BLOCK_COUNT, CoinVariant::Standard)).unwrap(); // the MTP is block time of 1773385 in this case assert_eq!(1583159915, mtp); } @@ -1321,10 +1346,7 @@ fn test_get_median_time_past_from_electrum_btc() { "electrum3.cipig.net:10000", ]); - let mtp = client - .get_median_time_past(632858, KMD_MTP_BLOCK_COUNT, CoinVariant::Standard) - .wait() - .unwrap(); + let mtp = block_on_f01(client.get_median_time_past(632858, KMD_MTP_BLOCK_COUNT, CoinVariant::Standard)).unwrap(); assert_eq!(1591173041, mtp); } @@ -1348,10 +1370,7 @@ fn test_get_median_time_past_from_native_has_median_in_get_block() { ) }); - let mtp = client - .get_median_time_past(632858, KMD_MTP_BLOCK_COUNT, CoinVariant::Standard) - .wait() - .unwrap(); + let mtp = block_on_f01(client.get_median_time_past(632858, KMD_MTP_BLOCK_COUNT, CoinVariant::Standard)).unwrap(); assert_eq!(1591173041, mtp); } @@ -1394,10 +1413,7 @@ fn test_get_median_time_past_from_native_does_not_have_median_in_get_block() { MockResult::Return(Box::new(futures01::future::ok(block))) }); - let mtp = client - .get_median_time_past(632858, KMD_MTP_BLOCK_COUNT, CoinVariant::Standard) - .wait() - .unwrap(); + let mtp = block_on_f01(client.get_median_time_past(632858, KMD_MTP_BLOCK_COUNT, CoinVariant::Standard)).unwrap(); assert_eq!(1591173041, mtp); } @@ -1526,33 +1542,44 @@ fn test_network_info_negative_time_offset() { #[test] fn test_unavailable_electrum_proto_version() { - ElectrumClientImpl::new.mock_safe( - |coin_ticker, event_handlers, block_headers_storage, abortable_system, _, _| { + ElectrumClientImpl::try_new_arc.mock_safe( + |client_settings, block_headers_storage, abortable_system, event_handlers, scripthash_notification_sender| { MockResult::Return(ElectrumClientImpl::with_protocol_version( - coin_ticker, - event_handlers, - OrdRange::new(1.8, 1.9).unwrap(), + client_settings, block_headers_storage, abortable_system, - None, + event_handlers, + scripthash_notification_sender, + OrdRange::new(1.8, 1.9).unwrap(), )) }, ); let conf = json!({"coin":"RICK","asset":"RICK","rpcport":8923}); + let servers = ["electrum1.cipig.net:10020"]; let req = json!({ "method": "electrum", - "servers": [{"url":"electrum1.cipig.net:10020"}], + "servers": servers.iter().map(|server| json!({"url": server})).collect::>(), }); let ctx = MmCtxBuilder::new().into_mm_arc(); let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); let priv_key = Secp256k1Secret::from([1; 32]); - let error = block_on(utxo_standard_coin_with_priv_key(&ctx, "RICK", &conf, ¶ms, priv_key)) - .err() - .unwrap(); - log!("Error: {}", error); - assert!(error.contains("There are no Electrums with the required protocol version")); + let coin = block_on(utxo_standard_coin_with_priv_key(&ctx, "RICK", &conf, ¶ms, priv_key)).unwrap(); + // Wait a little bit to make sure the servers are removed due to version mismatch. + block_on(Timer::sleep(2.)); + if let UtxoRpcClientEnum::Electrum(ref electrum_client) = coin.as_ref().rpc_client { + for server in servers { + let error = block_on(electrum_client.get_block_count_from(server).compat()) + .err() + .unwrap() + .to_string(); + log!("{}", error); + assert!(error.contains("Unknown server address")); + } + } else { + panic!("Expected Electrum client"); + } } #[test] @@ -1595,20 +1622,29 @@ fn test_spam_rick() { #[test] fn test_one_unavailable_electrum_proto_version() { + // Patch the electurm client construct to require protocol version 1.4 only. + ElectrumClientImpl::try_new_arc.mock_safe( + |client_settings, block_headers_storage, abortable_system, event_handlers, scripthash_notification_sender| { + MockResult::Return(ElectrumClientImpl::with_protocol_version( + client_settings, + block_headers_storage, + abortable_system, + event_handlers, + scripthash_notification_sender, + 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")); + // When an electrum server doesn't support our protocol version range, it gets removed by the client, + // wait a little bit to make sure this is the case. + block_on(Timer::sleep(2.)); + let error = block_on_f01(client.get_block_count_from("electrum-mona.bitbank.cc:50001")) + .unwrap_err() + .to_string(); + log!("{}", error); + assert!(error.contains("Unknown server address")); drop(client); log!("Run BTC coin to test the server.version loop"); @@ -1628,7 +1664,7 @@ fn test_one_unavailable_electrum_proto_version() { block_on(async { Timer::sleep(0.5).await }); - assert!(coin.as_ref().rpc_client.get_block_count().wait().is_ok()); + assert!(block_on_f01(coin.as_ref().rpc_client.get_block_count()).is_ok()); } #[test] @@ -1685,7 +1721,7 @@ fn test_qtum_add_delegation() { address: address.to_string(), fee: Some(10), }; - let res = coin.add_delegation(request).wait().unwrap(); + let res = block_on_f01(coin.add_delegation(request)).unwrap(); // Eligible for delegation assert!(res.my_balance_change.is_negative()); assert_eq!(res.total_amount, res.spent_by_me); @@ -1695,7 +1731,7 @@ fn test_qtum_add_delegation() { address: "fake_address".to_string(), fee: Some(10), }; - let res = coin.add_delegation(request).wait(); + let res = block_on_f01(coin.add_delegation(request)); // Wrong address assert!(res.is_err()); } @@ -1728,7 +1764,7 @@ fn test_qtum_add_delegation_on_already_delegating() { address: address.to_string(), fee: Some(10), }; - let res = coin.add_delegation(request).wait(); + let res = block_on_f01(coin.add_delegation(request)); // Already Delegating assert!(res.is_err()); } @@ -1754,7 +1790,7 @@ fn test_qtum_get_delegation_infos() { keypair.private().secret, )) .unwrap(); - let staking_infos = coin.get_delegation_infos().wait().unwrap(); + let staking_infos = block_on_f01(coin.get_delegation_infos()).unwrap(); match staking_infos.staking_infos_details { StakingInfosDetails::Qtum(staking_details) => { assert!(staking_details.am_i_staking); @@ -1784,49 +1820,49 @@ fn test_qtum_remove_delegation() { keypair.private().secret, )) .unwrap(); - let res = coin.remove_delegation().wait(); + let res = block_on_f01(coin.remove_delegation()); assert!(res.is_ok()); } #[test] fn test_qtum_my_balance() { QtumCoin::get_mature_unspent_ordered_list.mock_safe(move |coin, _address| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - // spendable balance (66.0) - let mature = vec![ - UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + // spendable balance (66.0) + let mature = vec![ + UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 5000000000, + height: Default::default(), + script: Vec::new().into(), }, - value: 5000000000, - height: Default::default(), - script: Vec::new().into(), - }, - UnspentInfo { + UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1600000000, + height: Default::default(), + script: Vec::new().into(), + }, + ]; + // unspendable (2.0) + let immature = vec![UnspentInfo { outpoint: OutPoint { hash: 1.into(), index: 0, }, - value: 1600000000, + value: 200000000, height: Default::default(), script: Vec::new().into(), - }, - ]; - // unspendable (2.0) - let immature = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 200000000, - height: Default::default(), - script: Vec::new().into(), - }]; - MockResult::Return(Box::pin(futures::future::ok(( - MatureUnspentList { mature, immature }, - cache, - )))) + }]; + Ok((MatureUnspentList { mature, immature }, cache)) + }; + MockResult::Return(fut.boxed()) }); let conf = json!({"coin":"tQTUM","rpcport":13889,"pubtype":120,"p2shtype":110}); @@ -1845,7 +1881,7 @@ fn test_qtum_my_balance() { let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); let coin = block_on(qtum_coin_with_priv_key(&ctx, "tQTUM", &conf, ¶ms, priv_key)).unwrap(); - let CoinBalance { spendable, unspendable } = coin.my_balance().wait().unwrap(); + let CoinBalance { spendable, unspendable } = block_on_f01(coin.my_balance()).unwrap(); let expected_spendable = BigDecimal::from(66); let expected_unspendable = BigDecimal::from(2); assert_eq!(spendable, expected_spendable); @@ -1881,7 +1917,7 @@ fn test_qtum_my_balance_with_check_utxo_maturity_false() { let params = UtxoActivationParams::from_legacy_req(&req).unwrap(); let coin = block_on(qtum_coin_with_priv_key(&ctx, "tQTUM", &conf, ¶ms, priv_key)).unwrap(); - let CoinBalance { spendable, unspendable } = coin.my_balance().wait().unwrap(); + let CoinBalance { spendable, unspendable } = block_on_f01(coin.my_balance()).unwrap(); let expected_spendable = BigDecimal::from(DISPLAY_BALANCE); let expected_unspendable = BigDecimal::from(0); assert_eq!(spendable, expected_spendable); @@ -1899,7 +1935,7 @@ fn test_get_mature_unspent_ordered_map_from_cache_impl( const TX_HASH: &str = "b43f9ed47f7b97d4766b6f1614136fa0c55b9a52c97342428333521fa13ad714"; let tx_hash: H256Json = hex::decode(TX_HASH).unwrap().as_slice().into(); let client = electrum_client_for_test(DOC_ELECTRUM_ADDRS); - let mut verbose = client.get_verbose_transaction(&tx_hash).wait().unwrap(); + let mut verbose = block_on_f01(client.get_verbose_transaction(&tx_hash)).unwrap(); verbose.confirmations = cached_confs; verbose.height = cached_height; @@ -2517,15 +2553,13 @@ fn test_find_output_spend_skips_conflicting_transactions() { let tx: UtxoTx = "0400008085202f89027f57730fcbbc2c72fb18bcc3766a713044831a117bb1cade3ed88644864f7333020000006a47304402206e3737b2fcf078b61b16fa67340cc3e79c5d5e2dc9ffda09608371552a3887450220460a332aa1b8ad8f2de92d319666f70751078b221199951f80265b4f7cef8543012102d8c948c6af848c588517288168faa397d6ba3ea924596d03d1d84f224b5123c2ffffffff42b916a80430b80a77e114445b08cf120735447a524de10742fac8f6a9d4170f000000006a473044022004aa053edafb9d161ea8146e0c21ed1593aa6b9404dd44294bcdf920a1695fd902202365eac15dbcc5e9f83e2eed56a8f2f0e5aded36206f9c3fabc668fd4665fa2d012102d8c948c6af848c588517288168faa397d6ba3ea924596d03d1d84f224b5123c2ffffffff03547b16000000000017a9143e8ad0e2bf573d32cb0b3d3a304d9ebcd0c2023b870000000000000000166a144e2b3c0323ab3c2dc6f86dc5ec0729f11e42f56103970400000000001976a91450f4f098306f988d8843004689fae28c83ef16e888ac89c5925f000000000000000000000000000000".into(); let vout = 0; let from_block = 0; - let actual = client - .find_output_spend( - tx.hash(), - &tx.outputs[vout].script_pubkey, - vout, - BlockHashOrHeight::Height(from_block), - TxHashAlgo::DSHA256, - ) - .wait(); + let actual = block_on_f01(client.find_output_spend( + tx.hash(), + &tx.outputs[vout].script_pubkey, + vout, + BlockHashOrHeight::Height(from_block), + TxHashAlgo::DSHA256, + )); assert_eq!(actual, Ok(None)); assert_eq!(unsafe { GET_RAW_TRANSACTION_BYTES_CALLED }, 1); } @@ -2609,7 +2643,7 @@ fn test_get_sender_trade_fee_dynamic_tx_fee() { ); coin_fields.tx_fee = TxFee::Dynamic(EstimateFeeMethod::Standard); let coin = utxo_coin_from_fields(coin_fields); - let my_balance = coin.my_spendable_balance().wait().expect("!my_balance"); + let my_balance = block_on_f01(coin.my_spendable_balance()).expect("!my_balance"); let expected_balance = BigDecimal::from_str("2.22222").expect("!BigDecimal::from_str"); assert_eq!(my_balance, expected_balance); @@ -2657,7 +2691,7 @@ fn test_validate_fee_wrong_sender() { min_block_number: 0, uuid: &[], }; - let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + let error = block_on(coin.validate_fee(validate_fee_args)).unwrap_err().into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains(INVALID_SENDER_ERR_LOG)), @@ -2682,7 +2716,7 @@ fn test_validate_fee_min_block() { min_block_number: 278455, uuid: &[], }; - let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + let error = block_on(coin.validate_fee(validate_fee_args)).unwrap_err().into_inner(); match error { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("confirmed before min_block")), _ => panic!("Expected `WrongPaymentTx` early confirmation, found {:?}", error), @@ -2711,7 +2745,7 @@ fn test_validate_fee_bch_70_bytes_signature() { min_block_number: 0, uuid: &[], }; - coin.validate_fee(validate_fee_args).wait().unwrap(); + block_on(coin.validate_fee(validate_fee_args)).unwrap(); } #[test] @@ -2766,7 +2800,7 @@ fn firo_lelantus_tx() { "electrumx02.firo.org:50001", "electrumx03.firo.org:50001", ]); - let _tx = electrum.get_verbose_transaction(&tx_hash).wait().unwrap(); + let _tx = block_on_f01(electrum.get_verbose_transaction(&tx_hash)).unwrap(); } #[test] @@ -2903,9 +2937,7 @@ fn doge_mtp() { "electrum2.cipig.net:10060", "electrum3.cipig.net:10060", ]); - let mtp = electrum - .get_median_time_past(3631820, NonZeroU64::new(11).unwrap(), CoinVariant::Standard) - .wait() + let mtp = block_on_f01(electrum.get_median_time_past(3631820, NonZeroU64::new(11).unwrap(), CoinVariant::Standard)) .unwrap(); assert_eq!(mtp, 1614849084); } @@ -2917,9 +2949,7 @@ fn firo_mtp() { "electrumx02.firo.org:50001", "electrumx03.firo.org:50001", ]); - let mtp = electrum - .get_median_time_past(356730, NonZeroU64::new(11).unwrap(), CoinVariant::Standard) - .wait() + let mtp = block_on_f01(electrum.get_median_time_past(356730, NonZeroU64::new(11).unwrap(), CoinVariant::Standard)) .unwrap(); assert_eq!(mtp, 1616492629); } @@ -2927,9 +2957,7 @@ fn firo_mtp() { #[test] fn verus_mtp() { let electrum = electrum_client_for_test(&["el0.verus.io:17485", "el1.verus.io:17485", "el2.verus.io:17485"]); - let mtp = electrum - .get_median_time_past(1480113, NonZeroU64::new(11).unwrap(), CoinVariant::Standard) - .wait() + let mtp = block_on_f01(electrum.get_median_time_past(1480113, NonZeroU64::new(11).unwrap(), CoinVariant::Standard)) .unwrap(); assert_eq!(mtp, 1618579909); } @@ -2941,9 +2969,7 @@ fn sys_mtp() { "electrum2.cipig.net:10064", "electrum3.cipig.net:10064", ]); - let mtp = electrum - .get_median_time_past(1006678, NonZeroU64::new(11).unwrap(), CoinVariant::Standard) - .wait() + let mtp = block_on_f01(electrum.get_median_time_past(1006678, NonZeroU64::new(11).unwrap(), CoinVariant::Standard)) .unwrap(); assert_eq!(mtp, 1620019628); } @@ -2955,9 +2981,7 @@ fn btc_mtp() { "electrum2.cipig.net:10000", "electrum3.cipig.net:10000", ]); - let mtp = electrum - .get_median_time_past(681659, NonZeroU64::new(11).unwrap(), CoinVariant::Standard) - .wait() + let mtp = block_on_f01(electrum.get_median_time_past(681659, NonZeroU64::new(11).unwrap(), CoinVariant::Standard)) .unwrap(); assert_eq!(mtp, 1620019527); } @@ -2969,9 +2993,7 @@ fn rvn_mtp() { "electrum2.cipig.net:10051", "electrum3.cipig.net:10051", ]); - let mtp = electrum - .get_median_time_past(1968120, NonZeroU64::new(11).unwrap(), CoinVariant::Standard) - .wait() + let mtp = block_on_f01(electrum.get_median_time_past(1968120, NonZeroU64::new(11).unwrap(), CoinVariant::Standard)) .unwrap(); assert_eq!(mtp, 1633946264); } @@ -2983,10 +3005,8 @@ fn qtum_mtp() { "electrum2.cipig.net:10050", "electrum3.cipig.net:10050", ]); - let mtp = electrum - .get_median_time_past(681659, NonZeroU64::new(11).unwrap(), CoinVariant::Qtum) - .wait() - .unwrap(); + let mtp = + block_on_f01(electrum.get_median_time_past(681659, NonZeroU64::new(11).unwrap(), CoinVariant::Qtum)).unwrap(); assert_eq!(mtp, 1598854128); } @@ -2997,9 +3017,7 @@ fn zer_mtp() { "electrum2.cipig.net:10065", "electrum3.cipig.net:10065", ]); - let mtp = electrum - .get_median_time_past(1130915, NonZeroU64::new(11).unwrap(), CoinVariant::Standard) - .wait() + let mtp = block_on_f01(electrum.get_median_time_past(1130915, NonZeroU64::new(11).unwrap(), CoinVariant::Standard)) .unwrap(); assert_eq!(mtp, 1623240214); } @@ -3196,7 +3214,7 @@ fn test_withdraw_to_p2pk_fails() { }; assert!(matches!( - coin.withdraw(withdraw_req).wait().unwrap_err().into_inner(), + block_on_f01(coin.withdraw(withdraw_req)).unwrap_err().into_inner(), WithdrawError::InvalidAddress(..) )) } @@ -3209,19 +3227,22 @@ fn test_withdraw_to_p2pkh() { let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None, false); UtxoStandardCoin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); // Create a p2pkh address for the test coin @@ -3249,7 +3270,7 @@ fn test_withdraw_to_p2pkh() { memo: None, ibc_source_channel: None, }; - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); let transaction: UtxoTx = deserialize(tx_details.tx.tx_hex().unwrap().as_slice()).unwrap(); let output_script: Script = transaction.outputs[0].script_pubkey.clone().into(); @@ -3266,19 +3287,22 @@ fn test_withdraw_to_p2sh() { let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None, false); UtxoStandardCoin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); // Create a p2sh address for the test coin @@ -3306,7 +3330,7 @@ fn test_withdraw_to_p2sh() { memo: None, ibc_source_channel: None, }; - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); let transaction: UtxoTx = deserialize(tx_details.tx.tx_hex().unwrap().as_slice()).unwrap(); let output_script: Script = transaction.outputs[0].script_pubkey.clone().into(); @@ -3323,19 +3347,22 @@ fn test_withdraw_to_p2wpkh() { let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None, true); UtxoStandardCoin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - script: coin - .script_for_address(&block_on(coin.as_ref().derivation_method.unwrap_single_addr())) - .unwrap(), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + script: coin + .script_for_address(&coin.as_ref().derivation_method.unwrap_single_addr().await) + .unwrap(), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); // Create a p2wpkh address for the test coin @@ -3363,7 +3390,7 @@ fn test_withdraw_to_p2wpkh() { memo: None, ibc_source_channel: None, }; - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); let transaction: UtxoTx = deserialize(tx_details.tx.tx_hex().unwrap().as_slice()).unwrap(); let output_script: Script = transaction.outputs[0].script_pubkey.clone().into(); @@ -3380,22 +3407,29 @@ fn test_withdraw_p2pk_balance() { let coin = utxo_coin_for_test(UtxoRpcClientEnum::Native(client), None, false); UtxoStandardCoin::get_unspent_ordered_list.mock_safe(|coin, _| { - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = vec![UnspentInfo { - outpoint: OutPoint { - hash: 1.into(), - index: 0, - }, - value: 1000000000, - height: Default::default(), - // Use a p2pk output script for this UTXO - script: output_script_p2pk( - &block_on(coin.as_ref().derivation_method.unwrap_single_addr()) - .pubkey() - .unwrap(), - ), - }]; - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = vec![UnspentInfo { + outpoint: OutPoint { + hash: 1.into(), + index: 0, + }, + value: 1000000000, + height: Default::default(), + // Use a p2pk output script for this UTXO + script: output_script_p2pk( + &coin + .as_ref() + .derivation_method + .unwrap_single_addr() + .await + .pubkey() + .unwrap(), + ), + }]; + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); // Create a dummy p2pkh address to withdraw the coins to. @@ -3411,7 +3445,7 @@ fn test_withdraw_p2pk_balance() { memo: None, ibc_source_channel: None, }; - let tx_details = coin.withdraw(withdraw_req).wait().unwrap(); + let tx_details = block_on_f01(coin.withdraw(withdraw_req)).unwrap(); let transaction: UtxoTx = deserialize(tx_details.tx.tx_hex().unwrap().as_slice()).unwrap(); // The change should be in a p2pkh script. @@ -3432,8 +3466,11 @@ fn test_utxo_standard_with_check_utxo_maturity_true() { UtxoStandardCoin::get_mature_unspent_ordered_list.mock_safe(|coin, _| { unsafe { GET_MATURE_UNSPENT_ORDERED_LIST_CALLED = true }; - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - MockResult::Return(Box::pin(futures::future::ok((MatureUnspentList::default(), cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + Ok((MatureUnspentList::default(), cache)) + }; + MockResult::Return(fut.boxed()) }); let conf = json!({"coin":"RICK","asset":"RICK","rpcport":25435,"txversion":4,"overwintered":1,"mm2":1,"protocol":{"type":"UTXO"}}); @@ -3451,7 +3488,7 @@ fn test_utxo_standard_with_check_utxo_maturity_true() { let address = Address::from_legacyaddress("R9o9xTocqr6CeEDGDH6mEYpwLoMz6jNjMW", &KMD_PREFIXES).unwrap(); // Don't use `block_on` here because it's used within a mock of [`GetUtxoListOps::get_mature_unspent_ordered_list`]. - coin.get_unspent_ordered_list(&address).compat().wait().unwrap(); + block_on_f01(coin.get_unspent_ordered_list(&address).compat()).unwrap(); assert!(unsafe { GET_MATURE_UNSPENT_ORDERED_LIST_CALLED }); } @@ -3464,9 +3501,11 @@ fn test_utxo_standard_without_check_utxo_maturity() { UtxoStandardCoin::get_all_unspent_ordered_list.mock_safe(|coin, _| { unsafe { GET_ALL_UNSPENT_ORDERED_LIST_CALLED = true }; - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = Vec::new(); - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + Ok((Vec::new(), cache)) + }; + MockResult::Return(fut.boxed()) }); UtxoStandardCoin::get_mature_unspent_ordered_list.mock_safe(|_, _| { @@ -3487,7 +3526,7 @@ fn test_utxo_standard_without_check_utxo_maturity() { let address = Address::from_legacyaddress("R9o9xTocqr6CeEDGDH6mEYpwLoMz6jNjMW", &KMD_PREFIXES).unwrap(); // Don't use `block_on` here because it's used within a mock of [`UtxoStandardCoin::get_all_unspent_ordered_list`]. - coin.get_unspent_ordered_list(&address).compat().wait().unwrap(); + block_on_f01(coin.get_unspent_ordered_list(&address).compat()).unwrap(); assert!(unsafe { GET_ALL_UNSPENT_ORDERED_LIST_CALLED }); } @@ -3500,8 +3539,11 @@ fn test_qtum_without_check_utxo_maturity() { QtumCoin::get_mature_unspent_ordered_list.mock_safe(|coin, _| { unsafe { GET_MATURE_UNSPENT_ORDERED_LIST_CALLED = true }; - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - MockResult::Return(Box::pin(futures::future::ok((MatureUnspentList::default(), cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + Ok((MatureUnspentList::default(), cache)) + }; + MockResult::Return(fut.boxed()) }); let conf = json!({"coin":"tQTUM","rpcport":13889,"pubtype":120,"p2shtype":110}); @@ -3526,7 +3568,7 @@ fn test_qtum_without_check_utxo_maturity() { ) .unwrap(); // Don't use `block_on` here because it's used within a mock of [`QtumCoin::get_mature_unspent_ordered_list`]. - coin.get_unspent_ordered_list(&address).compat().wait().unwrap(); + block_on_f01(coin.get_unspent_ordered_list(&address).compat()).unwrap(); assert!(unsafe { GET_MATURE_UNSPENT_ORDERED_LIST_CALLED }); } @@ -3604,9 +3646,12 @@ fn test_qtum_with_check_utxo_maturity_false() { QtumCoin::get_all_unspent_ordered_list.mock_safe(|coin, _address| { unsafe { GET_ALL_UNSPENT_ORDERED_LIST_CALLED = true }; - let cache = block_on(coin.as_ref().recently_spent_outpoints.lock()); - let unspents = Vec::new(); - MockResult::Return(Box::pin(futures::future::ok((unspents, cache)))) + let fut = async move { + let cache = coin.as_ref().recently_spent_outpoints.lock().await; + let unspents = Vec::new(); + Ok((unspents, cache)) + }; + MockResult::Return(fut.boxed()) }); QtumCoin::get_mature_unspent_ordered_list.mock_safe(|_, _| { panic!( @@ -3637,14 +3682,14 @@ fn test_qtum_with_check_utxo_maturity_false() { ) .unwrap(); // Don't use `block_on` here because it's used within a mock of [`QtumCoin::get_all_unspent_ordered_list`]. - coin.get_unspent_ordered_list(&address).compat().wait().unwrap(); + block_on_f01(coin.get_unspent_ordered_list(&address).compat()).unwrap(); assert!(unsafe { GET_ALL_UNSPENT_ORDERED_LIST_CALLED }); } #[test] fn test_account_balance_rpc() { let mut addresses_map: HashMap = HashMap::new(); - let mut balances_by_der_path: HashMap> = HashMap::new(); + let mut balances_by_der_path: HashMap> = HashMap::new(); macro_rules! known_address { ($der_path:literal, $address:literal, $chain:expr, balance = $balance:literal) => { @@ -3653,7 +3698,10 @@ fn test_account_balance_rpc() { address: $address.to_string(), derivation_path: RpcDerivationPath(DerivationPath::from_str($der_path).unwrap()), chain: $chain, - balance: CoinBalance::new(BigDecimal::from($balance)), + balance: HashMap::from([( + TEST_COIN_NAME.to_string(), + CoinBalance::new(BigDecimal::from($balance)), + )]), }) }; } @@ -3743,7 +3791,7 @@ fn test_account_balance_rpc() { account_index: 0, derivation_path: DerivationPath::from_str("m/44'/141'/0'").unwrap().into(), addresses: get_balances!("m/44'/141'/0'/0/0", "m/44'/141'/0'/0/1", "m/44'/141'/0'/0/2"), - page_balance: CoinBalance::new(BigDecimal::from(0)), + page_balance: HashMap::from([(coin.ticker().to_string(), CoinBalance::new(BigDecimal::from(0)))]), limit: 3, skipped: 0, total: 7, @@ -3765,7 +3813,7 @@ fn test_account_balance_rpc() { account_index: 0, derivation_path: DerivationPath::from_str("m/44'/141'/0'").unwrap().into(), addresses: get_balances!("m/44'/141'/0'/0/3", "m/44'/141'/0'/0/4", "m/44'/141'/0'/0/5"), - page_balance: CoinBalance::new(BigDecimal::from(99)), + page_balance: HashMap::from([(coin.ticker().to_string(), CoinBalance::new(BigDecimal::from(99)))]), limit: 3, skipped: 3, total: 7, @@ -3787,7 +3835,7 @@ fn test_account_balance_rpc() { account_index: 0, derivation_path: DerivationPath::from_str("m/44'/141'/0'").unwrap().into(), addresses: get_balances!("m/44'/141'/0'/0/6"), - page_balance: CoinBalance::new(BigDecimal::from(32)), + page_balance: HashMap::from([(coin.ticker().to_string(), CoinBalance::new(BigDecimal::from(32)))]), limit: 3, skipped: 6, total: 7, @@ -3809,7 +3857,7 @@ fn test_account_balance_rpc() { account_index: 0, derivation_path: DerivationPath::from_str("m/44'/141'/0'").unwrap().into(), addresses: Vec::new(), - page_balance: CoinBalance::default(), + page_balance: HashMap::default(), limit: 3, skipped: 7, total: 7, @@ -3831,7 +3879,7 @@ fn test_account_balance_rpc() { account_index: 0, derivation_path: DerivationPath::from_str("m/44'/141'/0'").unwrap().into(), addresses: get_balances!("m/44'/141'/0'/1/1", "m/44'/141'/0'/1/2"), - page_balance: CoinBalance::new(BigDecimal::from(54)), + page_balance: HashMap::from([(coin.ticker().to_string(), CoinBalance::new(BigDecimal::from(54)))]), limit: 3, skipped: 1, total: 3, @@ -3853,7 +3901,7 @@ fn test_account_balance_rpc() { account_index: 1, derivation_path: DerivationPath::from_str("m/44'/141'/1'").unwrap().into(), addresses: Vec::new(), - page_balance: CoinBalance::default(), + page_balance: HashMap::default(), limit: 3, skipped: 0, total: 0, @@ -3875,7 +3923,7 @@ fn test_account_balance_rpc() { account_index: 1, derivation_path: DerivationPath::from_str("m/44'/141'/1'").unwrap().into(), addresses: get_balances!("m/44'/141'/1'/1/0"), - page_balance: CoinBalance::new(BigDecimal::from(0)), + page_balance: HashMap::from([(coin.ticker().to_string(), CoinBalance::new(BigDecimal::from(0)))]), limit: 3, skipped: 0, total: 1, @@ -3897,7 +3945,7 @@ fn test_account_balance_rpc() { account_index: 1, derivation_path: DerivationPath::from_str("m/44'/141'/1'").unwrap().into(), addresses: Vec::new(), - page_balance: CoinBalance::default(), + page_balance: HashMap::default(), limit: 3, skipped: 1, total: 1, @@ -3939,7 +3987,7 @@ fn test_scan_for_new_addresses() { // The list of addresses with a non-empty transaction history. let mut non_empty_addresses: HashSet = HashSet::new(); // The map of results by the addresses. - let mut balances_by_der_path: HashMap> = HashMap::new(); + let mut balances_by_der_path: HashMap> = HashMap::new(); macro_rules! new_address { ($der_path:literal, $address:literal, $chain:expr, balance = $balance:expr) => {{ @@ -3952,7 +4000,9 @@ fn test_scan_for_new_addresses() { address: $address.to_string(), derivation_path: RpcDerivationPath(DerivationPath::from_str($der_path).unwrap()), chain: $chain, - balance: CoinBalance::new(BigDecimal::from($balance.unwrap_or(0i32))), + balance: $balance.map_or_else(HashMap::default, |balance| { + HashMap::from([(TEST_COIN_NAME.to_string(), CoinBalance::new(BigDecimal::from(balance)))]) + }), }); }}; } @@ -3982,7 +4032,7 @@ fn test_scan_for_new_addresses() { // Account#0, internal addresses. new_address!("m/44'/141'/0'/1/1", "RPj9JXUVnewWwVpxZDeqGB25qVqz5qJzwP", Bip44Chain::Internal, balance = Some(98)); - new_address!("m/44'/141'/0'/1/2", "RSYdSLRYWuzBson2GDbWBa632q2PmFnCaH", Bip44Chain::Internal, balance = None); + new_address!("m/44'/141'/0'/1/2", "RSYdSLRYWuzBson2GDbWBa632q2PmFnCaH", Bip44Chain::Internal, balance = None::); new_address!("m/44'/141'/0'/1/3", "RQstQeTUEZLh6c3YWJDkeVTTQoZUsfvNCr", Bip44Chain::Internal, balance = Some(14)); unused_address!("m/44'/141'/0'/1/4", "RT54m6pfj9scqwSLmYdfbmPcrpxnWGAe9J"); unused_address!("m/44'/141'/0'/1/5", "RYWfEFxqA6zya9c891Dj7vxiDojCmuWR9T"); @@ -3992,8 +4042,8 @@ fn test_scan_for_new_addresses() { // Account#1, external addresses. new_address!("m/44'/141'/1'/0/0", "RBQFLwJ88gVcnfkYvJETeTAB6AAYLow12K", Bip44Chain::External, balance = Some(9)); new_address!("m/44'/141'/1'/0/1", "RCyy77sRWFa2oiFPpyimeTQfenM1aRoiZs", Bip44Chain::External, balance = Some(7)); - new_address!("m/44'/141'/1'/0/2", "RDnNa3pQmisfi42KiTZrfYfuxkLC91PoTJ", Bip44Chain::External, balance = None); - new_address!("m/44'/141'/1'/0/3", "RQRGgXcGJz93CoAfQJoLgBz2r9HtJYMX3Z", Bip44Chain::External, balance = None); + new_address!("m/44'/141'/1'/0/2", "RDnNa3pQmisfi42KiTZrfYfuxkLC91PoTJ", Bip44Chain::External, balance = None::); + new_address!("m/44'/141'/1'/0/3", "RQRGgXcGJz93CoAfQJoLgBz2r9HtJYMX3Z", Bip44Chain::External, balance = None::); new_address!("m/44'/141'/1'/0/4", "RM6cqSFCFZ4J1LngLzqKkwo2ouipbDZUbm", Bip44Chain::External, balance = Some(11)); unused_address!("m/44'/141'/1'/0/5", "RX2fGBZjNZMNdNcnc5QBRXvmsXTvadvTPN"); unused_address!("m/44'/141'/1'/0/6", "RJJ7muUETyp59vxVXna9KAZ9uQ1QSqmcjE"); @@ -4365,7 +4415,9 @@ fn test_for_non_existent_tx_hex_utxo_electrum() { wait_until: timeout, check_every: 1, }; - let actual = coin.wait_for_confirmations(confirm_payment_input).wait().err().unwrap(); + let actual = block_on_f01(coin.wait_for_confirmations(confirm_payment_input)) + .err() + .unwrap(); assert!(actual.contains( "Tx d342ff9da528a2e262bddf2b6f9a27d1beb7aeb03f0fc8d9eac2987266447e44 was not found on chain after 10 tries" )); @@ -4408,10 +4460,7 @@ fn test_native_display_balances() { Address::from_legacyaddress("RJeDDtDRtKUoL8BCKdH7TNCHqUKr7kQRsi", &KMD_PREFIXES).unwrap(), Address::from_legacyaddress("RQHn9VPHBqNjYwyKfJbZCiaxVrWPKGQjeF", &KMD_PREFIXES).unwrap(), ]; - let actual = rpc_client - .display_balances(addresses, TEST_COIN_DECIMALS) - .wait() - .unwrap(); + let actual = block_on_f01(rpc_client.display_balances(addresses, TEST_COIN_DECIMALS)).unwrap(); let expected: Vec<(Address, BigDecimal)> = vec![ ( @@ -4549,7 +4598,6 @@ fn test_utxo_validate_valid_and_invalid_pubkey() { #[test] fn test_block_header_utxo_loop() { use crate::utxo::utxo_builder::{block_header_utxo_loop, BlockHeaderUtxoLoopExtraArgs}; - use futures::future::{Either, FutureExt}; use keys::hash::H256 as H256Json; static mut CURRENT_BLOCK_COUNT: u64 = 13; diff --git a/mm2src/coins/utxo/utxo_wasm_tests.rs b/mm2src/coins/utxo/utxo_wasm_tests.rs index a33e1ba039..bd059c8627 100644 --- a/mm2src/coins/utxo/utxo_wasm_tests.rs +++ b/mm2src/coins/utxo/utxo_wasm_tests.rs @@ -42,7 +42,7 @@ pub async fn electrum_client_for_test(servers: &[&str]) -> ElectrumClient { let servers = servers.into_iter().map(|s| json::from_value(s).unwrap()).collect(); let abortable_system = AbortableQueue::default(); builder - .electrum_client(abortable_system, args, servers, None) + .electrum_client(abortable_system, args, servers, (None, None), None) .await .unwrap() } diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index 81d0aca85c..b8c7c8c944 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -11,7 +11,7 @@ use crate::coin_errors::{MyAddressError, ValidatePaymentResult}; use crate::hd_wallet::HDPathAccountToAddressId; use crate::my_tx_history_v2::{MyTxHistoryErrorV2, MyTxHistoryRequestV2, MyTxHistoryResponseV2}; use crate::rpc_command::init_withdraw::{InitWithdrawCoin, WithdrawInProgressStatus, WithdrawTaskHandleShared}; -use crate::utxo::rpc_clients::{ElectrumRpcRequest, UnspentInfo, UtxoRpcClientEnum, UtxoRpcError, UtxoRpcFut, +use crate::utxo::rpc_clients::{ElectrumConnectionSettings, UnspentInfo, UtxoRpcClientEnum, UtxoRpcError, UtxoRpcFut, UtxoRpcResult}; use crate::utxo::utxo_builder::UtxoCoinBuildError; use crate::utxo::utxo_builder::{UtxoCoinBuilder, UtxoCoinBuilderCommonOps, UtxoFieldsWithGlobalHDBuilder, @@ -70,6 +70,7 @@ use serialization::CoinVariant; use std::collections::{HashMap, HashSet}; use std::convert::TryInto; use std::iter; +use std::num::TryFromIntError; use std::path::PathBuf; use std::sync::Arc; pub use z_coin_errors::*; @@ -138,7 +139,7 @@ cfg_native!( const SAPLING_OUTPUT_EXPECTED_HASH: &str = "2f0ebbcbb9bb0bcffe95a397e7eba89c29eb4dde6191c339db88570e3f3fb0e4"; ); -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct ZcoinConsensusParams { // we don't support coins without overwinter and sapling active so these are mandatory overwinter_activation_height: u32, @@ -155,7 +156,7 @@ pub struct ZcoinConsensusParams { b58_script_address_prefix: [u8; 2], } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct CheckPointBlockInfo { height: u32, hash: H256Json, @@ -163,7 +164,7 @@ pub struct CheckPointBlockInfo { sapling_tree: BytesJson, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct ZcoinProtocolInfo { consensus_params: ZcoinConsensusParams, check_point_block: Option, @@ -750,7 +751,12 @@ pub enum ZcoinRpcMode { #[serde(alias = "Electrum")] Light { #[serde(alias = "servers")] - electrum_servers: Vec, + /// The settings of each electrum server. + electrum_servers: Vec, + /// The minimum number of connections to electrum servers to keep alive/maintained at all times. + min_connected: Option, + /// The maximum number of connections to electrum servers to not exceed at any time. + max_connected: Option, light_wallet_d_servers: Vec, /// Specifies the parameters for synchronizing the wallet from a specific block. This overrides the /// `CheckPointBlockInfo` configuration in the coin settings. @@ -967,8 +973,15 @@ impl<'a> ZCoinBuilder<'a> { let utxo_mode = match &z_coin_params.mode { #[cfg(not(target_arch = "wasm32"))] ZcoinRpcMode::Native => UtxoRpcMode::Native, - ZcoinRpcMode::Light { electrum_servers, .. } => UtxoRpcMode::Electrum { + ZcoinRpcMode::Light { + electrum_servers, + min_connected, + max_connected, + .. + } => UtxoRpcMode::Electrum { servers: electrum_servers.clone(), + min_connected: *min_connected, + max_connected: *max_connected, }, }; let utxo_params = UtxoActivationParams { @@ -1161,7 +1174,7 @@ impl MarketCoinOps for ZCoin { utxo_common::wait_for_confirmations(self.as_ref(), input) } - fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionFut { + async fn wait_for_htlc_tx_spend(&self, args: WaitForHTLCTxSpendArgs<'_>) -> TransactionResult { utxo_common::wait_for_output_spend( self.clone(), args.tx_bytes, @@ -1170,6 +1183,7 @@ impl MarketCoinOps for ZCoin { args.wait_until, args.check_every, ) + .await } fn tx_enum_from_bytes(&self, bytes: &[u8]) -> Result> { @@ -1200,62 +1214,56 @@ impl MarketCoinOps for ZCoin { #[async_trait] impl SwapOps for ZCoin { - fn send_taker_fee(&self, _fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8], _expire_at: u64) -> TransactionFut { - let selfi = self.clone(); + async fn send_taker_fee( + &self, + _fee_addr: &[u8], + dex_fee: DexFee, + uuid: &[u8], + _expire_at: u64, + ) -> TransactionResult { let uuid = uuid.to_owned(); - let fut = async move { - let tx = try_tx_s!(z_send_dex_fee(&selfi, dex_fee.fee_amount().into(), &uuid).await); - Ok(tx.into()) - }; - Box::new(fut.boxed().compat()) + let tx = try_tx_s!(z_send_dex_fee(self, dex_fee.fee_amount().into(), &uuid).await); + Ok(tx.into()) } - fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionFut { - let selfi = self.clone(); + async fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { let maker_key_pair = self.derive_htlc_key_pair(maker_payment_args.swap_unique_data); - let taker_pub = try_tx_fus!(Public::from_slice(maker_payment_args.other_pubkey)); + let taker_pub = try_tx_s!(Public::from_slice(maker_payment_args.other_pubkey)); let secret_hash = maker_payment_args.secret_hash.to_vec(); - let time_lock = try_tx_fus!(maker_payment_args.time_lock.try_into()); + let time_lock = try_tx_s!(maker_payment_args.time_lock.try_into()); let amount = maker_payment_args.amount; - let fut = async move { - let utxo_tx = try_tx_s!( - z_send_htlc( - &selfi, - time_lock, - maker_key_pair.public(), - &taker_pub, - &secret_hash, - amount - ) - .await - ); - Ok(utxo_tx.into()) - }; - Box::new(fut.boxed().compat()) + let utxo_tx = try_tx_s!( + z_send_htlc( + self, + time_lock, + maker_key_pair.public(), + &taker_pub, + &secret_hash, + amount + ) + .await + ); + Ok(utxo_tx.into()) } - fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionFut { - let selfi = self.clone(); + async fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionResult { let taker_keypair = self.derive_htlc_key_pair(taker_payment_args.swap_unique_data); - let maker_pub = try_tx_fus!(Public::from_slice(taker_payment_args.other_pubkey)); + let maker_pub = try_tx_s!(Public::from_slice(taker_payment_args.other_pubkey)); let secret_hash = taker_payment_args.secret_hash.to_vec(); - let time_lock = try_tx_fus!(taker_payment_args.time_lock.try_into()); + let time_lock = try_tx_s!(taker_payment_args.time_lock.try_into()); let amount = taker_payment_args.amount; - let fut = async move { - let utxo_tx = try_tx_s!( - z_send_htlc( - &selfi, - time_lock, - taker_keypair.public(), - &maker_pub, - &secret_hash, - amount - ) - .await - ); - Ok(utxo_tx.into()) - }; - Box::new(fut.boxed().compat()) + let utxo_tx = try_tx_s!( + z_send_htlc( + self, + time_lock, + taker_keypair.public(), + &maker_pub, + &secret_hash, + amount + ) + .await + ); + Ok(utxo_tx.into()) } async fn send_maker_spends_taker_payment( @@ -1369,90 +1377,88 @@ impl SwapOps for ZCoin { Ok(tx.into()) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { + async fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentResult<()> { let z_tx = match validate_fee_args.fee_tx { TransactionEnum::ZTransaction(t) => t.clone(), - _ => panic!("Unexpected tx {:?}", validate_fee_args.fee_tx), + fee_tx => { + return MmError::err(ValidatePaymentError::InternalError(format!( + "Invalid fee tx type. fee tx: {:?}", + fee_tx + ))) + }, }; - let amount_sat = try_f!(validate_fee_args.dex_fee.fee_uamount(self.utxo_arc.decimals)); + let amount_sat = validate_fee_args.dex_fee.fee_uamount(self.utxo_arc.decimals)?; let expected_memo = MemoBytes::from_bytes(validate_fee_args.uuid).expect("Uuid length < 512"); - let min_block_number = validate_fee_args.min_block_number; - let coin = self.clone(); - let fut = async move { - let tx_hash = H256::from(z_tx.txid().0).reversed(); - let tx_from_rpc = coin - .utxo_rpc_client() - .get_verbose_transaction(&tx_hash.into()) - .compat() - .await - .map_err(|e| MmError::new(ValidatePaymentError::InvalidRpcResponse(e.into_inner().to_string())))?; - - let mut encoded = Vec::with_capacity(1024); - z_tx.write(&mut encoded).expect("Writing should not fail"); - if encoded != tx_from_rpc.hex.0 { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Encoded transaction {:?} does not match the tx {:?} from RPC", - encoded, tx_from_rpc - ))); - } + let tx_hash = H256::from(z_tx.txid().0).reversed(); + let tx_from_rpc = self + .utxo_rpc_client() + .get_verbose_transaction(&tx_hash.into()) + .compat() + .await + .map_err(|e| MmError::new(ValidatePaymentError::InvalidRpcResponse(e.into_inner().to_string())))?; + + let mut encoded = Vec::with_capacity(1024); + z_tx.write(&mut encoded).expect("Writing should not fail"); + if encoded != tx_from_rpc.hex.0 { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Encoded transaction {:?} does not match the tx {:?} from RPC", + encoded, tx_from_rpc + ))); + } - let block_height = match tx_from_rpc.height { - Some(h) => { - if h < min_block_number { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Dex fee tx {:?} confirmed before min block {}", - z_tx, min_block_number - ))); - } else { - BlockHeight::from_u32(h as u32) - } - }, - None => H0, - }; - - for shielded_out in z_tx.shielded_outputs.iter() { - if let Some((note, address, memo)) = - try_sapling_output_recovery(coin.consensus_params_ref(), block_height, &DEX_FEE_OVK, shielded_out) - { - if address != coin.z_fields.dex_fee_addr { - let encoded = - encode_payment_address(z_mainnet_constants::HRP_SAPLING_PAYMENT_ADDRESS, &address); - let expected = encode_payment_address( - z_mainnet_constants::HRP_SAPLING_PAYMENT_ADDRESS, - &coin.z_fields.dex_fee_addr, - ); - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Dex fee was sent to the invalid address {}, expected {}", - encoded, expected - ))); - } - - if note.value != amount_sat { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Dex fee has invalid amount {}, expected {}", - note.value, amount_sat - ))); - } - - if memo != expected_memo { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Dex fee has invalid memo {:?}, expected {:?}", - memo, expected_memo - ))); - } - - return Ok(()); + let block_height = match tx_from_rpc.height { + Some(h) => { + if h < validate_fee_args.min_block_number { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Dex fee tx {:?} confirmed before min block {}", + z_tx, validate_fee_args.min_block_number + ))); + } else { + BlockHeight::from_u32(h as u32) } - } - - MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "The dex fee tx {:?} has no shielded outputs or outputs decryption failed", - z_tx - ))) + }, + None => H0, }; - Box::new(fut.boxed().compat()) + for shielded_out in z_tx.shielded_outputs.iter() { + if let Some((note, address, memo)) = + try_sapling_output_recovery(self.consensus_params_ref(), block_height, &DEX_FEE_OVK, shielded_out) + { + if address != self.z_fields.dex_fee_addr { + let encoded = encode_payment_address(z_mainnet_constants::HRP_SAPLING_PAYMENT_ADDRESS, &address); + let expected = encode_payment_address( + z_mainnet_constants::HRP_SAPLING_PAYMENT_ADDRESS, + &self.z_fields.dex_fee_addr, + ); + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Dex fee was sent to the invalid address {}, expected {}", + encoded, expected + ))); + } + + if note.value != amount_sat { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Dex fee has invalid amount {}, expected {}", + note.value, amount_sat + ))); + } + + if memo != expected_memo { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Dex fee has invalid memo {:?}, expected {:?}", + memo, expected_memo + ))); + } + + return Ok(()); + } + } + + MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "The dex fee tx {:?} has no shielded outputs or outputs decryption failed", + z_tx + ))) } #[inline] @@ -1466,17 +1472,23 @@ impl SwapOps for ZCoin { } #[inline] - fn check_if_my_payment_sent( + async fn check_if_my_payment_sent( &self, if_my_payment_sent_args: CheckIfMyPaymentSentArgs<'_>, - ) -> Box, Error = String> + Send> { + ) -> Result, String> { + let time_lock = if_my_payment_sent_args + .time_lock + .try_into() + .map_err(|e: TryFromIntError| e.to_string())?; utxo_common::check_if_my_payment_sent( self.clone(), - try_fus!(if_my_payment_sent_args.time_lock.try_into()), + time_lock, if_my_payment_sent_args.other_pub, if_my_payment_sent_args.secret_hash, if_my_payment_sent_args.swap_unique_data, ) + .compat() + .await } #[inline] diff --git a/mm2src/coins/z_coin/storage.rs b/mm2src/coins/z_coin/storage.rs index 08e478f27a..b3c2c108c4 100644 --- a/mm2src/coins/z_coin/storage.rs +++ b/mm2src/coins/z_coin/storage.rs @@ -157,27 +157,8 @@ pub async fn scan_cached_block( ) }; - // Enforce that all roots match. This is slow, so only include in debug builds. - #[cfg(debug_assertions)] - { - let cur_root = tree.root(); - if witnesses.iter().any(|row| row.1.root() != cur_root) { - return Err(Error::InvalidWitnessAnchor(row.0, current_height).into()); - } - for tx in &txs { - for output in tx.shielded_outputs.iter() { - if output.witness.root() != cur_root { - return Err(Error::InvalidNewWitnessAnchor( - output.index, - tx.txid, - current_height, - output.witness.root(), - ) - .into()); - } - } - } - } + // To enforce that all roots match, + // see -> https://github.com/KomodoPlatform/librustzcash/blob/e92443a7bbd1c5e92e00e6deb45b5a33af14cea4/zcash_client_backend/src/data_api/chain.rs#L304-L326 let new_witnesses = data_guard .advance_by_block( diff --git a/mm2src/coins/z_coin/storage/walletdb/wasm/mod.rs b/mm2src/coins/z_coin/storage/walletdb/wasm/mod.rs index 907a007c55..4c68fec22c 100644 --- a/mm2src/coins/z_coin/storage/walletdb/wasm/mod.rs +++ b/mm2src/coins/z_coin/storage/walletdb/wasm/mod.rs @@ -253,7 +253,12 @@ mod wasm_test { // scan the cache let scan = DataConnStmtCacheWrapper::new(DataConnStmtCacheWasm(walletdb.clone())); blockdb - .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan), None, None) + .process_blocks_with_mode( + consensus_params.clone(), + BlockProcessingMode::Scan(scan, None), + None, + None, + ) .await .unwrap(); @@ -293,7 +298,12 @@ mod wasm_test { // Scan the cache again let scan = DataConnStmtCacheWrapper::new(DataConnStmtCacheWasm(walletdb.clone())); blockdb - .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan), None, None) + .process_blocks_with_mode( + consensus_params.clone(), + BlockProcessingMode::Scan(scan, None), + None, + None, + ) .await .unwrap(); @@ -347,7 +357,12 @@ mod wasm_test { // Scan the cache again let scan = DataConnStmtCacheWrapper::new(DataConnStmtCacheWasm(walletdb.clone())); blockdb - .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan), None, None) + .process_blocks_with_mode( + consensus_params.clone(), + BlockProcessingMode::Scan(scan, None), + None, + None, + ) .await .unwrap(); @@ -436,7 +451,12 @@ mod wasm_test { // Scan the cache again let scan = DataConnStmtCacheWrapper::new(DataConnStmtCacheWasm(walletdb.clone())); blockdb - .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan), None, None) + .process_blocks_with_mode( + consensus_params.clone(), + BlockProcessingMode::Scan(scan, None), + None, + None, + ) .await .unwrap(); @@ -520,7 +540,12 @@ mod wasm_test { // Scan the cache let scan = DataConnStmtCacheWrapper::new(DataConnStmtCacheWasm(walletdb.clone())); blockdb - .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan), None, None) + .process_blocks_with_mode( + consensus_params.clone(), + BlockProcessingMode::Scan(scan, None), + None, + None, + ) .await .unwrap(); @@ -545,7 +570,12 @@ mod wasm_test { // Scan the cache again let scan = DataConnStmtCacheWrapper::new(DataConnStmtCacheWasm(walletdb.clone())); blockdb - .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan), None, None) + .process_blocks_with_mode( + consensus_params.clone(), + BlockProcessingMode::Scan(scan, None), + None, + None, + ) .await .unwrap(); @@ -579,7 +609,12 @@ mod wasm_test { // Scan cache let scan = DataConnStmtCacheWrapper::new(DataConnStmtCacheWasm(walletdb.clone())); blockdb - .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan), None, None) + .process_blocks_with_mode( + consensus_params.clone(), + BlockProcessingMode::Scan(scan, None), + None, + None, + ) .await .unwrap(); @@ -592,7 +627,12 @@ mod wasm_test { // Scan the cache again let scan = DataConnStmtCacheWrapper::new(DataConnStmtCacheWasm(walletdb.clone())); let scan = blockdb - .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan), None, None) + .process_blocks_with_mode( + consensus_params.clone(), + BlockProcessingMode::Scan(scan, None), + None, + None, + ) .await .unwrap_err(); match scan.get_inner() { @@ -611,7 +651,12 @@ mod wasm_test { blockdb.insert_block(cb2.height as u32, cb2_bytes).await.unwrap(); let scan = DataConnStmtCacheWrapper::new(DataConnStmtCacheWasm(walletdb.clone())); assert!(blockdb - .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan), None, None) + .process_blocks_with_mode( + consensus_params.clone(), + BlockProcessingMode::Scan(scan, None), + None, + None + ) .await .is_ok()); @@ -650,7 +695,12 @@ mod wasm_test { // Scan the cache let scan = DataConnStmtCacheWrapper::new(DataConnStmtCacheWasm(walletdb.clone())); assert!(blockdb - .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan), None, None) + .process_blocks_with_mode( + consensus_params.clone(), + BlockProcessingMode::Scan(scan, None), + None, + None + ) .await .is_ok()); @@ -666,7 +716,12 @@ mod wasm_test { // Scan the cache again let scan = DataConnStmtCacheWrapper::new(DataConnStmtCacheWasm(walletdb.clone())); assert!(blockdb - .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan), None, None) + .process_blocks_with_mode( + consensus_params.clone(), + BlockProcessingMode::Scan(scan, None), + None, + None + ) .await .is_ok()); @@ -703,7 +758,12 @@ mod wasm_test { // Scan the cache let scan = DataConnStmtCacheWrapper::new(DataConnStmtCacheWasm(walletdb.clone())); assert!(blockdb - .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan), None, None) + .process_blocks_with_mode( + consensus_params.clone(), + BlockProcessingMode::Scan(scan, None), + None, + None + ) .await .is_ok()); @@ -728,7 +788,12 @@ mod wasm_test { // Scan the cache again let scan = DataConnStmtCacheWrapper::new(DataConnStmtCacheWasm(walletdb.clone())); let scan = blockdb - .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan), None, None) + .process_blocks_with_mode( + consensus_params.clone(), + BlockProcessingMode::Scan(scan, None), + None, + None, + ) .await; assert!(scan.is_ok()); @@ -767,7 +832,7 @@ mod wasm_test { // // Scan the cache // let scan = DataConnStmtCacheWrapper::new(DataConnStmtCacheWasm(walletdb.clone())); // assert!(blockdb - // .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan), None, None) + // .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan, None), None, None) // .await // .is_ok()); // @@ -787,7 +852,7 @@ mod wasm_test { // // Scan the cache // let scan = DataConnStmtCacheWrapper::new(DataConnStmtCacheWasm(walletdb.clone())); // assert!(blockdb - // .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan), None, None) + // .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan, None), None, None) // .await // .is_ok()); // @@ -832,7 +897,7 @@ mod wasm_test { // // Scan the cache // let scan = DataConnStmtCacheWrapper::new(DataConnStmtCacheWasm(walletdb.clone())); // assert!(blockdb - // .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan), None, None) + // .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan, None), None, None) // .await // .is_ok()); // @@ -863,7 +928,7 @@ mod wasm_test { // // Scan the cache // let scan = DataConnStmtCacheWrapper::new(DataConnStmtCacheWasm(walletdb.clone())); // assert!(blockdb - // .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan), None, None) + // .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan, None), None, None) // .await // .is_ok()); // @@ -1033,7 +1098,7 @@ mod wasm_test { // // Scan the cache // let scan = DataConnStmtCacheWrapper::new(DataConnStmtCacheWasm(walletdb.clone())); // blockdb - // .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan), None, None) + // .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan, None), None, None) // .await // .unwrap(); // assert_eq!(walletdb.get_balance(AccountId(0)).await.unwrap(), value); @@ -1090,7 +1155,7 @@ mod wasm_test { // // Scan the cache // let scan = DataConnStmtCacheWrapper::new(DataConnStmtCacheWasm(walletdb.clone())); // blockdb - // .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan), None, None) + // .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan, None), None, None) // .await // .unwrap(); // @@ -1126,7 +1191,7 @@ mod wasm_test { // // Scan the cache // let scan = DataConnStmtCacheWrapper::new(DataConnStmtCacheWasm(walletdb.clone())); // blockdb - // .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan), None, None) + // .process_blocks_with_mode(consensus_params.clone(), BlockProcessingMode::Scan(scan, None), None, None) // .await // .unwrap(); // diff --git a/mm2src/coins/z_coin/storage/walletdb/wasm/storage.rs b/mm2src/coins/z_coin/storage/walletdb/wasm/storage.rs index 9dbaf389a3..d9ac5ec322 100644 --- a/mm2src/coins/z_coin/storage/walletdb/wasm/storage.rs +++ b/mm2src/coins/z_coin/storage/walletdb/wasm/storage.rs @@ -808,7 +808,7 @@ impl WalletRead for WalletIndexedDb { let locked_db = self.lock_db().await?; let db_transaction = locked_db.get_inner().transaction().await?; let block_headers_db = db_transaction.table::().await?; - let earlist_block = block_headers_db + let earliest_block = block_headers_db .cursor_builder() .only("ticker", &self.ticker)? .bound("height", 0u32, u32::MAX) @@ -830,7 +830,7 @@ impl WalletRead for WalletIndexedDb { .next() .await?; - if let (Some(min), Some(max)) = (earlist_block, latest_block) { + if let (Some(min), Some(max)) = (earliest_block, latest_block) { Ok(Some((BlockHeight::from(min.1.height), BlockHeight::from(max.1.height)))) } else { Ok(None) diff --git a/mm2src/coins/z_coin/z_coin_native_tests.rs b/mm2src/coins/z_coin/z_coin_native_tests.rs index 9e4358727d..f554d7c5d3 100644 --- a/mm2src/coins/z_coin/z_coin_native_tests.rs +++ b/mm2src/coins/z_coin/z_coin_native_tests.rs @@ -6,9 +6,8 @@ use std::path::PathBuf; use std::time::Duration; use zcash_client_backend::encoding::decode_extended_spending_key; -use super::{z_coin_from_conf_and_params_with_z_key, z_mainnet_constants, Future, PrivKeyBuildPolicy, - RefundPaymentArgs, SendPaymentArgs, SpendPaymentArgs, SwapOps, ValidateFeeArgs, ValidatePaymentError, - ZTransaction}; +use super::{z_coin_from_conf_and_params_with_z_key, z_mainnet_constants, PrivKeyBuildPolicy, RefundPaymentArgs, + SendPaymentArgs, SpendPaymentArgs, SwapOps, ValidateFeeArgs, ValidatePaymentError, ZTransaction}; use crate::z_coin::{z_htlc::z_send_dex_fee, ZcoinActivationParams, ZcoinRpcMode}; use crate::DexFee; use crate::{CoinProtocol, SwapTxTypeWithSecretHash}; @@ -55,7 +54,7 @@ fn zombie_coin_send_and_refund_maker_payment() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let tx = coin.send_maker_payment(args).wait().unwrap(); + let tx = block_on(coin.send_maker_payment(args)).unwrap(); log!("swap tx {}", hex::encode(tx.tx_hash_as_bytes().0)); let refund_args = RefundPaymentArgs { @@ -116,7 +115,7 @@ fn zombie_coin_send_and_spend_maker_payment() { wait_for_confirmation_until: 0, }; - let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); + let tx = block_on(coin.send_maker_payment(maker_payment_args)).unwrap(); log!("swap tx {}", hex::encode(tx.tx_hash_as_bytes().0)); let maker_pub = taker_pub; @@ -234,7 +233,7 @@ fn zombie_coin_validate_dex_fee() { uuid: &[1; 16], }; // Invalid amount should return an error - let err = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + let err = block_on(coin.validate_fee(validate_fee_args)).unwrap_err().into_inner(); match err { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Dex fee has invalid amount")), _ => panic!("Expected `WrongPaymentTx`: {:?}", err), @@ -249,7 +248,7 @@ fn zombie_coin_validate_dex_fee() { min_block_number: 12000, uuid: &[2; 16], }; - let err = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + let err = block_on(coin.validate_fee(validate_fee_args)).unwrap_err().into_inner(); match err { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Dex fee has invalid memo")), _ => panic!("Expected `WrongPaymentTx`: {:?}", err), @@ -264,7 +263,7 @@ fn zombie_coin_validate_dex_fee() { min_block_number: 14000, uuid: &[1; 16], }; - let err = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + let err = block_on(coin.validate_fee(validate_fee_args)).unwrap_err().into_inner(); match err { ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("confirmed before min block")), _ => panic!("Expected `WrongPaymentTx`: {:?}", err), @@ -279,7 +278,7 @@ fn zombie_coin_validate_dex_fee() { min_block_number: 12000, uuid: &[1; 16], }; - coin.validate_fee(validate_fee_args).wait().unwrap(); + block_on(coin.validate_fee(validate_fee_args)).unwrap(); } fn default_zcoin_activation_params() -> ZcoinActivationParams { diff --git a/mm2src/coins_activation/Cargo.toml b/mm2src/coins_activation/Cargo.toml index aef4b9b557..be951c67d4 100644 --- a/mm2src/coins_activation/Cargo.toml +++ b/mm2src/coins_activation/Cargo.toml @@ -8,7 +8,6 @@ doctest = false [features] enable-sia = [] -enable-solana = [] default = [] for-tests = [] diff --git a/mm2src/coins_activation/src/erc20_token_activation.rs b/mm2src/coins_activation/src/erc20_token_activation.rs index 82b89026bb..77970284b4 100644 --- a/mm2src/coins_activation/src/erc20_token_activation.rs +++ b/mm2src/coins_activation/src/erc20_token_activation.rs @@ -10,6 +10,7 @@ use coins::{eth::{v2_activation::{Erc20Protocol, EthTokenActivationError}, use common::Future01CompatExt; use mm2_err_handle::prelude::*; use serde::Serialize; +use serde_json::Value as Json; use std::collections::HashMap; #[derive(Debug, Serialize)] @@ -43,6 +44,7 @@ impl From for EnableTokenError { EthTokenActivationError::InvalidPayload(e) => EnableTokenError::InvalidPayload(e), EthTokenActivationError::UnexpectedDerivationMethod(e) => EnableTokenError::UnexpectedDerivationMethod(e), EthTokenActivationError::PrivKeyPolicyNotAllowed(e) => EnableTokenError::PrivKeyPolicyNotAllowed(e), + EthTokenActivationError::CustomTokenError(e) => EnableTokenError::CustomTokenError(e), } } } @@ -81,6 +83,18 @@ impl TryFromCoinProtocol for Erc20Protocol { } } +impl TryFromCoinProtocol for NftProtocol { + fn try_from_coin_protocol(proto: CoinProtocol) -> Result> + where + Self: Sized, + { + match proto { + CoinProtocol::NFT { platform } => Ok(NftProtocol { platform }), + proto => MmError::err(proto), + } + } +} + impl TryFromCoinProtocol for EthTokenProtocol { fn try_from_coin_protocol(proto: CoinProtocol) -> Result> where @@ -121,13 +135,21 @@ impl TokenActivationOps for EthCoin { ticker: String, platform_coin: Self::PlatformCoin, activation_params: Self::ActivationParams, + token_conf: Json, protocol_conf: Self::ProtocolInfo, + is_custom: bool, ) -> Result<(Self, Self::ActivationResult), MmError> { match activation_params { EthTokenActivationParams::Erc20(erc20_init_params) => match protocol_conf { EthTokenProtocol::Erc20(erc20_protocol) => { let token = platform_coin - .initialize_erc20_token(erc20_init_params, erc20_protocol, ticker.clone()) + .initialize_erc20_token( + ticker.clone(), + erc20_init_params, + token_conf, + erc20_protocol, + is_custom, + ) .await?; let address = display_eth_address(&token.derivation_method().single_addr_or_err().await?); @@ -164,8 +186,8 @@ impl TokenActivationOps for EthCoin { )); } let nft_global = match &nft_init_params.provider { - NftProviderEnum::Moralis { url, proxy_auth } => { - platform_coin.global_nft_from_platform_coin(url, proxy_auth).await? + NftProviderEnum::Moralis { url, komodo_proxy } => { + platform_coin.initialize_global_nft(url, *komodo_proxy).await? }, }; let nfts = nft_global.nfts_infos.lock().await.clone(); diff --git a/mm2src/coins_activation/src/eth_with_token_activation.rs b/mm2src/coins_activation/src/eth_with_token_activation.rs index 487745419a..7bc62b444a 100644 --- a/mm2src/coins_activation/src/eth_with_token_activation.rs +++ b/mm2src/coins_activation/src/eth_with_token_activation.rs @@ -12,7 +12,7 @@ use coins::coin_balance::{CoinBalanceReport, EnableCoinBalanceOps}; use coins::eth::v2_activation::{eth_coin_from_conf_and_request_v2, Erc20Protocol, Erc20TokenActivationRequest, EthActivationV2Error, EthActivationV2Request, EthPrivKeyActivationPolicy}; use coins::eth::v2_activation::{EthTokenActivationError, NftActivationRequest, NftProviderEnum}; -use coins::eth::{Erc20TokenInfo, EthCoin, EthCoinType, EthPrivKeyBuildPolicy}; +use coins::eth::{display_eth_address, Erc20TokenDetails, EthCoin, EthCoinType, EthPrivKeyBuildPolicy}; use coins::hd_wallet::RpcTaskXPubExtractor; use coins::my_tx_history_v2::TxHistoryStorage; use coins::nft::nft_structs::NftInfo; @@ -85,6 +85,7 @@ impl From for EnablePlatformCoinWithTokensError { EthActivationV2Error::InvalidHardwareWalletCall => EnablePlatformCoinWithTokensError::Internal( "Hardware wallet must be used within rpc task manager".to_string(), ), + EthActivationV2Error::CustomTokenError(e) => EnablePlatformCoinWithTokensError::CustomTokenError(e), } } } @@ -118,6 +119,7 @@ impl From for InitTokensAsMmCoinsError { InitTokensAsMmCoinsError::UnexpectedDerivationMethod(e) }, EthTokenActivationError::PrivKeyPolicyNotAllowed(e) => InitTokensAsMmCoinsError::Internal(e.to_string()), + EthTokenActivationError::CustomTokenError(e) => InitTokensAsMmCoinsError::CustomTokenError(e), } } } @@ -143,7 +145,13 @@ impl TokenInitializer for Erc20Initializer { for param in activation_params { let token: EthCoin = self .platform_coin - .initialize_erc20_token(param.activation_request, param.protocol, param.ticker) + .initialize_erc20_token( + param.ticker, + param.activation_request, + param.conf, + param.protocol, + param.is_custom, + ) .await?; tokens.push(token); } @@ -183,7 +191,7 @@ impl RegisterTokenInfo for EthCoin { return; } - self.add_erc_token_info(token.ticker().to_string(), Erc20TokenInfo { + self.add_erc_token_info(token.ticker().to_string(), Erc20TokenDetails { token_address: token.erc20_token_address().unwrap(), decimals: token.decimals(), }); @@ -291,11 +299,11 @@ impl PlatformCoinWithTokensActivationOps for EthCoin { ) -> Result, MmError> { let (url, proxy_auth) = match &activation_request.nft_req { Some(nft_req) => match &nft_req.provider { - NftProviderEnum::Moralis { url, proxy_auth } => (url, proxy_auth), + NftProviderEnum::Moralis { url, komodo_proxy } => (url, *komodo_proxy), }, None => return Ok(None), }; - let nft_global = self.global_nft_from_platform_coin(url, proxy_auth).await?; + let nft_global = self.initialize_global_nft(url, proxy_auth).await?; Ok(Some(MmCoinEnum::EthCoin(nft_global))) } @@ -360,8 +368,11 @@ impl PlatformCoinWithTokensActivationOps for EthCoin { return Ok(EthWithTokensActivationResult::Iguana( IguanaEthWithTokensActivationResult { current_block, - eth_addresses_infos: HashMap::from([(my_address.to_string(), eth_address_info)]), - erc20_addresses_infos: HashMap::from([(my_address.to_string(), erc20_address_info)]), + eth_addresses_infos: HashMap::from([(display_eth_address(my_address), eth_address_info)]), + erc20_addresses_infos: HashMap::from([( + display_eth_address(my_address), + erc20_address_info, + )]), nfts_infos: nfts_map, }, )); @@ -385,8 +396,8 @@ impl PlatformCoinWithTokensActivationOps for EthCoin { Ok(EthWithTokensActivationResult::Iguana( IguanaEthWithTokensActivationResult { current_block, - eth_addresses_infos: HashMap::from([(my_address.to_string(), eth_address_info)]), - erc20_addresses_infos: HashMap::from([(my_address.to_string(), erc20_address_info)]), + eth_addresses_infos: HashMap::from([(display_eth_address(my_address), eth_address_info)]), + erc20_addresses_infos: HashMap::from([(display_eth_address(my_address), erc20_address_info)]), nfts_infos: nfts_map, }, )) diff --git a/mm2src/coins_activation/src/init_erc20_token_activation.rs b/mm2src/coins_activation/src/init_erc20_token_activation.rs index de322c9ee5..f162cb1754 100644 --- a/mm2src/coins_activation/src/init_erc20_token_activation.rs +++ b/mm2src/coins_activation/src/init_erc20_token_activation.rs @@ -7,7 +7,7 @@ use coins::coin_balance::{EnableCoinBalanceError, EnableCoinBalanceOps}; use coins::eth::v2_activation::{Erc20Protocol, EthTokenActivationError, InitErc20TokenActivationRequest}; use coins::eth::EthCoin; use coins::hd_wallet::RpcTaskXPubExtractor; -use coins::{MarketCoinOps, MmCoin, RegisterCoinError}; +use coins::{CustomTokenError, MarketCoinOps, MmCoin, RegisterCoinError}; use common::Future01CompatExt; use crypto::HwRpcError; use derive_more::Display; @@ -17,6 +17,7 @@ use mm2_err_handle::prelude::*; use rpc_task::RpcTaskError; use ser_error_derive::SerializeErrorType; use serde_derive::Serialize; +use serde_json::Value as Json; use std::time::Duration; pub type Erc20TokenTaskManagerShared = InitTokenTaskManagerShared; @@ -38,6 +39,8 @@ pub enum InitErc20Error { Transport(String), #[display(fmt = "Internal error: {}", _0)] Internal(String), + #[display(fmt = "Custom token error: {}", _0)] + CustomTokenError(CustomTokenError), } impl From for InitTokenError { @@ -45,13 +48,16 @@ impl From for InitTokenError { match e { InitErc20Error::HwError(hw) => InitTokenError::HwError(hw), InitErc20Error::TaskTimedOut { duration } => InitTokenError::TaskTimedOut { duration }, - InitErc20Error::TokenIsAlreadyActivated { ticker } => InitTokenError::TokenIsAlreadyActivated { ticker }, + InitErc20Error::TokenIsAlreadyActivated { ticker, .. } => { + InitTokenError::TokenIsAlreadyActivated { ticker } + }, InitErc20Error::TokenCreationError { ticker, error } => { InitTokenError::TokenCreationError { ticker, error } }, InitErc20Error::CouldNotFetchBalance(error) => InitTokenError::CouldNotFetchBalance(error), InitErc20Error::Transport(transport) => InitTokenError::Transport(transport), InitErc20Error::Internal(internal) => InitTokenError::Internal(internal), + InitErc20Error::CustomTokenError(error) => InitTokenError::CustomTokenError(error), } } } @@ -66,6 +72,7 @@ impl From for InitErc20Error { | EthTokenActivationError::CouldNotFetchBalance(_) | EthTokenActivationError::InvalidPayload(_) | EthTokenActivationError::Transport(_) => InitErc20Error::Transport(e.to_string()), + EthTokenActivationError::CustomTokenError(e) => InitErc20Error::CustomTokenError(e), } } } @@ -118,11 +125,19 @@ impl InitTokenActivationOps for EthCoin { ticker: String, platform_coin: Self::PlatformCoin, activation_request: &Self::ActivationRequest, + token_conf: Json, protocol_conf: Self::ProtocolInfo, _task_handle: InitTokenTaskHandleShared, + is_custom: bool, ) -> Result> { let token = platform_coin - .initialize_erc20_token(activation_request.clone().into(), protocol_conf, ticker) + .initialize_erc20_token( + ticker, + activation_request.clone().into(), + token_conf, + protocol_conf, + is_custom, + ) .await?; Ok(token) diff --git a/mm2src/coins_activation/src/init_token.rs b/mm2src/coins_activation/src/init_token.rs index dbc03b1754..01d47b3656 100644 --- a/mm2src/coins_activation/src/init_token.rs +++ b/mm2src/coins_activation/src/init_token.rs @@ -5,7 +5,8 @@ use crate::prelude::{coin_conf_with_protocol, CoinConfWithProtocolError, Current use crate::token::TokenProtocolParams; use async_trait::async_trait; use coins::coin_balance::CoinBalanceReport; -use coins::{lp_coinfind, lp_coinfind_or_err, CoinBalanceMap, CoinProtocol, CoinsContext, MmCoinEnum, RegisterCoinError}; +use coins::{lp_coinfind, lp_coinfind_or_err, CoinBalanceMap, CoinProtocol, CoinsContext, CustomTokenError, MmCoinEnum, + RegisterCoinError}; use common::{log, HttpStatusCode, StatusCode, SuccessResponse}; use crypto::hw_rpc_task::{HwConnectStatuses, HwRpcTaskAwaitingStatus, HwRpcTaskUserAction}; use crypto::HwRpcError; @@ -19,6 +20,7 @@ use rpc_task::{RpcTask, RpcTaskError, RpcTaskHandleShared, RpcTaskManager, RpcTa RpcTaskTypes, TaskId}; use ser_error_derive::SerializeErrorType; use serde_derive::{Deserialize, Serialize}; +use serde_json::Value as Json; use std::time::Duration; pub type InitTokenResponse = InitRpcTaskResponse; @@ -37,6 +39,7 @@ pub type CancelInitTokenError = CancelRpcTaskError; #[derive(Debug, Deserialize, Clone)] pub struct InitTokenReq { ticker: String, + protocol: Option, activation_params: T, } @@ -65,8 +68,10 @@ pub trait InitTokenActivationOps: Into + TokenOf + Clone + Send + Sy ticker: String, platform_coin: Self::PlatformCoin, activation_request: &Self::ActivationRequest, + token_conf: Json, protocol_conf: Self::ProtocolInfo, task_handle: InitTokenTaskHandleShared, + is_custom: bool, ) -> Result>; /// Returns the result of the token activation. @@ -94,7 +99,8 @@ where return MmError::err(InitTokenError::TokenIsAlreadyActivated { ticker: request.ticker }); } - let (_, token_protocol): (_, Token::ProtocolInfo) = coin_conf_with_protocol(&ctx, &request.ticker)?; + let (token_conf, token_protocol): (_, Token::ProtocolInfo) = + coin_conf_with_protocol(&ctx, &request.ticker, request.protocol.clone())?; let platform_coin = lp_coinfind_or_err(&ctx, token_protocol.platform_coin_ticker()) .await @@ -111,6 +117,7 @@ where let task = InitTokenTask:: { ctx, request, + token_conf, token_protocol, platform_coin, }; @@ -174,6 +181,7 @@ pub async fn cancel_init_token( pub struct InitTokenTask { ctx: MmArc, request: InitTokenReq, + token_conf: Json, token_protocol: Token::ProtocolInfo, platform_coin: Token::PlatformCoin, } @@ -210,8 +218,10 @@ where ticker.clone(), self.platform_coin.clone(), &self.request.activation_params, + self.token_conf.clone(), self.token_protocol.clone(), task_handle.clone(), + self.request.protocol.is_some(), ) .await?; @@ -297,8 +307,8 @@ pub enum InitTokenError { TokenConfigIsNotFound(String), #[display(fmt = "Token {} protocol parsing failed: {}", ticker, error)] TokenProtocolParseError { ticker: String, error: String }, - #[display(fmt = "Unexpected platform protocol {:?} for {}", protocol, ticker)] - UnexpectedTokenProtocol { ticker: String, protocol: CoinProtocol }, + #[display(fmt = "Unexpected platform protocol {} for {}", protocol, ticker)] + UnexpectedTokenProtocol { ticker: String, protocol: Json }, #[display(fmt = "Error on platform coin {} creation: {}", ticker, error)] TokenCreationError { ticker: String, error: String }, #[display(fmt = "Could not fetch balance: {}", _0)] @@ -310,6 +320,8 @@ pub enum InitTokenError { platform_coin_ticker: String, token_ticker: String, }, + #[display(fmt = "Custom token error: {}", _0)] + CustomTokenError(CustomTokenError), #[display(fmt = "{}", _0)] HwError(HwRpcError), #[display(fmt = "Transport error: {}", _0)] @@ -331,6 +343,7 @@ impl From for InitTokenError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitTokenError::UnexpectedTokenProtocol { ticker, protocol } }, + CoinConfWithProtocolError::CustomTokenError(e) => InitTokenError::CustomTokenError(e), } } } @@ -354,7 +367,8 @@ impl HttpStatusCode for InitTokenError { | InitTokenError::TokenProtocolParseError { .. } | InitTokenError::UnexpectedTokenProtocol { .. } | InitTokenError::TokenCreationError { .. } - | InitTokenError::PlatformCoinIsNotActivated(_) => StatusCode::BAD_REQUEST, + | InitTokenError::PlatformCoinIsNotActivated(_) + | InitTokenError::CustomTokenError(_) => StatusCode::BAD_REQUEST, InitTokenError::TaskTimedOut { .. } => StatusCode::REQUEST_TIMEOUT, InitTokenError::HwError(_) => StatusCode::GONE, InitTokenError::CouldNotFetchBalance(_) diff --git a/mm2src/coins_activation/src/l2/init_l2.rs b/mm2src/coins_activation/src/l2/init_l2.rs index 20e66ebbde..e6b0888700 100644 --- a/mm2src/coins_activation/src/l2/init_l2.rs +++ b/mm2src/coins_activation/src/l2/init_l2.rs @@ -79,7 +79,7 @@ where return MmError::err(InitL2Error::L2IsAlreadyActivated(ticker)); } - let (coin_conf_json, protocol_conf): (Json, L2::ProtocolInfo) = coin_conf_with_protocol(&ctx, &ticker)?; + let (coin_conf_json, protocol_conf): (Json, L2::ProtocolInfo) = coin_conf_with_protocol(&ctx, &ticker, None)?; let coin_conf = L2::coin_conf_from_json(coin_conf_json)?; let platform_coin = lp_coinfind_or_err(&ctx, protocol_conf.platform_coin_ticker()) diff --git a/mm2src/coins_activation/src/l2/init_l2_error.rs b/mm2src/coins_activation/src/l2/init_l2_error.rs index d23fd73078..71eb57fd23 100644 --- a/mm2src/coins_activation/src/l2/init_l2_error.rs +++ b/mm2src/coins_activation/src/l2/init_l2_error.rs @@ -1,11 +1,11 @@ use crate::prelude::CoinConfWithProtocolError; -use coins::CoinProtocol; use common::{HttpStatusCode, StatusCode}; use derive_more::Display; use rpc_task::rpc_common::{CancelRpcTaskError, RpcTaskStatusError, RpcTaskUserActionError}; use rpc_task::RpcTaskError; use ser_error_derive::SerializeErrorType; use serde_derive::Serialize; +use serde_json::Value as Json; use std::time::Duration; pub type InitL2StatusError = RpcTaskStatusError; @@ -24,10 +24,10 @@ pub enum InitL2Error { ticker: String, error: String, }, - #[display(fmt = "Unexpected layer 2 protocol {:?} for {}", protocol, ticker)] + #[display(fmt = "Unexpected layer 2 protocol {} for {}", protocol, ticker)] UnexpectedL2Protocol { ticker: String, - protocol: CoinProtocol, + protocol: Json, }, #[display(fmt = "Platform coin {} is not activated", _0)] PlatformCoinIsNotActivated(String), @@ -62,6 +62,9 @@ impl From for InitL2Error { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitL2Error::UnexpectedL2Protocol { ticker, protocol } }, + CoinConfWithProtocolError::CustomTokenError(e) => { + InitL2Error::Internal(format!("Custom tokens are not supported for L2: {}", e)) + }, } } } diff --git a/mm2src/coins_activation/src/lib.rs b/mm2src/coins_activation/src/lib.rs index 8a779b80f4..8020f91f27 100644 --- a/mm2src/coins_activation/src/lib.rs +++ b/mm2src/coins_activation/src/lib.rs @@ -10,20 +10,6 @@ mod platform_coin_with_tokens; mod prelude; #[cfg(feature = "enable-sia")] mod sia_coin_activation; mod slp_token_activation; -#[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") -))] -mod solana_with_tokens_activation; -#[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") -))] -mod spl_token_activation; mod standalone_coin; mod tendermint_token_activation; mod tendermint_with_assets_activation; diff --git a/mm2src/coins_activation/src/platform_coin_with_tokens.rs b/mm2src/coins_activation/src/platform_coin_with_tokens.rs index 051dd22fc3..d5ee5cfbf0 100644 --- a/mm2src/coins_activation/src/platform_coin_with_tokens.rs +++ b/mm2src/coins_activation/src/platform_coin_with_tokens.rs @@ -5,8 +5,8 @@ use crate::prelude::*; use async_trait::async_trait; use coins::my_tx_history_v2::TxHistoryStorage; use coins::tx_history_storage::{CreateTxHistoryStorageError, TxHistoryStorageBuilder}; -use coins::{lp_coinfind, lp_coinfind_any, CoinProtocol, CoinsContext, MmCoinEnum, PrivKeyPolicyNotAllowed, - UnexpectedDerivationMethod}; +use coins::{lp_coinfind, lp_coinfind_any, CoinProtocol, CoinsContext, CustomTokenError, MmCoinEnum, + PrivKeyPolicyNotAllowed, UnexpectedDerivationMethod}; use common::{log, HttpStatusCode, StatusCode, SuccessResponse}; use crypto::hw_rpc_task::{HwConnectStatuses, HwRpcTaskAwaitingStatus, HwRpcTaskUserAction}; use crypto::CryptoCtxError; @@ -38,6 +38,7 @@ pub type InitPlatformCoinWithTokensTaskManagerShared = #[derive(Clone, Debug, Deserialize)] pub struct TokenActivationRequest { ticker: String, + protocol: Option, #[serde(flatten)] request: Req, } @@ -51,8 +52,10 @@ pub trait TokenOf: Into { pub struct TokenActivationParams { pub(crate) ticker: String, + pub(crate) conf: Json, pub(crate) activation_request: Req, pub(crate) protocol: Protocol, + pub(crate) is_custom: bool, } #[async_trait] @@ -87,15 +90,15 @@ pub trait TokenAsMmCoinInitializer: Send + Sync { } pub enum InitTokensAsMmCoinsError { - TokenAlreadyActivated(String), TokenConfigIsNotFound(String), CouldNotFetchBalance(String), UnexpectedDerivationMethod(UnexpectedDerivationMethod), Internal(String), TokenProtocolParseError { ticker: String, error: String }, - UnexpectedTokenProtocol { ticker: String, protocol: CoinProtocol }, + UnexpectedTokenProtocol { ticker: String, protocol: Json }, Transport(String), InvalidPayload(String), + CustomTokenError(CustomTokenError), } impl From for InitTokensAsMmCoinsError { @@ -111,6 +114,7 @@ impl From for InitTokensAsMmCoinsError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitTokensAsMmCoinsError::UnexpectedTokenProtocol { ticker, protocol } }, + CoinConfWithProtocolError::CustomTokenError(e) => InitTokensAsMmCoinsError::CustomTokenError(e), } } } @@ -138,11 +142,14 @@ where let token_params = tokens_requests .into_iter() .map(|req| -> Result<_, MmError> { - let (_, protocol): (_, T::TokenProtocol) = coin_conf_with_protocol(&ctx, &req.ticker)?; + let (token_conf, protocol): (_, T::TokenProtocol) = + coin_conf_with_protocol(&ctx, &req.ticker, req.protocol.clone())?; Ok(TokenActivationParams { ticker: req.ticker, + conf: token_conf, activation_request: req.request, protocol, + is_custom: req.protocol.is_some(), }) }) .collect::, _>>()?; @@ -235,7 +242,6 @@ pub struct EnablePlatformCoinWithTokensReq { #[serde(tag = "error_type", content = "error_data")] pub enum EnablePlatformCoinWithTokensError { PlatformIsAlreadyActivated(String), - TokenIsAlreadyActivated(String), #[display(fmt = "Platform {} config is not found", _0)] PlatformConfigIsNotFound(String), #[display(fmt = "Platform coin {} protocol parsing failed: {}", ticker, error)] @@ -243,10 +249,10 @@ pub enum EnablePlatformCoinWithTokensError { ticker: String, error: String, }, - #[display(fmt = "Unexpected platform protocol {:?} for {}", protocol, ticker)] + #[display(fmt = "Unexpected platform protocol {} for {}", protocol, ticker)] UnexpectedPlatformProtocol { ticker: String, - protocol: CoinProtocol, + protocol: Json, }, #[display(fmt = "Token {} config is not found", _0)] TokenConfigIsNotFound(String), @@ -255,10 +261,10 @@ pub enum EnablePlatformCoinWithTokensError { ticker: String, error: String, }, - #[display(fmt = "Unexpected token protocol {:?} for {}", protocol, ticker)] + #[display(fmt = "Unexpected token protocol {} for {}", protocol, ticker)] UnexpectedTokenProtocol { ticker: String, - protocol: CoinProtocol, + protocol: Json, }, #[display(fmt = "Error on platform coin {} creation: {}", ticker, error)] PlatformCoinCreationError { @@ -283,6 +289,8 @@ pub enum EnablePlatformCoinWithTokensError { }, #[display(fmt = "Hardware policy must be activated within task manager")] UnexpectedDeviceActivationPolicy, + #[display(fmt = "Custom token error: {}", _0)] + CustomTokenError(CustomTokenError), } impl From for EnablePlatformCoinWithTokensError { @@ -300,6 +308,7 @@ impl From for EnablePlatformCoinWithTokensError { error: err.to_string(), } }, + CoinConfWithProtocolError::CustomTokenError(e) => EnablePlatformCoinWithTokensError::CustomTokenError(e), } } } @@ -307,9 +316,6 @@ impl From for EnablePlatformCoinWithTokensError { impl From for EnablePlatformCoinWithTokensError { fn from(err: InitTokensAsMmCoinsError) -> Self { match err { - InitTokensAsMmCoinsError::TokenAlreadyActivated(ticker) => { - EnablePlatformCoinWithTokensError::TokenIsAlreadyActivated(ticker) - }, InitTokensAsMmCoinsError::TokenConfigIsNotFound(ticker) => { EnablePlatformCoinWithTokensError::TokenConfigIsNotFound(ticker) }, @@ -327,6 +333,7 @@ impl From for EnablePlatformCoinWithTokensError { InitTokensAsMmCoinsError::UnexpectedDerivationMethod(e) => { EnablePlatformCoinWithTokensError::UnexpectedDerivationMethod(e.to_string()) }, + InitTokensAsMmCoinsError::CustomTokenError(e) => EnablePlatformCoinWithTokensError::CustomTokenError(e), } } } @@ -362,9 +369,9 @@ impl HttpStatusCode for EnablePlatformCoinWithTokensError { | EnablePlatformCoinWithTokensError::PrivKeyPolicyNotAllowed(_) | EnablePlatformCoinWithTokensError::UnexpectedDerivationMethod(_) | EnablePlatformCoinWithTokensError::Internal(_) - | EnablePlatformCoinWithTokensError::TaskTimedOut { .. } => StatusCode::INTERNAL_SERVER_ERROR, + | EnablePlatformCoinWithTokensError::TaskTimedOut { .. } + | EnablePlatformCoinWithTokensError::CustomTokenError(_) => StatusCode::INTERNAL_SERVER_ERROR, EnablePlatformCoinWithTokensError::PlatformIsAlreadyActivated(_) - | EnablePlatformCoinWithTokensError::TokenIsAlreadyActivated(_) | EnablePlatformCoinWithTokensError::PlatformConfigIsNotFound(_) | EnablePlatformCoinWithTokensError::TokenConfigIsNotFound(_) | EnablePlatformCoinWithTokensError::UnexpectedPlatformProtocol { .. } @@ -449,7 +456,7 @@ where )); } - let (platform_conf, platform_protocol) = coin_conf_with_protocol(&ctx, &req.ticker)?; + let (platform_conf, platform_protocol) = coin_conf_with_protocol(&ctx, &req.ticker, None)?; let platform_coin = Platform::enable_platform_coin( ctx.clone(), diff --git a/mm2src/coins_activation/src/prelude.rs b/mm2src/coins_activation/src/prelude.rs index 36fe5aa43b..42c93c1377 100644 --- a/mm2src/coins_activation/src/prelude.rs +++ b/mm2src/coins_activation/src/prelude.rs @@ -1,14 +1,13 @@ -use coins::nft::nft_structs::{Chain, ConvertChain}; #[cfg(feature = "enable-sia")] -use coins::sia::SiaCoinActivationParams; +use coins::siacoin::SiaCoinActivationParams; use coins::utxo::UtxoActivationParams; use coins::z_coin::ZcoinActivationParams; -use coins::{coin_conf, CoinBalance, CoinProtocol, DerivationMethodResponse, MmCoinEnum}; +use coins::{coin_conf, CoinBalance, CoinProtocol, CustomTokenError, DerivationMethodResponse, MmCoinEnum}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use mm2_number::BigDecimal; use serde_derive::Serialize; -use serde_json::{self as json, Value as Json}; +use serde_json::{self as json, json, Value as Json}; use std::collections::{HashMap, HashSet}; pub trait CurrentBlock { @@ -65,47 +64,79 @@ pub trait TryFromCoinProtocol { pub enum CoinConfWithProtocolError { ConfigIsNotFound(String), CoinProtocolParseError { ticker: String, err: json::Error }, - UnexpectedProtocol { ticker: String, protocol: CoinProtocol }, + UnexpectedProtocol { ticker: String, protocol: Json }, + CustomTokenError(CustomTokenError), } /// Determines the coin configuration and protocol information for a given coin or NFT ticker. -/// In the case of NFT ticker, it's platform coin config will be returned. -#[allow(clippy::result_large_err)] pub fn coin_conf_with_protocol( ctx: &MmArc, coin: &str, + protocol_from_request: Option, ) -> Result<(Json, T), MmError> { - let (conf, coin_protocol) = match Chain::from_nft_ticker(coin) { - Ok(chain) => { - let platform = chain.to_ticker(); - let platform_conf = coin_conf(ctx, platform); - let nft_protocol = CoinProtocol::NFT { - platform: platform.to_string(), - }; - (platform_conf, nft_protocol) - }, - Err(_) => { - let conf = coin_conf(ctx, coin); - let coin_protocol: CoinProtocol = json::from_value(conf["protocol"].clone()).map_to_mm(|err| { - CoinConfWithProtocolError::CoinProtocolParseError { - ticker: coin.into(), - err, - } - })?; - (conf, coin_protocol) - }, - }; - - if conf.is_null() { - return MmError::err(CoinConfWithProtocolError::ConfigIsNotFound(coin.into())); + let conf = coin_conf(ctx, coin); + let is_ticker_in_config = !conf.is_null(); + + // For `protocol_from_request`: None = config-based activation, Some = custom token activation + match (protocol_from_request, is_ticker_in_config) { + // Config-based activation requested with an existing configuration + // Proceed with parsing protocol info from config + (None, true) => parse_coin_protocol_from_config(conf, coin), + // Custom token activation requested and no matching ticker in config + // Proceed with custom token config creation from protocol info + (Some(protocol), false) => create_custom_token_config(ctx, coin, protocol), + // Custom token activation requested but a coin with the same ticker already exists in config + (Some(_), true) => Err(MmError::new(CoinConfWithProtocolError::CustomTokenError( + CustomTokenError::DuplicateTickerInConfig { + ticker_in_config: coin.to_string(), + }, + ))), + // Config-based activation requested but ticker not found in config + (None, false) => Err(MmError::new(CoinConfWithProtocolError::ConfigIsNotFound(coin.into()))), } +} + +fn parse_coin_protocol_from_config( + conf: Json, + coin: &str, +) -> Result<(Json, T), MmError> { + let protocol = json::from_value(conf["protocol"].clone()).map_to_mm(|err| { + CoinConfWithProtocolError::CoinProtocolParseError { + ticker: coin.into(), + err, + } + })?; + + let coin_protocol = + T::try_from_coin_protocol(protocol).mm_err(|p| CoinConfWithProtocolError::UnexpectedProtocol { + ticker: coin.into(), + protocol: json!(p), + })?; - let protocol = - T::try_from_coin_protocol(coin_protocol).mm_err(|protocol| CoinConfWithProtocolError::UnexpectedProtocol { + Ok((conf, coin_protocol)) +} + +fn create_custom_token_config( + ctx: &MmArc, + coin: &str, + protocol: CoinProtocol, +) -> Result<(Json, T), MmError> { + protocol + .custom_token_validations(ctx) + .mm_err(CoinConfWithProtocolError::CustomTokenError)?; + + let conf = json!({ + "protocol": protocol, + "wallet_only": true + }); + + let coin_protocol = + T::try_from_coin_protocol(protocol).mm_err(|p| CoinConfWithProtocolError::UnexpectedProtocol { ticker: coin.into(), - protocol, + protocol: json!(p), })?; - Ok((conf, protocol)) + + Ok((conf, coin_protocol)) } /// A trait to be implemented for coin activation requests to determine some information about the request. diff --git a/mm2src/coins_activation/src/sia_coin_activation.rs b/mm2src/coins_activation/src/sia_coin_activation.rs index 7dd7539f66..11c72955ab 100644 --- a/mm2src/coins_activation/src/sia_coin_activation.rs +++ b/mm2src/coins_activation/src/sia_coin_activation.rs @@ -7,8 +7,8 @@ use async_trait::async_trait; use coins::coin_balance::{CoinBalanceReport, IguanaWalletBalance}; use coins::coin_errors::MyAddressError; use coins::my_tx_history_v2::TxHistoryStorage; -use coins::sia::{sia_coin_from_conf_and_params, SiaCoin, SiaCoinActivationParams, SiaCoinBuildError, - SiaCoinProtocolInfo}; +use coins::siacoin::{sia_coin_from_conf_and_params, SiaCoin, SiaCoinActivationParams, SiaCoinBuildError, + SiaCoinProtocolInfo}; use coins::tx_history_storage::CreateTxHistoryStorageError; use coins::{BalanceError, CoinBalance, CoinProtocol, MarketCoinOps, PrivKeyBuildPolicy, RegisterCoinError}; use crypto::hw_rpc_task::{HwRpcTaskAwaitingStatus, HwRpcTaskUserAction}; diff --git a/mm2src/coins_activation/src/slp_token_activation.rs b/mm2src/coins_activation/src/slp_token_activation.rs index f9abc166f4..91dbf95ea7 100644 --- a/mm2src/coins_activation/src/slp_token_activation.rs +++ b/mm2src/coins_activation/src/slp_token_activation.rs @@ -7,6 +7,7 @@ use coins::{CoinBalance, CoinProtocol, MarketCoinOps, MmCoin, MmCoinEnum}; use mm2_err_handle::prelude::*; use rpc::v1::types::H256 as H256Json; use serde_derive::{Deserialize, Serialize}; +use serde_json::Value as Json; use std::collections::HashMap; impl TryPlatformCoinFromMmCoinEnum for BchCoin { @@ -82,7 +83,9 @@ impl TokenActivationOps for SlpToken { ticker: String, platform_coin: Self::PlatformCoin, activation_params: Self::ActivationParams, + _token_conf: Json, protocol_conf: Self::ProtocolInfo, + _is_custom: bool, ) -> Result<(Self, Self::ActivationResult), MmError> { // confirmation settings from activation params have the highest priority let required_confirmations = activation_params.required_confirmations.unwrap_or_else(|| { diff --git a/mm2src/coins_activation/src/solana_with_tokens_activation.rs b/mm2src/coins_activation/src/solana_with_tokens_activation.rs deleted file mode 100644 index f411995779..0000000000 --- a/mm2src/coins_activation/src/solana_with_tokens_activation.rs +++ /dev/null @@ -1,327 +0,0 @@ -use crate::context::CoinsActivationContext; -use crate::platform_coin_with_tokens::{EnablePlatformCoinWithTokensError, GetPlatformBalance, - InitPlatformCoinWithTokensAwaitingStatus, - InitPlatformCoinWithTokensInProgressStatus, InitPlatformCoinWithTokensTask, - InitPlatformCoinWithTokensTaskManagerShared, - InitPlatformCoinWithTokensUserAction, InitTokensAsMmCoinsError, - PlatformCoinWithTokensActivationOps, RegisterTokenInfo, TokenActivationParams, - TokenActivationRequest, TokenAsMmCoinInitializer, TokenInitializer, TokenOf}; -use crate::prelude::*; -use crate::prelude::{CoinAddressInfo, TokenBalances, TryFromCoinProtocol, TxHistory}; -use crate::spl_token_activation::SplActivationRequest; -use async_trait::async_trait; -use coins::coin_errors::MyAddressError; -use coins::my_tx_history_v2::TxHistoryStorage; -use coins::solana::solana_coin_with_policy; -use coins::solana::spl::{SplProtocolConf, SplTokenCreationError}; -use coins::{BalanceError, CoinBalance, CoinProtocol, DerivationMethodResponse, MarketCoinOps, MmCoinEnum, - PrivKeyBuildPolicy, SolanaActivationParams, SolanaCoin, SplToken}; -use common::Future01CompatExt; -use common::{drop_mutability, true_f}; -use crypto::CryptoCtxError; -use futures::future::try_join_all; -use mm2_core::mm_ctx::MmArc; -use mm2_err_handle::prelude::*; -use mm2_event_stream::EventStreamConfiguration; -use mm2_number::BigDecimal; -use rpc_task::RpcTaskHandleShared; -use serde_derive::{Deserialize, Serialize}; -use serde_json::Value as Json; -use std::collections::HashMap; - -pub struct SplTokenInitializer { - platform_coin: SolanaCoin, -} - -impl TokenOf for SplToken { - type PlatformCoin = SolanaCoin; -} - -pub struct SplTokenInitializerErr { - ticker: String, - inner: SplTokenCreationError, -} - -#[async_trait] -impl TokenInitializer for SplTokenInitializer { - type Token = SplToken; - type TokenActivationRequest = SplActivationRequest; - type TokenProtocol = SplProtocolConf; - type InitTokensError = SplTokenInitializerErr; - - fn tokens_requests_from_platform_request( - platform_params: &SolanaWithTokensActivationRequest, - ) -> Vec> { - platform_params.spl_tokens_requests.clone() - } - - async fn enable_tokens( - &self, - activation_params: Vec>, - ) -> Result, MmError> { - let tokens = activation_params - .into_iter() - .map(|param| { - let ticker = param.ticker.clone(); - SplToken::new( - param.protocol.decimals, - param.ticker, - param.protocol.token_contract_address, - self.platform_coin.clone(), - ) - .mm_err(|inner| SplTokenInitializerErr { ticker, inner }) - }) - .collect::, _>>()?; - Ok(tokens) - } - - fn platform_coin(&self) -> &SolanaCoin { &self.platform_coin } -} - -impl RegisterTokenInfo for SolanaCoin { - fn register_token_info(&self, token: &SplToken) { self.add_spl_token_info(token.ticker().into(), token.get_info()) } -} - -#[derive(Clone, Debug, Deserialize)] -pub struct SolanaWithTokensActivationRequest { - #[serde(flatten)] - platform_request: SolanaActivationParams, - spl_tokens_requests: Vec>, - #[serde(default = "true_f")] - pub get_balances: bool, -} - -impl TxHistory for SolanaWithTokensActivationRequest { - fn tx_history(&self) -> bool { false } -} - -impl ActivationRequestInfo for SolanaWithTokensActivationRequest { - fn is_hw_policy(&self) -> bool { false } // TODO: fix when device policy is added -} - -#[derive(Debug, Serialize, Clone)] -pub struct SolanaWithTokensActivationResult { - current_block: u64, - solana_addresses_infos: HashMap>, - spl_addresses_infos: HashMap>, -} - -impl GetPlatformBalance for SolanaWithTokensActivationResult { - fn get_platform_balance(&self) -> Option { - self.solana_addresses_infos - .iter() - .fold(Some(BigDecimal::from(0)), |total, (_, addr_info)| { - total.and_then(|t| addr_info.balances.as_ref().map(|b| t + b.get_total())) - }) - } -} - -impl CurrentBlock for SolanaWithTokensActivationResult { - fn current_block(&self) -> u64 { self.current_block } -} - -#[derive(Debug, Clone)] -pub enum SolanaWithTokensActivationError { - PlatformCoinCreationError { ticker: String, error: String }, - UnableToRetrieveMyAddress(String), - GetBalanceError(BalanceError), - Transport(String), - Internal(String), -} - -impl From for SolanaWithTokensActivationError { - fn from(err: MyAddressError) -> Self { Self::UnableToRetrieveMyAddress(err.to_string()) } -} - -impl From for EnablePlatformCoinWithTokensError { - fn from(e: SolanaWithTokensActivationError) -> Self { - match e { - SolanaWithTokensActivationError::PlatformCoinCreationError { ticker, error } => { - EnablePlatformCoinWithTokensError::PlatformCoinCreationError { ticker, error } - }, - SolanaWithTokensActivationError::UnableToRetrieveMyAddress(e) => { - EnablePlatformCoinWithTokensError::Internal(e) - }, - SolanaWithTokensActivationError::GetBalanceError(e) => { - EnablePlatformCoinWithTokensError::Internal(format!("{:?}", e)) - }, - SolanaWithTokensActivationError::Transport(e) => EnablePlatformCoinWithTokensError::Transport(e), - SolanaWithTokensActivationError::Internal(e) => EnablePlatformCoinWithTokensError::Internal(e), - } - } -} - -impl From for SolanaWithTokensActivationError { - fn from(e: BalanceError) -> Self { SolanaWithTokensActivationError::GetBalanceError(e) } -} - -impl From for SolanaWithTokensActivationError { - fn from(e: CryptoCtxError) -> Self { SolanaWithTokensActivationError::Internal(e.to_string()) } -} - -pub struct SolanaProtocolInfo {} - -impl TryFromCoinProtocol for SolanaProtocolInfo { - fn try_from_coin_protocol(proto: CoinProtocol) -> Result> - where - Self: Sized, - { - match proto { - CoinProtocol::SOLANA {} => Ok(SolanaProtocolInfo {}), - protocol => MmError::err(protocol), - } - } -} - -impl From for InitTokensAsMmCoinsError { - fn from(err: SplTokenInitializerErr) -> Self { - match err.inner { - SplTokenCreationError::InvalidPubkey(error) => InitTokensAsMmCoinsError::TokenProtocolParseError { - ticker: err.ticker, - error, - }, - SplTokenCreationError::Internal(internal) => InitTokensAsMmCoinsError::Internal(internal), - } - } -} - -#[async_trait] -impl PlatformCoinWithTokensActivationOps for SolanaCoin { - type ActivationRequest = SolanaWithTokensActivationRequest; - type PlatformProtocolInfo = SolanaProtocolInfo; - type ActivationResult = SolanaWithTokensActivationResult; - type ActivationError = SolanaWithTokensActivationError; - - type InProgressStatus = InitPlatformCoinWithTokensInProgressStatus; - type AwaitingStatus = InitPlatformCoinWithTokensAwaitingStatus; - type UserAction = InitPlatformCoinWithTokensUserAction; - - async fn enable_platform_coin( - ctx: MmArc, - ticker: String, - platform_conf: &Json, - activation_request: Self::ActivationRequest, - _protocol_conf: Self::PlatformProtocolInfo, - ) -> Result> { - let priv_key_policy = PrivKeyBuildPolicy::detect_priv_key_policy(&ctx)?; - solana_coin_with_policy( - &ctx, - &ticker, - platform_conf, - activation_request.platform_request, - priv_key_policy, - ) - .await - .map_to_mm(|error| SolanaWithTokensActivationError::PlatformCoinCreationError { ticker, error }) - } - - async fn enable_global_nft( - &self, - _activation_request: &Self::ActivationRequest, - ) -> Result, MmError> { - Ok(None) - } - - fn try_from_mm_coin(coin: MmCoinEnum) -> Option - where - Self: Sized, - { - match coin { - MmCoinEnum::SolanaCoin(coin) => Some(coin), - _ => None, - } - } - - fn token_initializers( - &self, - ) -> Vec>> { - vec![Box::new(SplTokenInitializer { - platform_coin: self.clone(), - })] - } - - async fn get_activation_result( - &self, - _task_handle: Option>>, - activation_request: &Self::ActivationRequest, - _nft_global: &Option, - ) -> Result> { - let current_block = self - .current_block() - .compat() - .await - .map_to_mm(Self::ActivationError::Internal)?; - - let my_address = self.my_address()?; - - let mut solana_address_info = CoinAddressInfo { - derivation_method: DerivationMethodResponse::Iguana, - pubkey: my_address.clone(), - balances: None, - tickers: None, - }; - - let mut spl_address_info = CoinAddressInfo { - derivation_method: DerivationMethodResponse::Iguana, - pubkey: my_address.clone(), - balances: None, - tickers: None, - }; - - if !activation_request.get_balances { - drop_mutability!(solana_address_info); - let tickers = self.get_spl_tokens_infos().into_keys().collect(); - spl_address_info.tickers = Some(tickers); - drop_mutability!(spl_address_info); - - return Ok(SolanaWithTokensActivationResult { - current_block, - solana_addresses_infos: HashMap::from([(my_address.clone(), solana_address_info)]), - spl_addresses_infos: HashMap::from([(my_address, spl_address_info)]), - }); - } - - let solana_balance = self - .my_balance() - .compat() - .await - .map_err(|e| Self::ActivationError::GetBalanceError(e.into_inner()))?; - solana_address_info.balances = Some(solana_balance); - drop_mutability!(solana_address_info); - - let (token_tickers, requests): (Vec<_>, Vec<_>) = self - .get_spl_tokens_infos() - .into_iter() - .map(|(ticker, info)| (ticker, self.my_balance_spl(info))) - .unzip(); - spl_address_info.balances = Some(token_tickers.into_iter().zip(try_join_all(requests).await?).collect()); - drop_mutability!(spl_address_info); - - Ok(SolanaWithTokensActivationResult { - current_block, - solana_addresses_infos: HashMap::from([(my_address.clone(), solana_address_info)]), - spl_addresses_infos: HashMap::from([(my_address, spl_address_info)]), - }) - } - - fn start_history_background_fetching( - &self, - _ctx: MmArc, - _storage: impl TxHistoryStorage + Send + 'static, - _initial_balance: Option, - ) { - } - - async fn handle_balance_streaming( - &self, - _config: &EventStreamConfiguration, - ) -> Result<(), MmError> { - Ok(()) - } - - fn rpc_task_manager( - _activation_ctx: &CoinsActivationContext, - ) -> &InitPlatformCoinWithTokensTaskManagerShared { - unimplemented!() - } -} diff --git a/mm2src/coins_activation/src/spl_token_activation.rs b/mm2src/coins_activation/src/spl_token_activation.rs deleted file mode 100644 index 2bf40b6a89..0000000000 --- a/mm2src/coins_activation/src/spl_token_activation.rs +++ /dev/null @@ -1,117 +0,0 @@ -use crate::prelude::{TryFromCoinProtocol, TryPlatformCoinFromMmCoinEnum}; -use crate::token::{EnableTokenError, TokenActivationOps, TokenProtocolParams}; -use async_trait::async_trait; -use coins::coin_errors::MyAddressError; -use coins::solana::spl::{SplProtocolConf, SplTokenCreationError}; -use coins::{BalanceError, CoinBalance, CoinProtocol, MarketCoinOps, MmCoinEnum, SolanaCoin, SplToken}; -use common::Future01CompatExt; -use mm2_err_handle::prelude::*; -use serde_derive::{Deserialize, Serialize}; -use std::collections::HashMap; - -impl TryPlatformCoinFromMmCoinEnum for SolanaCoin { - fn try_from_mm_coin(coin: MmCoinEnum) -> Option - where - Self: Sized, - { - match coin { - MmCoinEnum::SolanaCoin(coin) => Some(coin), - _ => None, - } - } -} - -#[derive(Clone, Debug, Deserialize)] -pub struct SplActivationRequest {} - -impl TryFromCoinProtocol for SplProtocolConf { - fn try_from_coin_protocol(proto: CoinProtocol) -> Result> - where - Self: Sized, - { - match proto { - CoinProtocol::SPLTOKEN { - platform, - token_contract_address, - decimals, - } => Ok(SplProtocolConf { - platform_coin_ticker: platform, - decimals, - token_contract_address, - }), - proto => MmError::err(proto), - } - } -} - -impl TokenProtocolParams for SplProtocolConf { - fn platform_coin_ticker(&self) -> &str { &self.platform_coin_ticker } -} - -#[derive(Debug, Serialize)] -pub struct SplInitResult { - balances: HashMap, - token_contract_address: String, - platform_coin: String, -} - -#[derive(Debug)] -#[allow(clippy::large_enum_variant)] -pub enum SplInitError { - GetBalanceError(BalanceError), - TokenCreationFailed(SplTokenCreationError), - MyAddressError(String), -} - -impl From for SplInitError { - fn from(err: MyAddressError) -> Self { Self::MyAddressError(err.to_string()) } -} - -impl From for SplInitError { - fn from(e: SplTokenCreationError) -> Self { SplInitError::TokenCreationFailed(e) } -} - -impl From for EnableTokenError { - fn from(err: SplInitError) -> Self { - match err { - SplInitError::GetBalanceError(rpc_err) => rpc_err.into(), - SplInitError::TokenCreationFailed(e) => EnableTokenError::Internal(format! {"{:?}", e}), - SplInitError::MyAddressError(e) => EnableTokenError::Internal(e), - } - } -} - -#[async_trait] -impl TokenActivationOps for SplToken { - type ActivationParams = SplActivationRequest; - type ProtocolInfo = SplProtocolConf; - type ActivationResult = SplInitResult; - type ActivationError = SplInitError; - - async fn enable_token( - ticker: String, - platform_coin: Self::PlatformCoin, - _activation_params: Self::ActivationParams, - protocol_conf: Self::ProtocolInfo, - ) -> Result<(Self, Self::ActivationResult), MmError> { - let token = Self::new( - protocol_conf.decimals, - ticker, - protocol_conf.token_contract_address, - platform_coin, - )?; - let balance = token - .my_balance() - .compat() - .await - .map_err(|e| SplInitError::GetBalanceError(e.into_inner()))?; - let my_address = token.my_address()?; - let balances = HashMap::from([(my_address, balance)]); - let init_result = SplInitResult { - balances, - token_contract_address: token.conf.token_contract_address.to_string(), - platform_coin: token.platform_coin.ticker().to_owned(), - }; - Ok((token, init_result)) - } -} diff --git a/mm2src/coins_activation/src/standalone_coin/init_standalone_coin.rs b/mm2src/coins_activation/src/standalone_coin/init_standalone_coin.rs index 314f3066b4..a90f53e968 100644 --- a/mm2src/coins_activation/src/standalone_coin/init_standalone_coin.rs +++ b/mm2src/coins_activation/src/standalone_coin/init_standalone_coin.rs @@ -90,7 +90,7 @@ where return MmError::err(InitStandaloneCoinError::CoinIsAlreadyActivated { ticker: request.ticker }); } - let (coin_conf, protocol_info) = coin_conf_with_protocol(&ctx, &request.ticker)?; + let (coin_conf, protocol_info) = coin_conf_with_protocol(&ctx, &request.ticker, None)?; let coins_act_ctx = CoinsActivationContext::from_ctx(&ctx).map_to_mm(InitStandaloneCoinError::Internal)?; let spawner = ctx.spawner(); diff --git a/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs b/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs index 1f0b5db764..21b1d696a9 100644 --- a/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs +++ b/mm2src/coins_activation/src/standalone_coin/init_standalone_coin_error.rs @@ -1,5 +1,4 @@ use crate::prelude::CoinConfWithProtocolError; -use coins::CoinProtocol; use common::{HttpStatusCode, StatusCode}; use crypto::HwRpcError; use derive_more::Display; @@ -7,6 +6,7 @@ use rpc_task::rpc_common::{CancelRpcTaskError, RpcTaskStatusError, RpcTaskUserAc use rpc_task::{RpcTaskError, TaskId}; use ser_error_derive::SerializeErrorType; use serde_derive::Serialize; +use serde_json::Value as Json; use std::time::Duration; pub type InitStandaloneCoinStatusError = RpcTaskStatusError; @@ -26,8 +26,8 @@ pub enum InitStandaloneCoinError { CoinConfigIsNotFound(String), #[display(fmt = "Coin {} protocol parsing failed: {}", ticker, error)] CoinProtocolParseError { ticker: String, error: String }, - #[display(fmt = "Unexpected platform protocol {:?} for {}", protocol, ticker)] - UnexpectedCoinProtocol { ticker: String, protocol: CoinProtocol }, + #[display(fmt = "Unexpected platform protocol {} for {}", protocol, ticker)] + UnexpectedCoinProtocol { ticker: String, protocol: Json }, #[display(fmt = "Error on platform coin {} creation: {}", ticker, error)] CoinCreationError { ticker: String, error: String }, #[display(fmt = "{}", _0)] @@ -51,6 +51,10 @@ impl From for InitStandaloneCoinError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { InitStandaloneCoinError::UnexpectedCoinProtocol { ticker, protocol } }, + CoinConfWithProtocolError::CustomTokenError(e) => InitStandaloneCoinError::Internal(format!( + "Custom tokens are not supported for standalone coins: {}", + e + )), } } } diff --git a/mm2src/coins_activation/src/tendermint_token_activation.rs b/mm2src/coins_activation/src/tendermint_token_activation.rs index 29598969af..12808505a9 100644 --- a/mm2src/coins_activation/src/tendermint_token_activation.rs +++ b/mm2src/coins_activation/src/tendermint_token_activation.rs @@ -7,6 +7,7 @@ use coins::{tendermint::{TendermintCoin, TendermintToken, TendermintTokenActivat use common::Future01CompatExt; use mm2_err_handle::prelude::{MapMmError, MmError}; use serde::Serialize; +use serde_json::Value as Json; use std::collections::HashMap; impl From for EnableTokenError { @@ -54,7 +55,9 @@ impl TokenActivationOps for TendermintToken { ticker: String, platform_coin: Self::PlatformCoin, _activation_params: Self::ActivationParams, + _token_conf: Json, protocol_conf: Self::ProtocolInfo, + _is_custom: bool, ) -> Result<(Self, Self::ActivationResult), MmError> { let token = TendermintToken::new(ticker, platform_coin, protocol_conf.decimals, protocol_conf.denom)?; diff --git a/mm2src/coins_activation/src/tendermint_with_assets_activation.rs b/mm2src/coins_activation/src/tendermint_with_assets_activation.rs index 5a79bbeb6e..349e37b23d 100644 --- a/mm2src/coins_activation/src/tendermint_with_assets_activation.rs +++ b/mm2src/coins_activation/src/tendermint_with_assets_activation.rs @@ -11,9 +11,9 @@ use async_trait::async_trait; use coins::hd_wallet::HDPathAccountToAddressId; use coins::my_tx_history_v2::TxHistoryStorage; use coins::tendermint::tendermint_tx_history_v2::tendermint_history_loop; -use coins::tendermint::{tendermint_priv_key_policy, TendermintActivationPolicy, TendermintCoin, TendermintCommons, - TendermintConf, TendermintInitError, TendermintInitErrorKind, TendermintProtocolInfo, - TendermintPublicKey, TendermintToken, TendermintTokenActivationParams, +use coins::tendermint::{tendermint_priv_key_policy, RpcNode, TendermintActivationPolicy, TendermintCoin, + TendermintCommons, TendermintConf, TendermintInitError, TendermintInitErrorKind, + TendermintProtocolInfo, TendermintPublicKey, TendermintToken, TendermintTokenActivationParams, TendermintTokenInitError, TendermintTokenProtocolInfo}; use coins::{CoinBalance, CoinProtocol, MarketCoinOps, MmCoin, MmCoinEnum, PrivKeyBuildPolicy}; use common::executor::{AbortSettings, SpawnAbortable}; @@ -40,7 +40,7 @@ impl RegisterTokenInfo for TendermintCoin { #[derive(Clone, Deserialize)] pub struct TendermintActivationParams { - rpc_urls: Vec, + nodes: Vec, pub tokens_params: Vec>, #[serde(default)] tx_history: bool, @@ -265,7 +265,7 @@ impl PlatformCoinWithTokensActivationOps for TendermintCoin { ticker, conf, protocol_conf, - activation_request.rpc_urls, + activation_request.nodes, activation_request.tx_history, activation_policy, is_keplr_from_ledger, diff --git a/mm2src/coins_activation/src/token.rs b/mm2src/coins_activation/src/token.rs index 0493c68fdb..5001a0f3de 100644 --- a/mm2src/coins_activation/src/token.rs +++ b/mm2src/coins_activation/src/token.rs @@ -4,7 +4,7 @@ use crate::platform_coin_with_tokens::{self, RegisterTokenInfo}; use crate::prelude::*; use async_trait::async_trait; use coins::utxo::rpc_clients::UtxoRpcError; -use coins::{lp_coinfind, lp_coinfind_or_err, BalanceError, CoinProtocol, CoinsContext, MmCoinEnum, +use coins::{lp_coinfind, lp_coinfind_or_err, BalanceError, CoinProtocol, CoinsContext, CustomTokenError, MmCoinEnum, PrivKeyPolicyNotAllowed, RegisterCoinError, UnexpectedDerivationMethod}; use common::{HttpStatusCode, StatusCode}; use derive_more::Display; @@ -12,6 +12,7 @@ use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use ser_error_derive::SerializeErrorType; use serde_derive::{Deserialize, Serialize}; +use serde_json::Value as Json; pub trait TokenProtocolParams { fn platform_coin_ticker(&self) -> &str; @@ -28,7 +29,9 @@ pub trait TokenActivationOps: Into + platform_coin_with_tokens::Toke ticker: String, platform_coin: Self::PlatformCoin, activation_params: Self::ActivationParams, + token_conf: Json, protocol_conf: Self::ProtocolInfo, + is_custom: bool, ) -> Result<(Self, Self::ActivationResult), MmError>; } @@ -44,10 +47,10 @@ pub enum EnableTokenError { ticker: String, error: String, }, - #[display(fmt = "Unexpected token protocol {:?} for {}", protocol, ticker)] + #[display(fmt = "Unexpected token protocol {} for {}", protocol, ticker)] UnexpectedTokenProtocol { ticker: String, - protocol: CoinProtocol, + protocol: Json, }, #[display(fmt = "Platform coin {} is not activated", _0)] PlatformCoinIsNotActivated(String), @@ -64,6 +67,8 @@ pub enum EnableTokenError { Internal(String), InvalidPayload(String), PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), + #[display(fmt = "Custom token error: {}", _0)] + CustomTokenError(CustomTokenError), } impl From for EnableTokenError { @@ -88,6 +93,7 @@ impl From for EnableTokenError { CoinConfWithProtocolError::UnexpectedProtocol { ticker, protocol } => { EnableTokenError::UnexpectedTokenProtocol { ticker, protocol } }, + CoinConfWithProtocolError::CustomTokenError(e) => EnableTokenError::CustomTokenError(e), } } } @@ -105,6 +111,7 @@ impl From for EnableTokenError { #[derive(Debug, Deserialize)] pub struct EnableTokenRequest { ticker: String, + protocol: Option, activation_params: T, } @@ -121,7 +128,8 @@ where return MmError::err(EnableTokenError::TokenIsAlreadyActivated(req.ticker)); } - let (_, token_protocol): (_, Token::ProtocolInfo) = coin_conf_with_protocol(&ctx, &req.ticker)?; + let (token_conf, token_protocol): (_, Token::ProtocolInfo) = + coin_conf_with_protocol(&ctx, &req.ticker, req.protocol.clone())?; let platform_coin = lp_coinfind_or_err(&ctx, token_protocol.platform_coin_ticker()) .await @@ -134,8 +142,15 @@ where } })?; - let (token, activation_result) = - Token::enable_token(req.ticker, platform_coin.clone(), req.activation_params, token_protocol).await?; + let (token, activation_result) = Token::enable_token( + req.ticker, + platform_coin.clone(), + req.activation_params, + token_conf, + token_protocol, + req.protocol.is_some(), + ) + .await?; let coins_ctx = CoinsContext::from_ctx(&ctx).unwrap(); coins_ctx.add_token(token.clone().into()).await?; @@ -164,7 +179,8 @@ impl HttpStatusCode for EnableTokenError { | EnableTokenError::PlatformCoinIsNotActivated(_) | EnableTokenError::TokenConfigIsNotFound { .. } | EnableTokenError::UnexpectedTokenProtocol { .. } - | EnableTokenError::InvalidPayload(_) => StatusCode::BAD_REQUEST, + | EnableTokenError::InvalidPayload(_) + | EnableTokenError::CustomTokenError(_) => StatusCode::BAD_REQUEST, EnableTokenError::TokenProtocolParseError { .. } | EnableTokenError::UnsupportedPlatformCoin { .. } | EnableTokenError::UnexpectedDerivationMethod(_) diff --git a/mm2src/coins_activation/src/utxo_activation/common_impl.rs b/mm2src/coins_activation/src/utxo_activation/common_impl.rs index 8e3d071025..43cc0e32d6 100644 --- a/mm2src/coins_activation/src/utxo_activation/common_impl.rs +++ b/mm2src/coins_activation/src/utxo_activation/common_impl.rs @@ -8,7 +8,7 @@ use coins::hd_wallet::RpcTaskXPubExtractor; use coins::my_tx_history_v2::TxHistoryStorage; use coins::utxo::utxo_tx_history_v2::{utxo_history_loop, UtxoTxHistoryOps}; use coins::utxo::{UtxoActivationParams, UtxoCoinFields}; -use coins::{CoinBalance, CoinFutSpawner, MarketCoinOps, PrivKeyActivationPolicy, PrivKeyBuildPolicy}; +use coins::{CoinBalanceMap, CoinFutSpawner, MarketCoinOps, PrivKeyActivationPolicy, PrivKeyBuildPolicy}; use common::executor::{AbortSettings, SpawnAbortable}; use crypto::hw_rpc_task::HwConnectStatuses; use crypto::{CryptoCtxError, HwRpcError}; @@ -31,7 +31,7 @@ where InProgressStatus = UtxoStandardInProgressStatus, AwaitingStatus = UtxoStandardAwaitingStatus, UserAction = UtxoStandardUserAction, - > + EnableCoinBalanceOps + > + EnableCoinBalanceOps + MarketCoinOps, { let ticker = coin.ticker().to_owned(); diff --git a/mm2src/coins_activation/src/utxo_activation/utxo_standard_activation_result.rs b/mm2src/coins_activation/src/utxo_activation/utxo_standard_activation_result.rs index d9f67ae9b5..2fd75e5ab8 100644 --- a/mm2src/coins_activation/src/utxo_activation/utxo_standard_activation_result.rs +++ b/mm2src/coins_activation/src/utxo_activation/utxo_standard_activation_result.rs @@ -1,6 +1,6 @@ use crate::prelude::{CurrentBlock, GetAddressesBalances}; use coins::coin_balance::CoinBalanceReport; -use coins::CoinBalance; +use coins::CoinBalanceMap; use mm2_number::BigDecimal; use serde_derive::Serialize; use std::collections::HashMap; @@ -9,7 +9,7 @@ use std::collections::HashMap; pub struct UtxoStandardActivationResult { pub ticker: String, pub current_block: u64, - pub wallet_balance: CoinBalanceReport, + pub wallet_balance: CoinBalanceReport, } impl CurrentBlock for UtxoStandardActivationResult { diff --git a/mm2src/common/Cargo.toml b/mm2src/common/Cargo.toml index 6549d02cc1..6a5395b360 100644 --- a/mm2src/common/Cargo.toml +++ b/mm2src/common/Cargo.toml @@ -25,6 +25,7 @@ fnv = "1.0.6" futures01 = { version = "0.1", package = "futures" } futures = { version = "0.3", package = "futures", features = ["compat", "async-await", "thread-pool"] } futures-timer = "3.0" +gstuff = "0.7" hex = "0.4.2" http = "0.2" http-body = "0.1" @@ -49,7 +50,6 @@ instant = { version = "0.1.12" } [target.'cfg(target_arch = "wasm32")'.dependencies] chrono = { version = "0.4", features = ["wasmbind"] } -gstuff = { version = "0.7", features = ["nightly"] } instant = { version = "0.1.12", features = ["wasm-bindgen"] } js-sys = "0.3.27" serde_repr = "0.1.6" @@ -62,7 +62,6 @@ web-sys = { version = "0.3.55", features = ["console", "CloseEvent", "DomExcepti [target.'cfg(not(target_arch = "wasm32"))'.dependencies] anyhow = "1.0" chrono = "0.4" -gstuff = { version = "0.7", features = ["nightly"] } hyper = { version = "0.14.26", features = ["client", "http2", "server", "tcp"] } # using webpki-tokio to avoid rejecting valid certificates # got "invalid certificate: UnknownIssuer" for https://ropsten.infura.io on iOS using default-features @@ -79,4 +78,4 @@ findshlibs = "0.5" [build-dependencies] cc = "1.0" -gstuff = { version = "0.7", features = ["nightly"] } +gstuff = "0.7" diff --git a/mm2src/common/common.rs b/mm2src/common/common.rs index 6892b8f777..de201856d8 100644 --- a/mm2src/common/common.rs +++ b/mm2src/common/common.rs @@ -129,6 +129,7 @@ pub mod custom_futures; pub mod custom_iter; #[path = "executor/mod.rs"] pub mod executor; pub mod expirable_map; +pub mod notifier; pub mod number_type_casting; pub mod password_policy; pub mod seri; @@ -204,6 +205,8 @@ pub const SATOSHIS: u64 = 100_000_000; pub const DEX_FEE_ADDR_PUBKEY: &str = "03bc2c7ba671bae4a6fc835244c9762b41647b9827d4780a89a949b984a8ddcc06"; +pub const PROXY_REQUEST_EXPIRATION_SEC: i64 = 15; + lazy_static! { pub static ref DEX_FEE_ADDR_RAW_PUBKEY: Vec = hex::decode(DEX_FEE_ADDR_PUBKEY).expect("DEX_FEE_ADDR_PUBKEY is expected to be a hexadecimal string"); @@ -375,10 +378,9 @@ pub fn stack_trace_frame(instr_ptr: *mut c_void, buf: &mut dyn Write, symbol: &b // Skip common and less than informative frames. match name { - "mm2::crash_reports::rust_seh_handler" + "common::crash_reports::rust_seh_handler" | "veh_exception_filter" | "common::stack_trace" - | "common::log_stacktrace" // Super-main on Windows. | "__scrt_common_main_seh" => return, _ => (), @@ -396,7 +398,7 @@ pub fn stack_trace_frame(instr_ptr: *mut c_void, buf: &mut dyn Write, symbol: &b || name.starts_with("core::ops::") || name.starts_with("futures::") || name.starts_with("hyper::") - || name.starts_with("mm2::crash_reports::signal_handler") + || name.starts_with("common::crash_reports::signal_handler") || name.starts_with("panic_unwind::") || name.starts_with("std::") || name.starts_with("scoped_tls::") @@ -517,19 +519,6 @@ pub fn set_panic_hook() { })) } -/// Simulates the panic-in-panic crash. -pub fn double_panic_crash() { - struct Panicker; - impl Drop for Panicker { - fn drop(&mut self) { panic!("panic in drop") } - } - let panicker = Panicker; - if 1 < 2 { - panic!("first panic") - } - drop(panicker) // Delays the drop. -} - /// 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. @@ -633,7 +622,20 @@ pub fn var(name: &str) -> Result { #[cfg(target_arch = "wasm32")] pub fn var(_name: &str) -> Result { ERR!("Environment variable not supported in WASM") } +/// Runs the given future on MM2's executor and waits for the result. +/// +/// This is compatible with futures 0.1. +pub fn block_on_f01(f: F) -> Result +where + F: Future, +{ + block_on(f.compat()) +} + #[cfg(not(target_arch = "wasm32"))] +/// Runs the given future on MM2's executor and waits for the result. +/// +/// This is compatible with futures 0.3. pub fn block_on(f: F) -> F::Output where F: Future03, diff --git a/mm2src/common/executor/abortable_system/abortable_queue.rs b/mm2src/common/executor/abortable_system/abortable_queue.rs index 99ffc70ca3..89781bcfa4 100644 --- a/mm2src/common/executor/abortable_system/abortable_queue.rs +++ b/mm2src/common/executor/abortable_system/abortable_queue.rs @@ -40,9 +40,7 @@ impl From> for AbortableQueue { impl AbortableSystem for AbortableQueue { type Inner = QueueInnerState; - /// Aborts all spawned futures and initiates aborting of critical futures - /// after the specified [`AbortSettings::critical_timeout_s`]. - fn abort_all(&self) -> Result<(), AbortedError> { self.inner.lock().abort_all() } + fn __inner(&self) -> InnerShared { self.inner.clone() } fn __push_subsystem_abort_tx(&self, subsystem_abort_tx: oneshot::Sender<()>) -> Result<(), AbortedError> { self.inner.lock().insert_handle(subsystem_abort_tx).map(|_| ()) @@ -98,12 +96,15 @@ impl WeakSpawner { match select(abortable_fut.boxed(), wait_till_abort.boxed()).await { // The future has finished normally. - Either::Left(_) => { + Either::Left((_, wait_till_abort_fut)) => { if let Some(on_finish) = settings.on_finish { log::log!(on_finish.level, "{}", on_finish.msg); } if let Some(queue_inner) = inner_weak.upgrade() { + // Drop the `wait_till_abort_fut` so to render the corresponding `abort_tx` sender canceled. + // This way we can query the `abort_tx` sender to check if it's canceled, thus safe to mark as finished. + drop(wait_till_abort_fut); queue_inner.lock().on_future_finished(future_id); } }, @@ -203,8 +204,18 @@ impl QueueInnerState { /// Releases the `finished_future_id` so it can be reused later on [`QueueInnerState::insert_handle`]. fn on_future_finished(&mut self, finished_future_id: FutureId) { - if let QueueInnerState::Ready { finished_futures, .. } = self { - finished_futures.push(finished_future_id); + if let QueueInnerState::Ready { + finished_futures, + abort_handlers, + } = self + { + // Only mark this ID as finished if a future existed for it and is canceled. We can get false + // `on_future_finished` signals from futures that aren't in the `abort_handlers` anymore (abortable queue was reset). + if let Some(handle) = abort_handlers.get(finished_future_id) { + if handle.is_canceled() { + finished_futures.push(finished_future_id); + } + } } } @@ -234,6 +245,8 @@ impl SystemInner for QueueInnerState { *self = QueueInnerState::Aborted; Ok(()) } + + fn is_aborted(&self) -> bool { matches!(self, QueueInnerState::Aborted) } } #[cfg(test)] diff --git a/mm2src/common/executor/abortable_system/graceful_shutdown.rs b/mm2src/common/executor/abortable_system/graceful_shutdown.rs index 3feee076b2..6a902faab7 100644 --- a/mm2src/common/executor/abortable_system/graceful_shutdown.rs +++ b/mm2src/common/executor/abortable_system/graceful_shutdown.rs @@ -32,7 +32,7 @@ impl From> for GracefulShutdownRegistry { impl AbortableSystem for GracefulShutdownRegistry { type Inner = ShutdownInnerState; - fn abort_all(&self) -> Result<(), AbortedError> { self.inner.lock().abort_all() } + fn __inner(&self) -> InnerShared { self.inner.clone() } fn __push_subsystem_abort_tx(&self, subsystem_abort_tx: oneshot::Sender<()>) -> Result<(), AbortedError> { self.inner.lock().insert_handle(subsystem_abort_tx) @@ -73,4 +73,6 @@ impl SystemInner for ShutdownInnerState { *self = ShutdownInnerState::Aborted; Ok(()) } + + fn is_aborted(&self) -> bool { matches!(self, ShutdownInnerState::Aborted) } } diff --git a/mm2src/common/executor/abortable_system/mod.rs b/mm2src/common/executor/abortable_system/mod.rs index b5399ad6dd..82ef564278 100644 --- a/mm2src/common/executor/abortable_system/mod.rs +++ b/mm2src/common/executor/abortable_system/mod.rs @@ -24,7 +24,23 @@ pub trait AbortableSystem: From> { /// Aborts all spawned futures and subsystems if they present. /// The abortable system is considered not to be - fn abort_all(&self) -> Result<(), AbortedError>; + fn abort_all(&self) -> Result<(), AbortedError> { self.__inner().lock().abort_all() } + + /// Aborts all the spawned futures & subsystems if present, and resets the system + /// to the initial state for further use. + fn abort_all_and_reset(&self) -> Result<(), AbortedError> { + let inner = self.__inner(); + let mut inner_locked = inner.lock(); + // Don't allow resetting the system state if the system is already aborted. If the system is + // aborted this is because its parent was aborted as well. Resetting it will leave the system + // dangling with no parent to abort it (could still be aborted manually of course). + if inner_locked.is_aborted() { + return Err(AbortedError); + } + let mut previous_inner = std::mem::take(&mut *inner_locked); + previous_inner.abort_all().ok(); + Ok(()) + } /// Creates a new subsystem `S` linked to `Self` the way that /// if `Self` is aborted, the futures spawned by the subsystem will be aborted as well. @@ -56,12 +72,17 @@ pub trait AbortableSystem: From> { Ok(S::from(inner_shared)) } + fn __inner(&self) -> InnerShared; + fn __push_subsystem_abort_tx(&self, subsystem_abort_tx: oneshot::Sender<()>) -> Result<(), AbortedError>; } pub trait SystemInner: Default + Send + 'static { /// Aborts all spawned futures and subsystems if they present. fn abort_all(&mut self) -> Result<(), AbortedError>; + + /// Returns whether the system has already been aborted. + fn is_aborted(&self) -> bool; } #[cfg(test)] diff --git a/mm2src/common/executor/abortable_system/simple_map.rs b/mm2src/common/executor/abortable_system/simple_map.rs index c7cd9fc6cd..d759d53a04 100644 --- a/mm2src/common/executor/abortable_system/simple_map.rs +++ b/mm2src/common/executor/abortable_system/simple_map.rs @@ -35,7 +35,7 @@ impl AbortableSimpleMap { impl AbortableSystem for AbortableSimpleMap { type Inner = SimpleMapInnerState; - fn abort_all(&self) -> Result<(), AbortedError> { self.inner.lock().abort_all() } + fn __inner(&self) -> InnerShared { self.inner.clone() } fn __push_subsystem_abort_tx(&self, subsystem_abort_tx: oneshot::Sender<()>) -> Result<(), AbortedError> { self.inner.lock().insert_subsystem(subsystem_abort_tx) @@ -81,6 +81,8 @@ impl SystemInner for SimpleMapInnerState { *self = SimpleMapInnerState::Aborted; Ok(()) } + + fn is_aborted(&self) -> bool { matches!(self, SimpleMapInnerState::Aborted) } } impl SimpleMapInnerState { diff --git a/mm2src/common/expirable_map.rs b/mm2src/common/expirable_map.rs index 9b85dea84c..0b3110c066 100644 --- a/mm2src/common/expirable_map.rs +++ b/mm2src/common/expirable_map.rs @@ -5,7 +5,7 @@ use instant::{Duration, Instant}; use rustc_hash::FxHashMap; -use std::hash::Hash; +use std::{collections::BTreeMap, hash::Hash}; #[derive(Clone, Debug)] pub struct ExpirableEntry { @@ -14,53 +14,111 @@ pub struct ExpirableEntry { } impl ExpirableEntry { + #[inline(always)] + pub fn new(v: V, exp: Duration) -> Self { + Self { + expires_at: Instant::now() + exp, + value: v, + } + } + + #[inline(always)] pub fn get_element(&self) -> &V { &self.value } + #[inline(always)] + pub fn update_value(&mut self, v: V) { self.value = v } + + #[inline(always)] pub fn update_expiration(&mut self, expires_at: Instant) { self.expires_at = expires_at } + + /// Checks whether entry has longer ttl than the given one. + #[inline(always)] + pub fn has_longer_life_than(&self, min_ttl: Duration) -> bool { self.expires_at > Instant::now() + min_ttl } } -impl Default for ExpirableMap { +impl Default for ExpirableMap { fn default() -> Self { Self::new() } } /// A map that allows associating values with keys and expiring entries. -/// It is important to note that this implementation does not automatically -/// remove any entries; it is the caller's responsibility to invoke `clear_expired_entries` -/// at specified intervals. +/// It is important to note that this implementation does not have a background worker to +/// automatically clear expired entries. Outdated entries are only removed when the control flow +/// is handed back to the map mutably (i.e. some mutable method of the map is invoked). /// /// WARNING: This is designed for performance-oriented use-cases utilizing `FxHashMap` /// under the hood and is not suitable for cryptographic purposes. #[derive(Clone, Debug)] -pub struct ExpirableMap(FxHashMap>); +pub struct ExpirableMap { + map: FxHashMap>, + /// A sorted inverse map from expiration times to keys to speed up expired entries clearing. + expiries: BTreeMap, +} -impl ExpirableMap { +impl ExpirableMap { /// Creates a new empty `ExpirableMap` #[inline] - pub fn new() -> Self { Self(FxHashMap::default()) } + pub fn new() -> Self { + Self { + map: FxHashMap::default(), + expiries: BTreeMap::new(), + } + } - /// Returns the associated value if present. + /// Returns the associated value if present and not expired. #[inline] - pub fn get(&mut self, k: &K) -> Option<&V> { self.0.get(k).map(|v| &v.value) } + pub fn get(&self, k: &K) -> Option<&V> { + self.map + .get(k) + .filter(|v| v.expires_at > Instant::now()) + .map(|v| &v.value) + } + + /// Removes a key-value pair from the map and returns the associated value if present and not expired. + #[inline] + pub fn remove(&mut self, k: &K) -> Option { + self.map.remove(k).filter(|v| v.expires_at > Instant::now()).map(|v| { + self.expiries.remove(&v.expires_at); + v.value + }) + } /// Inserts a key-value pair with an expiration duration. /// /// If a value already exists for the given key, it will be updated and then /// the old one will be returned. pub fn insert(&mut self, k: K, v: V, exp: Duration) -> Option { - let entry = ExpirableEntry { - expires_at: Instant::now() + exp, - value: v, - }; + self.clear_expired_entries(); + let entry = ExpirableEntry::new(v, exp); + self.expiries.insert(entry.expires_at, k); + self.map.insert(k, entry).map(|v| v.value) + } - self.0.insert(k, entry).map(|v| v.value) + /// Clears the map. + pub fn clear(&mut self) { + self.map.clear(); + self.expiries.clear(); } /// Removes expired entries from the map. - pub fn clear_expired_entries(&mut self) { self.0.retain(|_k, v| Instant::now() < v.expires_at); } - - /// Removes a key-value pair from the map and returns the associated value if present. - #[inline] - pub fn remove(&mut self, k: &K) -> Option { self.0.remove(k).map(|v| v.value) } + /// + /// Iterates through the `expiries` in order, removing entries that have expired. + /// Stops at the first non-expired entry, leveraging the sorted nature of `BTreeMap`. + fn clear_expired_entries(&mut self) { + let now = Instant::now(); + + // `pop_first()` is used here as it efficiently removes expired entries. + // `first_key_value()` was considered as it wouldn't need re-insertion for + // non-expired entries, but it would require an extra remove operation for + // each expired entry. `pop_first()` needs only one re-insertion per call, + // which is an acceptable trade-off compared to multiple remove operations. + while let Some((exp, key)) = self.expiries.pop_first() { + if exp > now { + self.expiries.insert(exp, key); + break; + } + self.map.remove(&key); + } + } } #[cfg(any(test, target_arch = "wasm32"))] @@ -80,8 +138,8 @@ mod tests { let exp = Duration::from_secs(1); // Insert 2 entries with 1 sec expiration time - expirable_map.insert("key1".to_string(), value.to_string(), exp); - expirable_map.insert("key2".to_string(), value.to_string(), exp); + expirable_map.insert("key1", value, exp); + expirable_map.insert("key2", value, exp); // Wait for entries to expire Timer::sleep(2.).await; @@ -90,14 +148,14 @@ mod tests { expirable_map.clear_expired_entries(); // We waited for 2 seconds, so we shouldn't have any entry accessible - assert_eq!(expirable_map.0.len(), 0); + assert_eq!(expirable_map.map.len(), 0); // Insert 5 entries - expirable_map.insert("key1".to_string(), value.to_string(), Duration::from_secs(5)); - expirable_map.insert("key2".to_string(), value.to_string(), Duration::from_secs(4)); - expirable_map.insert("key3".to_string(), value.to_string(), Duration::from_secs(7)); - expirable_map.insert("key4".to_string(), value.to_string(), Duration::from_secs(2)); - expirable_map.insert("key5".to_string(), value.to_string(), Duration::from_millis(3750)); + expirable_map.insert("key1", value, Duration::from_secs(5)); + expirable_map.insert("key2", value, Duration::from_secs(4)); + expirable_map.insert("key3", value, Duration::from_secs(7)); + expirable_map.insert("key4", value, Duration::from_secs(2)); + expirable_map.insert("key5", value, Duration::from_millis(3750)); // Wait 2 seconds to expire some entries Timer::sleep(2.).await; @@ -106,6 +164,6 @@ mod tests { expirable_map.clear_expired_entries(); // We waited for 2 seconds, only one entry should expire - assert_eq!(expirable_map.0.len(), 4); + assert_eq!(expirable_map.map.len(), 4); }); } diff --git a/mm2src/common/jsonrpc_client.rs b/mm2src/common/jsonrpc_client.rs index 94a1ca809b..3f9e4cf6f6 100644 --- a/mm2src/common/jsonrpc_client.rs +++ b/mm2src/common/jsonrpc_client.rs @@ -2,7 +2,7 @@ use futures01::Future; use itertools::Itertools; use serde::de::DeserializeOwned; use serde_json::{self as json, Value as Json}; -use std::collections::{BTreeSet, HashMap}; +use std::collections::HashMap; use std::fmt; /// Macro generating functions for RPC requests. @@ -69,10 +69,10 @@ impl From for JsonRpcRemoteAddr { /// The identifier is designed to uniquely match outgoing requests and incoming responses. /// Even if the batch response is sorted in a different order, `BTreeSet` allows it to be matched to the request. -#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] +#[derive(Copy, Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] pub enum JsonRpcId { - Single(String), - Batch(BTreeSet), + Single(u64), + Batch(u64), } /// Serializable RPC request that is either single or batch. @@ -114,19 +114,15 @@ impl fmt::Debug for JsonRpcRequestEnum { pub struct JsonRpcRequest { pub jsonrpc: String, #[serde(default)] - pub id: String, + pub id: u64, pub method: String, pub params: Vec, } impl JsonRpcRequest { - // Returns [`JsonRpcRequest::id`]. - #[inline] - pub fn get_id(&self) -> &str { &self.id } - /// Returns a `JsonRpcId` identifier of the request. #[inline] - pub fn rpc_id(&self) -> JsonRpcId { JsonRpcId::Single(self.id.clone()) } + pub fn rpc_id(&self) -> JsonRpcId { JsonRpcId::Single(self.id) } } impl From for JsonRpcRequestEnum { @@ -140,7 +136,12 @@ pub struct JsonRpcBatchRequest(Vec); impl JsonRpcBatchRequest { /// Returns a `JsonRpcId` identifier of the request. #[inline] - pub fn rpc_id(&self) -> JsonRpcId { JsonRpcId::Batch(self.orig_sequence_ids().collect()) } + pub fn rpc_id(&self) -> JsonRpcId { + // This shouldn't be called on an empty batch, but let's + // simply set the batch ID to maximum if the batch is empty. + let batch_id = self.0.iter().map(|res| res.id).max().unwrap_or(u64::MAX); + JsonRpcId::Batch(batch_id) + } /// Returns the number of the requests in the batch. #[inline] @@ -153,7 +154,7 @@ impl JsonRpcBatchRequest { /// Returns original sequence of identifiers. /// The method is used to process batch responses in the same order in which the requests were sent. #[inline] - fn orig_sequence_ids(&self) -> impl Iterator + '_ { self.0.iter().map(|req| req.id.clone()) } + fn orig_sequence_ids(&self) -> impl Iterator + '_ { self.0.iter().map(|req| req.id) } } impl From for JsonRpcRequestEnum { @@ -185,7 +186,7 @@ pub struct JsonRpcResponse { #[serde(default)] pub jsonrpc: String, #[serde(default)] - pub id: String, + pub id: u64, #[serde(default)] pub result: Json, #[serde(default)] @@ -195,7 +196,7 @@ pub struct JsonRpcResponse { impl JsonRpcResponse { /// Returns a `JsonRpcId` identifier of the response. #[inline] - pub fn rpc_id(&self) -> JsonRpcId { JsonRpcId::Single(self.id.clone()) } + pub fn rpc_id(&self) -> JsonRpcId { JsonRpcId::Single(self.id) } } /// Deserializable RPC batch response. @@ -204,7 +205,12 @@ pub struct JsonRpcBatchResponse(Vec); impl JsonRpcBatchResponse { /// Returns a `JsonRpcId` identifier of the response. - pub fn rpc_id(&self) -> JsonRpcId { JsonRpcId::Batch(self.0.iter().map(|res| res.id.clone()).collect()) } + pub fn rpc_id(&self) -> JsonRpcId { + // This shouldn't be called on an empty batch, but let's + // simply set the batch ID to maximum if the batch is empty. + let batch_id = self.0.iter().map(|res| res.id).max().unwrap_or(u64::MAX); + JsonRpcId::Batch(batch_id) + } /// Returns the number of the requests in the batch. #[inline] @@ -272,8 +278,8 @@ pub trait JsonRpcClient { /// Returns a stringified version of the JSON-RPC protocol. fn version(&self) -> &'static str; - /// Returns a stringified identifier of the next request. - fn next_id(&self) -> String; + /// Returns a unique identifier for the next request. + fn next_id(&self) -> u64; /// Get info that is used in particular to supplement the error info fn client_info(&self) -> String; @@ -395,8 +401,7 @@ fn process_transport_batch_result( }; // Turn the vector of responses into a hashmap by their IDs to get quick access to the content of the responses. - let mut response_map: HashMap = - batch.into_iter().map(|res| (res.id.clone(), res)).collect(); + let mut response_map: HashMap<_, _> = batch.into_iter().map(|res| (res.id, res)).collect(); if response_map.len() != orig_ids.len() { return Err(JsonRpcErrorType::Parse( remote_addr, diff --git a/mm2src/common/notifier.rs b/mm2src/common/notifier.rs new file mode 100644 index 0000000000..a82253b537 --- /dev/null +++ b/mm2src/common/notifier.rs @@ -0,0 +1,53 @@ +//! A simple notification system based on mpsc channels. +//! +//! Since this is based on mpsc, multiple notifiers (senders) are allowed while only a single +//! notifiee (receiver) listens for notifications. +//! +//! NOTE: This implementation memory leaks (in contrast to tokio's, but not used here to avoid tokio dependency on wasm). +//! This is because with each `clone()` of the sender we have a new slot in the channel (this is how `futures-rs` does mpsc). +//! These are removed when the receiver calls `wait()`, which calls `clear()`. But if the receiver never `wait()`s for any reason, +//! and there is a thread that doesn't stop `notify()`ing, the channel will keep growing unbounded. +//! +//! So one must make sure that either `wait()` is called after some time or the receiver is dropped when it's no longer needed. +use futures::{channel::mpsc, StreamExt}; + +#[derive(Clone, Debug)] +pub struct Notifier(mpsc::Sender<()>); + +#[derive(Debug)] +pub struct Notifiee(mpsc::Receiver<()>); + +impl Notifier { + /// Create a new notifier and notifiee pair. + pub fn new() -> (Notifier, Notifiee) { + let (sender, receiver) = mpsc::channel(0); + (Notifier(sender), Notifiee(receiver)) + } + + /// Notify the receiver. + /// + /// This will error if the receiver has been dropped (disconnected). + pub fn notify(&self) -> Result<(), &'static str> { + if let Err(e) = self.0.clone().try_send(()) { + if e.is_disconnected() { + return Err("Notification receiver has been dropped."); + } + } + Ok(()) + } +} + +impl Notifiee { + /// Wait for a notification from any notifier. + /// + /// This will error if all notifiers have been dropped (disconnected). + pub async fn wait(&mut self) -> Result<(), &'static str> { + let result = self.0.next().await.ok_or("All notifiers have been dropped."); + // Clear pending notifications if there are any, since we have already been notified. + self.clear(); + result + } + + /// Clears the pending notifications if there are any. + fn clear(&mut self) { while let Ok(Some(_)) = self.0.try_next() {} } +} diff --git a/mm2src/floodsub/CHANGELOG.md b/mm2src/floodsub/CHANGELOG.md deleted file mode 100644 index b361796860..0000000000 --- a/mm2src/floodsub/CHANGELOG.md +++ /dev/null @@ -1,15 +0,0 @@ -# 0.22.0 [2020-09-09] - -- Update `libp2p-swarm` and `libp2p-core`. - -# 0.21.0 [2020-08-18] - -- Bump `libp2p-core` and `libp2p-swarm` dependency. - -# 0.20.0 [2020-07-01] - -- Updated dependencies. - -# 0.19.1 [2020-06-22] - -- Updated dependencies. diff --git a/mm2src/floodsub/Cargo.toml b/mm2src/floodsub/Cargo.toml deleted file mode 100644 index b54b60448c..0000000000 --- a/mm2src/floodsub/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "libp2p-floodsub" -edition = "2018" -description = "Floodsub protocol for libp2p" -version = "0.22.0" -authors = ["Parity Technologies "] -license = "MIT" -repository = "https://github.com/libp2p/rust-libp2p" -keywords = ["peer-to-peer", "libp2p", "networking"] -categories = ["network-programming", "asynchronous"] - -[lib] -doctest = false - -[dependencies] -cuckoofilter = "0.3.2" -futures = "0.3.1" -libp2p-core = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45.1" } -libp2p-swarm = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45.1" } -prost = "0.10" -rand = "0.7" -smallvec = "1.0" - -[build-dependencies] -prost-build = { version = "0.10.4", default-features = false } diff --git a/mm2src/floodsub/build.rs b/mm2src/floodsub/build.rs deleted file mode 100644 index 9671983699..0000000000 --- a/mm2src/floodsub/build.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -fn main() { prost_build::compile_protos(&["src/rpc.proto"], &["src"]).unwrap(); } diff --git a/mm2src/floodsub/src/layer.rs b/mm2src/floodsub/src/layer.rs deleted file mode 100644 index 7151948a95..0000000000 --- a/mm2src/floodsub/src/layer.rs +++ /dev/null @@ -1,401 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::protocol::{FloodsubMessage, FloodsubProtocol, FloodsubRpc, FloodsubSubscription, FloodsubSubscriptionAction}; -use crate::topic::Topic; -use crate::FloodsubConfig; -use cuckoofilter::CuckooFilter; -use libp2p_core::{connection::ConnectionId, ConnectedPoint, Multiaddr, PeerId}; -use libp2p_swarm::{IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, OneShotHandler, - PollParameters}; -use smallvec::SmallVec; -use std::collections::hash_map::{DefaultHasher, HashMap}; -use std::task::{Context, Poll}; -use std::{collections::VecDeque, iter}; - -/// Network behaviour that handles the floodsub protocol. -pub struct Floodsub { - /// Events that need to be yielded to the outside when polling. - events: - VecDeque>>, - - config: FloodsubConfig, - - /// List of peers the network is connected to, and the topics that they're subscribed to. - // TODO: filter out peers that don't support floodsub, so that we avoid hammering them with - // opened substreams - connected_peers: HashMap>, - - // List of topics we're subscribed to. Necessary to filter out messages that we receive - // erroneously. - subscribed_topics: SmallVec<[Topic; 16]>, - - // We keep track of the messages we received (in the format `hash(source ID, seq_no)`) so that - // we don't dispatch the same message twice if we receive it twice on the network. - received: CuckooFilter, -} - -impl Floodsub { - /// Creates a `Floodsub` with default configuration. - pub fn new(local_peer_id: PeerId, forward_messages: bool) -> Self { - Self::from_config(FloodsubConfig::new(local_peer_id, forward_messages)) - } - - /// Creates a `Floodsub` with the given configuration. - pub fn from_config(config: FloodsubConfig) -> Self { - Floodsub { - events: VecDeque::new(), - config, - connected_peers: HashMap::new(), - subscribed_topics: SmallVec::new(), - received: CuckooFilter::new(), - } - } - - /// Subscribes to a topic. - /// - /// Returns true if the subscription worked. Returns false if we were already subscribed. - pub fn subscribe(&mut self, topic: Topic) -> bool { - if self.subscribed_topics.iter().any(|t| t.id() == topic.id()) { - return false; - } - - for peer in self.connected_peers.keys() { - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: *peer, - handler: NotifyHandler::Any, - event: FloodsubRpc { - messages: Vec::new(), - subscriptions: vec![FloodsubSubscription { - topic: topic.clone(), - action: FloodsubSubscriptionAction::Subscribe, - }], - }, - }); - } - - self.subscribed_topics.push(topic); - true - } - - /// Unsubscribes from a topic. - /// - /// Note that this only requires the topic name. - /// - /// Returns true if we were subscribed to this topic. - pub fn unsubscribe(&mut self, topic: Topic) -> bool { - let pos = match self.subscribed_topics.iter().position(|t| *t == topic) { - Some(pos) => pos, - None => return false, - }; - - self.subscribed_topics.remove(pos); - - for peer in self.connected_peers.keys() { - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: *peer, - handler: NotifyHandler::Any, - event: FloodsubRpc { - messages: Vec::new(), - subscriptions: vec![FloodsubSubscription { - topic: topic.clone(), - action: FloodsubSubscriptionAction::Unsubscribe, - }], - }, - }); - } - - true - } - - /// Publishes a message to the network, if we're subscribed to the topic only. - pub fn publish(&mut self, topic: impl Into, data: impl Into>) { - self.publish_many(iter::once(topic), data) - } - - /// Publishes a message to the network, even if we're not subscribed to the topic. - pub fn publish_any(&mut self, topic: impl Into, data: impl Into>) { - self.publish_many_any(iter::once(topic), data) - } - - /// Publishes a message with multiple topics to the network. - /// - /// - /// > **Note**: Doesn't do anything if we're not subscribed to any of the topics. - pub fn publish_many(&mut self, topic: impl IntoIterator>, data: impl Into>) { - self.publish_many_inner(topic, data, true) - } - - /// Publishes a message with multiple topics to the network, even if we're not subscribed to any of the topics. - pub fn publish_many_any(&mut self, topic: impl IntoIterator>, data: impl Into>) { - self.publish_many_inner(topic, data, false) - } - - fn publish_many_inner( - &mut self, - topic: impl IntoIterator>, - data: impl Into>, - check_self_subscriptions: bool, - ) { - let message = FloodsubMessage { - source: self.config.local_peer_id, - data: data.into(), - // If the sequence numbers are predictable, then an attacker could flood the network - // with packets with the predetermined sequence numbers and absorb our legitimate - // messages. We therefore use a random number. - sequence_number: rand::random::<[u8; 20]>().to_vec(), - topics: topic.into_iter().map(Into::into).collect(), - }; - - let self_subscribed = self - .subscribed_topics - .iter() - .any(|t| message.topics.iter().any(|u| t == u)); - if self_subscribed { - self.received.add(&message); - if self.config.subscribe_local_messages { - self.events - .push_back(NetworkBehaviourAction::GenerateEvent(FloodsubEvent::Message( - message.clone(), - ))); - } - } - // Don't publish the message if we have to check subscriptions - // and we're not subscribed ourselves to any of the topics. - if check_self_subscriptions && !self_subscribed { - return; - } - - // Send to peers we know are subscribed to the topic. - for (peer_id, sub_topic) in self.connected_peers.iter() { - if !sub_topic.iter().any(|t| message.topics.iter().any(|u| t == u)) { - continue; - } - - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: *peer_id, - handler: NotifyHandler::Any, - event: FloodsubRpc { - subscriptions: Vec::new(), - messages: vec![message.clone()], - }, - }); - } - } -} - -impl NetworkBehaviour for Floodsub { - type ConnectionHandler = OneShotHandler; - type OutEvent = FloodsubEvent; - - fn new_handler(&mut self) -> Self::ConnectionHandler { Default::default() } - - fn addresses_of_peer(&mut self, _: &PeerId) -> Vec { Vec::new() } - - fn inject_connection_established( - &mut self, - id: &PeerId, - _: &ConnectionId, - _: &ConnectedPoint, - _: Option<&Vec>, - other_established: usize, - ) { - if other_established > 0 { - // We only care about the first time a peer connects. - return; - } - - // We need to send our subscriptions to the newly-connected node. - for topic in self.subscribed_topics.iter().cloned() { - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: *id, - handler: NotifyHandler::Any, - event: FloodsubRpc { - messages: Vec::new(), - subscriptions: vec![FloodsubSubscription { - topic, - action: FloodsubSubscriptionAction::Subscribe, - }], - }, - }); - } - - self.connected_peers.insert(*id, SmallVec::new()); - } - - fn inject_connection_closed( - &mut self, - id: &PeerId, - _: &ConnectionId, - _: &ConnectedPoint, - _: ::Handler, - remaining_established: usize, - ) { - if remaining_established > 0 { - // we only care about peer disconnections - return; - } - - let was_in = self.connected_peers.remove(id); - debug_assert!(was_in.is_some()); - } - - fn inject_event(&mut self, propagation_source: PeerId, _connection: ConnectionId, event: InnerMessage) { - // We ignore successful sends or timeouts. - let event = match event { - InnerMessage::Rx(event) => event, - InnerMessage::Sent => return, - }; - - // Update connected peers topics - for subscription in event.subscriptions { - let remote_peer_topics = self.connected_peers - .get_mut(&propagation_source) - .expect("connected_peers is kept in sync with the peers we are connected to; we are guaranteed to only receive events from connected peers; QED"); - match subscription.action { - FloodsubSubscriptionAction::Subscribe => { - if !remote_peer_topics.contains(&subscription.topic) { - remote_peer_topics.push(subscription.topic.clone()); - } - self.events - .push_back(NetworkBehaviourAction::GenerateEvent(FloodsubEvent::Subscribed { - peer_id: propagation_source, - topic: subscription.topic, - })); - }, - FloodsubSubscriptionAction::Unsubscribe => { - if let Some(pos) = remote_peer_topics.iter().position(|t| t == &subscription.topic) { - remote_peer_topics.remove(pos); - } - self.events - .push_back(NetworkBehaviourAction::GenerateEvent(FloodsubEvent::Unsubscribed { - peer_id: propagation_source, - topic: subscription.topic, - })); - }, - } - } - - // List of messages we're going to propagate on the network. - let mut rpcs_to_dispatch: Vec<(PeerId, FloodsubRpc)> = Vec::new(); - - for message in event.messages { - // Use `self.received` to skip the messages that we have already received in the past. - // Note that this can false positive. - if !self.received.test_and_add(&message) { - continue; - } - - // Add the message to be dispatched to the user. - if self - .subscribed_topics - .iter() - .any(|t| message.topics.iter().any(|u| t == u)) - { - let event = FloodsubEvent::Message(message.clone()); - self.events.push_back(NetworkBehaviourAction::GenerateEvent(event)); - } - - if self.config.forward_messages { - // Propagate the message to everyone else who is subscribed to any of the topics. - for (peer_id, subscr_topics) in self.connected_peers.iter() { - if peer_id == &propagation_source { - continue; - } - - if !subscr_topics.iter().any(|t| message.topics.iter().any(|u| t == u)) { - continue; - } - - if let Some(pos) = rpcs_to_dispatch.iter().position(|(p, _)| p == peer_id) { - rpcs_to_dispatch[pos].1.messages.push(message.clone()); - } else { - rpcs_to_dispatch.push((*peer_id, FloodsubRpc { - subscriptions: Vec::new(), - messages: vec![message.clone()], - })); - } - } - } - } - - for (peer_id, rpc) in rpcs_to_dispatch { - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id, - handler: NotifyHandler::Any, - event: rpc, - }); - } - } - - fn poll( - &mut self, - _: &mut Context<'_>, - _: &mut impl PollParameters, - ) -> Poll> { - if let Some(event) = self.events.pop_front() { - return Poll::Ready(event); - } - - Poll::Pending - } -} - -/// Transmission between the `OneShotHandler` and the `FloodsubHandler`. -#[derive(Debug)] -pub enum InnerMessage { - /// We received an RPC from a remote. - Rx(FloodsubRpc), - /// We successfully sent an RPC request. - Sent, -} - -impl From for InnerMessage { - #[inline] - fn from(rpc: FloodsubRpc) -> InnerMessage { InnerMessage::Rx(rpc) } -} - -impl From<()> for InnerMessage { - #[inline] - fn from(_: ()) -> InnerMessage { InnerMessage::Sent } -} - -/// Event that can happen on the floodsub behaviour. -#[derive(Debug)] -pub enum FloodsubEvent { - /// A message has been received. - Message(FloodsubMessage), - - /// A remote subscribed to a topic. - Subscribed { - /// Remote that has subscribed. - peer_id: PeerId, - /// The topic it has subscribed to. - topic: Topic, - }, - - /// A remote unsubscribed from a topic. - Unsubscribed { - /// Remote that has unsubscribed. - peer_id: PeerId, - /// The topic it has subscribed from. - topic: Topic, - }, -} diff --git a/mm2src/floodsub/src/lib.rs b/mm2src/floodsub/src/lib.rs deleted file mode 100644 index 90592a9fe5..0000000000 --- a/mm2src/floodsub/src/lib.rs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Implements the floodsub protocol, see also the: -//! [spec](https://github.com/libp2p/specs/tree/master/pubsub). -//! The implementation is customized for AtomicDEX purposes, all peers are considered as "target_peers" -//! So "target_peers" are removed from Floodsub behaviour - -use libp2p_core::PeerId; - -pub mod protocol; - -mod layer; -mod topic; - -mod rpc_proto { - include!(concat!(env!("OUT_DIR"), "/floodsub.pb.rs")); -} - -pub use self::layer::{Floodsub, FloodsubEvent}; -pub use self::protocol::{FloodsubMessage, FloodsubRpc}; -pub use self::topic::Topic; - -/// Configuration options for the Floodsub protocol. -pub struct FloodsubConfig { - /// Peer id of the local node. Used for the source of the messages that we publish. - pub local_peer_id: PeerId, - - /// `true` if messages published by local node should be propagated as messages received from - /// the network, `false` by default. - pub subscribe_local_messages: bool, - - pub forward_messages: bool, -} - -impl FloodsubConfig { - pub fn new(local_peer_id: PeerId, forward_messages: bool) -> Self { - Self { - local_peer_id, - subscribe_local_messages: false, - forward_messages, - } - } -} diff --git a/mm2src/floodsub/src/protocol.rs b/mm2src/floodsub/src/protocol.rs deleted file mode 100644 index bf24053ffb..0000000000 --- a/mm2src/floodsub/src/protocol.rs +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::rpc_proto; -use crate::topic::Topic; -use futures::{io::{AsyncRead, AsyncWrite}, - Future}; -use libp2p_core::{upgrade, InboundUpgrade, OutboundUpgrade, PeerId, UpgradeInfo}; -use prost::Message; -use std::{error, fmt, io, iter, pin::Pin}; - -/// Implementation of `ConnectionUpgrade` for the floodsub protocol. -#[derive(Debug, Clone, Default)] -pub struct FloodsubProtocol {} - -impl FloodsubProtocol { - /// Builds a new `FloodsubProtocol`. - pub fn new() -> FloodsubProtocol { FloodsubProtocol {} } -} - -impl UpgradeInfo for FloodsubProtocol { - type Info = &'static [u8]; - type InfoIter = iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { iter::once(b"/floodsub/1.0.0") } -} - -type PinBoxTryFut = Pin> + Send>>; - -impl InboundUpgrade for FloodsubProtocol -where - TSocket: AsyncRead + AsyncWrite + Send + Unpin + 'static, -{ - type Output = FloodsubRpc; - type Error = FloodsubDecodeError; - type Future = PinBoxTryFut; - - fn upgrade_inbound(self, mut socket: TSocket, _: Self::Info) -> Self::Future { - Box::pin(async move { - let packet = upgrade::read_length_prefixed(&mut socket, 2048).await?; - let rpc = rpc_proto::Rpc::decode(&packet[..])?; - - let mut messages = Vec::with_capacity(rpc.publish.len()); - for publish in rpc.publish.into_iter() { - messages.push(FloodsubMessage { - source: PeerId::from_bytes(&publish.from.unwrap_or_default()) - .map_err(|_| FloodsubDecodeError::InvalidPeerId)?, - data: publish.data.unwrap_or_default(), - sequence_number: publish.seqno.unwrap_or_default(), - topics: publish.topic_ids.into_iter().map(Topic::new).collect(), - }); - } - - Ok(FloodsubRpc { - messages, - subscriptions: rpc - .subscriptions - .into_iter() - .map(|sub| FloodsubSubscription { - action: if Some(true) == sub.subscribe { - FloodsubSubscriptionAction::Subscribe - } else { - FloodsubSubscriptionAction::Unsubscribe - }, - topic: Topic::new(sub.topic_id.unwrap_or_default()), - }) - .collect(), - }) - }) - } -} - -/// Reach attempt interrupt errors. -#[derive(Debug)] -pub enum FloodsubDecodeError { - /// Error when reading the packet from the socket. - ReadError(io::Error), - /// Error when decoding the raw buffer into a protobuf. - ProtobufError(prost::DecodeError), - /// Error when parsing the `PeerId` in the message. - InvalidPeerId, -} - -impl From for FloodsubDecodeError { - fn from(err: io::Error) -> Self { FloodsubDecodeError::ReadError(err) } -} - -impl From for FloodsubDecodeError { - fn from(err: prost::DecodeError) -> Self { FloodsubDecodeError::ProtobufError(err) } -} - -impl fmt::Display for FloodsubDecodeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - FloodsubDecodeError::ReadError(ref err) => write!(f, "Error while reading from socket: {}", err), - FloodsubDecodeError::ProtobufError(ref err) => write!(f, "Error while decoding protobuf: {}", err), - FloodsubDecodeError::InvalidPeerId => write!(f, "Error while decoding PeerId from message"), - } - } -} - -impl error::Error for FloodsubDecodeError { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - match *self { - FloodsubDecodeError::ReadError(ref err) => Some(err), - FloodsubDecodeError::ProtobufError(ref err) => Some(err), - FloodsubDecodeError::InvalidPeerId => None, - } - } -} - -/// An RPC received by the floodsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FloodsubRpc { - /// List of messages that were part of this RPC query. - pub messages: Vec, - /// List of subscriptions. - pub subscriptions: Vec, -} - -impl UpgradeInfo for FloodsubRpc { - type Info = &'static [u8]; - type InfoIter = iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { iter::once(b"/floodsub/1.0.0") } -} - -impl OutboundUpgrade for FloodsubRpc -where - TSocket: AsyncWrite + AsyncRead + Send + Unpin + 'static, -{ - type Output = (); - type Error = io::Error; - type Future = PinBoxTryFut; - - fn upgrade_outbound(self, mut socket: TSocket, _: Self::Info) -> Self::Future { - Box::pin(async move { - let bytes = self.into_bytes(); - upgrade::write_length_prefixed(&mut socket, bytes).await?; - Ok(()) - }) - } -} - -impl FloodsubRpc { - /// Turns this `FloodsubRpc` into a message that can be sent to a substream. - fn into_bytes(self) -> Vec { - let rpc = rpc_proto::Rpc { - publish: self - .messages - .into_iter() - .map(|msg| rpc_proto::Message { - from: Some(msg.source.to_bytes()), - data: Some(msg.data), - seqno: Some(msg.sequence_number), - topic_ids: msg.topics.into_iter().map(|topic| topic.into()).collect(), - }) - .collect(), - - subscriptions: self - .subscriptions - .into_iter() - .map(|topic| rpc_proto::rpc::SubOpts { - subscribe: Some(topic.action == FloodsubSubscriptionAction::Subscribe), - topic_id: Some(topic.topic.into()), - }) - .collect(), - }; - - let mut buf = Vec::with_capacity(rpc.encoded_len()); - rpc.encode(&mut buf).expect("Vec provides capacity as needed"); - buf - } -} - -/// A message received by the floodsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FloodsubMessage { - /// Id of the peer that published this message. - pub source: PeerId, - - /// Content of the message. Its meaning is out of scope of this library. - pub data: Vec, - - /// An incrementing sequence number. - pub sequence_number: Vec, - - /// List of topics this message belongs to. - /// - /// Each message can belong to multiple topics at once. - pub topics: Vec, -} - -/// A subscription received by the floodsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FloodsubSubscription { - /// Action to perform. - pub action: FloodsubSubscriptionAction, - /// The topic from which to subscribe or unsubscribe. - pub topic: Topic, -} - -/// Action that a subscription wants to perform. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum FloodsubSubscriptionAction { - /// The remote wants to subscribe to the given topic. - Subscribe, - /// The remote wants to unsubscribe from the given topic. - Unsubscribe, -} diff --git a/mm2src/floodsub/src/rpc.proto b/mm2src/floodsub/src/rpc.proto deleted file mode 100644 index 84f0ea5179..0000000000 --- a/mm2src/floodsub/src/rpc.proto +++ /dev/null @@ -1,20 +0,0 @@ -syntax = "proto2"; - -package floodsub.pb; - -message RPC { - repeated SubOpts subscriptions = 1; - repeated Message publish = 2; - - message SubOpts { - optional bool subscribe = 1; // subscribe or unsubcribe - optional string topic_id = 2; - } -} - -message Message { - optional bytes from = 1; - optional bytes data = 2; - optional bytes seqno = 3; - repeated string topic_ids = 4; -} diff --git a/mm2src/floodsub/src/topic.rs b/mm2src/floodsub/src/topic.rs deleted file mode 100644 index 41d2b253b8..0000000000 --- a/mm2src/floodsub/src/topic.rs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2018 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -/// Built topic. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Topic(String); - -impl Topic { - /// Returns the id of the topic. - #[inline] - pub fn id(&self) -> &str { &self.0 } - - pub fn new(name: S) -> Topic - where - S: Into, - { - Topic(name.into()) - } -} - -impl From for String { - fn from(topic: Topic) -> String { topic.0 } -} diff --git a/mm2src/gossipsub/CHANGELOG.md b/mm2src/gossipsub/CHANGELOG.md deleted file mode 100644 index d2a5c025a7..0000000000 --- a/mm2src/gossipsub/CHANGELOG.md +++ /dev/null @@ -1,11 +0,0 @@ -# 0.20.0 [2020-07-01] - -- Updated dependencies. - -# 0.19.3 [2020-06-23] - -- Maintenance release fixing linter warnings. - -# 0.19.2 [2020-06-22] - -- Updated dependencies. diff --git a/mm2src/gossipsub/Cargo.toml b/mm2src/gossipsub/Cargo.toml deleted file mode 100644 index b34f1911a3..0000000000 --- a/mm2src/gossipsub/Cargo.toml +++ /dev/null @@ -1,41 +0,0 @@ -[package] -name = "atomicdex-gossipsub" -edition = "2018" -description = "Gossipsub protocol for AtomicDEX, based on libp2p gossipsub" -version = "0.20.0" -authors = ["Age Manning "] -license = "MIT" -repository = "https://github.com/libp2p/rust-libp2p" -keywords = ["peer-to-peer", "libp2p", "networking"] -categories = ["network-programming", "asynchronous"] - -[lib] -doctest = false - -[dependencies] -base64 = "0.21.2" -bytes = "0.5.4" -byteorder = "1.3.2" -common = { path = "../common" } -fnv = "1.0.6" -futures = "0.3.1" -futures_codec = "0.4.0" -libp2p-swarm = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45.1" } -libp2p-core = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45.1" } -log = "0.4.17" -prost = "0.10" -rand = "0.7" -sha2 = "0.10" -smallvec = "1.1.0" -unsigned-varint = { version = "0.4.0", features = ["futures-codec"] } -wasm-timer = "0.2.4" - -[dev-dependencies] -async-std = "1.6.2" -env_logger = "0.9.3" -libp2p-plaintext = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45.1" } -libp2p-yamux = { git = "https://github.com/libp2p/rust-libp2p.git", tag ="v0.45.1" } -quickcheck= { version = "0.9.2", default-features = false } - -[build-dependencies] -prost-build = { version = "0.10.4", default-features = false } diff --git a/mm2src/gossipsub/build.rs b/mm2src/gossipsub/build.rs deleted file mode 100644 index 9671983699..0000000000 --- a/mm2src/gossipsub/build.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -fn main() { prost_build::compile_protos(&["src/rpc.proto"], &["src"]).unwrap(); } diff --git a/mm2src/gossipsub/src/behaviour.rs b/mm2src/gossipsub/src/behaviour.rs deleted file mode 100644 index 19eb7a2627..0000000000 --- a/mm2src/gossipsub/src/behaviour.rs +++ /dev/null @@ -1,1526 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::config::GossipsubConfig; -use crate::handler::GossipsubHandler; -use crate::mcache::MessageCache; -use crate::protocol::{GossipsubControlAction, GossipsubMessage, GossipsubSubscription, GossipsubSubscriptionAction, - MessageId}; -use crate::topic::{Topic, TopicHash}; -use common::time_cache::{Entry as TimeCacheEntry, TimeCache}; -use futures::prelude::*; -use libp2p_core::{connection::ConnectionId, ConnectedPoint, Multiaddr, PeerId}; -use libp2p_swarm::{IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters}; -use log::{debug, error, info, trace, warn}; -use rand::seq::SliceRandom; -use smallvec::SmallVec; -use std::collections::hash_map::Entry; -use std::collections::{HashMap, HashSet, VecDeque}; -use std::iter; -use std::sync::Arc; -use std::task::{Context, Poll}; -use std::time::Duration; -use wasm_timer::{Instant, Interval}; - -mod tests; - -/// Network behaviour that handles the gossipsub protocol. -pub struct Gossipsub { - /// Configuration providing gossipsub performance parameters. - config: GossipsubConfig, - - /// Events that need to be yielded to the outside when polling. - events: VecDeque>>, - - /// Pools non-urgent control messages between heartbeats. - control_pool: HashMap>, - - /// Peer id of the local node. Used for the source of the messages that we publish. - local_peer_id: PeerId, - - /// A map of all connected peers - A map of topic hash to a list of gossipsub peer Ids. - topic_peers: HashMap>, - - /// A map of all connected peers to their subscribed topics. - peer_topics: HashMap>, - - /// The peer ids of connected relay nodes - connected_relays: HashSet, - - /// relays to which we forward the messages. Also tracks the relay mesh size of nodes in mesh. - relays_mesh: HashMap, - - /// Peers included our node to their relays mesh - included_to_relays_mesh: HashSet, - - /// Overlay network of connected peers - Maps topics to connected gossipsub peers. - mesh: HashMap>, - - /// Map of topics to list of peers that we publish to, but don't subscribe to. - fanout: HashMap>, - - /// The last publish time for fanout topics. - fanout_last_pub: HashMap, - - /// Message cache for the last few heartbeats. - mcache: MessageCache, - - /// We keep track of the messages we received (in the format `string(source ID, seq_no)`) so that - /// we don't dispatch the same message twice if we receive it twice on the network. - /// Also store the peers from which message was received so we don't manually propagate already known message to them - received: TimeCache>, - - /// Heartbeat interval stream. - heartbeat: Interval, - - /// Relay mesh maintenance interval stream. - relay_mesh_maintenance_interval: Interval, - - peer_connections: HashMap>, - - connected_addresses: Vec, - - /// The relay list which are forcefully kept in relay mesh - explicit_relay_list: Vec, -} - -impl Gossipsub { - /// Creates a `Gossipsub` struct given a set of parameters specified by `gs_config`. - pub fn new(local_peer_id: PeerId, gs_config: GossipsubConfig) -> Self { - let local_peer_id = if gs_config.no_source_id { - PeerId::from_bytes(&crate::config::IDENTITY_SOURCE).expect("Valid peer id") - } else { - local_peer_id - }; - - Gossipsub { - config: gs_config.clone(), - events: VecDeque::new(), - control_pool: HashMap::new(), - local_peer_id, - topic_peers: HashMap::new(), - peer_topics: HashMap::new(), - mesh: HashMap::new(), - fanout: HashMap::new(), - fanout_last_pub: HashMap::new(), - mcache: MessageCache::new( - gs_config.history_gossip, - gs_config.history_length, - gs_config.message_id_fn, - ), - received: TimeCache::new(gs_config.duplicate_cache_time), - heartbeat: Interval::new_at( - Instant::now() + gs_config.heartbeat_initial_delay, - gs_config.heartbeat_interval, - ), - relay_mesh_maintenance_interval: Interval::new_at( - Instant::now() + Duration::from_secs(10), - Duration::from_secs(10), - ), - peer_connections: HashMap::new(), - connected_relays: HashSet::new(), - relays_mesh: HashMap::new(), - included_to_relays_mesh: HashSet::new(), - connected_addresses: Vec::new(), - explicit_relay_list: Vec::new(), - } - } - - pub fn add_explicit_relay(&mut self, peer_id: PeerId) { - info!("Adding peer {} to explicit relay list", peer_id); - self.explicit_relay_list.push(peer_id); - } - - /// Subscribe to a topic. - /// - /// Returns true if the subscription worked. Returns false if we were already subscribed. - pub fn subscribe(&mut self, topic: Topic) -> bool { - debug!("Subscribing to topic: {}", topic); - if self.config.i_am_relay { - debug!("Relay is subscribed to all topics by default. Subscribe has no effect."); - return false; - } - let topic_hash = self.topic_hash(topic.clone()); - if self.mesh.get(&topic_hash).is_some() { - debug!("Topic: {} is already in the mesh.", topic); - return false; - } - - let peers: Vec<_> = self.peer_topics.keys().copied().collect(); - for peer_id in peers { - let mut fixed_event = None; // initialise the event once if needed - if fixed_event.is_none() { - fixed_event = Some(Arc::new(GossipsubRpc { - messages: Vec::new(), - subscriptions: vec![GossipsubSubscription { - topic_hash: topic_hash.clone(), - action: GossipsubSubscriptionAction::Subscribe, - }], - control_msgs: Vec::new(), - })); - } - - let event = fixed_event.expect("event has been initialised"); - - debug!("Sending SUBSCRIBE to peer: {:?}", peer_id); - self.notify_primary(peer_id, event); - } - - // call JOIN(topic) - // this will add new peers to the mesh for the topic - self.join(&topic_hash); - info!("Subscribed to topic: {}", topic); - true - } - - pub fn is_subscribed(&self, topic_hash: &TopicHash) -> bool { self.mesh.contains_key(topic_hash) } - - /// Unsubscribes from a topic. - /// - /// Returns true if we were subscribed to this topic. - pub fn unsubscribe(&mut self, topic: Topic) -> bool { - debug!("Unsubscribing from topic: {}", topic); - if self.config.i_am_relay { - debug!("Relay is subscribed to all topics by default. Unsubscribe has no effect"); - return false; - } - let topic_hash = &self.topic_hash(topic); - - if self.mesh.get(topic_hash).is_none() { - debug!("Already unsubscribed from topic: {:?}", topic_hash); - // we are not subscribed - return false; - } - - // announce to all peers in the topic - let mut fixed_event = None; // initialise the event once if needed - if let Some(peer_list) = self.topic_peers.get(topic_hash) { - if fixed_event.is_none() { - fixed_event = Some(Arc::new(GossipsubRpc { - messages: Vec::new(), - subscriptions: vec![GossipsubSubscription { - topic_hash: topic_hash.clone(), - action: GossipsubSubscriptionAction::Unsubscribe, - }], - control_msgs: Vec::new(), - })); - } - - let event = fixed_event.expect("event has been initialised"); - - for peer in peer_list.clone() { - debug!("Sending UNSUBSCRIBE to peer: {:?}", peer); - self.notify_primary(peer, event.clone()); - } - } - - // call LEAVE(topic) - // this will remove the topic from the mesh - self.leave(topic_hash); - - info!("Unsubscribed from topic: {:?}", topic_hash); - true - } - - /// Publishes a message to the network. - pub fn publish(&mut self, topic: &Topic, data: impl Into>) { - self.publish_many_from(iter::once(topic.clone()), data, self.local_peer_id) - } - - /// Publishes a message with multiple topics to the network. - pub fn publish_many(&mut self, topic: impl IntoIterator, data: impl Into>) { - self.publish_many_from(topic, data, self.local_peer_id); - } - - /// Publishes a message with multiple topics to the network. - pub fn publish_many_from( - &mut self, - topic: impl IntoIterator, - data: impl Into>, - source: PeerId, - ) { - let message = GossipsubMessage { - source, - data: data.into(), - // To be interoperable with the go-implementation this is treated as a 64-bit - // big-endian uint. - sequence_number: rand::random(), - topics: topic.into_iter().map(|t| self.topic_hash(t)).collect(), - }; - - debug!("Publishing message: {:?}", (self.config.message_id_fn)(&message)); - - // forward the message to mesh peers - self.forward_msg(message.clone(), &source); - - let mut recipient_peers = HashSet::new(); - for topic_hash in &message.topics { - // if not subscribed to the topic, use fanout peers - if self.mesh.get(topic_hash).is_none() { - debug!("Topic: {:?} not in the mesh", topic_hash); - // build a list of peers to forward the message to - // if we have fanout peers add them to the map - if self.fanout.contains_key(topic_hash) { - for peer in self.fanout.get(topic_hash).expect("Topic must exist") { - recipient_peers.insert(*peer); - } - } else { - // we have no fanout peers, select mesh_n of them and add them to the fanout - let mesh_n = self.config.mesh_n; - let new_peers = Self::get_random_peers(&self.topic_peers, topic_hash, mesh_n, |_| true); - // add the new peers to the fanout and recipient peers - self.fanout.insert(topic_hash.clone(), new_peers.clone()); - for peer in new_peers { - debug!("Peer added to fanout: {:?}", peer); - recipient_peers.insert(peer); - } - } - // we are publishing to fanout peers - update the time we published - self.fanout_last_pub.insert(topic_hash.clone(), Instant::now()); - } - } - - // add published message to our received caches - let msg_id = (self.config.message_id_fn)(&message); - self.mcache.put(message.clone()); - self.received.insert(msg_id.clone(), SmallVec::from_elem(source, 1)); - - debug!("Published message: {:?}", msg_id); - - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: vec![message], - control_msgs: Vec::new(), - }); - // Send to peers we know are subscribed to the topic. - for peer_id in recipient_peers.iter() { - debug!("Sending message to peer: {:?}", peer_id); - self.notify_primary(*peer_id, event.clone()); - } - } - - /// This function should be called when `config.manual_propagation` is `true` in order to - /// propagate messages. Messages are stored in the ['Memcache'] and validation is expected to be - /// fast enough that the messages should still exist in the cache. - /// - /// Calling this function will propagate a message stored in the cache, if it still exists. - /// If the message still exists in the cache, it will be forwarded and this function will return true, - /// otherwise it will return false. - pub fn propagate_message(&mut self, message_id: &MessageId, propagation_source: &PeerId) -> bool { - let message = match self.mcache.get(message_id) { - Some(message) => message.clone(), - None => { - warn!( - "Message not in cache. Ignoring forwarding. Message Id: {}", - message_id.0 - ); - return false; - }, - }; - self.forward_msg(message, propagation_source); - true - } - - /// Gossipsub JOIN(topic) - adds topic peers to mesh and sends them GRAFT messages. - fn join(&mut self, topic_hash: &TopicHash) { - debug!("Running JOIN for topic: {:?}", topic_hash); - - // if we are already in the mesh, return - if self.mesh.contains_key(topic_hash) { - info!("JOIN: The topic is already in the mesh, ignoring JOIN"); - return; - } - - let mut added_peers = vec![]; - - // check if we have mesh_n peers in fanout[topic] and add them to the mesh if we do, - // removing the fanout entry. - if let Some((_, peers)) = self.fanout.remove_entry(topic_hash) { - debug!("JOIN: Removing peers from the fanout for topic: {:?}", topic_hash); - // add up to mesh_n of them them to the mesh - // Note: These aren't randomly added, currently FIFO - let add_peers = std::cmp::min(peers.len(), self.config.mesh_n); - debug!( - "JOIN: Adding {:?} peers from the fanout for topic: {:?}", - add_peers, topic_hash - ); - added_peers.extend_from_slice(&peers[..add_peers]); - self.mesh.insert(topic_hash.clone(), peers[..add_peers].to_vec()); - // remove the last published time - self.fanout_last_pub.remove(topic_hash); - } - - // check if we need to get more peers, which we randomly select - if added_peers.len() < self.config.mesh_n { - // get the peers - let new_peers = Self::get_random_peers( - &self.topic_peers, - topic_hash, - self.config.mesh_n - added_peers.len(), - |_| true, - ); - added_peers.extend_from_slice(&new_peers); - // add them to the mesh - debug!("JOIN: Inserting {:?} random peers into the mesh", new_peers.len()); - let mesh_peers = self.mesh.entry(topic_hash.clone()).or_insert_with(Vec::new); - mesh_peers.extend_from_slice(&new_peers); - } - - for peer_id in added_peers { - // Send a GRAFT control message - info!("JOIN: Sending Graft message to peer: {:?}", peer_id); - Self::control_pool_add(&mut self.control_pool, peer_id, GossipsubControlAction::Graft { - topic_hash: topic_hash.clone(), - }); - } - debug!("Completed JOIN for topic: {:?}", topic_hash); - } - - /// Gossipsub LEAVE(topic) - Notifies mesh\[topic\] peers with PRUNE messages. - fn leave(&mut self, topic_hash: &TopicHash) { - debug!("Running LEAVE for topic {:?}", topic_hash); - - // if our mesh contains the topic, send prune to peers and delete it from the mesh - if let Some((_, peers)) = self.mesh.remove_entry(topic_hash) { - for peer in peers { - // Send a PRUNE control message - info!("LEAVE: Sending PRUNE to peer: {:?}", peer); - Self::control_pool_add(&mut self.control_pool, peer, GossipsubControlAction::Prune { - topic_hash: topic_hash.clone(), - }); - } - } - debug!("Completed LEAVE for topic: {:?}", topic_hash); - } - - /// Handles an IHAVE control message. Checks our cache of messages. If the message is unknown, - /// requests it with an IWANT control message. - fn handle_ihave(&mut self, peer_id: &PeerId, ihave_msgs: Vec<(TopicHash, Vec)>) { - debug!("Handling IHAVE for peer: {:?}", peer_id); - // use a hashset to avoid duplicates efficiently - let mut iwant_ids = HashSet::new(); - - for (topic, ids) in ihave_msgs { - // only process the message if we are subscribed - if !self.mesh.contains_key(&topic) { - debug!("IHAVE: Ignoring IHAVE - Not subscribed to topic: {:?}", topic); - continue; - } - - for id in ids { - if !self.received.contains_key(&id) { - // have not seen this message, request it - iwant_ids.insert(id); - } - } - } - - if !iwant_ids.is_empty() { - // Send the list of IWANT control messages - debug!("IHAVE: Sending IWANT message"); - Self::control_pool_add(&mut self.control_pool, *peer_id, GossipsubControlAction::IWant { - message_ids: iwant_ids.iter().cloned().collect(), - }); - } - debug!("Completed IHAVE handling for peer: {:?}", peer_id); - } - - /// Handles an IWANT control message. Checks our cache of messages. If the message exists it is - /// forwarded to the requesting peer. - fn handle_iwant(&mut self, peer_id: &PeerId, iwant_msgs: Vec) { - debug!("Handling IWANT for peer: {:?}", peer_id); - // build a hashmap of available messages - let mut cached_messages = HashMap::new(); - - for id in iwant_msgs { - // if we have it, add it do the cached_messages mapping - if let Some(msg) = self.mcache.get(&id) { - cached_messages.insert(id.clone(), msg.clone()); - } - } - - if !cached_messages.is_empty() { - debug!("IWANT: Sending cached messages to peer: {:?}", peer_id); - // Send the messages to the peer - let message_list = cached_messages.into_iter().map(|entry| entry.1).collect(); - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: message_list, - control_msgs: Vec::new(), - }); - self.notify_primary(*peer_id, event); - } - debug!("Completed IWANT handling for peer: {:?}", peer_id); - } - - /// Handles GRAFT control messages. If subscribed to the topic, adds the peer to mesh, if not, - /// responds with PRUNE messages. - fn handle_graft(&mut self, peer_id: &PeerId, topics: Vec) { - debug!("Handling GRAFT message for peer: {:?}", peer_id); - - let mut to_prune_topics = HashSet::new(); - for topic_hash in topics { - if let Some(peers) = self.mesh.get_mut(&topic_hash) { - // if we are subscribed, add peer to the mesh, if not already added - info!( - "GRAFT: Mesh link added for peer: {:?} in topic: {:?}", - peer_id, topic_hash - ); - // ensure peer is not already added - if !peers.contains(peer_id) { - peers.push(*peer_id); - } - } else { - to_prune_topics.insert(topic_hash.clone()); - } - } - - if !to_prune_topics.is_empty() { - // build the prune messages to send - let prune_messages = to_prune_topics - .iter() - .map(|t| GossipsubControlAction::Prune { topic_hash: t.clone() }) - .collect(); - // Send the prune messages to the peer - info!( - "GRAFT: Not subscribed to topics - Sending PRUNE to peer: {:?}", - peer_id - ); - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: Vec::new(), - control_msgs: prune_messages, - }); - self.notify_primary(*peer_id, event); - } - debug!("Completed GRAFT handling for peer: {:?}", peer_id); - } - - /// Handles PRUNE control messages. Removes peer from the mesh. - fn handle_prune(&mut self, peer_id: &PeerId, topics: Vec) { - debug!("Handling PRUNE message for peer: {:?}", peer_id); - for topic_hash in topics { - if let Some(peers) = self.mesh.get_mut(&topic_hash) { - // remove the peer if it exists in the mesh - info!( - "PRUNE: Removing peer: {:?} from the mesh for topic: {:?}", - peer_id, topic_hash - ); - peers.retain(|p| p != peer_id); - } - } - debug!("Completed PRUNE handling for peer: {:?}", peer_id); - } - - /// Handles IAmrelay control message, does nothing if remote peer already subscribed to some topic - fn handle_i_am_relay(&mut self, peer_id: &PeerId, is_relay: bool) { - debug!("Handling IAmrelay message for peer: {:?}", peer_id); - if self.peer_topics.entry(*peer_id).or_insert_with(Vec::new).is_empty() && is_relay { - info!("IAmrelay: Adding peer: {:?} to the relays list", peer_id); - self.connected_relays.insert(*peer_id); - if self.relays_mesh.len() < self.config.mesh_n_low { - info!("IAmrelay: Adding peer: {:?} to the relay mesh", peer_id); - self.add_peers_to_relays_mesh(vec![*peer_id]); - } - } - debug!("Completed IAmrelay handling for peer: {:?}", peer_id); - } - - /// Handles IncludedTorelaysMesh message - fn handle_included_to_relays_mesh(&mut self, peer_id: &PeerId, is_included: bool, other_mesh_size: usize) { - if self.is_relay() { - debug!( - "Handling IncludedTorelaysMesh message for peer: {:?}, is_included: {}", - peer_id, is_included - ); - if is_included { - if self.connected_relays.contains(peer_id) { - if self.relays_mesh.len() > self.config.mesh_n_high { - self.notify_excluded_from_relay_mesh(*peer_id); - } else { - debug!("Adding peer {:?} to relays_mesh", peer_id); - self.relays_mesh.insert(*peer_id, other_mesh_size); - } - } else { - debug!("Adding peer {:?} to included_to_relays_mesh", peer_id); - self.included_to_relays_mesh.insert(*peer_id); - } - } else { - debug!( - "Removing peer {:?} from included_to_relays_mesh and relays mesh", - peer_id - ); - self.included_to_relays_mesh.remove(peer_id); - self.relays_mesh.remove(peer_id); - } - } else { - debug!( - "Ignoring IncludedTorelaysMesh message for peer: {:?}, is_included: {}", - peer_id, is_included - ); - } - } - - /// Handles a newly received GossipsubMessage. - /// Forwards the message to all peers in the mesh. - fn handle_received_message(&mut self, msg: GossipsubMessage, propagation_source: &PeerId) { - let msg_id = (self.config.message_id_fn)(&msg); - debug!("Handling message: {:?} from peer: {:?}", msg_id, propagation_source); - match self.received.entry(msg_id.clone()) { - TimeCacheEntry::Occupied(entry) => { - debug!("Message already received, ignoring. Message: {:?}", msg_id); - entry.into_mut().push(*propagation_source); - return; - }, - TimeCacheEntry::Vacant(entry) => { - entry.insert(SmallVec::from_elem(*propagation_source, 1)); - }, - } - // add to the memcache - self.mcache.put(msg.clone()); - - // dispatch the message to the user - debug!("Sending received message to user"); - self.events - .push_back(NetworkBehaviourAction::GenerateEvent(GossipsubEvent::Message( - *propagation_source, - msg_id, - msg.clone(), - ))); - - // forward the message to mesh peers, if no validation is required - if !self.config.manual_propagation { - let message_id = (self.config.message_id_fn)(&msg); - self.forward_msg(msg, propagation_source); - debug!("Completed message handling for message: {:?}", message_id); - } - } - - /// Handles received subscriptions. - fn handle_received_subscriptions(&mut self, subscriptions: &[GossipsubSubscription], propagation_source: &PeerId) { - debug!( - "Handling subscriptions: {:?}, from source: {:?}", - subscriptions, propagation_source - ); - let subscribed_topics = match self.peer_topics.get_mut(propagation_source) { - Some(topics) => topics, - None => { - error!("Subscription by unknown peer: {:?}", &propagation_source); - return; - }, - }; - - for subscription in subscriptions { - // get the peers from the mapping, or insert empty lists if topic doesn't exist - let peer_list = self - .topic_peers - .entry(subscription.topic_hash.clone()) - .or_insert_with(Vec::new); - - match subscription.action { - GossipsubSubscriptionAction::Subscribe => { - if !peer_list.contains(propagation_source) { - debug!( - "SUBSCRIPTION: topic_peer: Adding gossip peer: {:?} to topic: {:?}", - propagation_source, subscription.topic_hash - ); - peer_list.push(*propagation_source); - } - - // add to the peer_topics mapping - if !subscribed_topics.contains(&subscription.topic_hash) { - info!( - "SUBSCRIPTION: Adding peer: {:?} to topic: {:?}", - propagation_source, subscription.topic_hash - ); - subscribed_topics.push(subscription.topic_hash.clone()); - } - - // if the mesh needs peers add the peer to the mesh - if let Some(peers) = self.mesh.get_mut(&subscription.topic_hash) { - if peers.len() < self.config.mesh_n_low { - debug!("SUBSCRIPTION: Adding peer {:?} to the mesh", propagation_source,); - } - peers.push(*propagation_source); - } - // generates a subscription event to be polled - self.events - .push_back(NetworkBehaviourAction::GenerateEvent(GossipsubEvent::Subscribed { - peer_id: *propagation_source, - topic: subscription.topic_hash.clone(), - })); - }, - GossipsubSubscriptionAction::Unsubscribe => { - if let Some(pos) = peer_list.iter().position(|p| p == propagation_source) { - info!( - "SUBSCRIPTION: Removing gossip peer: {:?} from topic: {:?}", - propagation_source, subscription.topic_hash - ); - peer_list.remove(pos); - } - // remove topic from the peer_topics mapping - if let Some(pos) = subscribed_topics.iter().position(|t| t == &subscription.topic_hash) { - subscribed_topics.remove(pos); - } - // remove the peer from the mesh if it exists - if let Some(peers) = self.mesh.get_mut(&subscription.topic_hash) { - peers.retain(|peer| peer != propagation_source); - } - - // generate an unsubscribe event to be polled - self.events - .push_back(NetworkBehaviourAction::GenerateEvent(GossipsubEvent::Unsubscribed { - peer_id: *propagation_source, - topic: subscription.topic_hash.clone(), - })); - }, - } - } - trace!("Completed handling subscriptions from source: {:?}", propagation_source); - } - - /// Heartbeat function which shifts the memcache and updates the mesh. - fn heartbeat(&mut self) { - debug!("Starting heartbeat"); - - let mut to_graft = HashMap::new(); - let mut to_prune = HashMap::new(); - - // maintain the mesh for each topic - for (topic_hash, peers) in self.mesh.iter_mut() { - // too little peers - add some - if peers.len() < self.config.mesh_n_low { - debug!( - "HEARTBEAT: Mesh low. Topic: {:?} Contains: {:?} needs: {:?}", - topic_hash.clone().into_string(), - peers.len(), - self.config.mesh_n_low - ); - // not enough peers - get mesh_n - current_length more - let desired_peers = self.config.mesh_n - peers.len(); - let peer_list = Self::get_random_peers(&self.topic_peers, topic_hash, desired_peers, { - |peer| !peers.contains(peer) - }); - for peer in &peer_list { - let current_topic = to_graft.entry(*peer).or_insert_with(Vec::new); - current_topic.push(topic_hash.clone()); - } - // update the mesh - debug!("Updating mesh, new mesh: {:?}", peer_list); - peers.extend(peer_list); - } - - // too many peers - remove some - if peers.len() > self.config.mesh_n_high { - debug!( - "HEARTBEAT: Mesh high. Topic: {:?} Contains: {:?} needs: {:?}", - topic_hash, - peers.len(), - self.config.mesh_n_high - ); - let excess_peer_no = peers.len() - self.config.mesh_n; - // shuffle the peers - let mut rng = rand::thread_rng(); - peers.shuffle(&mut rng); - // remove the first excess_peer_no peers adding them to to_prune - for _ in 0..excess_peer_no { - let peer = peers.pop().expect("There should always be enough peers to remove"); - let current_topic = to_prune.entry(peer).or_insert_with(Vec::new); - current_topic.push(topic_hash.clone()); - } - } - } - - // remove expired fanout topics - { - let fanout = &mut self.fanout; // help the borrow checker - let fanout_ttl = self.config.fanout_ttl; - self.fanout_last_pub.retain(|topic_hash, last_pub_time| { - if *last_pub_time + fanout_ttl < Instant::now() { - debug!( - "HEARTBEAT: Fanout topic removed due to timeout. Topic: {:?}", - topic_hash - ); - fanout.remove(topic_hash); - return false; - } - true - }); - } - - // maintain fanout - // check if our peers are still a part of the topic - for (topic_hash, peers) in self.fanout.iter_mut() { - let mut to_remove_peers = Vec::new(); - for peer in peers.iter() { - // is the peer still subscribed to the topic? - match self.peer_topics.get(peer) { - Some(topics) => { - if !topics.contains(topic_hash) { - debug!("HEARTBEAT: Peer removed from fanout for topic: {:?}", topic_hash); - to_remove_peers.push(*peer); - } - }, - None => { - // remove if the peer has disconnected - to_remove_peers.push(*peer); - }, - } - } - peers.retain(|peer| !to_remove_peers.contains(peer)); - - // not enough peers - if peers.len() < self.config.mesh_n { - debug!( - "HEARTBEAT: Fanout low. Contains: {:?} needs: {:?}", - peers.len(), - self.config.mesh_n - ); - let needed_peers = self.config.mesh_n - peers.len(); - let new_peers = Self::get_random_peers(&self.topic_peers, topic_hash, needed_peers, |peer| { - !peers.contains(peer) - }); - peers.extend(new_peers); - } - } - - self.emit_gossip(); - - // send graft/prunes - if !to_graft.is_empty() | !to_prune.is_empty() { - self.send_graft_prune(to_graft, to_prune); - } - - // piggyback pooled control messages - self.flush_control_pool(); - - // shift the memcache - self.mcache.shift(); - debug!("Completed Heartbeat"); - } - - /// Emits gossip - Send IHAVE messages to a random set of gossip peers. This is applied to mesh - /// and fanout peers - fn emit_gossip(&mut self) { - debug!("Started gossip"); - for (topic_hash, peers) in self.mesh.iter().chain(self.fanout.iter()) { - let message_ids = self.mcache.get_gossip_ids(topic_hash); - if message_ids.is_empty() { - return; - } - - // get gossip_lazy random peers - let to_msg_peers = Self::get_random_peers(&self.topic_peers, topic_hash, self.config.gossip_lazy, |peer| { - !peers.contains(peer) - }); - for peer in to_msg_peers { - // send an IHAVE message - Self::control_pool_add(&mut self.control_pool, peer, GossipsubControlAction::IHave { - topic_hash: topic_hash.clone(), - message_ids: message_ids.clone(), - }); - } - } - debug!("Completed gossip"); - } - - /// Handles multiple GRAFT/PRUNE messages and coalesces them into chunked gossip control - /// messages. - fn send_graft_prune( - &mut self, - to_graft: HashMap>, - mut to_prune: HashMap>, - ) { - // handle the grafts and overlapping prunes - for (peer, topics) in to_graft.iter() { - let mut grafts: Vec = topics - .iter() - .map(|topic_hash| GossipsubControlAction::Graft { - topic_hash: topic_hash.clone(), - }) - .collect(); - let mut prunes: Vec = to_prune - .remove(peer) - .unwrap_or_default() - .iter() - .map(|topic_hash| GossipsubControlAction::Prune { - topic_hash: topic_hash.clone(), - }) - .collect(); - grafts.append(&mut prunes); - - // send the control messages - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: Vec::new(), - control_msgs: grafts, - }); - self.notify_primary(*peer, event); - } - - // handle the remaining prunes - for (peer, topics) in to_prune.iter() { - let remaining_prunes = topics - .iter() - .map(|topic_hash| GossipsubControlAction::Prune { - topic_hash: topic_hash.clone(), - }) - .collect(); - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: Vec::new(), - control_msgs: remaining_prunes, - }); - self.notify_primary(*peer, event); - } - } - - /// Helper function which forwards a message to mesh\[topic\] peers. - fn forward_msg(&mut self, message: GossipsubMessage, source: &PeerId) { - let msg_id = (self.config.message_id_fn)(&message); - debug!("Forwarding message: {:?}", msg_id); - let mut recipient_peers = HashSet::new(); - - if self.config.i_am_relay { - // relay simply forwards the message to topic peers that included the relay to their relays mesh - for topic in &message.topics { - if let Some(topic_peers) = self.topic_peers.get(topic) { - for peer_id in topic_peers { - if peer_id != source - && peer_id != &message.source - && self.included_to_relays_mesh.contains(peer_id) - { - recipient_peers.insert(*peer_id); - } - } - } - } - } else { - // add mesh peers if the node is not relay - for topic in &message.topics { - if let Some(mesh_peers) = self.mesh.get(topic) { - for peer_id in mesh_peers { - if peer_id != source && peer_id != &message.source { - recipient_peers.insert(*peer_id); - } - } - } - } - } - - // forward the message to peers - if !recipient_peers.is_empty() { - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: vec![message.clone()], - control_msgs: Vec::new(), - }); - - for peer in recipient_peers.iter() { - if let Some(received_from_peers) = self.received.get(&msg_id) { - if received_from_peers.contains(peer) { - continue; - } - } - - debug!("Sending message: {:?} to peer {:?}", msg_id, peer); - self.notify_primary(*peer, event.clone()); - } - } - - if !self.relays_mesh.is_empty() { - debug!("Forwarding message to relays: {:?}", msg_id); - let message_source = message.source; - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: vec![message], - control_msgs: Vec::new(), - }); - - let relays: Vec<_> = self.relays_mesh.keys().copied().collect(); - for relay in relays { - if let Some(received_from_peers) = self.received.get(&msg_id) { - if received_from_peers.contains(&relay) { - continue; - } - } - - if relay != *source && relay != message_source { - debug!("Sending message: {:?} to relay {:?}", msg_id, relay); - self.notify_primary(relay, event.clone()); - } - } - debug!("Completed forwarding message to relays"); - } - debug!("Completed forwarding message"); - } - - /// Helper function to get a set of `n` random gossipsub peers for a `topic_hash` - /// filtered by the function `f`. - fn get_random_peers( - topic_peers: &HashMap>, - topic_hash: &TopicHash, - n: usize, - mut f: impl FnMut(&PeerId) -> bool, - ) -> Vec { - let mut gossip_peers = match topic_peers.get(topic_hash) { - // if they exist, filter the peers by `f` - Some(peer_list) => peer_list.iter().cloned().filter(|p| f(p)).collect(), - None => Vec::new(), - }; - - // if we have less than needed, return them - if gossip_peers.len() <= n { - debug!("RANDOM PEERS: Got {:?} peers", gossip_peers.len()); - return gossip_peers.to_vec(); - } - - // we have more peers than needed, shuffle them and return n of them - let mut rng = rand::thread_rng(); - gossip_peers.partial_shuffle(&mut rng, n); - - debug!("RANDOM PEERS: Got {:?} peers", n); - - gossip_peers[..n].to_vec() - } - - /// The helper function to get a set of `n` random peers from the `relays` hashset - /// filtered by the function `f`. - fn get_random_relays(relays: &HashSet, n: usize, mut f: impl FnMut(&PeerId) -> bool) -> Vec { - let mut relays: Vec<_> = relays.iter().cloned().filter(|p| f(p)).collect(); - - // if we have less than needed, return them - if relays.len() <= n { - debug!("RANDOM RELAYS: Got {:?} peers", relays.len()); - return relays; - } - - // we have more peers than needed, shuffle them and return n of them - let mut rng = rand::thread_rng(); - relays.partial_shuffle(&mut rng, n); - debug!("RANDOM RELAYS: Got {:?} peers", n); - - relays[..n].to_vec() - } - - // adds a control action to control_pool - fn control_pool_add( - control_pool: &mut HashMap>, - peer: PeerId, - control: GossipsubControlAction, - ) { - control_pool.entry(peer).or_insert_with(Vec::new).push(control); - } - - /// Produces a `TopicHash` for a topic given the gossipsub configuration. - fn topic_hash(&self, topic: Topic) -> TopicHash { - if self.config.hash_topics { - topic.sha256_hash() - } else { - topic.no_hash() - } - } - - /// Takes each control action mapping and turns it into a message - fn flush_control_pool(&mut self) { - let control_pool: Vec<_> = self.control_pool.drain().collect(); - for (peer, controls) in control_pool { - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: Vec::new(), - control_msgs: controls, - }); - self.notify_primary(peer, event); - } - } - - pub fn get_mesh_peers(&self, topic: &TopicHash) -> Vec { self.mesh.get(topic).cloned().unwrap_or_default() } - - pub fn get_topic_peers(&self, topic: &TopicHash) -> Vec { - self.topic_peers.get(topic).cloned().unwrap_or_default() - } - - pub fn get_num_peers(&self) -> usize { self.peer_topics.len() } - - pub fn get_peers_connections(&self) -> HashMap> { - self.peer_connections.clone() - } - - pub fn get_mesh(&self) -> &HashMap> { &self.mesh } - - pub fn get_relay_mesh(&self) -> Vec { self.relays_mesh.keys().cloned().collect() } - - pub fn relay_mesh_len(&self) -> usize { self.relays_mesh.len() } - - pub fn get_all_topic_peers(&self) -> &HashMap> { &self.topic_peers } - - pub fn get_all_peer_topics(&self) -> &HashMap> { &self.peer_topics } - - /// Get count of received messages in the [`GossipsubConfig::duplicate_cache_time`] period. - pub fn get_received_messages_in_period(&self) -> (Duration, usize) { (self.received.ttl(), self.received.len()) } - - pub fn get_config(&self) -> &GossipsubConfig { &self.config } - - /// Adds peers to relays mesh and notifies them they are added - fn add_peers_to_relays_mesh(&mut self, peers: Vec) { - for peer in &peers { - // other mesh size is unknown at this point - self.relays_mesh.insert(*peer, 0); - } - for peer in peers { - self.notify_included_to_relay_mesh(peer); - } - } - - #[allow(dead_code)] - fn remove_peer_from_relay_mesh(&mut self, peer: &PeerId) { - if self.relays_mesh.remove(peer).is_some() { - self.notify_excluded_from_relay_mesh(*peer) - } - } - - /// Cleans up relays mesh so it remains mesh_n peers - fn clean_up_relays_mesh(&mut self) { - let mesh_n = self.config.mesh_n; - let mut removed = Vec::with_capacity(self.relays_mesh.len() - mesh_n); - let explicit_relay_list = self.explicit_relay_list.clone(); - // perform 2 filter iterations to not keep excessive number of explicit peers in mesh - self.relays_mesh = self - .relays_mesh - .drain() - .enumerate() - .filter_map(|(i, peer)| { - if i < mesh_n || explicit_relay_list.contains(&peer.0) { - Some(peer) - } else { - removed.push(peer); - None - } - }) - .collect(); - - self.relays_mesh = self - .relays_mesh - .drain() - .enumerate() - .filter_map(|(i, peer)| { - if i < mesh_n { - Some(peer) - } else { - removed.push(peer); - None - } - }) - .collect(); - - for (peer, _) in removed { - self.notify_excluded_from_relay_mesh(peer) - } - } - - fn notify_included_to_relay_mesh(&mut self, peer: PeerId) { - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: Vec::new(), - control_msgs: vec![GossipsubControlAction::IncludedToRelaysMesh { - included: true, - mesh_size: self.relay_mesh_len(), - }], - }); - self.notify_primary(peer, event); - } - - fn notify_excluded_from_relay_mesh(&mut self, peer: PeerId) { - let event = Arc::new(GossipsubRpc { - subscriptions: Vec::new(), - messages: Vec::new(), - control_msgs: vec![GossipsubControlAction::IncludedToRelaysMesh { - included: false, - mesh_size: self.relay_mesh_len(), - }], - }); - self.notify_primary(peer, event); - } - - /// Notify the primary connection (the first connected point) of the peer. - /// Since `NotifyHandler::All` has been removed, the original `libp2p_gossipsub` notifies the connected peers using their primary connections. - /// See an example: https://github.com/libp2p/rust-libp2p/blob/v0.38.0/protocols/gossipsub/src/behaviour.rs#L3013 - fn notify_primary(&mut self, peer_id: PeerId, event: Arc) { - if let Some(points) = self.peer_connections.get(&peer_id) { - if !points.is_empty() { - let conn_id = points[0].0; - return self.notify_one(peer_id, conn_id, event); - } - } - warn!("Expected at least one connection of the peer '{}'", peer_id); - self.notify_any(peer_id, event); - } - - #[allow(dead_code)] - fn notify_all(&mut self, peer_id: PeerId, event: Arc) { - match self.peer_connections.get(&peer_id) { - Some(connected_points) => { - let connections: Vec<_> = connected_points.iter().map(|(conn_id, _point)| *conn_id).collect(); - for conn_id in connections { - self.notify_one(peer_id, conn_id, event.clone()); - } - }, - None => { - warn!( - "An attempt to notify a peer '{:?}' that is not in 'Gossipsub::peer_connections'", - peer_id - ) - }, - } - } - - fn notify_any(&mut self, peer_id: PeerId, event: Arc) { - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id, - handler: NotifyHandler::Any, - event, - }); - } - - fn notify_one(&mut self, peer_id: PeerId, conn_id: ConnectionId, event: Arc) { - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id, - handler: NotifyHandler::One(conn_id), - event, - }); - } - - fn maintain_relays_mesh(&mut self) { - if self.relays_mesh.len() < self.config.mesh_n_low { - info!( - "HEARTBEAT: relays low. Contains: {:?} needs: {:?}", - self.relays_mesh.len(), - self.config.mesh_n_low, - ); - // add peers 1 by 1 to avoid overloading peaks when node connects to several other nodes at once - let required = 1; - // get `n` relays that are not in the `relays_mesh` - let to_add = - Self::get_random_relays(&self.connected_relays, required, |p| !self.relays_mesh.contains_key(p)); - self.add_peers_to_relays_mesh(to_add); - } - - if self.relays_mesh.len() > self.config.mesh_n_high { - info!( - "HEARTBEAT: relays high. Contains: {:?} needs: {:?}", - self.relays_mesh.len(), - self.config.mesh_n, - ); - self.clean_up_relays_mesh(); - } - - let size = self.relays_mesh.len(); - for relay in self.relays_mesh.keys() { - Self::control_pool_add(&mut self.control_pool, *relay, GossipsubControlAction::MeshSize(size)); - } - } - - pub fn is_relay(&self) -> bool { self.config.i_am_relay } - - pub fn connected_relays(&self) -> Vec { self.connected_relays.iter().cloned().collect() } - - pub fn connected_relays_len(&self) -> usize { self.connected_relays.len() } - - pub fn is_connected_to_addr(&self, addr: &Multiaddr) -> bool { self.connected_addresses.contains(addr) } -} - -impl NetworkBehaviour for Gossipsub { - type ConnectionHandler = GossipsubHandler; - type OutEvent = GossipsubEvent; - - fn new_handler(&mut self) -> Self::ConnectionHandler { - GossipsubHandler::new(self.config.protocol_id.clone(), self.config.max_transmit_size) - } - - fn addresses_of_peer(&mut self, _: &PeerId) -> Vec { Vec::new() } - - fn inject_connection_established( - &mut self, - id: &PeerId, - conn_id: &ConnectionId, - point: &ConnectedPoint, - _: Option<&Vec>, - other_established: usize, - ) { - self.peer_connections - .entry(*id) - .or_insert_with(Default::default) - .push((*conn_id, point.clone())); - self.connected_addresses.push(point.get_remote_address().clone()); - - if other_established > 0 { - // For other actions, we only care about the first time a peer connects. - return; - } - - info!("New peer connected: {:?}", id); - // We need to send our subscriptions to the newly-connected node if we are not relay. - // Notify peer that we act as relay otherwise - if self.config.i_am_relay { - debug!("Sending IAmRelay to peer {:?}", id); - let event = Arc::new(GossipsubRpc { - messages: Vec::new(), - subscriptions: Vec::new(), - control_msgs: vec![GossipsubControlAction::IAmRelay(true)], - }); - self.notify_primary(*id, event); - } else { - let mut subscriptions = vec![]; - for topic_hash in self.mesh.keys() { - subscriptions.push(GossipsubSubscription { - topic_hash: topic_hash.clone(), - action: GossipsubSubscriptionAction::Subscribe, - }); - } - - if !subscriptions.is_empty() { - // send our subscriptions to the peer - let event = Arc::new(GossipsubRpc { - messages: Vec::new(), - subscriptions, - control_msgs: Vec::new(), - }); - self.notify_primary(*id, event); - } - } - // For the time being assume all gossipsub peers - self.peer_topics.insert(*id, Vec::new()); - } - - fn inject_connection_closed( - &mut self, - peer_id: &PeerId, - disconnected_conn_id: &ConnectionId, - disconnected_point: &ConnectedPoint, - _: ::Handler, - remaining_established: usize, - ) { - if let Entry::Occupied(mut o) = self.peer_connections.entry(*peer_id) { - let connected_points = o.get_mut(); - connected_points.retain(|(conn_id, _point)| conn_id != disconnected_conn_id); - if connected_points.is_empty() { - o.remove_entry(); - } - } - - self.connected_addresses - .retain(|addr| addr != disconnected_point.get_remote_address()); - - if remaining_established > 0 { - return; - } - - // remove from mesh, topic_peers, peer_topic and fanout - debug!("Peer disconnected: {:?}", peer_id); - { - let topics = match self.peer_topics.get(peer_id) { - Some(topics) => topics, - None => { - warn!("Disconnected node, not in connected nodes"); - return; - }, - }; - - // remove peer from all mappings - for topic in topics { - // check the mesh for the topic - if let Some(mesh_peers) = self.mesh.get_mut(topic) { - // check if the peer is in the mesh and remove it - if let Some(pos) = mesh_peers.iter().position(|p| p == peer_id) { - mesh_peers.remove(pos); - } - } - - // remove from topic_peers - if let Some(peer_list) = self.topic_peers.get_mut(topic) { - if let Some(pos) = peer_list.iter().position(|p| p == peer_id) { - peer_list.remove(pos); - } - // debugging purposes - else { - warn!("Disconnected node: {:?} not in topic_peers peer list", peer_id); - } - } else { - warn!( - "Disconnected node: {:?} with topic: {:?} not in topic_peers", - &peer_id, &topic - ); - } - - // remove from fanout - if let Some(peers) = self.fanout.get_mut(topic) { - peers.retain(|p| p != peer_id) - } - } - } - - self.relays_mesh.remove(peer_id); - self.connected_relays.remove(peer_id); - self.included_to_relays_mesh.remove(peer_id); - self.peer_connections.remove(peer_id); - // remove peer from peer_topics - let was_in = self.peer_topics.remove(peer_id); - debug_assert!(was_in.is_some()); - } - - fn inject_event(&mut self, propagation_source: PeerId, _: ConnectionId, event: GossipsubRpc) { - // Handle subscriptions - // Update connected peers topics - debug!("Event injected {:?}, source {:?}", event, propagation_source); - self.handle_received_subscriptions(&event.subscriptions, &propagation_source); - - // Handle messages - for message in event.messages { - self.handle_received_message(message, &propagation_source); - } - - // Handle control messages - // group some control messages, this minimises SendEvents (code is simplified to handle each event at a time however) - let mut ihave_msgs = vec![]; - let mut graft_msgs = vec![]; - let mut prune_msgs = vec![]; - for control_msg in event.control_msgs { - match control_msg { - GossipsubControlAction::IHave { - topic_hash, - message_ids, - } => { - ihave_msgs.push((topic_hash, message_ids)); - }, - GossipsubControlAction::IWant { message_ids } => self.handle_iwant(&propagation_source, message_ids), - GossipsubControlAction::Graft { topic_hash } => graft_msgs.push(topic_hash), - GossipsubControlAction::Prune { topic_hash } => prune_msgs.push(topic_hash), - GossipsubControlAction::IAmRelay(is_relay) => self.handle_i_am_relay(&propagation_source, is_relay), - GossipsubControlAction::IncludedToRelaysMesh { included, mesh_size } => { - self.handle_included_to_relays_mesh(&propagation_source, included, mesh_size) - }, - GossipsubControlAction::MeshSize(size) => { - if let Some(old_size) = self.relays_mesh.get_mut(&propagation_source) { - *old_size = size; - } - }, - } - } - if !ihave_msgs.is_empty() { - self.handle_ihave(&propagation_source, ihave_msgs); - } - if !graft_msgs.is_empty() { - self.handle_graft(&propagation_source, graft_msgs); - } - if !prune_msgs.is_empty() { - self.handle_prune(&propagation_source, prune_msgs); - } - } - - fn poll( - &mut self, - cx: &mut Context, - _: &mut impl PollParameters, - ) -> Poll> { - if let Some(event) = self.events.pop_front() { - // clone send event reference if others references are present - match event { - NetworkBehaviourAction::NotifyHandler { - peer_id, - handler, - event: send_event, - } => match Arc::try_unwrap(send_event) { - Ok(event) => { - return Poll::Ready(NetworkBehaviourAction::NotifyHandler { - peer_id, - event, - handler, - }); - }, - Err(event) => { - return Poll::Ready(NetworkBehaviourAction::NotifyHandler { - peer_id, - event: (*event).clone(), - handler, - }); - }, - }, - NetworkBehaviourAction::GenerateEvent(e) => { - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(e)); - }, - NetworkBehaviourAction::Dial { opts, handler } => { - return Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }); - }, - NetworkBehaviourAction::ReportObservedAddr { address, score } => { - return Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { address, score }); - }, - NetworkBehaviourAction::CloseConnection { peer_id, connection } => { - return Poll::Ready(NetworkBehaviourAction::CloseConnection { peer_id, connection }); - }, - } - } - - while let Poll::Ready(Some(())) = self.heartbeat.poll_next_unpin(cx) { - self.heartbeat(); - } - - while let Poll::Ready(Some(())) = self.relay_mesh_maintenance_interval.poll_next_unpin(cx) { - self.maintain_relays_mesh(); - } - - Poll::Pending - } -} - -/// An RPC received/sent. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct GossipsubRpc { - /// List of messages that were part of this RPC query. - pub messages: Vec, - /// List of subscriptions. - pub subscriptions: Vec, - /// List of Gossipsub control messages. - pub control_msgs: Vec, -} - -/// Event that can happen on the gossipsub behaviour. -#[derive(Debug)] -pub enum GossipsubEvent { - /// A message has been received. This contains the PeerId that we received the message from, - /// the message id (used if the application layer needs to propagate the message) and the - /// message itself. - Message(PeerId, MessageId, GossipsubMessage), - - /// A remote subscribed to a topic. - Subscribed { - /// Remote that has subscribed. - peer_id: PeerId, - /// The topic it has subscribed to. - topic: TopicHash, - }, - - /// A remote unsubscribed from a topic. - Unsubscribed { - /// Remote that has unsubscribed. - peer_id: PeerId, - /// The topic it has subscribed from. - topic: TopicHash, - }, -} diff --git a/mm2src/gossipsub/src/behaviour/tests.rs b/mm2src/gossipsub/src/behaviour/tests.rs deleted file mode 100644 index 1c1923df0b..0000000000 --- a/mm2src/gossipsub/src/behaviour/tests.rs +++ /dev/null @@ -1,900 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -// collection of tests for the gossipsub network behaviour - -#[cfg(test)] -#[allow(clippy::module_inception)] -mod tests { - use super::super::*; - use crate::GossipsubConfigBuilder; - use libp2p_core::Endpoint; - use std::net::IpAddr; - use std::str::FromStr; - - // helper functions for testing - - // This function generates `peer_no` random PeerId's, subscribes to `topics` and subscribes the - // injected nodes to all topics if `to_subscribe` is set. All nodes are considered gossipsub nodes. - fn build_and_inject_nodes( - peer_no: usize, - topics: Vec, - gs_config: GossipsubConfig, - to_subscribe: bool, - ) -> (Gossipsub, Vec, Vec) { - // create a gossipsub struct - let mut gs: Gossipsub = Gossipsub::new(PeerId::random(), gs_config); - - let mut topic_hashes = vec![]; - - // subscribe to the topics - for t in topics { - let topic = Topic::new(t); - gs.subscribe(topic.clone()); - topic_hashes.push(topic.no_hash().clone()); - } - - // build and connect peer_no random peers - let mut peers = vec![]; - - for i in 0..peer_no { - let peer = PeerId::random(); - peers.push(peer); - ::inject_connection_established( - &mut gs, - &peer, - &ConnectionId::new(i), - &ConnectedPoint::Dialer { - address: Multiaddr::from(IpAddr::from_str("127.0.0.1").unwrap()), - role_override: Endpoint::Dialer, - }, - None, - 0, - ); - if to_subscribe { - gs.handle_received_subscriptions( - &topic_hashes - .iter() - .cloned() - .map(|t| GossipsubSubscription { - action: GossipsubSubscriptionAction::Subscribe, - topic_hash: t, - }) - .collect::>(), - &peer, - ); - }; - } - - (gs, peers, topic_hashes) - } - - #[test] - /// Test local node subscribing to a topic - fn test_subscribe() { - // The node should: - // - Create an empty vector in mesh[topic] - // - Send subscription request to all peers - // - run JOIN(topic) - - let subscribe_topic = vec![String::from("test_subscribe")]; - let (gs, _, topic_hashes) = build_and_inject_nodes(20, subscribe_topic, GossipsubConfig::default(), true); - - assert!( - gs.mesh.get(&topic_hashes[0]).is_some(), - "Subscribe should add a new entry to the mesh[topic] hashmap" - ); - - // collect all the subscriptions - let subscriptions = gs.events.iter().fold(vec![], |mut collected_subscriptions, e| match e { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - for s in &event.subscriptions { - if s.action == GossipsubSubscriptionAction::Subscribe { - collected_subscriptions.push(s.clone()) - } - } - collected_subscriptions - }, - _ => collected_subscriptions, - }); - - // we sent a subscribe to all known peers - assert!( - subscriptions.len() == 20, - "Should send a subscription to all known peers" - ); - } - - #[test] - /// Test unsubscribe. - fn test_unsubscribe() { - // Unsubscribe should: - // - Remove the mesh entry for topic - // - Send UNSUBSCRIBE to all known peers - // - Call Leave - - let topic_strings = vec![String::from("topic1"), String::from("topic2")]; - let topics = topic_strings - .iter() - .map(|t| Topic::new(t.clone())) - .collect::>(); - - // subscribe to topic_strings - let (mut gs, _, topic_hashes) = build_and_inject_nodes(20, topic_strings, GossipsubConfig::default(), true); - - for topic_hash in &topic_hashes { - assert!( - gs.topic_peers.get(topic_hash).is_some(), - "Topic_peers contain a topic entry" - ); - assert!(gs.mesh.get(topic_hash).is_some(), "mesh should contain a topic entry"); - } - - // unsubscribe from both topics - assert!( - gs.unsubscribe(topics[0].clone()), - "should be able to unsubscribe successfully from each topic", - ); - assert!( - gs.unsubscribe(topics[1].clone()), - "should be able to unsubscribe successfully from each topic", - ); - - let subscriptions = gs.events.iter().fold(vec![], |mut collected_subscriptions, e| match e { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - for s in &event.subscriptions { - if s.action == GossipsubSubscriptionAction::Unsubscribe { - collected_subscriptions.push(s.clone()) - } - } - collected_subscriptions - }, - _ => collected_subscriptions, - }); - - // we sent a unsubscribe to all known peers, for two topics - assert!( - subscriptions.len() == 40, - "Should send an unsubscribe event to all known peers" - ); - - // check we clean up internal structures - for topic_hash in &topic_hashes { - assert!( - gs.mesh.get(topic_hash).is_none(), - "All topics should have been removed from the mesh" - ); - } - } - - #[test] - /// Test JOIN(topic) functionality. - fn test_join() { - // The Join function should: - // - Remove peers from fanout[topic] - // - Add any fanout[topic] peers to the mesh (up to mesh_n) - // - Fill up to mesh_n peers from known gossipsub peers in the topic - // - Send GRAFT messages to all nodes added to the mesh - - // This test is not an isolated unit test, rather it uses higher level, - // subscribe/unsubscribe to perform the test. - - let topic_strings = vec![String::from("topic1"), String::from("topic2")]; - let topics = topic_strings - .iter() - .map(|t| Topic::new(t.clone())) - .collect::>(); - - let (mut gs, _, topic_hashes) = build_and_inject_nodes(20, topic_strings, GossipsubConfig::default(), true); - - // unsubscribe, then call join to invoke functionality - assert!( - gs.unsubscribe(topics[0].clone()), - "should be able to unsubscribe successfully" - ); - assert!( - gs.unsubscribe(topics[1].clone()), - "should be able to unsubscribe successfully" - ); - - // re-subscribe - there should be peers associated with the topic - assert!( - gs.subscribe(topics[0].clone()), - "should be able to subscribe successfully" - ); - - // should have added mesh_n nodes to the mesh - assert!( - gs.mesh.get(&topic_hashes[0]).unwrap().len() == 6, - "Should have added 6 nodes to the mesh" - ); - - // there should be mesh_n GRAFT messages. - let graft_messages = gs - .control_pool - .iter() - .fold(vec![], |mut collected_grafts, (_, controls)| { - for c in controls.iter() { - if let GossipsubControlAction::Graft { topic_hash: _ } = c { - collected_grafts.push(c.clone()) - } - } - collected_grafts - }); - - assert_eq!( - graft_messages.len(), - 6, - "There should be 6 grafts messages sent to peers" - ); - - // verify fanout nodes - // add 3 random peers to the fanout[topic1] - gs.fanout.insert(topic_hashes[1].clone(), vec![]); - let new_peers = vec![]; - for _ in 0..3 { - let fanout_peers = gs.fanout.get_mut(&topic_hashes[1]).unwrap(); - fanout_peers.push(PeerId::random()); - } - - // subscribe to topic1 - gs.subscribe(topics[1].clone()); - - // the three new peers should have been added, along with 3 more from the pool. - assert!( - gs.mesh.get(&topic_hashes[1]).unwrap().len() == 6, - "Should have added 6 nodes to the mesh" - ); - let mesh_peers = gs.mesh.get(&topic_hashes[1]).unwrap(); - for new_peer in new_peers { - assert!( - mesh_peers.contains(new_peer), - "Fanout peer should be included in the mesh" - ); - } - - // there should now be 12 graft messages to be sent - let graft_messages = gs - .control_pool - .iter() - .fold(vec![], |mut collected_grafts, (_, controls)| { - for c in controls.iter() { - if let GossipsubControlAction::Graft { topic_hash: _ } = c { - collected_grafts.push(c.clone()) - } - } - collected_grafts - }); - - assert!( - graft_messages.len() == 12, - "There should be 12 grafts messages sent to peers" - ); - } - - /// Test local node publish to subscribed topic - #[test] - fn test_publish() { - // node should: - // - Send publish message to all peers - // - Insert message into gs.mcache and gs.received - - let publish_topic = String::from("test_publish"); - let (mut gs, _, topic_hashes) = - build_and_inject_nodes(20, vec![publish_topic.clone()], GossipsubConfig::default(), true); - - assert!( - gs.mesh.get(&topic_hashes[0]).is_some(), - "Subscribe should add a new entry to the mesh[topic] hashmap" - ); - - // publish on topic - let publish_data = vec![0; 42]; - gs.publish(&Topic::new(publish_topic), publish_data); - - // Collect all publish messages - let publishes = gs.events.iter().fold(vec![], |mut collected_publish, e| match e { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - for s in &event.messages { - collected_publish.push(s.clone()); - } - collected_publish - }, - _ => collected_publish, - }); - - let msg_id = (gs.config.message_id_fn)(publishes.first().expect("Should contain > 0 entries")); - - assert!( - publishes.len() == 20, - "Should send a publish message to all known peers" - ); - - assert!( - gs.mcache.get(&msg_id).is_some(), - "Message cache should contain published message" - ); - assert!( - gs.received.get(&msg_id).is_some(), - "Received cache should contain published message" - ); - } - - /// Test local node publish to unsubscribed topic - #[test] - fn test_fanout() { - // node should: - // - Populate fanout peers - // - Send publish message to fanout peers - // - Insert message into gs.mcache and gs.received - let fanout_topic = String::from("test_fanout"); - let (mut gs, _, topic_hashes) = - build_and_inject_nodes(20, vec![fanout_topic.clone()], GossipsubConfig::default(), true); - - assert!( - gs.mesh.get(&topic_hashes[0]).is_some(), - "Subscribe should add a new entry to the mesh[topic] hashmap" - ); - // Unsubscribe from topic - assert!( - gs.unsubscribe(Topic::new(fanout_topic.clone())), - "should be able to unsubscribe successfully from topic" - ); - - // Publish on unsubscribed topic - let publish_data = vec![0; 42]; - gs.publish(&Topic::new(fanout_topic.clone()), publish_data); - - assert_eq!( - gs.fanout.get(&TopicHash::from_raw(fanout_topic)).unwrap().len(), - gs.config.mesh_n, - "Fanout should contain `mesh_n` peers for fanout topic" - ); - - // Collect all publish messages - let publishes = gs.events.iter().fold(vec![], |mut collected_publish, e| match e { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - for s in &event.messages { - collected_publish.push(s.clone()); - } - collected_publish - }, - _ => collected_publish, - }); - - let msg_id = (gs.config.message_id_fn)(publishes.first().expect("Should contain > 0 entries")); - - assert_eq!( - publishes.len(), - gs.config.mesh_n, - "Should send a publish message to `mesh_n` fanout peers" - ); - - assert!( - gs.mcache.get(&msg_id).is_some(), - "Message cache should contain published message" - ); - assert!( - gs.received.get(&msg_id).is_some(), - "Received cache should contain published message" - ); - } - - #[test] - /// Test the gossipsub NetworkBehaviour peer connection logic. - fn test_inject_connected() { - let (gs, peers, topic_hashes) = build_and_inject_nodes( - 20, - vec![String::from("topic1"), String::from("topic2")], - GossipsubConfig::default(), - true, - ); - - // check that our subscriptions are sent to each of the peers - // collect all the SendEvents - let send_events: Vec<&NetworkBehaviourAction>> = gs - .events - .iter() - .filter(|e| matches!(e, NetworkBehaviourAction::NotifyHandler { .. })) - .collect(); - - // check that there are two subscriptions sent to each peer - for sevent in send_events.clone() { - if let NetworkBehaviourAction::NotifyHandler { event, .. } = sevent { - assert!( - event.subscriptions.len() == 2, - "There should be two subscriptions sent to each peer (1 for each topic)." - ); - }; - } - - // check that there are 20 send events created - assert!( - send_events.len() == 20, - "There should be a subscription event sent to each peer." - ); - - // should add the new peers to `peer_topics` with an empty vec as a gossipsub node - for peer in peers { - let known_topics = gs.peer_topics.get(&peer).unwrap(); - assert!( - known_topics == &topic_hashes, - "The topics for each node should all topics" - ); - } - } - - #[test] - /// Test subscription handling - fn test_handle_received_subscriptions() { - // For every subscription: - // SUBSCRIBE: - Add subscribed topic to peer_topics for peer. - // - Add peer to topics_peer. - // UNSUBSCRIBE - Remove topic from peer_topics for peer. - // - Remove peer from topic_peers. - - let topics = vec!["topic1", "topic2", "topic3", "topic4"] - .iter() - .map(|&t| String::from(t)) - .collect(); - let (mut gs, peers, topic_hashes) = build_and_inject_nodes(20, topics, GossipsubConfig::default(), false); - - // The first peer sends 3 subscriptions and 1 unsubscription - let mut subscriptions = topic_hashes[..3] - .iter() - .map(|topic_hash| GossipsubSubscription { - action: GossipsubSubscriptionAction::Subscribe, - topic_hash: topic_hash.clone(), - }) - .collect::>(); - - subscriptions.push(GossipsubSubscription { - action: GossipsubSubscriptionAction::Unsubscribe, - topic_hash: topic_hashes[topic_hashes.len() - 1].clone(), - }); - - let unknown_peer = PeerId::random(); - // process the subscriptions - // first and second peers send subscriptions - gs.handle_received_subscriptions(&subscriptions, &peers[0]); - gs.handle_received_subscriptions(&subscriptions, &peers[1]); - // unknown peer sends the same subscriptions - gs.handle_received_subscriptions(&subscriptions, &unknown_peer); - - // verify the result - - let peer_topics = gs.peer_topics.get(&peers[0]).unwrap().clone(); - assert!( - peer_topics == topic_hashes[..3].to_vec(), - "First peer should be subscribed to three topics" - ); - let peer_topics = gs.peer_topics.get(&peers[1]).unwrap().clone(); - assert!( - peer_topics == topic_hashes[..3].to_vec(), - "Second peer should be subscribed to three topics" - ); - - assert!( - gs.peer_topics.get(&unknown_peer).is_none(), - "Unknown peer should not have been added" - ); - - for topic_hash in topic_hashes[..3].iter() { - let topic_peers = gs.topic_peers.get(topic_hash).unwrap().clone(); - assert!( - topic_peers == peers[..2].to_vec(), - "Two peers should be added to the first three topics" - ); - } - - // Peer 0 unsubscribes from the first topic - - gs.handle_received_subscriptions( - &[GossipsubSubscription { - action: GossipsubSubscriptionAction::Unsubscribe, - topic_hash: topic_hashes[0].clone(), - }], - &peers[0], - ); - - let peer_topics = gs.peer_topics.get(&peers[0]).unwrap().clone(); - assert!( - peer_topics == topic_hashes[1..3].to_vec(), - "Peer should be subscribed to two topics" - ); - - let topic_peers = gs.topic_peers.get(&topic_hashes[0]).unwrap().clone(); // only gossipsub at the moment - assert!( - topic_peers == peers[1..2].to_vec(), - "Only the second peers should be in the first topic" - ); - } - - #[test] - /// Test Gossipsub.get_random_peers() function - fn test_get_random_peers() { - // generate a default GossipsubConfig - let gs_config = GossipsubConfig::default(); - // create a gossipsub struct - let mut gs: Gossipsub = Gossipsub::new(PeerId::random(), gs_config); - - // create a topic and fill it with some peers - let topic_hash = Topic::new("Test".into()).no_hash(); - let mut peers = vec![]; - for _ in 0..20 { - peers.push(PeerId::random()) - } - - gs.topic_peers.insert(topic_hash.clone(), peers.clone()); - - let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 5, |_| true); - assert!(random_peers.len() == 5, "Expected 5 peers to be returned"); - let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 30, |_| true); - assert!(random_peers.len() == 20, "Expected 20 peers to be returned"); - assert!(random_peers == peers, "Expected no shuffling"); - let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 20, |_| true); - assert!(random_peers.len() == 20, "Expected 20 peers to be returned"); - assert!(random_peers == peers, "Expected no shuffling"); - let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 0, |_| true); - assert!(random_peers.is_empty(), "Expected 0 peers to be returned"); - // test the filter - let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 5, |_| false); - assert!(random_peers.is_empty(), "Expected 0 peers to be returned"); - let random_peers = Gossipsub::get_random_peers(&gs.topic_peers, &topic_hash, 10, |peer| peers.contains(peer)); - assert!(random_peers.len() == 10, "Expected 10 peers to be returned"); - } - - /// Tests that the correct message is sent when a peer asks for a message in our cache. - #[test] - fn test_handle_iwant_msg_cached() { - let (mut gs, peers, _) = build_and_inject_nodes(20, Vec::new(), GossipsubConfig::default(), true); - - let id = gs.config.message_id_fn; - - let message = GossipsubMessage { - source: peers[11], - data: vec![1, 2, 3, 4], - sequence_number: 1u64, - topics: Vec::new(), - }; - let msg_id = id(&message); - gs.mcache.put(message); - - gs.handle_iwant(&peers[7], vec![msg_id.clone()]); - - // the messages we are sending - let sent_messages = gs.events.iter().fold(vec![], |mut collected_messages, e| match e { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - for c in &event.messages { - collected_messages.push(c.clone()) - } - collected_messages - }, - _ => collected_messages, - }); - - assert!( - sent_messages.iter().any(|msg| id(msg) == msg_id), - "Expected the cached message to be sent to an IWANT peer" - ); - } - - /// Tests that messages are sent correctly depending on the shifting of the message cache. - #[test] - fn test_handle_iwant_msg_cached_shifted() { - let (mut gs, peers, _) = build_and_inject_nodes(20, Vec::new(), GossipsubConfig::default(), true); - - let id = gs.config.message_id_fn; - // perform 10 memshifts and check that it leaves the cache - for shift in 1..10 { - let message = GossipsubMessage { - source: peers[11], - data: vec![1, 2, 3, 4], - sequence_number: shift, - topics: Vec::new(), - }; - let msg_id = id(&message); - gs.mcache.put(message.clone()); - for _ in 0..shift { - gs.mcache.shift(); - } - - gs.handle_iwant(&peers[7], vec![msg_id.clone()]); - - // is the message is being sent? - let message_exists = gs.events.iter().any(|e| match e { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - event.messages.iter().any(|msg| id(msg) == msg_id) - }, - _ => false, - }); - // default history_length is 5, expect no messages after shift > 5 - if shift < 5 { - assert!( - message_exists, - "Expected the cached message to be sent to an IWANT peer before 5 shifts" - ); - } else { - assert!( - !message_exists, - "Expected the cached message to not be sent to an IWANT peer after 5 shifts" - ); - } - } - } - - #[test] - // tests that an event is not created when a peers asks for a message not in our cache - fn test_handle_iwant_msg_not_cached() { - let (mut gs, peers, _) = build_and_inject_nodes(20, Vec::new(), GossipsubConfig::default(), true); - - let events_before = gs.events.len(); - gs.handle_iwant(&peers[7], vec![MessageId(String::from("unknown id"))]); - let events_after = gs.events.len(); - - assert_eq!(events_before, events_after, "Expected event count to stay the same"); - } - - #[test] - // tests that an event is created when a peer shares that it has a message we want - fn test_handle_ihave_subscribed_and_msg_not_cached() { - let (mut gs, peers, topic_hashes) = - build_and_inject_nodes(20, vec![String::from("topic1")], GossipsubConfig::default(), true); - - gs.handle_ihave(&peers[7], vec![(topic_hashes[0].clone(), vec![MessageId( - String::from("unknown id"), - )])]); - - // check that we sent an IWANT request for `unknown id` - let iwant_exists = match gs.control_pool.get(&peers[7]) { - Some(controls) => controls.iter().any(|c| match c { - GossipsubControlAction::IWant { message_ids } => - { - #[allow(clippy::cmp_owned)] - message_ids.iter().any(|m| *m.0 == String::from("unknown id")) - }, - _ => false, - }), - _ => false, - }; - - assert!( - iwant_exists, - "Expected to send an IWANT control message for unkown message id" - ); - } - - #[test] - // tests that an event is not created when a peer shares that it has a message that - // we already have - fn test_handle_ihave_subscribed_and_msg_cached() { - let (mut gs, peers, topic_hashes) = - build_and_inject_nodes(20, vec![String::from("topic1")], GossipsubConfig::default(), true); - - let msg_id = MessageId(String::from("known id")); - gs.received.insert(msg_id.clone(), SmallVec::new()); - - let events_before = gs.events.len(); - gs.handle_ihave(&peers[7], vec![(topic_hashes[0].clone(), vec![msg_id])]); - let events_after = gs.events.len(); - - assert_eq!(events_before, events_after, "Expected event count to stay the same") - } - - #[test] - // test that an event is not created when a peer shares that it has a message in - // a topic that we are not subscribed to - fn test_handle_ihave_not_subscribed() { - let (mut gs, peers, _) = build_and_inject_nodes(20, vec![], GossipsubConfig::default(), true); - - let events_before = gs.events.len(); - gs.handle_ihave(&peers[7], vec![( - TopicHash::from_raw(String::from("unsubscribed topic")), - vec![MessageId(String::from("irrelevant id"))], - )]); - let events_after = gs.events.len(); - - assert_eq!(events_before, events_after, "Expected event count to stay the same") - } - - #[test] - // tests that a peer is added to our mesh when we are both subscribed - // to the same topic - fn test_handle_graft_is_subscribed() { - let (mut gs, peers, topic_hashes) = - build_and_inject_nodes(20, vec![String::from("topic1")], GossipsubConfig::default(), true); - - gs.handle_graft(&peers[7], topic_hashes.clone()); - - assert!( - gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]), - "Expected peer to have been added to mesh" - ); - } - - #[test] - // tests that a peer is not added to our mesh when they are subscribed to - // a topic that we are not - fn test_handle_graft_is_not_subscribed() { - let (mut gs, peers, topic_hashes) = - build_and_inject_nodes(20, vec![String::from("topic1")], GossipsubConfig::default(), true); - - gs.handle_graft(&peers[7], vec![TopicHash::from_raw(String::from("unsubscribed topic"))]); - - assert!( - gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]), - "Expected peer to have been added to mesh" - ); - } - - #[test] - // tests multiple topics in a single graft message - fn test_handle_graft_multiple_topics() { - let topics: Vec = vec!["topic1", "topic2", "topic3", "topic4"] - .iter() - .map(|&t| String::from(t)) - .collect(); - - let (mut gs, peers, topic_hashes) = build_and_inject_nodes(20, topics, GossipsubConfig::default(), true); - - let mut their_topics = topic_hashes.clone(); - // their_topics = [topic1, topic2, topic3] - // our_topics = [topic1, topic2, topic4] - their_topics.pop(); - gs.leave(&their_topics[2]); - - gs.handle_graft(&peers[7], their_topics.clone()); - - for item in topic_hashes.iter().take(2) { - assert!( - gs.mesh.get(item).unwrap().contains(&peers[7]), - "Expected peer to be in the mesh for the first 2 topics" - ); - } - - assert!( - gs.mesh.get(&topic_hashes[2]).is_none(), - "Expected the second topic to not be in the mesh" - ); - } - - #[test] - // tests that a peer is removed from our mesh - fn test_handle_prune_peer_in_mesh() { - let (mut gs, peers, topic_hashes) = - build_and_inject_nodes(20, vec![String::from("topic1")], GossipsubConfig::default(), true); - - // insert peer into our mesh for 'topic1' - gs.mesh.insert(topic_hashes[0].clone(), peers.clone()); - assert!( - gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]), - "Expected peer to be in mesh" - ); - - gs.handle_prune(&peers[7], topic_hashes.clone()); - assert!( - !gs.mesh.get(&topic_hashes[0]).unwrap().contains(&peers[7]), - "Expected peer to be removed from mesh" - ); - } - - #[test] - fn test_maintain_relays_mesh_n_high() { - let peer_no = 20; - let (mut gs, peers, _) = build_and_inject_nodes(peer_no, vec![], GossipsubConfig::default(), false); - for peer in peers { - gs.relays_mesh.insert(peer, 0); - } - assert_eq!(peer_no, gs.relays_mesh.len(), "relays mesh must contain 20 peers"); - gs.maintain_relays_mesh(); - assert_eq!( - gs.config.mesh_n, - gs.relays_mesh.len(), - "relays mesh must contain mesh_n peers after maintenance" - ); - assert_eq!(gs.events.len(), 14); - for event in gs.events { - match event { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - assert_eq!(event.control_msgs, vec![GossipsubControlAction::IncludedToRelaysMesh { - included: false, - mesh_size: gs.relays_mesh.len(), - }]); - }, - _ => panic!("Invalid NetworkBehaviourAction variant"), - } - } - } - - #[test] - fn test_maintain_relays_mesh_n_low() { - let peer_no = 20; - let (mut gs, peers, _) = build_and_inject_nodes(peer_no, vec![], GossipsubConfig::default(), false); - for (i, peer) in peers.into_iter().enumerate() { - if i < 3 { - gs.relays_mesh.insert(peer, 0); - } else { - gs.connected_relays.insert(peer); - } - } - assert_eq!(3, gs.relays_mesh.len(), "relays mesh must contain 3 peers"); - gs.maintain_relays_mesh(); - assert_eq!( - 4, - gs.relays_mesh.len(), - "relays mesh must contain 1 more peer after maintenance (4 total)" - ); - assert_eq!(gs.events.len(), 1); - for event in gs.events { - match event { - NetworkBehaviourAction::NotifyHandler { event, .. } => { - assert_eq!(event.control_msgs, vec![GossipsubControlAction::IncludedToRelaysMesh { - included: true, - mesh_size: gs.relays_mesh.len(), - }]); - }, - _ => panic!("Invalid NetworkBehaviourAction variant"), - } - } - } - - #[test] - fn test_process_included_to_relays_mesh() { - let peer_no = 2; - let config = GossipsubConfigBuilder::default().i_am_relay(true).build(); - let (mut gs, peers, _) = build_and_inject_nodes(peer_no, vec![], config, false); - for peer in &peers { - gs.connected_relays.insert(*peer); - } - - gs.handle_included_to_relays_mesh(&peers[0], true, 1); - assert!(gs.relays_mesh.contains_key(&peers[0])); - - gs.handle_included_to_relays_mesh(&peers[0], false, 1); - assert!(!gs.relays_mesh.contains_key(&peers[0])); - } - - #[test] - fn test_process_included_to_relays_mesh_n_high_exceeded() { - let peer_no = 14; - let config = GossipsubConfigBuilder::default().i_am_relay(true).build(); - let (mut gs, peers, _) = build_and_inject_nodes(peer_no, vec![], config, false); - for (i, peer) in peers.iter().enumerate() { - gs.connected_relays.insert(*peer); - if i < 13 { - gs.relays_mesh.insert(*peer, 0); - } - } - - gs.handle_included_to_relays_mesh(&peers[13], true, 1); - assert!(!gs.relays_mesh.contains_key(&peers[13])); - - match gs.events.pop_back().unwrap() { - NetworkBehaviourAction::NotifyHandler { event, peer_id, .. } => { - assert_eq!(event.control_msgs, vec![GossipsubControlAction::IncludedToRelaysMesh { - included: false, - mesh_size: gs.relay_mesh_len(), - }]); - assert_eq!(peer_id, peers[13]); - }, - _ => panic!("Invalid NetworkBehaviourAction variant"), - } - } -} diff --git a/mm2src/gossipsub/src/config.rs b/mm2src/gossipsub/src/config.rs deleted file mode 100644 index f74343e21d..0000000000 --- a/mm2src/gossipsub/src/config.rs +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::protocol::{GossipsubMessage, MessageId}; -use std::borrow::Cow; -use std::time::Duration; - -/// If the `no_source_id` flag is set, the IDENTITY_SOURCE value is used as the source of the -/// packet. -pub const IDENTITY_SOURCE: [u8; 3] = [0, 1, 0]; - -/// Configuration parameters that define the performance of the gossipsub network. -#[derive(Clone)] -pub struct GossipsubConfig { - /// The protocol id to negotiate this protocol (default is `/meshsub/1.0.0`). - pub protocol_id: Cow<'static, [u8]>, - - // Overlay network parameters. - /// Number of heartbeats to keep in the `memcache` (default is 5). - pub history_length: usize, - - /// Number of past heartbeats to gossip about (default is 3). - pub history_gossip: usize, - - /// Target number of peers for the mesh network (D in the spec, default is 6). - pub mesh_n: usize, - - /// Minimum number of peers in mesh network before adding more (D_lo in the spec, default is 4). - pub mesh_n_low: usize, - - /// Maximum number of peers in mesh network before removing some (D_high in the spec, default - /// is 12). - pub mesh_n_high: usize, - - /// Number of peers to emit gossip to during a heartbeat (D_lazy in the spec, default is 6). - pub gossip_lazy: usize, - - /// Initial delay in each heartbeat (default is 5 seconds). - pub heartbeat_initial_delay: Duration, - - /// Time between each heartbeat (default is 1 second). - pub heartbeat_interval: Duration, - - /// Time to live for fanout peers (default is 60 seconds). - pub fanout_ttl: Duration, - - /// The maximum byte size for each gossip (default is 2048 bytes). - pub max_transmit_size: usize, - - /// Duplicates are prevented by storing message id's of known messages in an LRU time cache. - /// This settings sets the time period that messages are stored in the cache. Duplicates can be - /// received if duplicate messages are sent at a time greater than this setting apart. The - /// default is 1 minute. - pub duplicate_cache_time: Duration, - - /// Flag determining if gossipsub topics are hashed or sent as plain strings (default is false). - pub hash_topics: bool, - - /// When set, all published messages will have a 0 source `PeerId` (default is false). - pub no_source_id: bool, - - /// When set to `true`, prevents automatic forwarding of all received messages. This setting - /// allows a user to validate the messages before propagating them to their peers. If set to - /// true, the user must manually call `propagate_message()` on the behaviour to forward message - /// once validated (default is false). - pub manual_propagation: bool, - - /// A user-defined function allowing the user to specify the message id of a gossipsub message. - /// The default value is to concatenate the source peer id with a sequence number. Setting this - /// parameter allows the user to address packets arbitrarily. One example is content based - /// addressing, where this function may be set to `hash(message)`. This would prevent messages - /// of the same content from being duplicated. - /// - /// The function takes a `GossipsubMessage` as input and outputs a String to be interpreted as - /// the message id. - pub message_id_fn: fn(&GossipsubMessage) -> MessageId, - - pub i_am_relay: bool, -} - -impl Default for GossipsubConfig { - fn default() -> GossipsubConfig { - GossipsubConfig { - protocol_id: Cow::Borrowed(b"/meshsub/1.0.0"), - history_length: 5, - history_gossip: 3, - mesh_n: 6, - mesh_n_low: 4, - mesh_n_high: 12, - gossip_lazy: 6, // default to mesh_n - heartbeat_initial_delay: Duration::from_secs(5), - heartbeat_interval: Duration::from_secs(1), - fanout_ttl: Duration::from_secs(60), - max_transmit_size: 2048, - duplicate_cache_time: Duration::from_secs(60), - hash_topics: false, // default compatibility with floodsub - no_source_id: false, - manual_propagation: false, - message_id_fn: |message| { - // default message id is: source + sequence number - let mut source_string = message.source.to_base58(); - source_string.push_str(&message.sequence_number.to_string()); - MessageId(source_string) - }, - i_am_relay: false, - } - } -} - -#[derive(Default)] -pub struct GossipsubConfigBuilder { - config: GossipsubConfig, -} - -impl GossipsubConfigBuilder { - // set default values - pub fn new() -> GossipsubConfigBuilder { GossipsubConfigBuilder::default() } - - pub fn protocol_id(&mut self, protocol_id: impl Into>) -> &mut Self { - self.config.protocol_id = protocol_id.into(); - self - } - - pub fn history_length(&mut self, history_length: usize) -> &mut Self { - assert!( - history_length >= self.config.history_gossip, - "The history_length must be greater than or equal to the history_gossip length" - ); - self.config.history_length = history_length; - self - } - - pub fn history_gossip(&mut self, history_gossip: usize) -> &mut Self { - assert!( - self.config.history_length >= history_gossip, - "The history_length must be greater than or equal to the history_gossip length" - ); - self.config.history_gossip = history_gossip; - self - } - - pub fn mesh_n(&mut self, mesh_n: usize) -> &mut Self { - assert!( - self.config.mesh_n_low <= mesh_n && mesh_n <= self.config.mesh_n_high, - "The following equality doesn't hold mesh_n_low <= mesh_n <= mesh_n_high" - ); - self.config.mesh_n = mesh_n; - self - } - - pub fn mesh_n_low(&mut self, mesh_n_low: usize) -> &mut Self { - assert!( - mesh_n_low <= self.config.mesh_n && self.config.mesh_n <= self.config.mesh_n_high, - "The following equality doesn't hold mesh_n_low <= mesh_n <= mesh_n_high" - ); - self.config.mesh_n_low = mesh_n_low; - self - } - - pub fn mesh_n_high(&mut self, mesh_n_high: usize) -> &mut Self { - assert!( - self.config.mesh_n_low <= self.config.mesh_n && self.config.mesh_n <= mesh_n_high, - "The following equality doesn't hold mesh_n_low <= mesh_n <= mesh_n_high" - ); - self.config.mesh_n_high = mesh_n_high; - self - } - - pub fn gossip_lazy(&mut self, gossip_lazy: usize) -> &mut Self { - self.config.gossip_lazy = gossip_lazy; - self - } - - pub fn heartbeat_initial_delay(&mut self, heartbeat_initial_delay: Duration) -> &mut Self { - self.config.heartbeat_initial_delay = heartbeat_initial_delay; - self - } - pub fn heartbeat_interval(&mut self, heartbeat_interval: Duration) -> &mut Self { - self.config.heartbeat_interval = heartbeat_interval; - self - } - pub fn fanout_ttl(&mut self, fanout_ttl: Duration) -> &mut Self { - self.config.fanout_ttl = fanout_ttl; - self - } - pub fn max_transmit_size(&mut self, max_transmit_size: usize) -> &mut Self { - self.config.max_transmit_size = max_transmit_size; - self - } - - pub fn hash_topics(&mut self) -> &mut Self { - self.config.hash_topics = true; - self - } - - pub fn no_source_id(&mut self) -> &mut Self { - self.config.no_source_id = true; - self - } - - pub fn manual_propagation(&mut self) -> &mut Self { - self.config.manual_propagation = true; - self - } - - pub fn message_id_fn(&mut self, id_fn: fn(&GossipsubMessage) -> MessageId) -> &mut Self { - self.config.message_id_fn = id_fn; - self - } - - pub fn i_am_relay(&mut self, i_am_relay: bool) -> &mut Self { - self.config.i_am_relay = i_am_relay; - self - } - - pub fn build(&self) -> GossipsubConfig { self.config.clone() } -} - -impl std::fmt::Debug for GossipsubConfig { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let mut builder = f.debug_struct("GossipsubConfig"); - let _ = builder.field("protocol_id", &self.protocol_id); - let _ = builder.field("history_length", &self.history_length); - let _ = builder.field("history_gossip", &self.history_gossip); - let _ = builder.field("mesh_n", &self.mesh_n); - let _ = builder.field("mesh_n_low", &self.mesh_n_low); - let _ = builder.field("mesh_n_high", &self.mesh_n_high); - let _ = builder.field("gossip_lazy", &self.gossip_lazy); - let _ = builder.field("heartbeat_initial_delay", &self.heartbeat_initial_delay); - let _ = builder.field("heartbeat_interval", &self.heartbeat_interval); - let _ = builder.field("fanout_ttl", &self.fanout_ttl); - let _ = builder.field("max_transmit_size", &self.max_transmit_size); - let _ = builder.field("hash_topics", &self.hash_topics); - let _ = builder.field("no_source_id", &self.no_source_id); - let _ = builder.field("manual_propagation", &self.manual_propagation); - let _ = builder.field("i_am_relay", &self.i_am_relay); - builder.finish() - } -} diff --git a/mm2src/gossipsub/src/handler.rs b/mm2src/gossipsub/src/handler.rs deleted file mode 100644 index 2b71cf13dd..0000000000 --- a/mm2src/gossipsub/src/handler.rs +++ /dev/null @@ -1,315 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::behaviour::GossipsubRpc; -use crate::protocol::{GossipsubCodec, ProtocolConfig}; -use futures::prelude::*; -use futures_codec::Framed; -use libp2p_core::upgrade::{InboundUpgrade, OutboundUpgrade}; -use libp2p_swarm::handler::{ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerUpgrErr, KeepAlive, - SubstreamProtocol}; -use libp2p_swarm::NegotiatedSubstream; -use log::{debug, error, trace, warn}; -use smallvec::SmallVec; -use std::{borrow::Cow, - io, - pin::Pin, - task::{Context, Poll}}; - -/// Protocol Handler that manages a single long-lived substream with a peer. -pub struct GossipsubHandler { - /// Upgrade configuration for the gossipsub protocol. - listen_protocol: SubstreamProtocol, - - /// The single long-lived outbound substream. - outbound_substream: Option, - - /// The single long-lived inbound substream. - inbound_substream: Option, - - /// Queue of values that we want to send to the remote. - send_queue: SmallVec<[GossipsubRpc; 16]>, - - /// Flag indicating that an outbound substream is being established to prevent duplicate - /// requests. - outbound_substream_establishing: bool, - - /// Flag determining whether to maintain the connection to the peer. - keep_alive: KeepAlive, -} - -/// State of the inbound substream, opened either by us or by the remote. -#[allow(clippy::large_enum_variant)] -enum InboundSubstreamState { - /// Waiting for a message from the remote. The idle state for an inbound substream. - WaitingInput(Framed), - /// The substream is being closed. - Closing(Framed), - /// An error occurred during processing. - Poisoned, -} - -/// State of the outbound substream, opened either by us or by the remote. -#[allow(clippy::large_enum_variant)] -enum OutboundSubstreamState { - /// Waiting for the user to send a message. The idle state for an outbound substream. - WaitingOutput(Framed), - /// Waiting to send a message to the remote. - PendingSend(Framed, GossipsubRpc), - /// Waiting to flush the substream so that the data arrives to the remote. - PendingFlush(Framed), - /// The substream is being closed. Used by either substream. - _Closing(Framed), - /// An error occurred during processing. - Poisoned, -} - -impl GossipsubHandler { - /// Builds a new `GossipsubHandler`. - pub fn new(protocol_id: impl Into>, max_transmit_size: usize) -> Self { - GossipsubHandler { - listen_protocol: SubstreamProtocol::new(ProtocolConfig::new(protocol_id, max_transmit_size), ()), - inbound_substream: None, - outbound_substream: None, - send_queue: SmallVec::new(), - keep_alive: KeepAlive::Yes, - outbound_substream_establishing: false, - } - } -} - -impl Default for GossipsubHandler { - fn default() -> Self { - GossipsubHandler { - listen_protocol: SubstreamProtocol::new(ProtocolConfig::default(), ()), - inbound_substream: None, - outbound_substream: None, - send_queue: SmallVec::new(), - keep_alive: KeepAlive::Yes, - outbound_substream_establishing: false, - } - } -} - -impl ConnectionHandler for GossipsubHandler { - type InEvent = GossipsubRpc; - type OutEvent = GossipsubRpc; - type Error = io::Error; - type InboundProtocol = ProtocolConfig; - type OutboundProtocol = ProtocolConfig; - type OutboundOpenInfo = GossipsubRpc; - type InboundOpenInfo = (); - - fn listen_protocol(&self) -> SubstreamProtocol { - self.listen_protocol.clone() - } - - fn inject_fully_negotiated_inbound( - &mut self, - substream: >::Output, - _info: Self::InboundOpenInfo, - ) { - // new inbound substream. Replace the current one, if it exists. - trace!("New inbound substream request"); - self.inbound_substream = Some(InboundSubstreamState::WaitingInput(substream)); - } - - fn inject_fully_negotiated_outbound( - &mut self, - substream: >::Output, - message: Self::OutboundOpenInfo, - ) { - self.outbound_substream_establishing = false; - // Should never establish a new outbound substream if one already exists. - // If this happens, an outbound message is not sent. - if self.outbound_substream.is_some() { - warn!("Established an outbound substream with one already available"); - // Add the message back to the send queue - self.send_queue.push(message); - } else { - self.outbound_substream = Some(OutboundSubstreamState::PendingSend(substream, message)); - } - } - - fn inject_event(&mut self, message: GossipsubRpc) { self.send_queue.push(message); } - - fn inject_dial_upgrade_error( - &mut self, - _: Self::OutboundOpenInfo, - _: ConnectionHandlerUpgrErr<>::Error>, - ) { - self.outbound_substream_establishing = false; - // Ignore upgrade errors for now. - // If a peer doesn't support this protocol, this will just ignore them, but not disconnect - // them. - } - - fn connection_keep_alive(&self) -> KeepAlive { self.keep_alive } - - #[allow(clippy::type_complexity)] - fn poll( - &mut self, - cx: &mut Context, - ) -> Poll> { - // determine if we need to create the stream - if !self.send_queue.is_empty() && self.outbound_substream.is_none() && !self.outbound_substream_establishing { - let message = self.send_queue.remove(0); - self.send_queue.shrink_to_fit(); - self.outbound_substream_establishing = true; - return Poll::Ready(ConnectionHandlerEvent::OutboundSubstreamRequest { - protocol: self.listen_protocol.clone().map_info(|()| message), - }); - } - - loop { - match std::mem::replace(&mut self.inbound_substream, Some(InboundSubstreamState::Poisoned)) { - // inbound idle state - Some(InboundSubstreamState::WaitingInput(mut substream)) => { - match substream.poll_next_unpin(cx) { - Poll::Ready(Some(Ok(message))) => { - self.inbound_substream = Some(InboundSubstreamState::WaitingInput(substream)); - return Poll::Ready(ConnectionHandlerEvent::Custom(message)); - }, - Poll::Ready(Some(Err(e))) => { - debug!("Inbound substream error while awaiting input: {:?}", e); - self.inbound_substream = Some(InboundSubstreamState::Closing(substream)); - }, - // peer closed the stream - Poll::Ready(None) => { - self.inbound_substream = Some(InboundSubstreamState::Closing(substream)); - }, - Poll::Pending => { - self.inbound_substream = Some(InboundSubstreamState::WaitingInput(substream)); - break; - }, - } - }, - Some(InboundSubstreamState::Closing(mut substream)) => { - match Sink::poll_close(Pin::new(&mut substream), cx) { - Poll::Ready(res) => { - if let Err(e) = res { - // Don't close the connection but just drop the inbound substream. - // In case the remote has more to send, they will open up a new - // substream. - debug!("Inbound substream error while closing: {:?}", e); - } - - self.inbound_substream = None; - if self.outbound_substream.is_none() { - self.keep_alive = KeepAlive::No; - } - break; - }, - Poll::Pending => { - self.inbound_substream = Some(InboundSubstreamState::Closing(substream)); - break; - }, - } - }, - None => { - self.inbound_substream = None; - break; - }, - Some(InboundSubstreamState::Poisoned) => panic!("Error occurred during inbound stream processing"), - } - } - - loop { - match std::mem::replace(&mut self.outbound_substream, Some(OutboundSubstreamState::Poisoned)) { - // outbound idle state - Some(OutboundSubstreamState::WaitingOutput(substream)) => { - if !self.send_queue.is_empty() { - let message = self.send_queue.remove(0); - self.send_queue.shrink_to_fit(); - self.outbound_substream = Some(OutboundSubstreamState::PendingSend(substream, message)); - } else { - self.outbound_substream = Some(OutboundSubstreamState::WaitingOutput(substream)); - break; - } - }, - Some(OutboundSubstreamState::PendingSend(mut substream, message)) => { - match Sink::poll_ready(Pin::new(&mut substream), cx) { - Poll::Ready(Ok(())) => match Sink::start_send(Pin::new(&mut substream), message) { - Ok(()) => self.outbound_substream = Some(OutboundSubstreamState::PendingFlush(substream)), - Err(e) => { - if let io::ErrorKind::PermissionDenied = e.kind() { - error!("Message over the maximum transmission limit was not sent."); - self.outbound_substream = Some(OutboundSubstreamState::WaitingOutput(substream)); - } else { - return Poll::Ready(ConnectionHandlerEvent::Close(e)); - } - }, - }, - Poll::Ready(Err(e)) => { - debug!("Outbound substream error while sending output: {:?}", e); - return Poll::Ready(ConnectionHandlerEvent::Close(e)); - }, - Poll::Pending => { - self.outbound_substream = Some(OutboundSubstreamState::PendingSend(substream, message)); - break; - }, - } - }, - Some(OutboundSubstreamState::PendingFlush(mut substream)) => { - match Sink::poll_flush(Pin::new(&mut substream), cx) { - Poll::Ready(Ok(())) => { - self.outbound_substream = Some(OutboundSubstreamState::WaitingOutput(substream)) - }, - Poll::Ready(Err(e)) => return Poll::Ready(ConnectionHandlerEvent::Close(e)), - Poll::Pending => { - self.outbound_substream = Some(OutboundSubstreamState::PendingFlush(substream)); - break; - }, - } - }, - // Currently never used - manual shutdown may implement this in the future - Some(OutboundSubstreamState::_Closing(mut substream)) => { - match Sink::poll_close(Pin::new(&mut substream), cx) { - Poll::Ready(Ok(())) => { - self.outbound_substream = None; - if self.inbound_substream.is_none() { - self.keep_alive = KeepAlive::No; - } - break; - }, - Poll::Ready(Err(e)) => { - debug!("Outbound substream error while closing: {:?}", e); - return Poll::Ready(ConnectionHandlerEvent::Close(io::Error::new( - io::ErrorKind::BrokenPipe, - "Failed to close outbound substream", - ))); - }, - Poll::Pending => { - self.outbound_substream = Some(OutboundSubstreamState::_Closing(substream)); - break; - }, - } - }, - None => { - self.outbound_substream = None; - break; - }, - Some(OutboundSubstreamState::Poisoned) => panic!("Error occurred during outbound stream processing"), - } - } - - Poll::Pending - } -} diff --git a/mm2src/gossipsub/src/lib.rs b/mm2src/gossipsub/src/lib.rs deleted file mode 100644 index e0efa95571..0000000000 --- a/mm2src/gossipsub/src/lib.rs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Gossipsub is a P2P pubsub (publish/subscription) routing layer designed to extend upon -//! flooodsub and meshsub routing protocols. -//! -//! # Overview -//! -//! *Note: The gossipsub protocol specifications -//! (https://github.com/libp2p/specs/tree/master/pubsub/gossipsub) provide an outline for the -//! routing protocol. They should be consulted for further detail.* -//! -//! Gossipsub is a blend of meshsub for data and randomsub for mesh metadata. It provides bounded -//! degree and amplification factor with the meshsub construction and augments it using gossip -//! propagation of metadata with the randomsub technique. -//! -//! The router maintains an overlay mesh network of peers on which to efficiently send messages and -//! metadata. Peers use control messages to broadcast and request known messages and -//! subscribe/unsubscribe from topics in the mesh network. -//! -//! # Important Discrepancies -//! -//! This section outlines the current implementation's potential discrepancies from that of other -//! implementations, due to undefined elements in the current specification. -//! -//! - **Topics** - In gossipsub, topics configurable by the `hash_topics` configuration parameter. -//! Topics are of type `TopicHash`. The current go implementation uses raw utf-8 strings, and this -//! is default configuration in rust-libp2p. Topics can be hashed (SHA256 hashed then base64 -//! encoded) by setting the `hash_topics` configuration parameter to true. -//! -//! - **Sequence Numbers** - A message on the gossipsub network is identified by the source -//! `PeerId` and a nonce (sequence number) of the message. The sequence numbers in this -//! implementation are sent as raw bytes across the wire. They are 64-bit big-endian unsigned -//! integers. They are chosen at random in this implementation of gossipsub, but are sequential in -//! the current go implementation. -//! -//! # Using Gossipsub -//! -//! ## GossipsubConfig -//! -//! The [`GossipsubConfig`] struct specifies various network performance/tuning configuration -//! parameters. Specifically it specifies: -//! -//! [`GossipsubConfig`]: struct.GossipsubConfig.html -//! -//! - `protocol_id` - The protocol id that this implementation will accept connections on. -//! - `history_length` - The number of heartbeats which past messages are kept in cache (default: 5). -//! - `history_gossip` - The number of past heartbeats that the node will send gossip metadata -//! about (default: 3). -//! - `mesh_n` - The target number of peers store in the local mesh network. -//! (default: 6). -//! - `mesh_n_low` - The minimum number of peers in the local mesh network before. -//! trying to add more peers to the mesh from the connected peer pool (default: 4). -//! - `mesh_n_high` - The maximum number of peers in the local mesh network before removing peers to -//! reach `mesh_n` peers (default: 12). -//! - `gossip_lazy` - The number of peers that the local node will gossip to during a heartbeat (default: `mesh_n` = 6). -//! - `heartbeat_initial_delay - The initial time delay before starting the first heartbeat (default: 5 seconds). -//! - `heartbeat_interval` - The time between each heartbeat (default: 1 second). -//! - `fanout_ttl` - The fanout time to live time period. The timeout required before removing peers from the fanout -//! for a given topic (default: 1 minute). -//! - `max_transmit_size` - This sets the maximum transmission size for total gossipsub messages on the network. -//! - `hash_topics` - Whether to hash the topics using base64(SHA256(topic)) or to leave as plain utf-8 strings. -//! - `manual_propagation` - Whether gossipsub should immediately forward received messages on the -//! network. For applications requiring message validation, this should be set to false, then the -//! application should call `propagate_message(message_id, propagation_source)` once validated, to -//! propagate the message to peers. -//! -//! This struct implements the `Default` trait and can be initialised via -//! `GossipsubConfig::default()`. -//! -//! -//! ## Gossipsub -//! -//! The [`Gossipsub`] struct implements the `NetworkBehaviour` trait allowing it to act as the -//! routing behaviour in a `Swarm`. This struct requires an instance of `PeerId` and -//! [`GossipsubConfig`]. -//! -//! [`Gossipsub`]: struct.Gossipsub.html - -//! ## Example -//! -//! An example of initialising a gossipsub compatible swarm: -//! -//! ```ignore -//! #extern crate libp2p; -//! #extern crate futures; -//! #extern crate tokio; -//! #use libp2p::gossipsub::GossipsubEvent; -//! #use libp2p::{gossipsub, secio, -//! # tokio_codec::{FramedRead, LinesCodec}, -//! #}; -//! let local_key = secio::SecioKeyPair::ed25519_generated().unwrap(); -//! let local_pub_key = local_key.to_public_key(); -//! -//! // Set up an encrypted TCP Transport over the Mplex and Yamux protocols -//! let transport = libp2p::build_development_transport(local_key); -//! -//! // Create a Floodsub/Gossipsub topic -//! let topic = libp2p::floodsub::TopicBuilder::new("example").build(); -//! -//! // Create a Swarm to manage peers and events -//! let mut swarm = { -//! // set default parameters for gossipsub -//! let gossipsub_config = gossipsub::GossipsubConfig::default(); -//! // build a gossipsub network behaviour -//! let mut gossipsub = -//! gossipsub::Gossipsub::new(local_pub_key.clone().into_peer_id(), gossipsub_config); -//! gossipsub.subscribe(topic.clone()); -//! libp2p::Swarm::new( -//! transport, -//! gossipsub, -//! libp2p::core::topology::MemoryTopology::empty(local_pub_key), -//! ) -//! }; -//! -//! // Listen on all interfaces and whatever port the OS assigns -//! let addr = libp2p::Swarm::listen_on(&mut swarm, "/ip4/0.0.0.0/tcp/0".parse().unwrap()).unwrap(); -//! println!("Listening on {:?}", addr); -//! ``` - -pub mod protocol; - -mod behaviour; -mod config; -mod handler; -mod mcache; -mod topic; - -mod rpc_proto { - include!(concat!(env!("OUT_DIR"), "/gossipsub.pb.rs")); -} - -pub use self::behaviour::{Gossipsub, GossipsubEvent, GossipsubRpc}; -pub use self::config::{GossipsubConfig, GossipsubConfigBuilder}; -pub use self::protocol::{GossipsubMessage, MessageId}; -pub use self::topic::{Topic, TopicHash}; diff --git a/mm2src/gossipsub/src/mcache.rs b/mm2src/gossipsub/src/mcache.rs deleted file mode 100644 index fe92cd3c93..0000000000 --- a/mm2src/gossipsub/src/mcache.rs +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -extern crate fnv; - -use crate::protocol::{GossipsubMessage, MessageId}; -use crate::topic::TopicHash; -use std::collections::HashMap; - -/// CacheEntry stored in the history. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct CacheEntry { - mid: MessageId, - topics: Vec, -} - -/// MessageCache struct holding history of messages. -#[derive(Clone)] -pub struct MessageCache { - msgs: HashMap, - history: Vec>, - gossip: usize, - msg_id: fn(&GossipsubMessage) -> MessageId, -} - -/// Implementation of the MessageCache. -impl MessageCache { - pub fn new(gossip: usize, history_capacity: usize, msg_id: fn(&GossipsubMessage) -> MessageId) -> MessageCache { - MessageCache { - gossip, - msgs: HashMap::default(), - history: vec![Vec::new(); history_capacity], - msg_id, - } - } - - /// Creates a `MessageCache` with a default message id function. - #[allow(dead_code)] - pub fn new_default(gossip: usize, history_capacity: usize) -> MessageCache { - let default_id = |message: &GossipsubMessage| { - // default message id is: source + sequence number - let mut source_string = message.source.to_base58(); - source_string.push_str(&message.sequence_number.to_string()); - MessageId(source_string) - }; - MessageCache { - gossip, - msgs: HashMap::default(), - history: vec![Vec::new(); history_capacity], - msg_id: default_id, - } - } - - /// Put a message into the memory cache - pub fn put(&mut self, msg: GossipsubMessage) { - let message_id = (self.msg_id)(&msg); - let cache_entry = CacheEntry { - mid: message_id.clone(), - topics: msg.topics.clone(), - }; - - self.msgs.insert(message_id, msg); - - self.history[0].push(cache_entry); - } - - /// Get a message with `message_id` - pub fn get(&self, message_id: &MessageId) -> Option<&GossipsubMessage> { self.msgs.get(message_id) } - - /// Get a list of GossipIds for a given topic - pub fn get_gossip_ids(&self, topic: &TopicHash) -> Vec { - self.history[..self.gossip] - .iter() - .fold(vec![], |mut current_entries, entries| { - // search for entries with desired topic - let mut found_entries: Vec = entries - .iter() - .filter_map(|entry| { - if entry.topics.iter().any(|t| t == topic) { - Some(entry.mid.clone()) - } else { - None - } - }) - .collect(); - - // generate the list - current_entries.append(&mut found_entries); - current_entries - }) - } - - /// Shift the history array down one and delete messages associated with the - /// last entry - pub fn shift(&mut self) { - for entry in self.history.pop().expect("history is always > 1") { - self.msgs.remove(&entry.mid); - } - - // Insert an empty vec in position 0 - self.history.insert(0, Vec::new()); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{Topic, TopicHash}; - use libp2p_core::PeerId; - - fn gen_testm(x: u64, topics: Vec) -> GossipsubMessage { - let u8x: u8 = x as u8; - let source = PeerId::random(); - let data: Vec = vec![u8x]; - let sequence_number = x; - - GossipsubMessage { - source, - data, - sequence_number, - topics, - } - } - - #[test] - /// Test that the message cache can be created. - fn test_new_cache() { - let default_id = |message: &GossipsubMessage| { - // default message id is: source + sequence number - let mut source_string = message.source.to_base58(); - source_string.push_str(&message.sequence_number.to_string()); - MessageId(source_string) - }; - let x: usize = 3; - let mc = MessageCache::new(x, 5, default_id); - - assert_eq!(mc.gossip, x); - } - - #[test] - /// Test you can put one message and get one. - fn test_put_get_one() { - let mut mc = MessageCache::new_default(10, 15); - - let topic1_hash = Topic::new("topic1".into()).no_hash(); - let topic2_hash = Topic::new("topic2".into()).no_hash(); - - let m = gen_testm(10, vec![topic1_hash, topic2_hash]); - - mc.put(m.clone()); - - assert!(mc.history[0].len() == 1); - - let fetched = mc.get(&(mc.msg_id)(&m)); - - assert!(fetched.is_some()); - - // Make sure it is the same fetched message - match fetched { - Some(x) => assert_eq!(*x, m), - _ => panic!("expected {:?}", m), - } - } - - #[test] - /// Test attempting to 'get' with a wrong id. - fn test_get_wrong() { - let mut mc = MessageCache::new_default(10, 15); - - let topic1_hash = Topic::new("topic1".into()).no_hash(); - let topic2_hash = Topic::new("topic2".into()).no_hash(); - - let m = gen_testm(10, vec![topic1_hash, topic2_hash]); - - mc.put(m); - - // Try to get an incorrect ID - let wrong_id = MessageId(String::from("wrongid")); - let fetched = mc.get(&wrong_id); - assert!(fetched.is_none()); - } - - #[test] - /// Test attempting to 'get' empty message cache. - fn test_get_empty() { - let mc = MessageCache::new_default(10, 15); - - // Try to get an incorrect ID - let wrong_string = MessageId(String::from("imempty")); - let fetched = mc.get(&wrong_string); - assert!(fetched.is_none()); - } - - #[test] - /// Test adding a message with no topics. - fn test_no_topic_put() { - let mut mc = MessageCache::new_default(3, 5); - - // Build the message - let m = gen_testm(1, vec![]); - mc.put(m.clone()); - - let fetched = mc.get(&(mc.msg_id)(&m)); - - // Make sure it is the same fetched message - match fetched { - Some(x) => assert_eq!(*x, m), - _ => panic!("expected {:?}", m), - } - } - - #[test] - /// Test shift mechanism. - fn test_shift() { - let mut mc = MessageCache::new_default(1, 5); - - let topic1_hash = Topic::new("topic1".into()).no_hash(); - let topic2_hash = Topic::new("topic2".into()).no_hash(); - - // Build the message - for i in 0..10 { - let m = gen_testm(i, vec![topic1_hash.clone(), topic2_hash.clone()]); - mc.put(m.clone()); - } - - mc.shift(); - - // Ensure the shift occurred - assert!(mc.history[0].is_empty()); - assert!(mc.history[1].len() == 10); - - // Make sure no messages deleted - assert!(mc.msgs.len() == 10); - } - - #[test] - /// Test Shift with no additions. - fn test_empty_shift() { - let mut mc = MessageCache::new_default(1, 5); - - let topic1_hash = Topic::new("topic1".into()).no_hash(); - let topic2_hash = Topic::new("topic2".into()).no_hash(); - // Build the message - for i in 0..10 { - let m = gen_testm(i, vec![topic1_hash.clone(), topic2_hash.clone()]); - mc.put(m.clone()); - } - - mc.shift(); - - // Ensure the shift occurred - assert!(mc.history[0].is_empty()); - assert!(mc.history[1].len() == 10); - - mc.shift(); - - assert!(mc.history[2].len() == 10); - assert!(mc.history[1].is_empty()); - assert!(mc.history[0].is_empty()); - } - - #[test] - /// Test shift to see if the last history messages are removed. - fn test_remove_last_from_shift() { - let mut mc = MessageCache::new_default(4, 5); - - let topic1_hash = Topic::new("topic1".into()).no_hash(); - let topic2_hash = Topic::new("topic2".into()).no_hash(); - // Build the message - for i in 0..10 { - let m = gen_testm(i, vec![topic1_hash.clone(), topic2_hash.clone()]); - mc.put(m.clone()); - } - - // Shift right until deleting messages - mc.shift(); - mc.shift(); - mc.shift(); - mc.shift(); - - assert_eq!(mc.history[mc.history.len() - 1].len(), 10); - - // Shift and delete the messages - mc.shift(); - assert_eq!(mc.history[mc.history.len() - 1].len(), 0); - assert_eq!(mc.history[0].len(), 0); - assert_eq!(mc.msgs.len(), 0); - } -} diff --git a/mm2src/gossipsub/src/protocol.rs b/mm2src/gossipsub/src/protocol.rs deleted file mode 100644 index 1173a89b22..0000000000 --- a/mm2src/gossipsub/src/protocol.rs +++ /dev/null @@ -1,397 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::behaviour::GossipsubRpc; -use crate::rpc_proto; -use crate::topic::TopicHash; -use byteorder::{BigEndian, ByteOrder}; -use bytes::Bytes; -use bytes::BytesMut; -use common::some_or_return_ok_none; -use futures::future; -use futures::prelude::*; -use futures_codec::{Decoder, Encoder, Framed}; -use libp2p_core::{InboundUpgrade, OutboundUpgrade, PeerId, UpgradeInfo}; -use prost::Message as ProtobufMessage; -use std::{borrow::Cow, io, iter, pin::Pin}; -use unsigned_varint::codec; - -/// Implementation of the `ConnectionUpgrade` for the Gossipsub protocol. -#[derive(Debug, Clone)] -pub struct ProtocolConfig { - protocol_id: Cow<'static, [u8]>, - max_transmit_size: usize, -} - -impl Default for ProtocolConfig { - fn default() -> Self { - Self { - protocol_id: Cow::Borrowed(b"/meshsub/1.0.0"), - max_transmit_size: 2048, - } - } -} - -impl ProtocolConfig { - /// Builds a new `ProtocolConfig`. - /// Sets the maximum gossip transmission size. - pub fn new(protocol_id: impl Into>, max_transmit_size: usize) -> ProtocolConfig { - ProtocolConfig { - protocol_id: protocol_id.into(), - max_transmit_size, - } - } -} - -impl UpgradeInfo for ProtocolConfig { - type Info = Cow<'static, [u8]>; - type InfoIter = iter::Once; - - fn protocol_info(&self) -> Self::InfoIter { iter::once(self.protocol_id.clone()) } -} - -type PinBoxFut = Pin> + Send>>; - -impl InboundUpgrade for ProtocolConfig -where - TSocket: AsyncRead + AsyncWrite + Unpin + Send + 'static, -{ - type Output = Framed; - type Error = io::Error; - type Future = PinBoxFut; - - fn upgrade_inbound(self, socket: TSocket, _: Self::Info) -> Self::Future { - let mut length_codec = codec::UviBytes::default(); - length_codec.set_max_len(self.max_transmit_size); - Box::pin(future::ok(Framed::new(socket, GossipsubCodec { length_codec }))) - } -} - -impl OutboundUpgrade for ProtocolConfig -where - TSocket: AsyncWrite + AsyncRead + Unpin + Send + 'static, -{ - type Output = Framed; - type Error = io::Error; - type Future = PinBoxFut; - - fn upgrade_outbound(self, socket: TSocket, _: Self::Info) -> Self::Future { - let mut length_codec = codec::UviBytes::default(); - length_codec.set_max_len(self.max_transmit_size); - Box::pin(future::ok(Framed::new(socket, GossipsubCodec { length_codec }))) - } -} - -/* Gossip codec for the framing */ - -pub struct GossipsubCodec { - /// Codec to encode/decode the Unsigned varint length prefix of the frames. - length_codec: codec::UviBytes, -} - -impl Encoder for GossipsubCodec { - type Item = GossipsubRpc; - type Error = io::Error; - - fn encode(&mut self, item: Self::Item, dst: &mut BytesMut) -> Result<(), Self::Error> { - // messages - let publish = item - .messages - .into_iter() - .map(|message| rpc_proto::Message { - from: Some(message.source.to_bytes()), - data: Some(message.data), - seqno: Some(message.sequence_number.to_be_bytes().to_vec()), - topic_ids: message.topics.into_iter().map(TopicHash::into_string).collect(), - }) - .collect::>(); - - // subscriptions - let subscriptions = item - .subscriptions - .into_iter() - .map(|sub| rpc_proto::rpc::SubOpts { - subscribe: Some(sub.action == GossipsubSubscriptionAction::Subscribe), - topic_id: Some(sub.topic_hash.into_string()), - }) - .collect::>(); - - // control messages - let mut control = rpc_proto::ControlMessage { - ihave: Vec::new(), - iwant: Vec::new(), - graft: Vec::new(), - prune: Vec::new(), - iamrelay: None, - included_to_relays_mesh: None, - mesh_size: None, - }; - - let empty_control_msg = item.control_msgs.is_empty(); - - for action in item.control_msgs { - match action { - // collect all ihave messages - GossipsubControlAction::IHave { - topic_hash, - message_ids, - } => { - let rpc_ihave = rpc_proto::ControlIHave { - topic_id: Some(topic_hash.into_string()), - message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), - }; - control.ihave.push(rpc_ihave); - }, - GossipsubControlAction::IWant { message_ids } => { - let rpc_iwant = rpc_proto::ControlIWant { - message_ids: message_ids.into_iter().map(|msg_id| msg_id.0).collect(), - }; - control.iwant.push(rpc_iwant); - }, - GossipsubControlAction::Graft { topic_hash } => { - let rpc_graft = rpc_proto::ControlGraft { - topic_id: Some(topic_hash.into_string()), - }; - control.graft.push(rpc_graft); - }, - GossipsubControlAction::Prune { topic_hash } => { - let rpc_prune = rpc_proto::ControlPrune { - topic_id: Some(topic_hash.into_string()), - }; - control.prune.push(rpc_prune); - }, - GossipsubControlAction::IAmRelay(is_relay) => { - control.iamrelay = Some(is_relay); - }, - GossipsubControlAction::IncludedToRelaysMesh { included, mesh_size } => { - control.included_to_relays_mesh = Some(rpc_proto::IncludedToRelaysMesh { - included, - mesh_size: mesh_size as u32, - }); - }, - GossipsubControlAction::MeshSize(size) => { - control.mesh_size = Some(size as u32); - }, - } - } - - let rpc = rpc_proto::Rpc { - subscriptions, - publish, - control: if empty_control_msg { None } else { Some(control) }, - }; - - let mut buf = Vec::with_capacity(rpc.encoded_len()); - - rpc.encode(&mut buf).expect("Buffer has sufficient capacity"); - - // length prefix the protobuf message, ensuring the max limit is not hit - self.length_codec.encode(Bytes::from(buf), dst) - } -} - -impl Decoder for GossipsubCodec { - type Item = GossipsubRpc; - type Error = io::Error; - - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - let packet = some_or_return_ok_none!(self.length_codec.decode(src)?); - - let rpc = rpc_proto::Rpc::decode(&packet[..])?; - - let mut messages = Vec::with_capacity(rpc.publish.len()); - for publish in rpc.publish.into_iter() { - // ensure the sequence number is a u64 - let seq_no = publish - .seqno - .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "sequence number was not provided"))?; - if seq_no.len() != 8 { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "sequence number has an incorrect size", - )); - } - messages.push(GossipsubMessage { - source: PeerId::from_bytes(&publish.from.unwrap_or_default()) - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid Peer Id"))?, - data: publish.data.unwrap_or_default(), - sequence_number: BigEndian::read_u64(&seq_no), - topics: publish.topic_ids.into_iter().map(TopicHash::from_raw).collect(), - }); - } - - let mut control_msgs = Vec::new(); - - if let Some(rpc_control) = rpc.control { - // Collect the gossipsub control messages - let ihave_msgs: Vec = rpc_control - .ihave - .into_iter() - .map(|ihave| GossipsubControlAction::IHave { - topic_hash: TopicHash::from_raw(ihave.topic_id.unwrap_or_default()), - message_ids: ihave.message_ids.into_iter().map(MessageId).collect::>(), - }) - .collect(); - - let iwant_msgs: Vec = rpc_control - .iwant - .into_iter() - .map(|iwant| GossipsubControlAction::IWant { - message_ids: iwant.message_ids.into_iter().map(MessageId).collect::>(), - }) - .collect(); - - let graft_msgs: Vec = rpc_control - .graft - .into_iter() - .map(|graft| GossipsubControlAction::Graft { - topic_hash: TopicHash::from_raw(graft.topic_id.unwrap_or_default()), - }) - .collect(); - - let prune_msgs: Vec = rpc_control - .prune - .into_iter() - .map(|prune| GossipsubControlAction::Prune { - topic_hash: TopicHash::from_raw(prune.topic_id.unwrap_or_default()), - }) - .collect(); - - control_msgs.extend(ihave_msgs); - control_msgs.extend(iwant_msgs); - control_msgs.extend(graft_msgs); - control_msgs.extend(prune_msgs); - - if let Some(is_relay) = rpc_control.iamrelay { - control_msgs.extend(iter::once(GossipsubControlAction::IAmRelay(is_relay))); - } - - if let Some(mesh_size) = rpc_control.mesh_size { - control_msgs.extend(iter::once(GossipsubControlAction::MeshSize(mesh_size as usize))); - } - - if let Some(msg) = rpc_control.included_to_relays_mesh { - control_msgs.extend(iter::once(GossipsubControlAction::IncludedToRelaysMesh { - included: msg.included, - mesh_size: msg.mesh_size as usize, - })); - } - } - - Ok(Some(GossipsubRpc { - messages, - subscriptions: rpc - .subscriptions - .into_iter() - .map(|sub| GossipsubSubscription { - action: if Some(true) == sub.subscribe { - GossipsubSubscriptionAction::Subscribe - } else { - GossipsubSubscriptionAction::Unsubscribe - }, - topic_hash: TopicHash::from_raw(sub.topic_id.unwrap_or_default()), - }) - .collect(), - control_msgs, - })) - } -} - -/// A type for gossipsub message ids. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct MessageId(pub String); - -impl std::fmt::Display for MessageId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } -} - -impl From for String { - fn from(mid: MessageId) -> Self { mid.0 } -} - -/// A message received by the gossipsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct GossipsubMessage { - /// Id of the peer that published this message. - pub source: PeerId, - - /// Content of the message. Its meaning is out of scope of this library. - pub data: Vec, - - /// A random sequence number. - pub sequence_number: u64, - - /// List of topics this message belongs to. - /// - /// Each message can belong to multiple topics at once. - pub topics: Vec, -} - -/// A subscription received by the gossipsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct GossipsubSubscription { - /// Action to perform. - pub action: GossipsubSubscriptionAction, - /// The topic from which to subscribe or unsubscribe. - pub topic_hash: TopicHash, -} - -/// Action that a subscription wants to perform. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum GossipsubSubscriptionAction { - /// The remote wants to subscribe to the given topic. - Subscribe, - /// The remote wants to unsubscribe from the given topic. - Unsubscribe, -} - -/// A Control message received by the gossipsub system. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum GossipsubControlAction { - /// Node broadcasts known messages per topic - IHave control message. - IHave { - /// The topic of the messages. - topic_hash: TopicHash, - /// A list of known message ids (peer_id + sequence _number) as a string. - message_ids: Vec, - }, - /// The node requests specific message ids (peer_id + sequence _number) - IWant control message. - IWant { - /// A list of known message ids (peer_id + sequence _number) as a string. - message_ids: Vec, - }, - /// The node has been added to the mesh - Graft control message. - Graft { - /// The mesh topic the peer should be added to. - topic_hash: TopicHash, - }, - /// The node has been removed from the mesh - Prune control message. - Prune { - /// The mesh topic the peer should be removed from. - topic_hash: TopicHash, - }, - IAmRelay(bool), - /// Whether the node included or excluded from other node relays mesh - IncludedToRelaysMesh { - included: bool, - mesh_size: usize, - }, - MeshSize(usize), -} diff --git a/mm2src/gossipsub/src/rpc.proto b/mm2src/gossipsub/src/rpc.proto deleted file mode 100644 index 8a194011a9..0000000000 --- a/mm2src/gossipsub/src/rpc.proto +++ /dev/null @@ -1,87 +0,0 @@ -syntax = "proto2"; - -package gossipsub.pb; - -message RPC { - repeated SubOpts subscriptions = 1; - repeated Message publish = 2; - - message SubOpts { - optional bool subscribe = 1; // subscribe or unsubscribe - optional string topic_id = 2; - } - - optional ControlMessage control = 3; -} - -message Message { - optional bytes from = 1; - optional bytes data = 2; - optional bytes seqno = 3; - repeated string topic_ids = 4; -} - -message IncludedToRelaysMesh { - required bool included = 1; - required uint32 mesh_size = 2; -} - -message ControlMessage { - repeated ControlIHave ihave = 1; - repeated ControlIWant iwant = 2; - repeated ControlGraft graft = 3; - repeated ControlPrune prune = 4; - optional bool iamrelay = 5; - optional IncludedToRelaysMesh included_to_relays_mesh = 6; - optional uint32 mesh_size = 7; -} - -message ControlIHave { - optional string topic_id = 1; - repeated string message_ids = 2; -} - -message ControlIWant { - repeated string message_ids= 1; -} - -message ControlGraft { - optional string topic_id = 1; -} - -message ControlGraftRelay {} - -message ControlPrune { - optional string topic_id = 1; -} - -message ControlPruneRelay {} - -// topicID = hash(topicDescriptor); (not the topic.name) -message TopicDescriptor { - optional string name = 1; - optional AuthOpts auth = 2; - optional EncOpts enc = 3; - - message AuthOpts { - optional AuthMode mode = 1; - repeated bytes keys = 2; // root keys to trust - - enum AuthMode { - NONE = 0; // no authentication, anyone can publish - KEY = 1; // only messages signed by keys in the topic descriptor are accepted - WOT = 2; // web of trust, certificates can allow publisher set to grow - } - } - - message EncOpts { - optional EncMode mode = 1; - repeated bytes key_hashes = 2; // the hashes of the shared keys used (salted) - - enum EncMode { - NONE = 0; // no encryption, anyone can read - SHAREDKEY = 1; // messages are encrypted with shared key - WOT = 2; // web of trust, certificates can allow publisher set to grow - } - } -} diff --git a/mm2src/gossipsub/src/topic.rs b/mm2src/gossipsub/src/topic.rs deleted file mode 100644 index 970ea8947a..0000000000 --- a/mm2src/gossipsub/src/topic.rs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2020 Sigma Prime Pty Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::rpc_proto; -use base64::encode; -use prost::Message; -use sha2::{Digest, Sha256}; -use std::fmt; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct TopicHash { - /// The topic hash. Stored as a string to align with the protobuf API. - hash: String, -} - -impl TopicHash { - pub fn from_raw(hash: impl Into) -> TopicHash { TopicHash { hash: hash.into() } } - - pub fn into_string(self) -> String { self.hash } - - pub fn as_str(&self) -> &str { &self.hash } -} - -/// A gossipsub topic. -#[derive(Debug, Clone)] -pub struct Topic { - topic: String, -} - -impl Topic { - pub fn new(topic: String) -> Self { Topic { topic } } - - /// Creates a `TopicHash` by SHA256 hashing the topic then base64 encoding the - /// hash. - pub fn sha256_hash(&self) -> TopicHash { - let topic_descripter = rpc_proto::TopicDescriptor { - name: Some(self.topic.clone()), - auth: None, - enc: None, - }; - let mut bytes = Vec::with_capacity(topic_descripter.encoded_len()); - topic_descripter.encode(&mut bytes).expect("buffer is large enough"); - let hash = encode(Sha256::digest(&bytes).as_slice()); - - TopicHash { hash } - } - - /// Creates a `TopicHash` as a raw string. - pub fn no_hash(&self) -> TopicHash { - TopicHash { - hash: self.topic.clone(), - } - } -} - -impl fmt::Display for Topic { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.topic) } -} - -impl fmt::Display for TopicHash { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.hash) } -} diff --git a/mm2src/gossipsub/tests/smoke.rs b/mm2src/gossipsub/tests/smoke.rs deleted file mode 100644 index 44c11416fb..0000000000 --- a/mm2src/gossipsub/tests/smoke.rs +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright 2019 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use futures::prelude::*; -use log::debug; -use quickcheck::{QuickCheck, TestResult}; -use rand::{random, seq::SliceRandom, SeedableRng}; -use std::{pin::Pin, - task::{Context, Poll}, - time::Duration}; - -use atomicdex_gossipsub::{Gossipsub, GossipsubConfigBuilder, GossipsubEvent, Topic}; -use futures::StreamExt; -use libp2p_core::{identity, multiaddr::Protocol, transport::MemoryTransport, upgrade, Multiaddr, Transport}; -use libp2p_plaintext::PlainText2Config; -use libp2p_swarm::{Swarm, SwarmEvent}; -use libp2p_yamux as yamux; - -struct Graph { - pub nodes: Vec<(Multiaddr, Swarm)>, -} - -impl Future for Graph { - type Output = (Multiaddr, GossipsubEvent); - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - for (addr, node) in &mut self.nodes { - loop { - match node.poll_next_unpin(cx) { - Poll::Ready(Some(SwarmEvent::Behaviour(event))) => return Poll::Ready((addr.clone(), event)), - Poll::Ready(Some(_)) => {}, - Poll::Ready(None) => panic!("unexpected None when polling nodes"), - Poll::Pending => break, - } - } - } - - Poll::Pending - } -} - -impl Graph { - fn new_connected(num_nodes: usize, seed: u64) -> Graph { - if num_nodes == 0 { - panic!("expecting at least one node"); - } - - let mut rng = rand::rngs::StdRng::seed_from_u64(seed); - - let mut not_connected_nodes = std::iter::once(()) - .cycle() - .take(num_nodes) - .map(|_| build_node()) - .collect::)>>(); - - let mut connected_nodes = vec![not_connected_nodes.pop().unwrap()]; - - while !not_connected_nodes.is_empty() { - connected_nodes.shuffle(&mut rng); - not_connected_nodes.shuffle(&mut rng); - - let mut next = not_connected_nodes.pop().unwrap(); - let connected_addr = &connected_nodes[0].0; - - // Memory transport can not handle addresses with `/p2p` suffix. - let mut connected_addr_no_p2p = connected_addr.clone(); - let p2p_suffix_connected = connected_addr_no_p2p.pop(); - - debug!( - "Connect: {} -> {}", - next.0.clone().pop().unwrap(), - p2p_suffix_connected.unwrap() - ); - - Swarm::dial(&mut next.1, connected_addr_no_p2p).unwrap(); - - connected_nodes.push(next); - } - - Graph { nodes: connected_nodes } - } - - /// Polls the graph and passes each event into the provided FnMut until the closure returns - /// `true`. - /// - /// Returns [`true`] on success and [`false`] on timeout. - fn wait_for bool>(&mut self, mut f: F) -> bool { - let fut = futures::future::poll_fn(move |cx| match self.poll_unpin(cx) { - Poll::Ready((_addr, ev)) if f(&ev) => Poll::Ready(()), - _ => Poll::Pending, - }); - - let fut = async_std::future::timeout(Duration::from_secs(10), fut); - - futures::executor::block_on(fut).is_ok() - } - - /// Polls the graph until Poll::Pending is obtained, completing the underlying polls. - fn drain_poll(self) -> Self { - // The future below should return self. Given that it is a FnMut and not a FnOnce, one needs - // to wrap `self` in an Option, leaving a `None` behind after the final `Poll::Ready`. - let mut this = Some(self); - - let fut = futures::future::poll_fn(move |cx| match &mut this { - Some(graph) => loop { - match graph.poll_unpin(cx) { - Poll::Ready(_) => {}, - Poll::Pending => return Poll::Ready(this.take().unwrap()), - } - }, - None => panic!("future called after final return"), - }); - let fut = async_std::future::timeout(Duration::from_secs(10), fut); - futures::executor::block_on(fut).unwrap() - } -} - -fn build_node() -> (Multiaddr, Swarm) { - let key = identity::Keypair::generate_ed25519(); - let public_key = key.public(); - - let transport = MemoryTransport::default() - .upgrade(upgrade::Version::V1) - .authenticate(PlainText2Config { - local_public_key: public_key.clone(), - }) - .multiplex(yamux::YamuxConfig::default()) - .boxed(); - - let peer_id = public_key.to_peer_id(); - - // NOTE: The graph of created nodes can be disconnected from the mesh point of view as nodes - // can reach their d_lo value and not add other nodes to their mesh. To speed up this test, we - // reduce the default values of the heartbeat, so that all nodes will receive gossip in a - // timely fashion. - - let config = GossipsubConfigBuilder::default() - .heartbeat_initial_delay(Duration::from_millis(100)) - .heartbeat_interval(Duration::from_millis(200)) - .history_length(10) - .history_gossip(10) - .build(); - let behaviour = Gossipsub::new(peer_id, config); - let mut swarm = Swarm::new(transport, behaviour, peer_id); - - let port = 1 + random::(); - let mut addr: Multiaddr = Protocol::Memory(port).into(); - swarm.listen_on(addr.clone()).unwrap(); - - addr = addr.with(libp2p_core::multiaddr::Protocol::P2p(public_key.to_peer_id().into())); - - (addr, swarm) -} - -#[test] -fn multi_hop_propagation() { - let _ = env_logger::try_init(); - - fn prop(num_nodes: u8, seed: u64) -> TestResult { - if !(2..=50).contains(&num_nodes) { - return TestResult::discard(); - } - - debug!("number nodes: {:?}, seed: {:?}", num_nodes, seed); - - let mut graph = Graph::new_connected(num_nodes as usize, seed); - let number_nodes = graph.nodes.len(); - - // Subscribe each node to the same topic. - let topic = Topic::new("test-net".into()); - for (_addr, node) in &mut graph.nodes { - node.behaviour_mut().subscribe(topic.clone()); - } - - // Wait for all nodes to be subscribed. - let mut subscribed = 0; - let all_subscribed = graph.wait_for(move |ev| { - if let GossipsubEvent::Subscribed { .. } = ev { - subscribed += 1; - if subscribed == (number_nodes - 1) * 2 { - return true; - } - } - - false - }); - if !all_subscribed { - return TestResult::error(format!( - "Timed out waiting for all nodes to subscribe but only have {:?}/{:?}.", - subscribed, num_nodes, - )); - } - - // It can happen that the publish occurs before all grafts have completed causing this test - // to fail. We drain all the poll messages before publishing. - graph = graph.drain_poll(); - - // Publish a single message. - graph.nodes[0].1.behaviour_mut().publish(&topic, vec![1, 2, 3]); - - // Wait for all nodes to receive the published message. - let mut received_msgs = 0; - let all_received = graph.wait_for(move |ev| { - if let GossipsubEvent::Message { .. } = ev { - received_msgs += 1; - if received_msgs == number_nodes - 1 { - return true; - } - } - - false - }); - if !all_received { - return TestResult::error(format!( - "Timed out waiting for all nodes to receive the msg but only have {:?}/{:?}.", - received_msgs, num_nodes, - )); - } - - TestResult::passed() - } - - QuickCheck::new() - .max_tests(5) - .quickcheck(prop as fn(u8, u64) -> TestResult) -} diff --git a/mm2src/mm2_bin_lib/Cargo.toml b/mm2src/mm2_bin_lib/Cargo.toml index c06d9a7814..7415f21b4f 100644 --- a/mm2src/mm2_bin_lib/Cargo.toml +++ b/mm2src/mm2_bin_lib/Cargo.toml @@ -5,16 +5,11 @@ [package] name = "mm2_bin_lib" -version = "2.1.0-beta" -authors = ["James Lee", "Artem Pikulin", "Artem Grinblat", "Omar S.", "Onur Ozkan", "Alina Sharon", "Caglar Kaya", "Cipi", "Sergey Boiko", "Samuel Onoja", "Roman Sztergbaum", "Kadan Stadelmann ", "Dimxy"] +version = "2.2.0-beta" +authors = ["James Lee", "Artem Pikulin", "Artem Grinblat", "Omar S.", "Onur Ozkan", "Alina Sharon", "Caglar Kaya", "Cipi", "Sergey Boiko", "Samuel Onoja", "Roman Sztergbaum", "Kadan Stadelmann ", "Dimxy", "Omer Yacine"] edition = "2018" default-run = "kdf" -# wasm-opt reduces the size from 17 Mb to 14. But it runs for few minutes, which is not good for CI. -# For production builds, it's recommended to run wasm-opt separately. -[package.metadata.wasm-pack.profile.release] -wasm-opt = false - [features] custom-swap-locktime = ["mm2_main/custom-swap-locktime"] # only for testing purposes, should never be activated on release builds. native = ["mm2_main/native"] # Deprecated diff --git a/mm2src/mm2_bin_lib/src/lib.rs b/mm2src/mm2_bin_lib/src/lib.rs index 008f0d775c..c78233e64a 100644 --- a/mm2src/mm2_bin_lib/src/lib.rs +++ b/mm2src/mm2_bin_lib/src/lib.rs @@ -1,6 +1,6 @@ use enum_primitive_derive::Primitive; use mm2_core::mm_ctx::MmArc; -use mm2_main::mm2::lp_dispatcher::{dispatch_lp_event, StopCtxEvent}; +use mm2_main::lp_dispatcher::{dispatch_lp_event, StopCtxEvent}; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::prelude::*; @@ -99,5 +99,5 @@ fn prepare_for_mm2_stop() -> PrepareForStopResult { async fn finalize_mm2_stop(ctx: MmArc) { dispatch_lp_event(ctx.clone(), StopCtxEvent.into()).await; - let _ = ctx.stop(); + let _ = ctx.stop().await; } diff --git a/mm2src/mm2_bin_lib/src/mm2_bin.rs b/mm2src/mm2_bin_lib/src/mm2_bin.rs index 053d9dd9b4..1587103cc4 100644 --- a/mm2src/mm2_bin_lib/src/mm2_bin.rs +++ b/mm2src/mm2_bin_lib/src/mm2_bin.rs @@ -1,4 +1,4 @@ -#[cfg(not(target_arch = "wasm32"))] use mm2_main::mm2::mm2_main; +#[cfg(not(target_arch = "wasm32"))] use mm2_main::mm2_main; #[cfg(not(target_arch = "wasm32"))] const MM_VERSION: &str = env!("MM_VERSION"); diff --git a/mm2src/mm2_bin_lib/src/mm2_native_lib.rs b/mm2src/mm2_bin_lib/src/mm2_native_lib.rs index 5f85596311..17d7a839bc 100644 --- a/mm2src/mm2_bin_lib/src/mm2_native_lib.rs +++ b/mm2src/mm2_bin_lib/src/mm2_native_lib.rs @@ -70,9 +70,7 @@ pub unsafe extern "C" fn mm2_main(conf: *const c_char, log_cb: extern "C" fn(lin return; } let ctx_cb = &|ctx| CTX.store(ctx, Ordering::Relaxed); - match catch_unwind(move || { - mm2_main::mm2::run_lp_main(Some(&conf), ctx_cb, MM_VERSION.into(), MM_DATETIME.into()) - }) { + match catch_unwind(move || mm2_main::run_lp_main(Some(&conf), ctx_cb, MM_VERSION.into(), MM_DATETIME.into())) { Ok(Ok(_)) => log!("run_lp_main finished"), Ok(Err(err)) => log!("run_lp_main error: {}", err), Err(err) => log!("run_lp_main panic: {:?}", any_to_str(&*err)), @@ -125,7 +123,7 @@ pub extern "C" fn mm2_test(torch: i32, log_cb: extern "C" fn(line: *const c_char }, }; let conf = json::to_string(&ctx.conf).unwrap(); - let hy_res = mm2_main::mm2::rpc::lp_commands_legacy::stop(ctx); + let hy_res = mm2_main::rpc::lp_commands::legacy::stop(ctx); let r = match block_on(hy_res) { Ok(r) => r, Err(err) => { diff --git a/mm2src/mm2_bin_lib/src/mm2_wasm_lib.rs b/mm2src/mm2_bin_lib/src/mm2_wasm_lib.rs index 4cafd6509e..ee56bd4045 100644 --- a/mm2src/mm2_bin_lib/src/mm2_wasm_lib.rs +++ b/mm2src/mm2_bin_lib/src/mm2_wasm_lib.rs @@ -16,7 +16,7 @@ use super::*; use common::log::{register_callback, LogLevel, WasmCallback}; use common::{console_err, console_info, deserialize_from_js, executor, serialize_to_js, set_panic_hook}; use enum_primitive_derive::Primitive; -use mm2_main::mm2::LpMainParams; +use mm2_main::LpMainParams; use mm2_rpc::data::legacy::MmVersionResponse; use mm2_rpc::wasm_rpc::WasmRpcResponse; use serde::{Deserialize, Serialize}; @@ -110,12 +110,12 @@ pub fn mm2_main(params: JsValue, log_cb: js_sys::Function) -> Result<(), JsValue let ctx_cb = |ctx| CTX.store(ctx, Ordering::Relaxed); // TODO figure out how to use catch_unwind here // use futures::FutureExt; - // match mm2::lp_main(params, &ctx_cb).catch_unwind().await { + // match mm2_main::lp_main(params, &ctx_cb).catch_unwind().await { // Ok(Ok(_)) => console_info!("run_lp_main finished"), // Ok(Err(err)) => console_err!("run_lp_main error: {}", err), // Err(err) => console_err!("run_lp_main panic: {:?}", any_to_str(&*err)), // }; - match mm2_main::mm2::lp_main(params, &ctx_cb, MM_VERSION.into(), MM_DATETIME.into()).await { + match mm2_main::lp_main(params, &ctx_cb, MM_VERSION.into(), MM_DATETIME.into()).await { Ok(()) => console_info!("run_lp_main finished"), Err(err) => console_err!("run_lp_main error: {}", err), }; diff --git a/mm2src/mm2_core/Cargo.toml b/mm2src/mm2_core/Cargo.toml index 943dcac280..d0df9dbe7c 100644 --- a/mm2src/mm2_core/Cargo.toml +++ b/mm2src/mm2_core/Cargo.toml @@ -17,6 +17,7 @@ derive_more = "0.99" futures = { version = "0.3", package = "futures", features = ["compat", "async-await", "thread-pool"] } hex = "0.4.2" lazy_static = "1.4" +libp2p = { git = "https://github.com/KomodoPlatform/rust-libp2p.git", tag = "k-0.52.4", default-features = false, features = ["identify"] } mm2_err_handle = { path = "../mm2_err_handle" } mm2_event_stream = { path = "../mm2_event_stream" } mm2_metrics = { path = "../mm2_metrics" } diff --git a/mm2src/mm2_core/src/data_asker.rs b/mm2src/mm2_core/src/data_asker.rs index 5d01310630..7f32f93365 100644 --- a/mm2src/mm2_core/src/data_asker.rs +++ b/mm2src/mm2_core/src/data_asker.rs @@ -113,8 +113,6 @@ pub async fn send_asked_data_rpc( asked_data: SendAskedDataRequest, ) -> Result> { let mut awaiting_asks = ctx.data_asker.awaiting_asks.lock().await; - awaiting_asks.clear_expired_entries(); - match awaiting_asks.remove(&asked_data.data_id) { Some(sender) => { sender.send(asked_data.data).map_to_mm(|_| { diff --git a/mm2src/mm2_core/src/lib.rs b/mm2src/mm2_core/src/lib.rs index 62e4bef09c..88b0fd5c81 100644 --- a/mm2src/mm2_core/src/lib.rs +++ b/mm2src/mm2_core/src/lib.rs @@ -1,10 +1,11 @@ -use derive_more::Display; -use rand::{thread_rng, Rng}; +#[cfg(target_arch = "wasm32")] use derive_more::Display; +#[cfg(target_arch = "wasm32")] use rand::{thread_rng, Rng}; pub mod data_asker; pub mod event_dispatcher; pub mod mm_ctx; +#[cfg(target_arch = "wasm32")] #[derive(Clone, Copy, Display, PartialEq, Default)] pub enum DbNamespaceId { #[display(fmt = "MAIN")] @@ -14,9 +15,13 @@ pub enum DbNamespaceId { Test(u64), } +#[cfg(target_arch = "wasm32")] impl DbNamespaceId { pub fn for_test() -> DbNamespaceId { let mut rng = thread_rng(); DbNamespaceId::Test(rng.gen()) } + + #[inline(always)] + pub fn for_test_with_id(id: u64) -> DbNamespaceId { DbNamespaceId::Test(id) } } diff --git a/mm2src/mm2_core/src/mm_ctx.rs b/mm2src/mm2_core/src/mm_ctx.rs index af0a6adece..8c417f2ce1 100644 --- a/mm2src/mm2_core/src/mm_ctx.rs +++ b/mm2src/mm2_core/src/mm_ctx.rs @@ -1,11 +1,15 @@ #[cfg(feature = "track-ctx-pointer")] use common::executor::Timer; -use common::executor::{abortable_queue::{AbortableQueue, WeakSpawner}, - graceful_shutdown, AbortSettings, AbortableSystem, SpawnAbortable, SpawnFuture}; use common::log::{self, LogLevel, LogOnError, LogState}; use common::{cfg_native, cfg_wasm32, small_rng}; +use common::{executor::{abortable_queue::{AbortableQueue, WeakSpawner}, + graceful_shutdown, AbortSettings, AbortableSystem, SpawnAbortable, SpawnFuture}, + expirable_map::ExpirableMap}; +use futures::channel::oneshot; +use futures::lock::Mutex as AsyncMutex; use gstuff::{try_s, Constructible, ERR, ERRL}; use lazy_static::lazy_static; +use libp2p::PeerId; use mm2_event_stream::{controller::Controller, Event, EventStreamConfiguration}; use mm2_metrics::{MetricsArc, MetricsOps}; use primitives::hash::H160; @@ -30,7 +34,6 @@ cfg_wasm32! { cfg_native! { use db_common::async_sql_conn::AsyncConnection; use db_common::sqlite::rusqlite::Connection; - use futures::lock::Mutex as AsyncMutex; use rustls::ServerName; use mm2_metrics::prometheus; use mm2_metrics::MmMetricsError; @@ -94,7 +97,6 @@ pub struct MmCtx { pub dispatcher_ctx: Mutex>>, pub message_service_ctx: Mutex>>, pub p2p_ctx: Mutex>>, - pub peer_id: Constructible, pub account_ctx: Mutex>>, /// The context belonging to the `coins` crate: `CoinsContext`. pub coins_ctx: Mutex>>, @@ -143,6 +145,8 @@ pub struct MmCtx { /// asynchronous handle for rusqlite connection. #[cfg(not(target_arch = "wasm32"))] pub async_sqlite_connection: Constructible>>, + /// Links the RPC context to the P2P context to handle health check responses. + pub healthcheck_response_handler: AsyncMutex>>, } impl MmCtx { @@ -164,7 +168,6 @@ impl MmCtx { dispatcher_ctx: Mutex::new(None), message_service_ctx: Mutex::new(None), p2p_ctx: Mutex::new(None), - peer_id: Constructible::default(), account_ctx: Mutex::new(None), coins_ctx: Mutex::new(None), coins_activation_ctx: Mutex::new(None), @@ -193,6 +196,7 @@ impl MmCtx { nft_ctx: Mutex::new(None), #[cfg(not(target_arch = "wasm32"))] async_sqlite_connection: Constructible::default(), + healthcheck_response_handler: AsyncMutex::new(ExpirableMap::default()), } } @@ -289,10 +293,12 @@ impl MmCtx { }) } + /// Returns the path to the MM databases root. + #[cfg(not(target_arch = "wasm32"))] + pub fn db_root(&self) -> PathBuf { path_to_db_root(self.conf["dbdir"].as_str()) } #[cfg(not(target_arch = "wasm32"))] pub fn wallet_file_path(&self, wallet_name: &str) -> PathBuf { - let db_root = path_to_db_root(self.conf["dbdir"].as_str()); - db_root.join(wallet_name.to_string() + ".dat") + self.db_root().join(wallet_name.to_string() + ".dat") } /// MM database path. @@ -502,7 +508,10 @@ lazy_static! { impl MmArc { pub fn new(ctx: MmCtx) -> MmArc { MmArc(SharedRc::new(ctx)) } - pub fn stop(&self) -> Result<(), String> { + pub async fn stop(&self) -> Result<(), String> { + #[cfg(not(target_arch = "wasm32"))] + try_s!(self.close_async_connection().await); + try_s!(self.stop.pin(true)); // Notify shutdown listeners. @@ -516,6 +525,16 @@ impl MmArc { Ok(()) } + #[cfg(not(target_arch = "wasm32"))] + async fn close_async_connection(&self) -> Result<(), db_common::async_sql_conn::AsyncConnError> { + if let Some(async_conn) = self.async_sqlite_connection.as_option() { + let mut conn = async_conn.lock().await; + conn.close().await?; + } + + Ok(()) + } + #[cfg(feature = "track-ctx-pointer")] fn track_ctx_pointer(&self) { let ctx_weak = self.weak(); @@ -739,6 +758,12 @@ impl MmCtxBuilder { self } + #[cfg(target_arch = "wasm32")] + pub fn with_test_db_namespace_with_id(mut self, id: u64) -> Self { + self.db_namespace = DbNamespaceId::for_test_with_id(id); + 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. diff --git a/mm2src/mm2_db/src/indexed_db/drivers/upgrader.rs b/mm2src/mm2_db/src/indexed_db/drivers/upgrader.rs index 8f51a220c1..fa55d87d8c 100644 --- a/mm2src/mm2_db/src/indexed_db/drivers/upgrader.rs +++ b/mm2src/mm2_db/src/indexed_db/drivers/upgrader.rs @@ -29,6 +29,8 @@ pub enum OnUpgradeError { old_version: u32, new_version: u32, }, + #[display(fmt = "Error occurred due to deleting the '{}' index: {}", index, description)] + ErrorDeletingIndex { index: String, description: String }, } pub struct DbUpgrader { @@ -108,4 +110,18 @@ impl TableUpgrader { description: stringify_js_error(&e), }) } + + /// Deletes an index. + /// Regardless of whether the index is created using one or multiple fields, the deleteIndex() + /// method is used to delete any type of index, and it works in the same way for both. + /// https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/deleteIndex + pub fn delete_index(&self, index: &str) -> OnUpgradeResult<()> { + self.object_store + .delete_index(index) + .map(|_| ()) + .map_to_mm(|e| OnUpgradeError::ErrorDeletingIndex { + index: index.to_owned(), + description: stringify_js_error(&e), + }) + } } diff --git a/mm2src/mm2_err_handle/src/map_to_mm_fut.rs b/mm2src/mm2_err_handle/src/map_to_mm_fut.rs index 01de1b7d7e..0cfeb9cf13 100644 --- a/mm2src/mm2_err_handle/src/map_to_mm_fut.rs +++ b/mm2src/mm2_err_handle/src/map_to_mm_fut.rs @@ -21,7 +21,7 @@ where /// /// ```rust /// let fut = futures01::future::err("An error".to_owned()); - /// let mapped_res: Result<(), MmError> = fut.map_to_mm_fut(|e| e.len()).wait(); + /// let mapped_res: Result<(), MmError> = block_on_f01(fut.map_to_mm_fut(|e| e.len())); /// ``` #[track_caller] fn map_to_mm_fut(self, f: F) -> MapToMmFuture<'a, T, E1, E2> diff --git a/mm2src/mm2_err_handle/src/mm_error.rs b/mm2src/mm2_err_handle/src/mm_error.rs index 56e7f29f50..1ea7bf1555 100644 --- a/mm2src/mm2_err_handle/src/mm_error.rs +++ b/mm2src/mm2_err_handle/src/mm_error.rs @@ -332,6 +332,7 @@ impl FormattedTrace for Vec { mod tests { use super::*; use crate::prelude::*; + use common::block_on_f01; use futures01::Future; use ser_error_derive::SerializeErrorType; use serde_json::{self as json, json}; @@ -425,10 +426,8 @@ mod tests { } let into_mm_line = line!() + 2; - let mm_err = generate_error("An error") - .map_to_mm_fut(|error| error.len()) - .wait() - .expect_err("Expected an error"); + let mm_err = + block_on_f01(generate_error("An error").map_to_mm_fut(|error| error.len())).expect_err("Expected an error"); assert_eq!(mm_err.etype, 8); assert_eq!(mm_err.trace, vec![TraceLocation::new("mm_error", into_mm_line)]); } diff --git a/mm2src/mm2_io/src/fs.rs b/mm2src/mm2_io/src/fs.rs index 94a323c0b8..489b3bad8b 100644 --- a/mm2src/mm2_io/src/fs.rs +++ b/mm2src/mm2_io/src/fs.rs @@ -192,19 +192,52 @@ where json::from_slice(&content).map_to_mm(FsJsonError::Deserializing) } -/// Read the `dir_path` entries trying to deserialize each as the `T` type. +async fn filter_files_by_extension(dir_path: &Path, extension: &str) -> IoResult> { + let ext = Some(OsStr::new(extension).to_ascii_lowercase()); + let entries = read_dir_async(dir_path) + .await? + .into_iter() + .filter(|path| path.extension().map(|ext| ext.to_ascii_lowercase()) == ext) + .collect(); + Ok(entries) +} + +/// Helper function to extract file names or stems based on the provided extraction function. +fn extract_file_identifiers<'a, F>(entries: Vec, extractor: F) -> impl Iterator + 'a +where + F: Fn(&Path) -> Option<&OsStr> + 'a, +{ + entries + .into_iter() + .filter_map(move |path| extractor(&path).and_then(OsStr::to_str).map(ToOwned::to_owned)) +} + +/// Lists files by the specified extension from the given directory path. +/// If include_extension is true, returns full file names; otherwise, returns file stems. +pub async fn list_files_by_extension( + dir_path: &Path, + extension: &str, + include_extension: bool, +) -> IoResult> { + let entries = filter_files_by_extension(dir_path, extension).await?; + let extractor = if include_extension { + Path::file_name + } else { + Path::file_stem + }; + Ok(extract_file_identifiers(entries, extractor)) +} + +/// Read the `dir_path` entries trying to deserialize each as the `T` type, +/// filtering by the specified extension. /// Please note that files that couldn't be deserialized are skipped. -pub async fn read_dir_json(dir_path: &Path) -> FsJsonResult> +pub async fn read_files_with_extension(dir_path: &Path, extension: &str) -> FsJsonResult> where T: DeserializeOwned, { - let json_ext = Some(OsStr::new("json")); - let entries: Vec<_> = read_dir_async(dir_path) + let entries = filter_files_by_extension(dir_path, extension) .await - .mm_err(FsJsonError::IoReading)? - .into_iter() - .filter(|path| path.extension() == json_ext) - .collect(); + .mm_err(FsJsonError::IoReading)?; let type_name = std::any::type_name::(); let mut result = Vec::new(); @@ -233,6 +266,16 @@ where Ok(result) } +/// Read the `dir_path` entries trying to deserialize each as the `T` type from JSON files. +/// Please note that files that couldn't be deserialized are skipped. +#[inline(always)] +pub async fn read_dir_json(dir_path: &Path) -> FsJsonResult> +where + T: DeserializeOwned, +{ + read_files_with_extension(dir_path, "json").await +} + pub async fn write_json(t: &T, path: &Path, use_tmp_file: bool) -> FsJsonResult<()> where T: Serialize, diff --git a/mm2src/mm2_libp2p/Cargo.toml b/mm2src/mm2_libp2p/Cargo.toml deleted file mode 100644 index bd01045799..0000000000 --- a/mm2src/mm2_libp2p/Cargo.toml +++ /dev/null @@ -1,43 +0,0 @@ -[package] -name = "mm2-libp2p" -version = "0.1.0" -authors = ["Artem Pikulin "] -edition = "2018" - -[lib] -doctest = false - -[dependencies] -async-trait = "0.1" -atomicdex-gossipsub = { path = "../gossipsub" } -common = { path = "../common" } -derive_more = "0.99" -libp2p-floodsub = { path = "../floodsub" } -futures = { version = "0.3.1", package = "futures", features = ["compat", "async-await"] } -hex = "0.4.2" -lazy_static = "1.4" -secp256k1 = { version = "0.20", features = ["rand"] } -log = "0.4.17" -rand = { package = "rand", version = "0.7", features = ["std", "wasm-bindgen"] } -regex = "1" -rmp-serde = "0.14.3" -serde = { version = "1.0", features = ["derive"] } -serde_bytes = "0.11.5" -sha2 = "0.10" -void = "1.0" -wasm-timer = "0.2.4" - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -futures-rustls = { version = "0.24" } -tokio = { version = "1.20", features = ["rt-multi-thread", "macros"] } -libp2p = { git = "https://github.com/libp2p/rust-libp2p.git", tag = "v0.45.1", default-features = false, features = ["dns-tokio", "floodsub", "mplex", "noise", "ping", "request-response", "secp256k1", "tcp-tokio", "websocket"] } - -[target.'cfg(target_arch = "wasm32")'.dependencies] -futures-rustls = { version = "0.22" } -libp2p = { git = "https://github.com/libp2p/rust-libp2p.git", tag = "v0.45.1", default-features = false, features = ["floodsub", "mplex", "noise", "ping", "request-response", "secp256k1", "wasm-ext", "wasm-ext-websocket"] } -wasm-bindgen-futures = "0.4.21" - -[dev-dependencies] -async-std = { version = "1.6.2", features = ["unstable"] } -env_logger = "0.9.3" -serde_json = { version = "1", features = ["preserve_order", "raw_value"] } diff --git a/mm2src/mm2_libp2p/src/adex_ping.rs b/mm2src/mm2_libp2p/src/adex_ping.rs deleted file mode 100644 index 73ffb94d83..0000000000 --- a/mm2src/mm2_libp2p/src/adex_ping.rs +++ /dev/null @@ -1,56 +0,0 @@ -use libp2p::swarm::NetworkBehaviour; -use libp2p::{ping::{Ping, PingConfig, PingEvent}, - swarm::{CloseConnection, NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters}, - NetworkBehaviour}; -use log::error; -use std::{collections::VecDeque, - num::NonZeroU32, - task::{Context, Poll}}; -use void::Void; - -/// Wrapper around libp2p Ping behaviour that forcefully disconnects a peer using NetworkBehaviourAction::DisconnectPeer -/// event. -/// Libp2p has unclear ConnectionHandlers keep alive logic so in some cases even if Ping handler emits Close event the -/// connection is kept active which is undesirable. -#[derive(NetworkBehaviour)] -#[behaviour(out_event = "Void", event_process = true)] -#[behaviour(poll_method = "poll_event")] -pub struct AdexPing { - ping: Ping, - #[behaviour(ignore)] - events: VecDeque::ConnectionHandler>>, -} - -impl NetworkBehaviourEventProcess for AdexPing { - fn inject_event(&mut self, event: PingEvent) { - if let Err(e) = event.result { - error!("Ping error {}. Disconnecting peer {}", e, event.peer); - self.events.push_back(NetworkBehaviourAction::CloseConnection { - peer_id: event.peer, - connection: CloseConnection::All, - }); - } - } -} - -#[allow(clippy::new_without_default)] -impl AdexPing { - pub fn new() -> Self { - AdexPing { - ping: Ping::new(PingConfig::new().with_max_failures(unsafe { NonZeroU32::new_unchecked(2) })), - events: VecDeque::new(), - } - } - - fn poll_event( - &mut self, - _cx: &mut Context, - _params: &mut impl PollParameters, - ) -> Poll::ConnectionHandler>> { - if let Some(event) = self.events.pop_front() { - return Poll::Ready(event); - } - - Poll::Pending - } -} diff --git a/mm2src/mm2_libp2p/src/atomicdex_behaviour.rs b/mm2src/mm2_libp2p/src/atomicdex_behaviour.rs deleted file mode 100644 index 482251fb93..0000000000 --- a/mm2src/mm2_libp2p/src/atomicdex_behaviour.rs +++ /dev/null @@ -1,984 +0,0 @@ -use crate::{adex_ping::AdexPing, - network::{get_all_network_seednodes, NETID_8762}, - peers_exchange::{PeerAddresses, PeersExchange}, - request_response::{build_request_response_behaviour, PeerRequest, PeerResponse, RequestResponseBehaviour, - RequestResponseBehaviourEvent, RequestResponseSender}, - runtime::SwarmRuntime, - NetworkInfo, NetworkPorts, RelayAddress, RelayAddressError}; -use atomicdex_gossipsub::{Gossipsub, GossipsubConfigBuilder, GossipsubEvent, GossipsubMessage, MessageId, Topic, - TopicHash}; -use common::executor::SpawnFuture; -use derive_more::Display; -use futures::{channel::{mpsc::{channel, Receiver, Sender}, - oneshot}, - future::{join_all, poll_fn}, - Future, FutureExt, SinkExt, StreamExt}; -use futures_rustls::rustls; -use libp2p::core::transport::Boxed as BoxedTransport; -use libp2p::{core::{ConnectedPoint, Multiaddr, Transport}, - identity, - multiaddr::Protocol, - noise, - request_response::ResponseChannel, - swarm::{NetworkBehaviourEventProcess, Swarm}, - NetworkBehaviour, PeerId}; -use libp2p_floodsub::{Floodsub, FloodsubEvent, Topic as FloodsubTopic}; -use log::{debug, error, info}; -use rand::seq::SliceRandom; -use rand::Rng; -use std::{collections::{hash_map::DefaultHasher, BTreeMap}, - hash::{Hash, Hasher}, - iter, - net::IpAddr, - task::{Context, Poll}, - time::Duration}; -use void::Void; -use wasm_timer::{Instant, Interval}; - -pub type AdexCmdTx = Sender; -pub type AdexEventRx = Receiver; - -#[cfg(test)] mod tests; - -pub const PEERS_TOPIC: &str = "PEERS"; -const CONNECTED_RELAYS_CHECK_INTERVAL: Duration = Duration::from_secs(30); -const ANNOUNCE_INTERVAL: Duration = Duration::from_secs(600); -const ANNOUNCE_INITIAL_DELAY: Duration = Duration::from_secs(60); -const CHANNEL_BUF_SIZE: usize = 1024 * 8; - -/// Returns info about connected peers -pub async fn get_peers_info(mut cmd_tx: AdexCmdTx) -> BTreeMap> { - let (result_tx, rx) = oneshot::channel(); - let cmd = AdexBehaviourCmd::GetPeersInfo { result_tx }; - cmd_tx.send(cmd).await.expect("Rx should be present"); - rx.await.expect("Tx should be present") -} - -/// Returns current gossipsub mesh state -pub async fn get_gossip_mesh(mut cmd_tx: AdexCmdTx) -> BTreeMap> { - let (result_tx, rx) = oneshot::channel(); - let cmd = AdexBehaviourCmd::GetGossipMesh { result_tx }; - cmd_tx.send(cmd).await.expect("Rx should be present"); - rx.await.expect("Tx should be present") -} - -pub async fn get_gossip_peer_topics(mut cmd_tx: AdexCmdTx) -> BTreeMap> { - let (result_tx, rx) = oneshot::channel(); - let cmd = AdexBehaviourCmd::GetGossipPeerTopics { result_tx }; - cmd_tx.send(cmd).await.expect("Rx should be present"); - rx.await.expect("Tx should be present") -} - -pub async fn get_gossip_topic_peers(mut cmd_tx: AdexCmdTx) -> BTreeMap> { - let (result_tx, rx) = oneshot::channel(); - let cmd = AdexBehaviourCmd::GetGossipTopicPeers { result_tx }; - cmd_tx.send(cmd).await.expect("Rx should be present"); - rx.await.expect("Tx should be present") -} - -pub async fn get_relay_mesh(mut cmd_tx: AdexCmdTx) -> Vec { - let (result_tx, rx) = oneshot::channel(); - let cmd = AdexBehaviourCmd::GetRelayMesh { result_tx }; - cmd_tx.send(cmd).await.expect("Rx should be present"); - rx.await.expect("Tx should be present") -} - -#[derive(Debug)] -pub struct AdexResponseChannel(ResponseChannel); - -impl From> for AdexResponseChannel { - fn from(res: ResponseChannel) -> Self { AdexResponseChannel(res) } -} - -impl From for ResponseChannel { - fn from(res: AdexResponseChannel) -> Self { res.0 } -} - -#[derive(Debug)] -pub enum AdexBehaviourCmd { - Subscribe { - /// Subscribe to this topic - topic: String, - }, - PublishMsg { - topics: Vec, - msg: Vec, - }, - PublishMsgFrom { - topics: Vec, - msg: Vec, - from: PeerId, - }, - /// Request relays sequential until a response is received. - RequestAnyRelay { - req: Vec, - response_tx: oneshot::Sender)>>, - }, - /// Request given peers and collect all their responses. - RequestPeers { - req: Vec, - peers: Vec, - response_tx: oneshot::Sender>, - }, - /// Request relays and collect all their responses. - RequestRelays { - req: Vec, - response_tx: oneshot::Sender>, - }, - /// Send a response using a `response_channel`. - SendResponse { - /// Response to a request. - res: AdexResponse, - /// Pass the same `response_channel` as that was obtained from [`AdexBehaviourEvent::PeerRequest`]. - response_channel: AdexResponseChannel, - }, - GetPeersInfo { - result_tx: oneshot::Sender>>, - }, - GetGossipMesh { - result_tx: oneshot::Sender>>, - }, - GetGossipPeerTopics { - result_tx: oneshot::Sender>>, - }, - GetGossipTopicPeers { - result_tx: oneshot::Sender>>, - }, - GetRelayMesh { - result_tx: oneshot::Sender>, - }, - /// Add a reserved peer to the peer exchange. - AddReservedPeer { - peer: PeerId, - addresses: PeerAddresses, - }, - PropagateMessage { - message_id: MessageId, - propagation_source: PeerId, - }, -} - -/// The structure is the same as `PeerResponse`, -/// but is used to prevent `PeerResponse` from being used outside the network implementation. -#[derive(Debug, Eq, PartialEq)] -pub enum AdexResponse { - Ok { response: Vec }, - None, - Err { error: String }, -} - -impl From for AdexResponse { - fn from(res: PeerResponse) -> Self { - match res { - PeerResponse::Ok { res } => AdexResponse::Ok { response: res }, - PeerResponse::None => AdexResponse::None, - PeerResponse::Err { err } => AdexResponse::Err { error: err }, - } - } -} - -impl From for PeerResponse { - fn from(res: AdexResponse) -> Self { - match res { - AdexResponse::Ok { response } => PeerResponse::Ok { res: response }, - AdexResponse::None => PeerResponse::None, - AdexResponse::Err { error } => PeerResponse::Err { err: error }, - } - } -} - -/// The structure consists of GossipsubEvent and RequestResponse events. -/// It is used to prevent the network events from being used outside the network implementation. -#[derive(Debug)] -pub enum AdexBehaviourEvent { - /// A message has been received. - /// Derived from GossipsubEvent. - Message(PeerId, MessageId, GossipsubMessage), - /// A remote subscribed to a topic. - Subscribed { - /// Remote that has subscribed. - peer_id: PeerId, - /// The topic it has subscribed to. - topic: TopicHash, - }, - /// A remote unsubscribed from a topic. - Unsubscribed { - /// Remote that has unsubscribed. - peer_id: PeerId, - /// The topic it has subscribed from. - topic: TopicHash, - }, - /// A remote peer sent a request and waits for a response. - PeerRequest { - /// Remote that sent this request. - peer_id: PeerId, - /// The serialized data. - request: Vec, - /// A channel for sending a response to this request. - /// The channel is used to identify the peer on the network that is waiting for an answer to this request. - /// See [`AdexBehaviourCmd::SendResponse`]. - response_channel: AdexResponseChannel, - }, -} - -impl From for AdexBehaviourEvent { - fn from(event: GossipsubEvent) -> Self { - match event { - GossipsubEvent::Message(peer_id, message_id, gossipsub_message) => { - AdexBehaviourEvent::Message(peer_id, message_id, gossipsub_message) - }, - GossipsubEvent::Subscribed { peer_id, topic } => AdexBehaviourEvent::Subscribed { peer_id, topic }, - GossipsubEvent::Unsubscribed { peer_id, topic } => AdexBehaviourEvent::Unsubscribed { peer_id, topic }, - } - } -} - -/// AtomicDEX libp2p Network behaviour implementation -#[derive(NetworkBehaviour)] -#[behaviour(event_process = true)] -pub struct AtomicDexBehaviour { - floodsub: Floodsub, - #[behaviour(ignore)] - event_tx: Sender, - #[behaviour(ignore)] - runtime: SwarmRuntime, - #[behaviour(ignore)] - cmd_rx: Receiver, - #[behaviour(ignore)] - netid: u16, - gossipsub: Gossipsub, - request_response: RequestResponseBehaviour, - peers_exchange: PeersExchange, - ping: AdexPing, -} - -impl AtomicDexBehaviour { - fn notify_on_adex_event(&mut self, event: AdexBehaviourEvent) { - if let Err(e) = self.event_tx.try_send(event) { - error!("notify_on_adex_event error {}", e); - } - } - - fn spawn(&self, fut: impl Future + Send + 'static) { self.runtime.spawn(fut) } - - fn process_cmd(&mut self, cmd: AdexBehaviourCmd) { - match cmd { - AdexBehaviourCmd::Subscribe { topic } => { - let topic = Topic::new(topic); - self.gossipsub.subscribe(topic); - }, - AdexBehaviourCmd::PublishMsg { topics, msg } => { - self.gossipsub.publish_many(topics.into_iter().map(Topic::new), msg); - }, - AdexBehaviourCmd::PublishMsgFrom { topics, msg, from } => { - self.gossipsub - .publish_many_from(topics.into_iter().map(Topic::new), msg, from); - }, - AdexBehaviourCmd::RequestAnyRelay { req, response_tx } => { - let relays = self.gossipsub.get_relay_mesh(); - // spawn the `request_any_peer` future - let future = request_any_peer(relays, req, self.request_response.sender(), response_tx); - self.spawn(future); - }, - AdexBehaviourCmd::RequestPeers { - req, - peers, - response_tx, - } => { - let peers = peers - .into_iter() - .filter_map(|peer| match peer.parse() { - Ok(p) => Some(p), - Err(e) => { - error!("Error on parse peer id {:?}: {:?}", peer, e); - None - }, - }) - .collect(); - let future = request_peers(peers, req, self.request_response.sender(), response_tx); - self.spawn(future); - }, - AdexBehaviourCmd::RequestRelays { req, response_tx } => { - let relays = self.gossipsub.get_relay_mesh(); - // spawn the `request_peers` future - let future = request_peers(relays, req, self.request_response.sender(), response_tx); - self.spawn(future); - }, - AdexBehaviourCmd::SendResponse { res, response_channel } => { - if let Err(response) = self.request_response.send_response(response_channel.into(), res.into()) { - error!("Error sending response: {:?}", response); - } - }, - AdexBehaviourCmd::GetPeersInfo { result_tx } => { - let result = self - .gossipsub - .get_peers_connections() - .into_iter() - .map(|(peer_id, connected_points)| { - let peer_id = peer_id.to_base58(); - let connected_points = connected_points - .into_iter() - .map(|(_conn_id, point)| match point { - ConnectedPoint::Dialer { address, .. } => address.to_string(), - ConnectedPoint::Listener { send_back_addr, .. } => send_back_addr.to_string(), - }) - .collect(); - (peer_id, connected_points) - }) - .collect(); - if result_tx.send(result).is_err() { - debug!("Result rx is dropped"); - } - }, - AdexBehaviourCmd::GetGossipMesh { result_tx } => { - let result = self - .gossipsub - .get_mesh() - .iter() - .map(|(topic, peers)| { - let topic = topic.to_string(); - let peers = peers.iter().map(|peer| peer.to_string()).collect(); - (topic, peers) - }) - .collect(); - if result_tx.send(result).is_err() { - debug!("Result rx is dropped"); - } - }, - AdexBehaviourCmd::GetGossipPeerTopics { result_tx } => { - let result = self - .gossipsub - .get_all_peer_topics() - .iter() - .map(|(peer, topics)| { - let peer = peer.to_string(); - let topics = topics.iter().map(|topic| topic.to_string()).collect(); - (peer, topics) - }) - .collect(); - if result_tx.send(result).is_err() { - error!("Result rx is dropped"); - } - }, - AdexBehaviourCmd::GetGossipTopicPeers { result_tx } => { - let result = self - .gossipsub - .get_all_topic_peers() - .iter() - .map(|(topic, peers)| { - let topic = topic.to_string(); - let peers = peers.iter().map(|peer| peer.to_string()).collect(); - (topic, peers) - }) - .collect(); - if result_tx.send(result).is_err() { - error!("Result rx is dropped"); - } - }, - AdexBehaviourCmd::GetRelayMesh { result_tx } => { - let result = self - .gossipsub - .get_relay_mesh() - .into_iter() - .map(|peer| peer.to_string()) - .collect(); - if result_tx.send(result).is_err() { - error!("Result rx is dropped"); - } - }, - AdexBehaviourCmd::AddReservedPeer { peer, addresses } => { - self.peers_exchange - .add_peer_addresses_to_reserved_peers(&peer, addresses); - }, - AdexBehaviourCmd::PropagateMessage { - message_id, - propagation_source, - } => { - self.gossipsub.propagate_message(&message_id, &propagation_source); - }, - } - } - - fn announce_listeners(&mut self, listeners: PeerAddresses) { - let serialized = rmp_serde::to_vec(&listeners).expect("PeerAddresses serialization should never fail"); - self.floodsub.publish(FloodsubTopic::new(PEERS_TOPIC), serialized); - } - - pub fn connected_relays_len(&self) -> usize { self.gossipsub.connected_relays_len() } - - pub fn relay_mesh_len(&self) -> usize { self.gossipsub.relay_mesh_len() } - - pub fn received_messages_in_period(&self) -> (Duration, usize) { self.gossipsub.get_received_messages_in_period() } - - pub fn connected_peers_len(&self) -> usize { self.gossipsub.get_num_peers() } -} - -impl NetworkBehaviourEventProcess for AtomicDexBehaviour { - fn inject_event(&mut self, event: GossipsubEvent) { self.notify_on_adex_event(event.into()); } -} - -impl NetworkBehaviourEventProcess for AtomicDexBehaviour { - fn inject_event(&mut self, event: FloodsubEvent) { - // do not process peer announce on 8762 temporary - if self.netid != NETID_8762 { - if let FloodsubEvent::Message(message) = &event { - for topic in &message.topics { - if topic == &FloodsubTopic::new(PEERS_TOPIC) { - let addresses: PeerAddresses = match rmp_serde::from_slice(&message.data) { - Ok(a) => a, - Err(_) => return, - }; - self.peers_exchange - .add_peer_addresses_to_known_peers(&message.source, addresses); - } - } - } - } - } -} - -impl NetworkBehaviourEventProcess for AtomicDexBehaviour { - fn inject_event(&mut self, _event: Void) {} -} - -impl NetworkBehaviourEventProcess<()> for AtomicDexBehaviour { - fn inject_event(&mut self, _event: ()) {} -} - -impl NetworkBehaviourEventProcess for AtomicDexBehaviour { - fn inject_event(&mut self, event: RequestResponseBehaviourEvent) { - match event { - RequestResponseBehaviourEvent::InboundRequest { - peer_id, - request, - response_channel, - } => { - let event = AdexBehaviourEvent::PeerRequest { - peer_id, - request: request.req, - response_channel: response_channel.into(), - }; - // forward the event to the AdexBehaviourCmd handler - self.notify_on_adex_event(event); - }, - } - } -} - -/// Custom types mapping the complex associated types of AtomicDexBehaviour to the ExpandedSwarm -type AtomicDexSwarm = Swarm; - -fn maintain_connection_to_relays(swarm: &mut AtomicDexSwarm, bootstrap_addresses: &[Multiaddr]) { - let behaviour = swarm.behaviour(); - let connected_relays = behaviour.gossipsub.connected_relays(); - let mesh_n_low = behaviour.gossipsub.get_config().mesh_n_low; - let mesh_n = behaviour.gossipsub.get_config().mesh_n; - // allow 2 * mesh_n_high connections to other nodes - let max_n = behaviour.gossipsub.get_config().mesh_n_high * 2; - - let mut rng = rand::thread_rng(); - if connected_relays.len() < mesh_n_low { - let to_connect_num = mesh_n - connected_relays.len(); - let to_connect = swarm - .behaviour_mut() - .peers_exchange - .get_random_peers(to_connect_num, |peer| !connected_relays.contains(peer)); - - // choose some random bootstrap addresses to connect if peers exchange returned not enough peers - if to_connect.len() < to_connect_num { - let connect_bootstrap_num = to_connect_num - to_connect.len(); - for addr in bootstrap_addresses - .iter() - .filter(|addr| !swarm.behaviour().gossipsub.is_connected_to_addr(addr)) - .collect::>() - .choose_multiple(&mut rng, connect_bootstrap_num) - { - if let Err(e) = libp2p::Swarm::dial(swarm, (*addr).clone()) { - error!("Bootstrap addr {} dial error {}", addr, e); - } - } - } - for (peer, addresses) in to_connect { - for addr in addresses { - if swarm.behaviour().gossipsub.is_connected_to_addr(&addr) { - continue; - } - if let Err(e) = libp2p::Swarm::dial(swarm, addr.clone()) { - error!("Peer {} address {} dial error {}", peer, addr, e); - } - } - } - } - - if connected_relays.len() > max_n { - let to_disconnect_num = connected_relays.len() - max_n; - let relays_mesh = swarm.behaviour().gossipsub.get_relay_mesh(); - let not_in_mesh: Vec<_> = connected_relays - .iter() - .filter(|peer| !relays_mesh.contains(peer)) - .collect(); - for peer in not_in_mesh.choose_multiple(&mut rng, to_disconnect_num) { - if !swarm.behaviour().peers_exchange.is_reserved_peer(peer) { - info!("Disconnecting peer {}", peer); - if Swarm::disconnect_peer_id(swarm, **peer).is_err() { - error!("Peer {} disconnect error", peer); - } - } - } - } - - for relay in connected_relays { - if !swarm.behaviour().peers_exchange.is_known_peer(&relay) { - swarm.behaviour_mut().peers_exchange.add_known_peer(relay); - } - } -} - -fn announce_my_addresses(swarm: &mut AtomicDexSwarm) { - let global_listeners: PeerAddresses = Swarm::listeners(swarm) - .filter(|listener| { - for protocol in listener.iter() { - if let Protocol::Ip4(ip) = protocol { - return ip.is_global(); - } - } - false - }) - .take(1) - .cloned() - .collect(); - if !global_listeners.is_empty() { - swarm.behaviour_mut().announce_listeners(global_listeners); - } -} - -#[derive(Debug, Display)] -pub enum AdexBehaviourError { - #[display(fmt = "{}", _0)] - ParsingRelayAddress(RelayAddressError), -} - -impl From for AdexBehaviourError { - fn from(e: RelayAddressError) -> Self { AdexBehaviourError::ParsingRelayAddress(e) } -} - -pub struct WssCerts { - pub server_priv_key: rustls::PrivateKey, - pub certs: Vec, -} - -pub enum NodeType { - Light { - network_ports: NetworkPorts, - }, - LightInMemory, - Relay { - ip: IpAddr, - network_ports: NetworkPorts, - wss_certs: Option, - }, - RelayInMemory { - port: u64, - }, -} - -impl NodeType { - pub fn to_network_info(&self) -> NetworkInfo { - match self { - NodeType::Light { network_ports } | NodeType::Relay { network_ports, .. } => NetworkInfo::Distributed { - network_ports: *network_ports, - }, - NodeType::LightInMemory | NodeType::RelayInMemory { .. } => NetworkInfo::InMemory, - } - } - - pub fn is_relay(&self) -> bool { matches!(self, NodeType::Relay { .. } | NodeType::RelayInMemory { .. }) } - - pub fn wss_certs(&self) -> Option<&WssCerts> { - match self { - NodeType::Relay { wss_certs, .. } => wss_certs.as_ref(), - _ => None, - } - } -} - -/// Creates and spawns new AdexBehaviour Swarm returning: -/// 1. tx to send control commands -/// 2. rx emitting gossip events to processing side -/// 3. our peer_id -/// 4. abort handle to stop the P2P processing fut. -pub async fn spawn_gossipsub( - netid: u16, - force_key: Option<[u8; 32]>, - runtime: SwarmRuntime, - to_dial: Vec, - node_type: NodeType, - on_poll: impl Fn(&AtomicDexSwarm) + Send + 'static, -) -> Result<(Sender, AdexEventRx, PeerId), AdexBehaviourError> { - let (result_tx, result_rx) = oneshot::channel(); - - let runtime_c = runtime.clone(); - let fut = async move { - let result = start_gossipsub(netid, force_key, runtime, to_dial, node_type, on_poll); - result_tx.send(result).unwrap(); - }; - - // `Libp2p` must be spawned on the tokio runtime - runtime_c.spawn(fut); - result_rx.await.expect("Fatal error on starting gossipsub") -} - -/// Creates and spawns new AdexBehaviour Swarm returning: -/// 1. tx to send control commands -/// 2. rx emitting gossip events to processing side -/// 3. our peer_id -/// 4. abort handle to stop the P2P processing fut -/// -/// Prefer using [`spawn_gossipsub`] to make sure the Swarm is initialized and spawned on the same runtime. -/// Otherwise, you can face the following error: -/// `panicked at 'there is no reactor running, must be called from the context of a Tokio 1.x runtime'`. -#[allow(clippy::too_many_arguments)] -fn start_gossipsub( - netid: u16, - force_key: Option<[u8; 32]>, - runtime: SwarmRuntime, - to_dial: Vec, - node_type: NodeType, - on_poll: impl Fn(&AtomicDexSwarm) + Send + 'static, -) -> Result<(Sender, AdexEventRx, PeerId), AdexBehaviourError> { - let i_am_relay = node_type.is_relay(); - let mut rng = rand::thread_rng(); - let local_key = generate_ed25519_keypair(&mut rng, force_key); - let local_peer_id = PeerId::from(local_key.public()); - info!("Local peer id: {:?}", local_peer_id); - - let noise_keys = noise::Keypair::::new() - .into_authentic(&local_key) - .expect("Signing libp2p-noise static DH keypair failed."); - - let network_info = node_type.to_network_info(); - let transport = match network_info { - NetworkInfo::InMemory => build_memory_transport(noise_keys), - NetworkInfo::Distributed { .. } => build_dns_ws_transport(noise_keys, node_type.wss_certs()), - }; - - let (cmd_tx, cmd_rx) = channel(CHANNEL_BUF_SIZE); - let (event_tx, event_rx) = channel(CHANNEL_BUF_SIZE); - - let bootstrap = to_dial - .into_iter() - .map(|addr| addr.try_to_multiaddr(network_info)) - .collect::, _>>()?; - - let (mesh_n_low, mesh_n, mesh_n_high) = if i_am_relay { (4, 6, 12) } else { (2, 3, 4) }; - - // Create a Swarm to manage peers and events - let mut swarm = { - // to set default parameters for gossipsub use: - // let gossipsub_config = gossipsub::GossipsubConfig::default(); - - // To content-address message, we can take the hash of message and use it as an ID. - let message_id_fn = |message: &GossipsubMessage| { - let mut s = DefaultHasher::new(); - message.data.hash(&mut s); - message.sequence_number.hash(&mut s); - MessageId(s.finish().to_string()) - }; - - // set custom gossipsub - let gossipsub_config = GossipsubConfigBuilder::new() - .message_id_fn(message_id_fn) - .i_am_relay(i_am_relay) - .mesh_n_low(mesh_n_low) - .mesh_n(mesh_n) - .mesh_n_high(mesh_n_high) - .manual_propagation() - .max_transmit_size(1024 * 1024 - 100) - .build(); - // build a gossipsub network behaviour - let mut gossipsub = Gossipsub::new(local_peer_id, gossipsub_config); - - let floodsub = Floodsub::new(local_peer_id, netid != NETID_8762); - - let mut peers_exchange = PeersExchange::new(network_info); - if !network_info.in_memory() { - // Please note WASM nodes don't support `PeersExchange` currently, - // so `get_all_network_seednodes` returns an empty list. - for (peer_id, addr) in get_all_network_seednodes(netid) { - let multiaddr = addr.try_to_multiaddr(network_info)?; - peers_exchange.add_peer_addresses_to_known_peers(&peer_id, iter::once(multiaddr).collect()); - gossipsub.add_explicit_relay(peer_id); - } - } - - // build a request-response network behaviour - let request_response = build_request_response_behaviour(); - - // use default ping config with 15s interval, 20s timeout and 1 max failure - let ping = AdexPing::new(); - - let adex_behavior = AtomicDexBehaviour { - floodsub, - event_tx, - runtime: runtime.clone(), - cmd_rx, - netid, - gossipsub, - request_response, - peers_exchange, - ping, - }; - libp2p::swarm::SwarmBuilder::new(transport, adex_behavior, local_peer_id) - .executor(Box::new(runtime.clone())) - .build() - }; - swarm - .behaviour_mut() - .floodsub - .subscribe(FloodsubTopic::new(PEERS_TOPIC.to_owned())); - - match node_type { - NodeType::Relay { - ip, - network_ports, - wss_certs, - } => { - let dns_addr: Multiaddr = format!("/ip4/{}/tcp/{}", ip, network_ports.tcp).parse().unwrap(); - libp2p::Swarm::listen_on(&mut swarm, dns_addr).unwrap(); - if wss_certs.is_some() { - let wss_addr: Multiaddr = format!("/ip4/{}/tcp/{}/wss", ip, network_ports.wss).parse().unwrap(); - libp2p::Swarm::listen_on(&mut swarm, wss_addr).unwrap(); - } - }, - NodeType::RelayInMemory { port } => { - let memory_addr: Multiaddr = format!("/memory/{}", port).parse().unwrap(); - libp2p::Swarm::listen_on(&mut swarm, memory_addr).unwrap(); - }, - _ => (), - } - - for relay in bootstrap.choose_multiple(&mut rng, mesh_n) { - match libp2p::Swarm::dial(&mut swarm, relay.clone()) { - Ok(_) => info!("Dialed {}", relay), - Err(e) => error!("Dial {:?} failed: {:?}", relay, e), - } - } - - let mut check_connected_relays_interval = Interval::new_at( - Instant::now() + CONNECTED_RELAYS_CHECK_INTERVAL, - CONNECTED_RELAYS_CHECK_INTERVAL, - ); - let mut announce_interval = Interval::new_at(Instant::now() + ANNOUNCE_INITIAL_DELAY, ANNOUNCE_INTERVAL); - let mut listening = false; - let polling_fut = poll_fn(move |cx: &mut Context| { - loop { - match swarm.behaviour_mut().cmd_rx.poll_next_unpin(cx) { - Poll::Ready(Some(cmd)) => swarm.behaviour_mut().process_cmd(cmd), - Poll::Ready(None) => return Poll::Ready(()), - Poll::Pending => break, - } - } - - loop { - match swarm.poll_next_unpin(cx) { - Poll::Ready(Some(event)) => debug!("Swarm event {:?}", event), - Poll::Ready(None) => return Poll::Ready(()), - Poll::Pending => break, - } - } - - if swarm.behaviour().gossipsub.is_relay() { - while let Poll::Ready(Some(())) = announce_interval.poll_next_unpin(cx) { - announce_my_addresses(&mut swarm); - } - } - - while let Poll::Ready(Some(())) = check_connected_relays_interval.poll_next_unpin(cx) { - maintain_connection_to_relays(&mut swarm, &bootstrap); - } - - if !listening && i_am_relay { - for listener in Swarm::listeners(&swarm) { - info!("Listening on {}", listener); - listening = true; - } - } - on_poll(&swarm); - Poll::Pending - }); - - runtime.spawn(polling_fut.then(|_| futures::future::ready(()))); - Ok((cmd_tx, event_rx, local_peer_id)) -} - -#[cfg(target_arch = "wasm32")] -fn build_dns_ws_transport( - noise_keys: libp2p::noise::AuthenticKeypair, - _wss_certs: Option<&WssCerts>, -) -> BoxedTransport<(PeerId, libp2p::core::muxing::StreamMuxerBox)> { - let websocket = libp2p::wasm_ext::ffi::websocket_transport(); - let transport = libp2p::wasm_ext::ExtTransport::new(websocket); - upgrade_transport(transport, noise_keys) -} - -#[cfg(not(target_arch = "wasm32"))] -fn build_dns_ws_transport( - noise_keys: libp2p::noise::AuthenticKeypair, - wss_certs: Option<&WssCerts>, -) -> BoxedTransport<(PeerId, libp2p::core::muxing::StreamMuxerBox)> { - use libp2p::websocket::tls as libp2p_tls; - - let ws_tcp = libp2p::dns::TokioDnsConfig::custom( - libp2p::tcp::TokioTcpConfig::new().nodelay(true), - libp2p::dns::ResolverConfig::google(), - Default::default(), - ) - .unwrap(); - let mut ws_dns_tcp = libp2p::websocket::WsConfig::new(ws_tcp); - - if let Some(certs) = wss_certs { - let server_priv_key = libp2p_tls::PrivateKey::new(certs.server_priv_key.0.clone()); - let certs = certs - .certs - .iter() - .map(|cert| libp2p_tls::Certificate::new(cert.0.clone())); - let wss_config = libp2p_tls::Config::new(server_priv_key, certs).unwrap(); - ws_dns_tcp.set_tls_config(wss_config); - } - - // This is for preventing port reuse of dns/tcp instead of - // websocket ports. - let dns_tcp = libp2p::dns::TokioDnsConfig::custom( - libp2p::tcp::TokioTcpConfig::new().nodelay(true), - libp2p::dns::ResolverConfig::google(), - Default::default(), - ) - .unwrap(); - - let transport = dns_tcp.or_transport(ws_dns_tcp); - upgrade_transport(transport, noise_keys) -} - -fn build_memory_transport( - noise_keys: libp2p::noise::AuthenticKeypair, -) -> BoxedTransport<(PeerId, libp2p::core::muxing::StreamMuxerBox)> { - let transport = libp2p::core::transport::MemoryTransport::default(); - upgrade_transport(transport, noise_keys) -} - -/// Set up an encrypted Transport over the Mplex protocol. -fn upgrade_transport( - transport: T, - noise_keys: libp2p::noise::AuthenticKeypair, -) -> BoxedTransport<(PeerId, libp2p::core::muxing::StreamMuxerBox)> -where - T: Transport + Send + Sync + 'static, - T::Output: futures::AsyncRead + futures::AsyncWrite + Unpin + Send + 'static, - T::ListenerUpgrade: Send, - T::Listener: Send, - T::Dial: Send, - T::Error: Send + Sync + 'static, -{ - transport - .upgrade(libp2p::core::upgrade::Version::V1) - .authenticate(noise::NoiseConfig::xx(noise_keys).into_authenticated()) - .multiplex(libp2p::mplex::MplexConfig::default()) - .timeout(std::time::Duration::from_secs(20)) - .map(|(peer, muxer), _| (peer, libp2p::core::muxing::StreamMuxerBox::new(muxer))) - .boxed() -} - -fn generate_ed25519_keypair(rng: &mut R, force_key: Option<[u8; 32]>) -> identity::Keypair { - let mut raw_key = match force_key { - Some(key) => key, - None => { - let mut key = [0; 32]; - rng.fill_bytes(&mut key); - key - }, - }; - let secret = identity::ed25519::SecretKey::from_bytes(&mut raw_key).expect("Secret length is 32 bytes"); - let keypair = identity::ed25519::Keypair::from(secret); - identity::Keypair::Ed25519(keypair) -} - -/// Request the peers sequential until a `PeerResponse::Ok()` will not be received. -async fn request_any_peer( - peers: Vec, - request_data: Vec, - request_response_tx: RequestResponseSender, - response_tx: oneshot::Sender)>>, -) { - debug!("start request_any_peer loop: peers {}", peers.len()); - for peer in peers { - match request_one_peer(peer, request_data.clone(), request_response_tx.clone()).await { - PeerResponse::Ok { res } => { - debug!("Received a response from peer {:?}, stop the request loop", peer); - if response_tx.send(Some((peer, res))).is_err() { - error!("Response oneshot channel was closed"); - } - return; - }, - PeerResponse::None => { - debug!("Received None from peer {:?}, request next peer", peer); - }, - PeerResponse::Err { err } => { - error!("Error on request {:?} peer: {:?}. Request next peer", peer, err); - }, - }; - } - - debug!("None of the peers responded to the request"); - if response_tx.send(None).is_err() { - error!("Response oneshot channel was closed"); - }; -} - -/// Request the peers and collect all their responses. -async fn request_peers( - peers: Vec, - request_data: Vec, - request_response_tx: RequestResponseSender, - response_tx: oneshot::Sender>, -) { - debug!("start request_any_peer loop: peers {}", peers.len()); - let mut futures = Vec::with_capacity(peers.len()); - for peer in peers { - let request_data = request_data.clone(); - let request_response_tx = request_response_tx.clone(); - futures.push(async move { - let response = request_one_peer(peer, request_data, request_response_tx).await; - (peer, response) - }) - } - - let responses = join_all(futures) - .await - .into_iter() - .map(|(peer_id, res)| { - let res: AdexResponse = res.into(); - (peer_id, res) - }) - .collect(); - - if response_tx.send(responses).is_err() { - error!("Response oneshot channel was closed"); - }; -} - -async fn request_one_peer(peer: PeerId, req: Vec, mut request_response_tx: RequestResponseSender) -> PeerResponse { - // Use the internal receiver to receive a response to this request. - let (internal_response_tx, internal_response_rx) = oneshot::channel(); - let request = PeerRequest { req }; - request_response_tx - .send((peer, request, internal_response_tx)) - .await - .unwrap(); - - match internal_response_rx.await { - Ok(response) => response, - Err(e) => PeerResponse::Err { - err: format!("Error on request the peer {:?}: \"{:?}\". Request next peer", peer, e), - }, - } -} diff --git a/mm2src/mm2_libp2p/src/atomicdex_behaviour/tests.rs b/mm2src/mm2_libp2p/src/atomicdex_behaviour/tests.rs deleted file mode 100644 index 4dfdfaa2cb..0000000000 --- a/mm2src/mm2_libp2p/src/atomicdex_behaviour/tests.rs +++ /dev/null @@ -1,368 +0,0 @@ -use super::{spawn_gossipsub, AdexBehaviourCmd, AdexBehaviourEvent, AdexResponse, NodeType, RelayAddress, SwarmRuntime}; -use async_std::task::spawn; -use common::executor::abortable_queue::AbortableQueue; -use futures::channel::{mpsc, oneshot}; -use futures::{SinkExt, StreamExt}; -use libp2p::PeerId; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use std::sync::Arc; -#[cfg(not(windows))] use std::sync::Mutex; -use std::time::Duration; - -static TEST_LISTEN_PORT: AtomicU64 = AtomicU64::new(1); - -lazy_static! { - static ref SYSTEM: AbortableQueue = AbortableQueue::default(); -} - -fn next_port() -> u64 { TEST_LISTEN_PORT.fetch_add(1, Ordering::Relaxed) } - -struct Node { - peer_id: PeerId, - cmd_tx: mpsc::Sender, -} - -impl Node { - async fn spawn(port: u64, seednodes: Vec, on_event: F) -> Node - where - F: Fn(mpsc::Sender, AdexBehaviourEvent) + Send + 'static, - { - let spawner = SwarmRuntime::new(SYSTEM.weak_spawner()); - let node_type = NodeType::RelayInMemory { port }; - let seednodes = seednodes.into_iter().map(RelayAddress::Memory).collect(); - let (cmd_tx, mut event_rx, peer_id) = spawn_gossipsub(333, None, spawner, seednodes, node_type, |_| {}) - .await - .expect("Error spawning AdexBehaviour"); - - // spawn a response future - let cmd_tx_fut = cmd_tx.clone(); - spawn(async move { - loop { - let cmd_tx_fut = cmd_tx_fut.clone(); - match event_rx.next().await { - Some(r) => on_event(cmd_tx_fut, r), - _ => { - log!("Finish response future"); - break; - }, - } - } - }); - - Node { peer_id, cmd_tx } - } - - async fn send_cmd(&mut self, cmd: AdexBehaviourCmd) { self.cmd_tx.send(cmd).await.unwrap(); } - - async fn wait_peers(&mut self, number: usize) { - let mut attempts = 0; - loop { - let (tx, rx) = oneshot::channel(); - self.cmd_tx - .send(AdexBehaviourCmd::GetPeersInfo { result_tx: tx }) - .await - .unwrap(); - match rx.await { - Ok(map) => { - if map.len() >= number { - return; - } - async_std::task::sleep(Duration::from_millis(500)).await; - }, - Err(e) => panic!("{}", e), - } - attempts += 1; - if attempts >= 10 { - panic!("wait_peers {} attempts exceeded", attempts); - } - } - } -} - -#[tokio::test] -async fn test_request_response_ok() { - let _ = env_logger::try_init(); - - let request_received = Arc::new(AtomicBool::new(false)); - let request_received_cpy = request_received.clone(); - - let node1_port = next_port(); - let node1 = Node::spawn(node1_port, vec![], move |mut cmd_tx, event| { - let (request, response_channel) = match event { - AdexBehaviourEvent::PeerRequest { - request, - response_channel, - .. - } => (request, response_channel), - _ => return, - }; - - request_received_cpy.store(true, Ordering::Relaxed); - assert_eq!(request, b"test request"); - - let res = AdexResponse::Ok { - response: b"test response".to_vec(), - }; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - }) - .await; - - let mut node2 = Node::spawn(next_port(), vec![node1_port], |_, _| ()).await; - - node2.wait_peers(1).await; - - let (response_tx, response_rx) = oneshot::channel(); - node2 - .send_cmd(AdexBehaviourCmd::RequestAnyRelay { - req: b"test request".to_vec(), - response_tx, - }) - .await; - - let response = response_rx.await.unwrap(); - assert_eq!(response, Some((node1.peer_id, b"test response".to_vec()))); - - assert!(request_received.load(Ordering::Relaxed)); -} - -#[tokio::test] -#[cfg(not(windows))] // https://github.com/KomodoPlatform/atomicDEX-API/issues/1712 -async fn test_request_response_ok_three_peers() { - let _ = env_logger::try_init(); - - #[derive(Default)] - struct RequestHandler { - requests: u8, - } - - impl RequestHandler { - fn handle(&mut self, mut cmd_tx: mpsc::Sender, event: AdexBehaviourEvent) { - let (request, response_channel) = match event { - AdexBehaviourEvent::PeerRequest { - request, - response_channel, - .. - } => (request, response_channel), - _ => return, - }; - - self.requests += 1; - - assert_eq!(request, b"test request"); - - // the first time we should respond the none - if self.requests == 1 { - let res = AdexResponse::None; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - return; - } - - // the second time we should respond an error - if self.requests == 2 { - let res = AdexResponse::Err { - error: "test error".into(), - }; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - return; - } - - // the third time we should respond an ok - if self.requests == 3 { - let res = AdexResponse::Ok { - response: format!("success {} request", self.requests).as_bytes().to_vec(), - }; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - return; - } - - panic!("Request received more than 3 times"); - } - } - - let request_handler = Arc::new(Mutex::new(RequestHandler::default())); - - let mut receivers = Vec::new(); - for _ in 0..3 { - let handler = request_handler.clone(); - let receiver_port = next_port(); - let receiver = Node::spawn(receiver_port, vec![], move |cmd_tx, event| { - let mut handler = handler.lock().unwrap(); - handler.handle(cmd_tx, event) - }) - .await; - receivers.push((receiver_port, receiver)); - } - - let mut sender = Node::spawn( - next_port(), - receivers.iter().map(|(port, _)| *port).collect(), - |_, _| (), - ) - .await; - - sender.wait_peers(3).await; - - let (response_tx, response_rx) = oneshot::channel(); - sender - .send_cmd(AdexBehaviourCmd::RequestAnyRelay { - req: b"test request".to_vec(), - response_tx, - }) - .await; - - let (_peer_id, res) = response_rx.await.unwrap().unwrap(); - assert_eq!(res, b"success 3 request".to_vec()); -} - -#[tokio::test] -async fn test_request_response_none() { - let _ = env_logger::try_init(); - - let request_received = Arc::new(AtomicBool::new(false)); - let request_received_cpy = request_received.clone(); - - let node1_port = next_port(); - let _node1 = Node::spawn(node1_port, vec![], move |mut cmd_tx, event| { - let (request, response_channel) = match event { - AdexBehaviourEvent::PeerRequest { - request, - response_channel, - .. - } => (request, response_channel), - _ => return, - }; - - request_received_cpy.store(true, Ordering::Relaxed); - assert_eq!(request, b"test request"); - - let res = AdexResponse::None; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - }) - .await; - - let mut node2 = Node::spawn(next_port(), vec![node1_port], |_, _| ()).await; - - node2.wait_peers(1).await; - - let (response_tx, response_rx) = oneshot::channel(); - node2 - .send_cmd(AdexBehaviourCmd::RequestAnyRelay { - req: b"test request".to_vec(), - response_tx, - }) - .await; - - assert_eq!(response_rx.await.unwrap(), None); - assert!(request_received.load(Ordering::Relaxed)); -} - -#[tokio::test] -#[cfg(target_os = "linux")] // https://github.com/KomodoPlatform/atomicDEX-API/issues/1712 -async fn test_request_peers_ok_three_peers() { - let _ = env_logger::try_init(); - - let receiver1_port = next_port(); - let receiver1 = Node::spawn(receiver1_port, vec![], move |mut cmd_tx, event| { - let (request, response_channel) = match event { - AdexBehaviourEvent::PeerRequest { - request, - response_channel, - .. - } => (request, response_channel), - _ => return, - }; - - assert_eq!(request, b"test request"); - - let res = AdexResponse::None; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - }) - .await; - - let receiver2_port = next_port(); - let receiver2 = Node::spawn(receiver2_port, vec![], move |mut cmd_tx, event| { - let (request, response_channel) = match event { - AdexBehaviourEvent::PeerRequest { - request, - response_channel, - .. - } => (request, response_channel), - _ => return, - }; - - assert_eq!(request, b"test request"); - - let res = AdexResponse::Err { - error: "test error".into(), - }; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - }) - .await; - - let receiver3_port = next_port(); - let receiver3 = Node::spawn(receiver3_port, vec![], move |mut cmd_tx, event| { - let (request, response_channel) = match event { - AdexBehaviourEvent::PeerRequest { - request, - response_channel, - .. - } => (request, response_channel), - _ => return, - }; - - assert_eq!(request, b"test request"); - - let res = AdexResponse::Ok { - response: b"test response".to_vec(), - }; - cmd_tx - .try_send(AdexBehaviourCmd::SendResponse { res, response_channel }) - .unwrap(); - }) - .await; - let mut sender = Node::spawn( - next_port(), - vec![receiver1_port, receiver2_port, receiver3_port], - |_, _| (), - ) - .await; - - sender.wait_peers(3).await; - - let (response_tx, response_rx) = oneshot::channel(); - sender - .send_cmd(AdexBehaviourCmd::RequestRelays { - req: b"test request".to_vec(), - response_tx, - }) - .await; - - let mut expected = vec![ - (receiver1.peer_id, AdexResponse::None), - (receiver2.peer_id, AdexResponse::Err { - error: "test error".into(), - }), - (receiver3.peer_id, AdexResponse::Ok { - response: b"test response".to_vec(), - }), - ]; - expected.sort_by(|x, y| x.0.cmp(&y.0)); - - let mut responses = response_rx.await.unwrap(); - responses.sort_by(|x, y| x.0.cmp(&y.0)); - assert_eq!(responses, expected); -} diff --git a/mm2src/mm2_libp2p/src/lib.rs b/mm2src/mm2_libp2p/src/lib.rs deleted file mode 100644 index ae554ca311..0000000000 --- a/mm2src/mm2_libp2p/src/lib.rs +++ /dev/null @@ -1,177 +0,0 @@ -#![feature(ip)] - -#[macro_use] extern crate lazy_static; - -mod adex_ping; -pub mod atomicdex_behaviour; -mod network; -pub mod peers_exchange; -pub mod relay_address; -pub mod request_response; -mod runtime; - -use lazy_static::lazy_static; -use secp256k1::{Message as SecpMessage, PublicKey as Secp256k1Pubkey, Secp256k1, SecretKey, SignOnly, Signature, - VerifyOnly}; -use sha2::{Digest, Sha256}; - -pub use atomicdex_behaviour::{spawn_gossipsub, AdexBehaviourError, NodeType, WssCerts}; -pub use atomicdex_gossipsub::{GossipsubEvent, GossipsubMessage, MessageId, TopicHash}; -pub use libp2p::identity::error::DecodingError; -pub use libp2p::identity::secp256k1::PublicKey as Libp2pSecpPublic; -pub use libp2p::identity::PublicKey as Libp2pPublic; -pub use libp2p::{Multiaddr, PeerId}; -pub use peers_exchange::PeerAddresses; -pub use relay_address::{RelayAddress, RelayAddressError}; -pub use runtime::SwarmRuntime; -use serde::{de, Deserialize, Serialize, Serializer}; - -lazy_static! { - static ref SECP_VERIFY: Secp256k1 = Secp256k1::verification_only(); - static ref SECP_SIGN: Secp256k1 = Secp256k1::signing_only(); -} - -#[derive(Clone, Copy)] -pub enum NetworkInfo { - /// The in-memory network. - InMemory, - /// The distributed network (out of the app memory). - Distributed { network_ports: NetworkPorts }, -} - -impl NetworkInfo { - pub fn in_memory(&self) -> bool { matches!(self, NetworkInfo::InMemory) } -} - -#[derive(Clone, Copy)] -pub struct NetworkPorts { - pub tcp: u16, - pub wss: u16, -} - -pub fn encode_message(message: &T) -> Result, rmp_serde::encode::Error> { - rmp_serde::to_vec(message) -} - -#[inline] -pub fn decode_message<'de, T: de::Deserialize<'de>>(bytes: &'de [u8]) -> Result { - rmp_serde::from_slice(bytes) -} - -#[derive(Deserialize, Serialize)] -struct SignedMessageSerdeHelper<'a> { - pubkey: PublicKey, - #[serde(with = "serde_bytes")] - signature: &'a [u8], - #[serde(with = "serde_bytes")] - payload: &'a [u8], -} - -pub fn encode_and_sign(message: &T, secret: &[u8; 32]) -> Result, rmp_serde::encode::Error> { - let secret = SecretKey::from_slice(secret).unwrap(); - let encoded = encode_message(message)?; - let sig_hash = SecpMessage::from_slice(&sha256(&encoded)).expect("Message::from_slice should never fail"); - let sig = SECP_SIGN.sign(&sig_hash, &secret); - let serialized_sig = sig.serialize_compact(); - let pubkey = PublicKey::from(Secp256k1Pubkey::from_secret_key(&*SECP_SIGN, &secret)); - let msg = SignedMessageSerdeHelper { - pubkey, - signature: &serialized_sig, - payload: &encoded, - }; - encode_message(&msg) -} - -pub fn decode_signed<'de, T: de::Deserialize<'de>>( - encoded: &'de [u8], -) -> Result<(T, Signature, PublicKey), rmp_serde::decode::Error> { - let helper: SignedMessageSerdeHelper = decode_message(encoded)?; - let signature = Signature::from_compact(helper.signature) - .map_err(|e| rmp_serde::decode::Error::Syntax(format!("Failed to parse signature {}", e)))?; - let sig_hash = SecpMessage::from_slice(&sha256(helper.payload)).expect("Message::from_slice should never fail"); - match &helper.pubkey { - PublicKey::Secp256k1(serialized_pub) => { - if SECP_VERIFY.verify(&sig_hash, &signature, &serialized_pub.0).is_err() { - return Err(rmp_serde::decode::Error::Syntax("Invalid message signature".into())); - } - }, - } - - let payload: T = decode_message(helper.payload)?; - Ok((payload, signature, helper.pubkey)) -} - -fn sha256(input: impl AsRef<[u8]>) -> [u8; 32] { Sha256::new().chain(input).finalize().into() } - -#[derive(Debug, Eq, PartialEq)] -pub struct Secp256k1PubkeySerialize(Secp256k1Pubkey); - -impl Serialize for Secp256k1PubkeySerialize { - fn serialize(&self, serializer: S) -> Result { - serializer.serialize_bytes(&self.0.serialize()) - } -} - -impl<'de> de::Deserialize<'de> for Secp256k1PubkeySerialize { - fn deserialize(deserializer: D) -> Result>::Error> - where - D: de::Deserializer<'de>, - { - let slice: &[u8] = de::Deserialize::deserialize(deserializer)?; - let pubkey = - Secp256k1Pubkey::from_slice(slice).map_err(|e| de::Error::custom(format!("Error {} parsing pubkey", e)))?; - - Ok(Secp256k1PubkeySerialize(pubkey)) - } -} - -#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] -pub enum PublicKey { - Secp256k1(Secp256k1PubkeySerialize), -} - -impl PublicKey { - pub fn to_bytes(&self) -> Vec { - match self { - PublicKey::Secp256k1(pubkey) => pubkey.0.serialize().to_vec(), - } - } - - pub fn to_hex(&self) -> String { - match self { - PublicKey::Secp256k1(pubkey) => hex::encode(pubkey.0.serialize().as_ref()), - } - } - - pub fn unprefixed(&self) -> [u8; 32] { - let mut res = [0; 32]; - match self { - PublicKey::Secp256k1(pubkey) => res.copy_from_slice(&pubkey.0.serialize()[1..33]), - } - res - } -} - -impl From for PublicKey { - fn from(pubkey: Secp256k1Pubkey) -> Self { PublicKey::Secp256k1(Secp256k1PubkeySerialize(pubkey)) } -} - -pub type TopicPrefix = &'static str; -pub const TOPIC_SEPARATOR: char = '/'; - -pub fn pub_sub_topic(prefix: TopicPrefix, topic: &str) -> String { - let mut res = prefix.to_owned(); - res.push(TOPIC_SEPARATOR); - res.push_str(topic); - res -} - -#[test] -fn signed_message_serde() { - let secret = [1u8; 32]; - let initial_msg = vec![0u8; 32]; - let signed_encoded = encode_and_sign(&initial_msg, &secret).unwrap(); - - let (decoded, ..) = decode_signed::>(&signed_encoded).unwrap(); - assert_eq!(decoded, initial_msg); -} diff --git a/mm2src/mm2_libp2p/src/network.rs b/mm2src/mm2_libp2p/src/network.rs deleted file mode 100644 index 0b09c444db..0000000000 --- a/mm2src/mm2_libp2p/src/network.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::RelayAddress; -use libp2p::PeerId; - -pub const NETID_8762: u16 = 8762; - -#[cfg_attr(target_arch = "wasm32", allow(dead_code))] -const ALL_NETID_8762_SEEDNODES: &[(&str, &str)] = &[ - ( - "12D3KooWHKkHiNhZtKceQehHhPqwU5W1jXpoVBgS1qst899GjvTm", - "168.119.236.251", - ), - ( - "12D3KooWAToxtunEBWCoAHjefSv74Nsmxranw8juy3eKEdrQyGRF", - "168.119.236.240", - ), - ( - "12D3KooWSmEi8ypaVzFA1AGde2RjxNW5Pvxw3qa2fVe48PjNs63R", - "168.119.236.239", - ), - ( - "12D3KooWJWBnkVsVNjiqUEPjLyHpiSmQVAJ5t6qt1Txv5ctJi9Xd", - "135.181.34.220", - ), - ( - "12D3KooWEsuiKcQaBaKEzuMtT6uFjs89P1E8MK3wGRZbeuCbCw6P", - "168.119.236.241", - ), - ( - "12D3KooWHBeCnJdzNk51G4mLnao9cDsjuqiMTEo5wMFXrd25bd1F", - "168.119.236.243", - ), - ( - "12D3KooWKxavLCJVrQ5Gk1kd9m6cohctGQBmiKPS9XQFoXEoyGmS", - "168.119.236.249", - ), - ( - "12D3KooW9soGyPfX6kcyh3uVXNHq1y2dPmQNt2veKgdLXkBiCVKq", - "168.119.236.246", - ), - ( - "12D3KooWL6yrrNACb7t7RPyTEPxKmq8jtrcbkcNd6H5G2hK7bXaL", - "168.119.236.233", - ), - ( - "12D3KooWMrjLmrv8hNgAoVf1RfumfjyPStzd4nv5XL47zN4ZKisb", - "168.119.237.8", - ), - ( - "12D3KooWPR2RoPi19vQtLugjCdvVmCcGLP2iXAzbDfP3tp81ZL4d", - "168.119.237.13", - ), - ( - "12D3KooWJDoV9vJdy6PnzwVETZ3fWGMhV41VhSbocR1h2geFqq9Y", - "65.108.90.210", - ), - ( - "12D3KooWEaZpH61H4yuQkaNG5AsyGdpBhKRppaLdAY52a774ab5u", - "46.4.78.11", - ), - ( - "12D3KooWAd5gPXwX7eDvKWwkr2FZGfoJceKDCA53SHmTFFVkrN7Q", - "46.4.87.18", - ), -]; - -#[cfg(target_arch = "wasm32")] -pub fn get_all_network_seednodes(_netid: u16) -> Vec<(PeerId, RelayAddress)> { Vec::new() } - -#[cfg(not(target_arch = "wasm32"))] -pub fn get_all_network_seednodes(netid: u16) -> Vec<(PeerId, RelayAddress)> { - use std::str::FromStr; - - if netid != NETID_8762 { - return Vec::new(); - } - ALL_NETID_8762_SEEDNODES - .iter() - .map(|(peer_id, ipv4)| { - let peer_id = PeerId::from_str(peer_id).expect("valid peer id"); - let address = RelayAddress::IPv4(ipv4.to_string()); - (peer_id, address) - }) - .collect() -} diff --git a/mm2src/mm2_libp2p/src/peers_exchange.rs b/mm2src/mm2_libp2p/src/peers_exchange.rs deleted file mode 100644 index 1721f73f8d..0000000000 --- a/mm2src/mm2_libp2p/src/peers_exchange.rs +++ /dev/null @@ -1,436 +0,0 @@ -use crate::request_response::Codec; -use crate::NetworkInfo; -use futures::StreamExt; -use libp2p::swarm::NetworkBehaviour; -use libp2p::{multiaddr::{Multiaddr, Protocol}, - request_response::{ProtocolName, ProtocolSupport, RequestResponse, RequestResponseConfig, - RequestResponseEvent, RequestResponseMessage}, - swarm::{NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters}, - NetworkBehaviour, PeerId}; -use log::{error, info, warn}; -use rand::seq::SliceRandom; -use serde::{de::Deserializer, ser::Serializer, Deserialize, Serialize}; -use std::collections::HashSet; -use std::{collections::{HashMap, VecDeque}, - iter, - task::{Context, Poll}, - time::Duration}; -use wasm_timer::{Instant, Interval}; - -pub type PeerAddresses = HashSet; - -#[derive(Debug, Clone)] -pub enum PeersExchangeProtocol { - Version1, -} - -impl ProtocolName for PeersExchangeProtocol { - fn protocol_name(&self) -> &[u8] { - match self { - PeersExchangeProtocol::Version1 => b"/peers-exchange/1", - } - } -} - -type PeersExchangeCodec = Codec; - -const DEFAULT_PEERS_NUM: usize = 20; -const REQUEST_PEERS_INITIAL_DELAY: u64 = 20; -const REQUEST_PEERS_INTERVAL: u64 = 300; -const MAX_PEERS: usize = 100; - -#[derive(Debug, Clone, Eq, Hash, PartialEq)] -pub struct PeerIdSerde(PeerId); - -impl From for PeerIdSerde { - fn from(peer_id: PeerId) -> PeerIdSerde { PeerIdSerde(peer_id) } -} - -impl Serialize for PeerIdSerde { - fn serialize(&self, serializer: S) -> Result { - self.0.clone().to_bytes().serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for PeerIdSerde { - fn deserialize>(deserializer: D) -> Result { - let bytes: Vec = Deserialize::deserialize(deserializer)?; - let peer_id = PeerId::from_bytes(&bytes).map_err(|_| serde::de::Error::custom("PeerId::from_bytes error"))?; - Ok(PeerIdSerde(peer_id)) - } -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum PeersExchangeRequest { - GetKnownPeers { num: usize }, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum PeersExchangeResponse { - KnownPeers { peers: HashMap }, -} - -/// Behaviour that requests known peers list from other peers at random -#[derive(NetworkBehaviour)] -#[behaviour(poll_method = "poll", event_process = true)] -pub struct PeersExchange { - request_response: RequestResponse, - #[behaviour(ignore)] - known_peers: Vec, - #[behaviour(ignore)] - reserved_peers: Vec, - #[behaviour(ignore)] - events: VecDeque::ConnectionHandler>>, - #[behaviour(ignore)] - maintain_peers_interval: Interval, - #[behaviour(ignore)] - network_info: NetworkInfo, -} - -#[allow(clippy::new_without_default)] -impl PeersExchange { - pub fn new(network_info: NetworkInfo) -> Self { - let codec = Codec::default(); - let protocol = iter::once((PeersExchangeProtocol::Version1, ProtocolSupport::Full)); - let config = RequestResponseConfig::default(); - let request_response = RequestResponse::new(codec, protocol, config); - PeersExchange { - request_response, - known_peers: Vec::new(), - reserved_peers: Vec::new(), - events: VecDeque::new(), - maintain_peers_interval: Interval::new_at( - Instant::now() + Duration::from_secs(REQUEST_PEERS_INITIAL_DELAY), - Duration::from_secs(REQUEST_PEERS_INTERVAL), - ), - network_info, - } - } - - fn get_random_known_peers(&mut self, num: usize) -> HashMap { - let mut result = HashMap::with_capacity(num); - let mut rng = rand::thread_rng(); - let peer_ids = self - .known_peers - .clone() - .into_iter() - .filter(|peer| !self.request_response.addresses_of_peer(peer).is_empty()) - .collect::>(); - - let peer_ids = peer_ids.choose_multiple(&mut rng, num); - for peer_id in peer_ids { - let addresses = self.request_response.addresses_of_peer(peer_id).into_iter().collect(); - result.insert((*peer_id).into(), addresses); - } - result - } - - fn forget_peer(&mut self, peer: &PeerId) { - self.known_peers.retain(|known_peer| known_peer != peer); - self.forget_peer_addresses(peer); - } - - fn forget_peer_addresses(&mut self, peer: &PeerId) { - for address in self.request_response.addresses_of_peer(peer) { - if !self.is_reserved_peer(peer) { - self.request_response.remove_address(peer, &address); - } - } - } - - pub fn add_peer_addresses_to_known_peers(&mut self, peer: &PeerId, addresses: PeerAddresses) { - for address in addresses.iter() { - if !self.validate_global_multiaddr(address) { - warn!("Attempt adding a not valid address of the peer '{}': {}", peer, address); - return; - } - } - if !self.known_peers.contains(peer) && !addresses.is_empty() { - self.known_peers.push(*peer); - } - let already_known = self.request_response.addresses_of_peer(peer); - for address in addresses { - if !already_known.contains(&address) { - self.request_response.add_address(peer, address); - } - } - } - - pub fn add_peer_addresses_to_reserved_peers(&mut self, peer: &PeerId, addresses: PeerAddresses) { - for address in addresses.iter() { - if !self.validate_global_multiaddr(address) { - return; - } - } - - if !self.reserved_peers.contains(peer) && !addresses.is_empty() { - self.reserved_peers.push(*peer); - } - - let already_reserved = self.request_response.addresses_of_peer(peer); - for address in addresses { - if !already_reserved.contains(&address) { - self.request_response.add_address(peer, address); - } - } - } - - fn maintain_known_peers(&mut self) { - if self.known_peers.len() > MAX_PEERS { - let mut rng = rand::thread_rng(); - let to_remove_num = self.known_peers.len() - MAX_PEERS; - self.known_peers.shuffle(&mut rng); - let removed_peers: Vec<_> = self.known_peers.drain(..to_remove_num).collect(); - for peer in removed_peers { - self.forget_peer_addresses(&peer); - } - } - self.request_known_peers_from_random_peer(); - } - - fn request_known_peers_from_random_peer(&mut self) { - let mut rng = rand::thread_rng(); - if let Some(from_peer) = self.known_peers.choose(&mut rng) { - info!("Try to request {} peers from peer {}", DEFAULT_PEERS_NUM, from_peer); - let request = PeersExchangeRequest::GetKnownPeers { num: DEFAULT_PEERS_NUM }; - self.request_response.send_request(from_peer, request); - } - } - - pub fn get_random_peers( - &mut self, - num: usize, - mut filter: impl FnMut(&PeerId) -> bool, - ) -> HashMap { - let mut result = HashMap::with_capacity(num); - let mut rng = rand::thread_rng(); - let peer_ids = self.known_peers.iter().filter(|peer| filter(peer)).collect::>(); - for peer_id in peer_ids.choose_multiple(&mut rng, num) { - let addresses = self.request_response.addresses_of_peer(peer_id).into_iter().collect(); - result.insert(**peer_id, addresses); - } - result - } - - pub fn is_known_peer(&self, peer: &PeerId) -> bool { self.known_peers.contains(peer) } - - pub fn is_reserved_peer(&self, peer: &PeerId) -> bool { self.reserved_peers.contains(peer) } - - pub fn add_known_peer(&mut self, peer: PeerId) { - if !self.is_known_peer(&peer) { - self.known_peers.push(peer) - } - } - - fn validate_global_multiaddr(&self, address: &Multiaddr) -> bool { - let network_ports = match self.network_info { - NetworkInfo::Distributed { network_ports } => network_ports, - NetworkInfo::InMemory => panic!("PeersExchange must not be used with in-memory network"), - }; - - let mut components = address.iter(); - match components.next() { - Some(Protocol::Ip4(addr)) => { - if !addr.is_global() { - return false; - } - }, - _ => return false, - } - - match components.next() { - Some(Protocol::Tcp(port)) => { - // currently, `NetworkPorts::ws` is not supported by `PeersExchange` - if port != network_ports.tcp { - return false; - } - }, - _ => return false, - } - - true - } - - fn validate_get_known_peers_response(&self, response: &HashMap) -> bool { - if response.is_empty() { - return false; - } - - if response.len() > DEFAULT_PEERS_NUM { - return false; - } - - for addresses in response.values() { - if addresses.is_empty() { - return false; - } - - for address in addresses { - if !self.validate_global_multiaddr(address) { - warn!("Received a not valid address: {}", address); - return false; - } - } - } - true - } - - fn poll( - &mut self, - cx: &mut Context, - _params: &mut impl PollParameters, - ) -> Poll::ConnectionHandler>> { - while let Poll::Ready(Some(())) = self.maintain_peers_interval.poll_next_unpin(cx) { - self.maintain_known_peers(); - } - - if let Some(event) = self.events.pop_front() { - return Poll::Ready(event); - } - - Poll::Pending - } -} - -impl NetworkBehaviourEventProcess> for PeersExchange { - fn inject_event(&mut self, event: RequestResponseEvent) { - match event { - RequestResponseEvent::Message { message, peer } => match message { - RequestResponseMessage::Request { request, channel, .. } => match request { - PeersExchangeRequest::GetKnownPeers { num } => { - // Should not send a response in such case - if num > DEFAULT_PEERS_NUM { - return; - } - let response = PeersExchangeResponse::KnownPeers { - peers: self.get_random_known_peers(num), - }; - if let Err(_response) = self.request_response.send_response(channel, response) { - warn!("Response channel has been closed already"); - } - }, - }, - RequestResponseMessage::Response { response, .. } => match response { - PeersExchangeResponse::KnownPeers { peers } => { - info!("Got peers {:?}", peers); - - if !self.validate_get_known_peers_response(&peers) { - // if peer provides invalid response forget it and try to request from other peer - self.forget_peer(&peer); - self.request_known_peers_from_random_peer(); - return; - } - - peers.into_iter().for_each(|(peer, addresses)| { - self.add_peer_addresses_to_known_peers(&peer.0, addresses); - }); - }, - }, - }, - RequestResponseEvent::OutboundFailure { - peer, - request_id, - error, - } => { - error!( - "Outbound failure {:?} while requesting {:?} to peer {}", - error, request_id, peer - ); - self.forget_peer(&peer); - self.request_known_peers_from_random_peer(); - }, - RequestResponseEvent::InboundFailure { peer, error, .. } => { - error!( - "Inbound failure {:?} while processing request from peer {}", - error, peer - ); - }, - RequestResponseEvent::ResponseSent { .. } => (), - } - } -} - -#[cfg(test)] -mod tests { - use super::{NetworkInfo, PeerIdSerde, PeersExchange}; - use crate::{NetworkPorts, PeerId}; - use libp2p::core::Multiaddr; - use std::collections::{HashMap, HashSet}; - use std::iter::FromIterator; - - #[test] - fn test_peer_id_serde() { - let peer_id = PeerIdSerde(PeerId::random()); - let serialized = rmp_serde::to_vec(&peer_id).unwrap(); - let deserialized: PeerIdSerde = rmp_serde::from_slice(&serialized).unwrap(); - assert_eq!(peer_id.0, deserialized.0); - } - - #[test] - fn test_validate_get_known_peers_response() { - let network_info = NetworkInfo::Distributed { - network_ports: NetworkPorts { tcp: 3000, wss: 3010 }, - }; - let behaviour = PeersExchange::new(network_info); - let response = HashMap::default(); - assert!(!behaviour.validate_get_known_peers_response(&response)); - - let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::new())]); - assert!(!behaviour.validate_get_known_peers_response(&response)); - - let address: Multiaddr = "/ip4/127.0.0.1/tcp/3000".parse().unwrap(); - let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); - assert!(!behaviour.validate_get_known_peers_response(&response)); - - let address: Multiaddr = "/ip4/216.58.210.142/tcp/3000".parse().unwrap(); - let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); - assert!(behaviour.validate_get_known_peers_response(&response)); - - let address: Multiaddr = "/ip4/216.58.210.142/tcp/3001".parse().unwrap(); - let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); - assert!(!behaviour.validate_get_known_peers_response(&response)); - - let address: Multiaddr = "/ip4/216.58.210.142".parse().unwrap(); - let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); - assert!(!behaviour.validate_get_known_peers_response(&response)); - - let address: Multiaddr = - "/ip4/168.119.236.241/tcp/3000/p2p/12D3KooWEsuiKcQaBaKEzuMtT6uFjs89P1E8MK3wGRZbeuCbCw6P" - .parse() - .unwrap(); - let response = HashMap::from_iter(vec![(PeerIdSerde(PeerId::random()), HashSet::from_iter(vec![address]))]); - assert!(behaviour.validate_get_known_peers_response(&response)); - - let address1: Multiaddr = - "/ip4/168.119.236.241/tcp/3000/p2p/12D3KooWEsuiKcQaBaKEzuMtT6uFjs89P1E8MK3wGRZbeuCbCw6P" - .parse() - .unwrap(); - - let address2: Multiaddr = "/ip4/168.119.236.241/tcp/3000".parse().unwrap(); - let response = HashMap::from_iter(vec![( - PeerIdSerde(PeerId::random()), - HashSet::from_iter(vec![address1, address2]), - )]); - assert!(behaviour.validate_get_known_peers_response(&response)); - } - - #[test] - fn test_get_random_known_peers() { - let mut behaviour = PeersExchange::new(NetworkInfo::InMemory); - let peer_id = PeerId::random(); - behaviour.add_known_peer(peer_id); - - let result = behaviour.get_random_known_peers(1); - assert!(result.is_empty()); - - let address: Multiaddr = "/ip4/168.119.236.241/tcp/3000".parse().unwrap(); - behaviour.request_response.add_address(&peer_id, address.clone()); - - let result = behaviour.get_random_known_peers(1); - assert_eq!(result.len(), 1); - - let addresses = result.get(&peer_id.into()).unwrap(); - assert_eq!(addresses.len(), 1); - assert!(addresses.contains(&address)); - } -} diff --git a/mm2src/mm2_libp2p/src/relay_address.rs b/mm2src/mm2_libp2p/src/relay_address.rs deleted file mode 100644 index d23c419632..0000000000 --- a/mm2src/mm2_libp2p/src/relay_address.rs +++ /dev/null @@ -1,186 +0,0 @@ -use crate::{NetworkInfo, NetworkPorts}; -use derive_more::Display; -use libp2p::Multiaddr; -use serde::{de, Deserialize, Deserializer, Serialize}; -use std::str::FromStr; - -#[derive(Clone, Debug, Display, Serialize)] -pub enum RelayAddressError { - #[display( - fmt = "Error parsing 'RelayAddress' from {}: address has unknown protocol, expected either IPv4 or DNS or Memory address", - found - )] - FromStrError { found: String }, - #[display( - fmt = "Error converting '{:?}' to Multiaddr: unexpected IPv4/DNS address on a memory network", - self_str - )] - DistributedAddrOnMemoryNetwork { self_str: String }, - #[display( - fmt = "Error converting '{:?}' to Multiaddr: unexpected memory address on a distributed network", - self_str - )] - MemoryAddrOnDistributedNetwork { self_str: String }, -} - -impl std::error::Error for RelayAddressError {} - -impl RelayAddressError { - fn distributed_addr_on_memory_network(addr: &RelayAddress) -> RelayAddressError { - RelayAddressError::DistributedAddrOnMemoryNetwork { - self_str: format!("{:?}", addr), - } - } - - fn memory_addr_on_distributed_network(addr: &RelayAddress) -> RelayAddressError { - RelayAddressError::MemoryAddrOnDistributedNetwork { - self_str: format!("{:?}", addr), - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum RelayAddress { - IPv4(String), - Dns(String), - Memory(u64), -} - -impl FromStr for RelayAddress { - type Err = RelayAddressError; - - fn from_str(s: &str) -> Result { - // check if the string is IPv4 - if std::net::Ipv4Addr::from_str(s).is_ok() { - return Ok(RelayAddress::IPv4(s.to_string())); - } - // check if the string is a domain name - if validate_domain_name(s) { - return Ok(RelayAddress::Dns(s.to_owned())); - } - // check if the string is a `/memory/` address - if let Some(port_str) = s.strip_prefix("/memory/") { - if let Ok(port) = port_str.parse() { - return Ok(RelayAddress::Memory(port)); - } - } - Err(RelayAddressError::FromStrError { found: s.to_owned() }) - } -} - -impl<'de> Deserialize<'de> for RelayAddress { - fn deserialize(deserializer: D) -> Result>::Error> - where - D: Deserializer<'de>, - { - let addr_str = String::deserialize(deserializer)?; - RelayAddress::from_str(&addr_str).map_err(de::Error::custom) - } -} - -impl RelayAddress { - /// Try to convert `RelayAddress` to `Multiaddr` using the given `network_info`. - pub fn try_to_multiaddr(&self, network_info: NetworkInfo) -> Result { - let network_ports = match network_info { - NetworkInfo::InMemory => match self { - RelayAddress::Memory(port) => return Ok(memory_multiaddr(*port)), - _ => return Err(RelayAddressError::distributed_addr_on_memory_network(self)), - }, - NetworkInfo::Distributed { network_ports } => network_ports, - }; - - match self { - RelayAddress::IPv4(ipv4) => Ok(ipv4_multiaddr(ipv4, network_ports)), - RelayAddress::Dns(dns) => Ok(dns_multiaddr(dns, network_ports)), - RelayAddress::Memory(_) => Err(RelayAddressError::memory_addr_on_distributed_network(self)), - } - } -} - -/// Use [this](https://regex101.com/r/94nCB5/1) regular expression to validate the domain name. -/// See examples at the linked resource above. -fn validate_domain_name(s: &str) -> bool { - use regex::Regex; - - lazy_static! { - static ref DNS_REGEX: Regex = Regex::new(r#"^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$"#).unwrap(); - } - - DNS_REGEX.is_match(s) -} - -fn memory_multiaddr(port: u64) -> Multiaddr { format!("/memory/{}", port).parse().unwrap() } - -#[cfg(target_arch = "wasm32")] -fn ipv4_multiaddr(ipv4_addr: &str, ports: NetworkPorts) -> Multiaddr { - format!("/ip4/{}/tcp/{}/wss", ipv4_addr, ports.wss).parse().unwrap() -} - -#[cfg(not(target_arch = "wasm32"))] -fn ipv4_multiaddr(ipv4_addr: &str, ports: NetworkPorts) -> Multiaddr { - format!("/ip4/{}/tcp/{}", ipv4_addr, ports.tcp).parse().unwrap() -} - -#[cfg(target_arch = "wasm32")] -fn dns_multiaddr(dns_addr: &str, ports: NetworkPorts) -> Multiaddr { - format!("/dns/{}/tcp/{}/wss", dns_addr, ports.wss).parse().unwrap() -} - -#[cfg(not(target_arch = "wasm32"))] -fn dns_multiaddr(dns_addr: &str, ports: NetworkPorts) -> Multiaddr { - format!("/dns/{}/tcp/{}", dns_addr, ports.tcp).parse().unwrap() -} - -#[test] -fn test_relay_address_from_str() { - let valid_addresses = vec![ - ("127.0.0.1", RelayAddress::IPv4("127.0.0.1".to_owned())), - ("255.255.255.255", RelayAddress::IPv4("255.255.255.255".to_owned())), - ("google.com", RelayAddress::Dns("google.com".to_owned())), - ("www.google.com", RelayAddress::Dns("www.google.com".to_owned())), - ("g.co", RelayAddress::Dns("g.co".to_owned())), - ( - "stackoverflow.co.uk", - RelayAddress::Dns("stackoverflow.co.uk".to_owned()), - ), - ("1.2.3.4.com", RelayAddress::Dns("1.2.3.4.com".to_owned())), - ("/memory/123", RelayAddress::Memory(123)), - ("/memory/71428421981", RelayAddress::Memory(71428421981)), - ]; - for (s, expected) in valid_addresses { - let actual = RelayAddress::from_str(s).unwrap_or_else(|_| panic!("Error parsing '{}'", s)); - assert_eq!(actual, expected); - } - - let invalid_addresses = vec![ - "127.0.0", - "127.0.0.0.2", - "google.c", - "http://google.com", - "https://google.com/", - "google.com/", - "/memory/", - "/memory/9999999999999999999999999999999", - ]; - for s in invalid_addresses { - let _ = RelayAddress::from_str(s).expect_err("Expected an error"); - } -} - -#[test] -fn test_deserialize_relay_address() { - #[derive(Deserialize, PartialEq)] - struct Config { - addresses: Vec, - } - - let Config { addresses: actual } = - serde_json::from_str(r#"{"addresses": ["foo.bar.com", "127.0.0.2", "/memory/12345"]}"#) - .expect("Error deserializing a list of RelayAddress"); - let expected = vec![ - RelayAddress::Dns("foo.bar.com".to_owned()), - RelayAddress::IPv4("127.0.0.2".to_owned()), - RelayAddress::Memory(12345), - ]; - assert_eq!(actual, expected); -} diff --git a/mm2src/mm2_libp2p/src/request_response.rs b/mm2src/mm2_libp2p/src/request_response.rs deleted file mode 100644 index 25b87e46de..0000000000 --- a/mm2src/mm2_libp2p/src/request_response.rs +++ /dev/null @@ -1,320 +0,0 @@ -use crate::{decode_message, encode_message}; -use async_trait::async_trait; -use core::iter; -use futures::channel::{mpsc, oneshot}; -use futures::io::{AsyncRead, AsyncWrite}; -use futures::task::{Context, Poll}; -use futures::StreamExt; -use libp2p::core::upgrade::{read_length_prefixed, write_length_prefixed}; -use libp2p::request_response::{ProtocolName, ProtocolSupport, RequestId, RequestResponse, RequestResponseCodec, - RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, ResponseChannel}; -use libp2p::swarm::{NetworkBehaviour, NetworkBehaviourAction, NetworkBehaviourEventProcess, PollParameters}; -use libp2p::NetworkBehaviour; -use libp2p::PeerId; -use log::{debug, error, warn}; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; -use std::collections::{HashMap, VecDeque}; -use std::io; -use std::time::Duration; -use wasm_timer::{Instant, Interval}; - -const MAX_BUFFER_SIZE: usize = 1024 * 1024 - 100; - -pub type RequestResponseReceiver = mpsc::UnboundedReceiver<(PeerId, PeerRequest, oneshot::Sender)>; -pub type RequestResponseSender = mpsc::UnboundedSender<(PeerId, PeerRequest, oneshot::Sender)>; - -/// Build a request-response network behaviour. -pub fn build_request_response_behaviour() -> RequestResponseBehaviour { - let config = RequestResponseConfig::default(); - let protocol = iter::once((Protocol::Version1, ProtocolSupport::Full)); - let inner = RequestResponse::new(Codec::default(), protocol, config); - - let (tx, rx) = mpsc::unbounded(); - let pending_requests = HashMap::new(); - let events = VecDeque::new(); - let timeout = Duration::from_secs(10); - let timeout_interval = Interval::new(Duration::from_secs(1)); - - RequestResponseBehaviour { - inner, - rx, - tx, - pending_requests, - events, - timeout, - timeout_interval, - } -} - -pub enum RequestResponseBehaviourEvent { - InboundRequest { - peer_id: PeerId, - request: PeerRequest, - response_channel: ResponseChannel, - }, -} - -struct PendingRequest { - tx: oneshot::Sender, - initiated_at: Instant, -} - -#[derive(NetworkBehaviour)] -#[behaviour(out_event = "RequestResponseBehaviourEvent", event_process = true)] -#[behaviour(poll_method = "poll_event")] -pub struct RequestResponseBehaviour { - /// The inner RequestResponse network behaviour. - inner: RequestResponse>, - #[behaviour(ignore)] - rx: RequestResponseReceiver, - #[behaviour(ignore)] - tx: RequestResponseSender, - #[behaviour(ignore)] - pending_requests: HashMap, - /// Events that need to be yielded to the outside when polling. - #[behaviour(ignore)] - events: VecDeque, - /// Timeout for pending requests - #[behaviour(ignore)] - timeout: Duration, - /// Interval for request timeout check - #[behaviour(ignore)] - timeout_interval: Interval, -} - -impl RequestResponseBehaviour { - pub fn sender(&self) -> RequestResponseSender { self.tx.clone() } - - pub fn send_response(&mut self, ch: ResponseChannel, rs: PeerResponse) -> Result<(), PeerResponse> { - self.inner.send_response(ch, rs) - } - - pub fn send_request( - &mut self, - peer_id: &PeerId, - request: PeerRequest, - response_tx: oneshot::Sender, - ) -> RequestId { - let request_id = self.inner.send_request(peer_id, request); - let pending_request = PendingRequest { - tx: response_tx, - initiated_at: Instant::now(), - }; - assert!(self.pending_requests.insert(request_id, pending_request).is_none()); - request_id - } - - fn poll_event( - &mut self, - cx: &mut Context, - _params: &mut impl PollParameters, - ) -> Poll::ConnectionHandler>> - { - // poll the `rx` - match self.rx.poll_next_unpin(cx) { - // received a request, forward it through the network and put to the `pending_requests` - Poll::Ready(Some((peer_id, request, response_tx))) => { - let _request_id = self.send_request(&peer_id, request, response_tx); - }, - // the channel was closed - Poll::Ready(None) => panic!("request-response channel has been closed"), - Poll::Pending => (), - } - - if let Some(event) = self.events.pop_front() { - // forward a pending event to the top - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)); - } - - while let Poll::Ready(Some(())) = self.timeout_interval.poll_next_unpin(cx) { - let now = Instant::now(); - let timeout = self.timeout; - self.pending_requests.retain(|request_id, pending_request| { - let retain = now.duration_since(pending_request.initiated_at) < timeout; - if !retain { - warn!("Request {} timed out", request_id); - } - retain - }); - } - - Poll::Pending - } - - fn process_request( - &mut self, - peer_id: PeerId, - request: PeerRequest, - response_channel: ResponseChannel, - ) { - self.events.push_back(RequestResponseBehaviourEvent::InboundRequest { - peer_id, - request, - response_channel, - }) - } - - fn process_response(&mut self, request_id: RequestId, response: PeerResponse) { - match self.pending_requests.remove(&request_id) { - Some(pending) => { - if let Err(e) = pending.tx.send(response) { - error!("{:?}. Request {:?} is not processed", e, request_id); - } - }, - _ => error!("Received unknown request {:?}", request_id), - } - } -} - -impl NetworkBehaviourEventProcess> for RequestResponseBehaviour { - fn inject_event(&mut self, event: RequestResponseEvent) { - let (peer_id, message) = match event { - RequestResponseEvent::Message { peer, message } => (peer, message), - RequestResponseEvent::InboundFailure { error, .. } => { - error!("Error on receive a request: {:?}", error); - return; - }, - RequestResponseEvent::OutboundFailure { - peer, - request_id, - error, - } => { - error!("Error on send request {:?} to peer {:?}: {:?}", request_id, peer, error); - let err_response = PeerResponse::Err { - err: format!("{:?}", error), - }; - self.process_response(request_id, err_response); - return; - }, - RequestResponseEvent::ResponseSent { .. } => return, - }; - - match message { - RequestResponseMessage::Request { request, channel, .. } => { - debug!("Received a request from {:?} peer", peer_id); - self.process_request(peer_id, request, channel) - }, - RequestResponseMessage::Response { request_id, response } => { - debug!( - "Received a response to the {:?} request from peer {:?}", - request_id, peer_id - ); - self.process_response(request_id, response) - }, - } - } -} - -#[derive(Clone)] -pub struct Codec { - phantom: std::marker::PhantomData<(Proto, Req, Res)>, -} - -impl Default for Codec { - fn default() -> Self { - Codec { - phantom: Default::default(), - } - } -} - -#[derive(Debug, Clone)] -pub enum Protocol { - Version1, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct PeerRequest { - pub req: Vec, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum PeerResponse { - Ok { res: Vec }, - None, - Err { err: String }, -} - -macro_rules! try_io { - ($e: expr) => { - match $e { - Ok(ok) => ok, - Err(err) => return Err(io::Error::new(io::ErrorKind::InvalidData, err)), - } - }; -} - -impl ProtocolName for Protocol { - fn protocol_name(&self) -> &[u8] { - match self { - Protocol::Version1 => b"/request-response/1", - } - } -} - -#[async_trait] -impl< - Proto: Clone + ProtocolName + Send + Sync, - Req: DeserializeOwned + Serialize + Send + Sync, - Res: DeserializeOwned + Serialize + Send + Sync, - > RequestResponseCodec for Codec -{ - type Protocol = Proto; - type Request = Req; - type Response = Res; - - async fn read_request(&mut self, _protocol: &Self::Protocol, io: &mut T) -> io::Result - where - T: AsyncRead + Unpin + Send, - { - read_to_end(io).await - } - - async fn read_response(&mut self, _protocol: &Self::Protocol, io: &mut T) -> io::Result - where - T: AsyncRead + Unpin + Send, - { - read_to_end(io).await - } - - async fn write_request(&mut self, _protocol: &Self::Protocol, io: &mut T, req: Self::Request) -> io::Result<()> - where - T: AsyncWrite + Unpin + Send, - { - write_all(io, &req).await - } - - async fn write_response(&mut self, _protocol: &Self::Protocol, io: &mut T, res: Self::Response) -> io::Result<()> - where - T: AsyncWrite + Unpin + Send, - { - write_all(io, &res).await - } -} - -async fn read_to_end(io: &mut T) -> io::Result -where - T: AsyncRead + Unpin + Send, - M: DeserializeOwned, -{ - match read_length_prefixed(io, MAX_BUFFER_SIZE).await { - Ok(data) => Ok(try_io!(decode_message(&data))), - Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e)), - } -} - -async fn write_all(io: &mut T, msg: &M) -> io::Result<()> -where - T: AsyncWrite + Unpin + Send, - M: Serialize, -{ - let data = try_io!(encode_message(msg)); - if data.len() > MAX_BUFFER_SIZE { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "Try to send data size over maximum", - )); - } - write_length_prefixed(io, data).await -} diff --git a/mm2src/mm2_libp2p/src/runtime.rs b/mm2src/mm2_libp2p/src/runtime.rs deleted file mode 100644 index 016cf2b455..0000000000 --- a/mm2src/mm2_libp2p/src/runtime.rs +++ /dev/null @@ -1,33 +0,0 @@ -use common::executor::{BoxFutureSpawner, SpawnFuture}; -use futures::Future; -use std::pin::Pin; -use std::sync::Arc; - -#[derive(Clone)] -pub struct SwarmRuntime { - inner: Arc, -} - -impl SwarmRuntime { - pub fn new(spawner: S) -> SwarmRuntime - where - S: BoxFutureSpawner + Send + Sync + 'static, - { - SwarmRuntime { - inner: Arc::new(spawner), - } - } -} - -impl SpawnFuture for SwarmRuntime { - fn spawn(&self, f: F) - where - F: Future + Send + 'static, - { - self.inner.spawn_boxed(Box::new(Box::pin(f))) - } -} - -impl libp2p::core::Executor for SwarmRuntime { - fn exec(&self, future: Pin + Send>>) { self.inner.spawn_boxed(Box::new(future)) } -} diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index 8be8b5ad3c..6900009b2f 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -9,6 +9,7 @@ version = "0.1.0" edition = "2018" [lib] +path = "src/mm2.rs" doctest = false [features] @@ -17,12 +18,12 @@ native = [] # Deprecated track-ctx-pointer = ["common/track-ctx-pointer"] zhtlc-native-tests = ["coins/zhtlc-native-tests"] run-docker-tests = ["coins/run-docker-tests"] -# TODO -enable-solana = [] default = [] trezor-udp = ["crypto/trezor-udp"] # use for tests to connect to trezor emulator over udp run-device-tests = [] -enable-sia = [] +enable-sia = ["coins/enable-sia", "coins_activation/enable-sia"] +sepolia-maker-swap-v2-tests = [] +sepolia-taker-swap-v2-tests = [] [dependencies] async-std = { version = "1.5", features = ["unstable"] } @@ -31,6 +32,7 @@ bitcrypto = { path = "../mm2_bitcoin/crypto" } blake2 = "0.10.6" bytes = "0.4" chain = { path = "../mm2_bitcoin/chain" } +chrono = "0.4" cfg-if = "1.0" coins = { path = "../coins" } coins_activation = { path = "../coins_activation" } @@ -63,9 +65,9 @@ mm2_err_handle = { path = "../mm2_err_handle" } mm2_event_stream = { path = "../mm2_event_stream" } mm2_gui_storage = { path = "../mm2_gui_storage" } mm2_io = { path = "../mm2_io" } -mm2-libp2p = { path = "../mm2_p2p", package = "mm2_p2p" } +mm2_libp2p = { path = "../mm2_p2p", package = "mm2_p2p", features = ["application"] } mm2_metrics = { path = "../mm2_metrics" } -mm2_net = { path = "../mm2_net", features = ["event-stream", "p2p"] } +mm2_net = { path = "../mm2_net" } mm2_number = { path = "../mm2_number" } mm2_rpc = { path = "../mm2_rpc", features = ["rpc_facilities"]} mm2_state_machine = { path = "../mm2_state_machine" } @@ -73,12 +75,9 @@ num-traits = "0.2" parity-util-mem = "0.11" parking_lot = { version = "0.12.0", features = ["nightly"] } primitives = { path = "../mm2_bitcoin/primitives" } -prost = "0.11" +prost = "0.12" rand = { version = "0.7", features = ["std", "small_rng"] } rand6 = { version = "0.6", package = "rand" } -# TODO: Reduce the size of regex by disabling the features we don't use. -# cf. https://github.com/rust-lang/regex/issues/583 -regex = "1" rmp-serde = "0.14.3" rpc = { path = "../mm2_bitcoin/rpc" } rpc_task = { path = "../rpc_task" } @@ -115,7 +114,7 @@ hyper = { version = "0.14.26", features = ["client", "http2", "server", "tcp"] } rcgen = "0.10" rustls = { version = "0.21", default-features = false } rustls-pemfile = "1.0.2" -tokio = { version = "1.20", features = ["io-util", "rt-multi-thread", "net"] } +tokio = { version = "1.20", features = ["io-util", "rt-multi-thread", "net", "signal"] } [target.'cfg(windows)'.dependencies] winapi = "0.3" @@ -131,9 +130,11 @@ ethabi = { version = "17.0.0" } rlp = { version = "0.5" } ethcore-transaction = { git = "https://github.com/KomodoPlatform/mm2-parity-ethereum.git", rev = "mm2-v2.1.1" } rustc-hex = "2" +sia-rust = { git = "https://github.com/KomodoPlatform/sia-rust", rev = "9f188b80b3213bcb604e7619275251ce08fae808" } +url = { version = "2.2.2", features = ["serde"] } [build-dependencies] chrono = "0.4" gstuff = { version = "0.7", features = ["nightly"] } -prost-build = { version = "0.11", default-features = false } +prost-build = { version = "0.12", default-features = false } regex = "1" diff --git a/mm2src/mm2_main/src/database.rs b/mm2src/mm2_main/src/database.rs index 1017f1fd6b..8f278d3adc 100644 --- a/mm2src/mm2_main/src/database.rs +++ b/mm2src/mm2_main/src/database.rs @@ -1,10 +1,9 @@ /// The module responsible to work with SQLite database /// -#[path = "database/my_orders.rs"] pub mod my_orders; -#[path = "database/my_swaps.rs"] pub mod my_swaps; -#[path = "database/stats_nodes.rs"] pub mod stats_nodes; -#[path = "database/stats_swaps.rs"] pub mod stats_swaps; +pub mod my_swaps; +pub mod stats_nodes; +pub mod stats_swaps; use crate::CREATE_MY_SWAPS_TABLE; use common::log::{debug, error, info}; diff --git a/mm2src/mm2_main/src/database/my_orders.rs b/mm2src/mm2_main/src/database/my_orders.rs index 898d1f1620..7e38c4bfa9 100644 --- a/mm2src/mm2_main/src/database/my_orders.rs +++ b/mm2src/mm2_main/src/database/my_orders.rs @@ -1,6 +1,6 @@ #![allow(deprecated)] // TODO: remove this once rusqlite is >= 0.29 -use crate::mm2::lp_ordermatch::{FilteringOrder, MakerOrder, MyOrdersFilter, RecentOrdersSelectResult, TakerOrder}; +use crate::lp_ordermatch::{FilteringOrder, MakerOrder, MyOrdersFilter, RecentOrdersSelectResult, TakerOrder}; /// This module contains code to work with my_orders table in MM2 SQLite DB use common::log::debug; use common::{now_ms, PagingOptions}; diff --git a/mm2src/mm2_main/src/database/my_swaps.rs b/mm2src/mm2_main/src/database/my_swaps.rs index 55b08f3957..2fe1a85890 100644 --- a/mm2src/mm2_main/src/database/my_swaps.rs +++ b/mm2src/mm2_main/src/database/my_swaps.rs @@ -1,7 +1,7 @@ #![allow(deprecated)] // TODO: remove this once rusqlite is >= 0.29 /// This module contains code to work with my_swaps table in MM2 SQLite DB -use crate::mm2::lp_swap::{MyRecentSwapsUuids, MySwapsFilter, SavedSwap, SavedSwapIo}; +use crate::lp_swap::{MyRecentSwapsUuids, MySwapsFilter, SavedSwap, SavedSwapIo}; use common::log::debug; use common::PagingOptions; use db_common::sqlite::rusqlite::{Connection, Error as SqlError, Result as SqlResult, ToSql}; diff --git a/mm2src/mm2_main/src/database/stats_nodes.rs b/mm2src/mm2_main/src/database/stats_nodes.rs index f5b24cf8a8..458f4159bc 100644 --- a/mm2src/mm2_main/src/database/stats_nodes.rs +++ b/mm2src/mm2_main/src/database/stats_nodes.rs @@ -1,5 +1,5 @@ /// This module contains code to work with nodes table for stats collection in MM2 SQLite DB -use crate::mm2::lp_stats::{NodeInfo, NodeVersionStat}; +use crate::lp_stats::{NodeInfo, NodeVersionStat}; use common::log::debug; use db_common::sqlite::rusqlite::{params_from_iter, Error as SqlError, Result as SqlResult}; use mm2_core::mm_ctx::MmArc; diff --git a/mm2src/mm2_main/src/database/stats_swaps.rs b/mm2src/mm2_main/src/database/stats_swaps.rs index b1127b2d36..3322245f01 100644 --- a/mm2src/mm2_main/src/database/stats_swaps.rs +++ b/mm2src/mm2_main/src/database/stats_swaps.rs @@ -1,6 +1,6 @@ #![allow(deprecated)] // TODO: remove this once rusqlite is >= 0.29 -use crate::mm2::lp_swap::{MakerSavedSwap, SavedSwap, SavedSwapIo, TakerSavedSwap}; +use crate::lp_swap::{MakerSavedSwap, SavedSwap, SavedSwapIo, TakerSavedSwap}; use common::log::{debug, error}; use db_common::{owned_named_params, sqlite::{rusqlite::{params_from_iter, Connection, OptionalExtension}, diff --git a/mm2src/mm2_main/src/for_tests/iris_nimda_rick_taker_swap.json b/mm2src/mm2_main/src/for_tests/iris_nimda_rick_taker_swap.json index b040229f93..93cab41558 100644 --- a/mm2src/mm2_main/src/for_tests/iris_nimda_rick_taker_swap.json +++ b/mm2src/mm2_main/src/for_tests/iris_nimda_rick_taker_swap.json @@ -145,12 +145,14 @@ "Started", "Negotiated", "TakerFeeSent", + "TakerPaymentInstructionsReceived", "MakerPaymentReceived", "MakerPaymentWaitConfirmStarted", "MakerPaymentValidatedAndConfirmed", "TakerPaymentSent", "TakerPaymentSpent", "MakerPaymentSpent", + "MakerPaymentSpendConfirmed", "Finished" ], "error_events": [ @@ -164,8 +166,12 @@ "TakerPaymentDataSendFailed", "TakerPaymentWaitForSpendFailed", "MakerPaymentSpendFailed", + "MakerPaymentSpendConfirmFailed", "TakerPaymentWaitRefundStarted", + "TakerPaymentRefundStarted", "TakerPaymentRefunded", - "TakerPaymentRefundFailed" + "TakerPaymentRefundedByWatcher", + "TakerPaymentRefundFailed", + "TakerPaymentRefundFinished" ] } \ No newline at end of file diff --git a/mm2src/mm2_main/src/for_tests/recreate_maker_swap_maker_payment_wait_confirm_failed_taker_saved.json b/mm2src/mm2_main/src/for_tests/recreate_maker_swap_maker_payment_wait_confirm_failed_taker_saved.json index 07ebe0d6b9..73261f641e 100644 --- a/mm2src/mm2_main/src/for_tests/recreate_maker_swap_maker_payment_wait_confirm_failed_taker_saved.json +++ b/mm2src/mm2_main/src/for_tests/recreate_maker_swap_maker_payment_wait_confirm_failed_taker_saved.json @@ -49,6 +49,6 @@ "taker_coin":"MORTY", "gui":"atomicDEX 0.5.1 iOS", "mm_version":"1b065636a", - "success_events":["Started","Negotiated","TakerFeeSent","TakerPaymentInstructionsReceived","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"], + "success_events":["Started","Negotiated","TakerFeeSent","TakerPaymentInstructionsReceived","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"], "error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"] } \ No newline at end of file diff --git a/mm2src/mm2_main/src/for_tests/recreate_maker_swap_taker_saved.json b/mm2src/mm2_main/src/for_tests/recreate_maker_swap_taker_saved.json index 8fe8622f74..a3158581c6 100644 --- a/mm2src/mm2_main/src/for_tests/recreate_maker_swap_taker_saved.json +++ b/mm2src/mm2_main/src/for_tests/recreate_maker_swap_taker_saved.json @@ -61,6 +61,6 @@ "taker_coin":"MORTY", "gui":"atomicDEX 0.5.1 iOS", "mm_version":"1b065636a", - "success_events":["Started","Negotiated","TakerFeeSent","TakerPaymentInstructionsReceived","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"], + "success_events":["Started","Negotiated","TakerFeeSent","TakerPaymentInstructionsReceived","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"], "error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"] } \ No newline at end of file diff --git a/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_expected.json b/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_expected.json index cfb9ab66f8..1c1cc39acf 100644 --- a/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_expected.json +++ b/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_expected.json @@ -57,6 +57,6 @@ "taker_coin":"MORTY", "gui":null, "mm_version":"", - "success_events":["Started","Negotiated","TakerFeeSent","TakerPaymentInstructionsReceived","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"], - "error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed","TakerPaymentRefundFinished"] + "success_events":["Started","Negotiated","TakerFeeSent","TakerPaymentInstructionsReceived","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"], + "error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed","TakerPaymentRefundFinished"] } \ No newline at end of file diff --git a/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_payment_wait_confirm_failed_taker_expected.json b/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_payment_wait_confirm_failed_taker_expected.json index cb3146b2a3..e743669805 100644 --- a/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_payment_wait_confirm_failed_taker_expected.json +++ b/mm2src/mm2_main/src/for_tests/recreate_taker_swap_taker_payment_wait_confirm_failed_taker_expected.json @@ -53,6 +53,6 @@ "taker_coin":"MORTY", "gui":null, "mm_version":"", - "success_events":["Started","Negotiated","TakerFeeSent","TakerPaymentInstructionsReceived","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"], - "error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed","TakerPaymentRefundFinished"] + "success_events":["Started","Negotiated","TakerFeeSent","TakerPaymentInstructionsReceived","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"], + "error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed","TakerPaymentRefundFinished"] } \ No newline at end of file diff --git a/mm2src/mm2_main/src/lib.rs b/mm2src/mm2_main/src/lib.rs deleted file mode 100644 index db644c2762..0000000000 --- a/mm2src/mm2_main/src/lib.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![feature(hash_raw_entry)] -// `mockable` implementation uses these -#![allow( - forgetting_references, - forgetting_copy_types, - clippy::swap_ptr_to_ref, - clippy::forget_non_drop, - clippy::let_unit_value -)] - -#[macro_use] extern crate common; -#[macro_use] extern crate gstuff; -#[macro_use] extern crate serde_json; -#[macro_use] extern crate serde_derive; -#[macro_use] extern crate ser_error_derive; -#[cfg(test)] extern crate mm2_test_helpers; - -pub mod mm2; - -#[cfg(all(target_arch = "wasm32", test))] mod wasm_tests; diff --git a/mm2src/mm2_main/src/lp_dispatcher.rs b/mm2src/mm2_main/src/lp_dispatcher.rs index 74f7a9a18d..8ea662873e 100644 --- a/mm2src/mm2_main/src/lp_dispatcher.rs +++ b/mm2src/mm2_main/src/lp_dispatcher.rs @@ -1,5 +1,5 @@ -use crate::mm2::lp_ordermatch::TradingBotEvent; -use crate::mm2::lp_swap::MakerSwapStatusChanged; +use crate::lp_ordermatch::TradingBotEvent; +use crate::lp_swap::MakerSwapStatusChanged; use async_std::sync::RwLock; use mm2_core::{event_dispatcher::{Dispatcher, EventUniqueId}, mm_ctx::{from_ctx, MmArc}}; diff --git a/mm2src/mm2_main/src/lp_healthcheck.rs b/mm2src/mm2_main/src/lp_healthcheck.rs new file mode 100644 index 0000000000..20a6004c95 --- /dev/null +++ b/mm2src/mm2_main/src/lp_healthcheck.rs @@ -0,0 +1,446 @@ +use async_std::prelude::FutureExt; +use chrono::Utc; +use common::executor::SpawnFuture; +use common::expirable_map::ExpirableEntry; +use common::{log, HttpStatusCode, StatusCode}; +use derive_more::Display; +use futures::channel::oneshot::{self, Receiver, Sender}; +use instant::{Duration, Instant}; +use lazy_static::lazy_static; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::MmError; +use mm2_err_handle::prelude::*; +use mm2_libp2p::p2p_ctx::P2PContext; +use mm2_libp2p::{decode_message, encode_message, pub_sub_topic, Libp2pPublic, PeerAddress, TopicPrefix}; +use ser_error_derive::SerializeErrorType; +use serde::{Deserialize, Serialize}; +use std::convert::TryFrom; +use std::sync::Mutex; + +use crate::lp_network::{broadcast_p2p_msg, P2PRequestError, P2PRequestResult}; + +pub(crate) const PEER_HEALTHCHECK_PREFIX: TopicPrefix = "hcheck"; + +const fn healthcheck_message_exp_secs() -> u64 { + #[cfg(test)] + return 3; + + #[cfg(not(test))] + 10 +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(any(test, target_arch = "wasm32"), derive(PartialEq))] +pub(crate) struct HealthcheckMessage { + #[serde(deserialize_with = "deserialize_bytes")] + signature: Vec, + data: HealthcheckData, +} + +#[derive(Debug, Display)] +enum SignValidationError { + #[display( + fmt = "Healthcheck message is expired. Current time in UTC: {now_secs}, healthcheck `expires_at` in UTC: {expires_at_secs}" + )] + Expired { now_secs: u64, expires_at_secs: u64 }, + #[display( + fmt = "Healthcheck message have too high expiration time. Max allowed expiration seconds: {max_allowed_expiration_secs}, received message expiration seconds: {remaining_expiration_secs}" + )] + LifetimeOverflow { + max_allowed_expiration_secs: u64, + remaining_expiration_secs: u64, + }, + #[display(fmt = "Public key is not valid.")] + InvalidPublicKey, + #[display(fmt = "Signature integrity doesn't match with the public key.")] + FakeSignature, + #[display(fmt = "Process failed unexpectedly due to this reason: {reason}")] + Internal { reason: String }, +} + +impl HealthcheckMessage { + pub(crate) fn generate_message(ctx: &MmArc, is_a_reply: bool) -> Result { + let p2p_ctx = P2PContext::fetch_from_mm_arc(ctx); + let keypair = p2p_ctx.keypair(); + let sender_public_key = keypair.public().encode_protobuf(); + + let data = HealthcheckData { + sender_public_key, + expires_at_secs: u64::try_from(Utc::now().timestamp()).map_err(|e| e.to_string())? + + healthcheck_message_exp_secs(), + is_a_reply, + }; + + let signature = try_s!(keypair.sign(&try_s!(data.encode()))); + + Ok(Self { signature, data }) + } + + fn generate_or_use_cached_message(ctx: &MmArc) -> Result { + const MIN_DURATION_FOR_REUSABLE_MSG: Duration = Duration::from_secs(5); + + lazy_static! { + static ref RECENTLY_GENERATED_MESSAGE: Mutex> = + Mutex::new(ExpirableEntry::new( + // Using dummy values in order to initialize `HealthcheckMessage` context. + HealthcheckMessage { + signature: vec![], + data: HealthcheckData { + sender_public_key: vec![], + expires_at_secs: 0, + is_a_reply: false, + }, + }, + Duration::from_secs(0) + )); + } + + // If recently generated message has longer life than `MIN_DURATION_FOR_REUSABLE_MSG`, we can reuse it to + // reduce the message generation overhead under high pressure. + let mut mutexed_msg = RECENTLY_GENERATED_MESSAGE.lock().unwrap(); + + if mutexed_msg.has_longer_life_than(MIN_DURATION_FOR_REUSABLE_MSG) { + Ok(mutexed_msg.get_element().clone()) + } else { + let new_msg = HealthcheckMessage::generate_message(ctx, true)?; + + mutexed_msg.update_value(new_msg.clone()); + mutexed_msg.update_expiration(Instant::now() + Duration::from_secs(healthcheck_message_exp_secs())); + + Ok(new_msg) + } + } + + fn is_received_message_valid(&self) -> Result { + let now_secs = u64::try_from(Utc::now().timestamp()) + .map_err(|e| SignValidationError::Internal { reason: e.to_string() })?; + + let remaining_expiration_secs = self.data.expires_at_secs.saturating_sub(now_secs); + + if remaining_expiration_secs == 0 { + return Err(SignValidationError::Expired { + now_secs, + expires_at_secs: self.data.expires_at_secs, + }); + } else if remaining_expiration_secs > healthcheck_message_exp_secs() { + return Err(SignValidationError::LifetimeOverflow { + max_allowed_expiration_secs: healthcheck_message_exp_secs(), + remaining_expiration_secs, + }); + } + + let Ok(public_key) = Libp2pPublic::try_decode_protobuf(&self.data.sender_public_key) else { + log::debug!("Couldn't decode public key from the healthcheck message."); + + return Err(SignValidationError::InvalidPublicKey); + }; + + let encoded_message = self + .data + .encode() + .map_err(|e| SignValidationError::Internal { reason: e.to_string() })?; + + if public_key.verify(&encoded_message, &self.signature) { + Ok(public_key.to_peer_id().into()) + } else { + Err(SignValidationError::FakeSignature) + } + } + + #[inline] + pub(crate) fn encode(&self) -> Result, rmp_serde::encode::Error> { encode_message(self) } + + #[inline] + pub(crate) fn decode(bytes: &[u8]) -> Result { decode_message(bytes) } + + #[inline] + pub(crate) fn should_reply(&self) -> bool { !self.data.is_a_reply } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(any(test, target_arch = "wasm32"), derive(PartialEq))] +struct HealthcheckData { + #[serde(deserialize_with = "deserialize_bytes")] + sender_public_key: Vec, + expires_at_secs: u64, + is_a_reply: bool, +} + +impl HealthcheckData { + #[inline] + fn encode(&self) -> Result, rmp_serde::encode::Error> { encode_message(self) } +} + +#[inline] +pub fn peer_healthcheck_topic(peer_address: &PeerAddress) -> String { + pub_sub_topic(PEER_HEALTHCHECK_PREFIX, &peer_address.to_string()) +} + +#[derive(Deserialize)] +pub struct RequestPayload { + peer_address: PeerAddress, +} + +fn deserialize_bytes<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + struct ByteVisitor; + + impl<'de> serde::de::Visitor<'de> for ByteVisitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a non-empty byte array up to 512 bytes") + } + + fn visit_seq(self, mut seq: A) -> Result, A::Error> + where + A: serde::de::SeqAccess<'de>, + { + let mut buffer = vec![]; + while let Some(byte) = seq.next_element()? { + if buffer.len() >= 512 { + return Err(serde::de::Error::invalid_length( + buffer.len(), + &"longest possible length allowed for this field is 512 bytes (with RSA algorithm).", + )); + } + + buffer.push(byte); + } + + if buffer.is_empty() { + return Err(serde::de::Error::custom("Can't be empty.")); + } + + Ok(buffer) + } + } + + deserializer.deserialize_seq(ByteVisitor) +} + +#[derive(Debug, Display, Serialize, SerializeErrorType)] +#[serde(tag = "error_type", content = "error_data")] +pub enum HealthcheckRpcError { + MessageGenerationFailed { reason: String }, + MessageEncodingFailed { reason: String }, + Internal { reason: String }, +} + +impl HttpStatusCode for HealthcheckRpcError { + fn status_code(&self) -> common::StatusCode { + match self { + HealthcheckRpcError::MessageGenerationFailed { .. } + | HealthcheckRpcError::Internal { .. } + | HealthcheckRpcError::MessageEncodingFailed { .. } => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +pub async fn peer_connection_healthcheck_rpc( + ctx: MmArc, + req: RequestPayload, +) -> Result> { + // When things go awry, we want records to clear themselves to keep the memory clean of unused data. + // This is unrelated to the timeout logic. + let address_record_exp = Duration::from_secs(healthcheck_message_exp_secs()); + + let target_peer_address = req.peer_address; + + let p2p_ctx = P2PContext::fetch_from_mm_arc(&ctx); + if target_peer_address == p2p_ctx.peer_id().into() { + // That's us, so return true. + return Ok(true); + } + + let message = HealthcheckMessage::generate_message(&ctx, false) + .map_err(|reason| HealthcheckRpcError::MessageGenerationFailed { reason })?; + + let encoded_message = message + .encode() + .map_err(|e| HealthcheckRpcError::MessageEncodingFailed { reason: e.to_string() })?; + + let (tx, rx): (Sender<()>, Receiver<()>) = oneshot::channel(); + + { + let mut book = ctx.healthcheck_response_handler.lock().await; + book.insert(target_peer_address.into(), tx, address_record_exp); + } + + broadcast_p2p_msg( + &ctx, + peer_healthcheck_topic(&target_peer_address), + encoded_message, + None, + ); + + let timeout_duration = Duration::from_secs(healthcheck_message_exp_secs()); + Ok(rx.timeout(timeout_duration).await == Ok(Ok(()))) +} + +pub(crate) async fn process_p2p_healthcheck_message( + ctx: &MmArc, + message: mm2_libp2p::GossipsubMessage, +) -> P2PRequestResult<()> { + macro_rules! try_or_return { + ($exp:expr, $msg: expr) => { + match $exp { + Ok(t) => t, + Err(e) => { + log::error!("{}, error: {e:?}", $msg); + return; + }, + } + }; + } + + let data = HealthcheckMessage::decode(&message.data) + .map_to_mm(|e| P2PRequestError::DecodeError(format!("Couldn't decode healthcheck message: {}", e)))?; + let sender_peer = data.is_received_message_valid().map_to_mm(|e| { + P2PRequestError::ValidationFailed(format!("Received an invalid healthcheck message. Error: {}", e)) + })?; + + let ctx = ctx.clone(); + + // Pass the remaining work to another thread to free up this one as soon as possible, + // so KDF can handle a high amount of healthcheck messages more efficiently. + ctx.spawner().spawn(async move { + if data.should_reply() { + // Reply the message so they know we are healthy. + + let msg = try_or_return!( + HealthcheckMessage::generate_or_use_cached_message(&ctx), + "Couldn't generate the healthcheck message, this is very unusual!" + ); + + let encoded_msg = try_or_return!( + msg.encode(), + "Couldn't encode healthcheck message, this is very unusual!" + ); + + let topic = peer_healthcheck_topic(&sender_peer); + broadcast_p2p_msg(&ctx, topic, encoded_msg, None); + } else { + // The requested peer is healthy; signal the response channel. + let mut response_handler = ctx.healthcheck_response_handler.lock().await; + if let Some(tx) = response_handler.remove(&sender_peer.into()) { + if tx.send(()).is_err() { + log::error!("Result channel isn't present for peer '{sender_peer}'."); + }; + } else { + log::info!("Peer '{sender_peer}' isn't recorded in the healthcheck response handler."); + }; + } + }); + + Ok(()) +} + +#[cfg(any(test, target_arch = "wasm32"))] +mod tests { + use std::mem::discriminant; + use std::str::FromStr; + + use super::*; + use common::cross_test; + use crypto::CryptoCtx; + use mm2_libp2p::behaviours::atomicdex::generate_ed25519_keypair; + use mm2_test_helpers::for_tests::mm_ctx_with_iguana; + + common::cfg_wasm32! { + use wasm_bindgen_test::*; + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + } + + fn ctx() -> MmArc { + let ctx = mm_ctx_with_iguana(Some("dummy-value")); + let p2p_key = { + let crypto_ctx = CryptoCtx::from_ctx(&ctx).unwrap(); + let key = bitcrypto::sha256(crypto_ctx.mm2_internal_privkey_slice()); + key.take() + }; + + let (cmd_tx, _) = futures::channel::mpsc::channel(0); + + let p2p_context = P2PContext::new(cmd_tx, generate_ed25519_keypair(p2p_key)); + p2p_context.store_to_mm_arc(&ctx); + + ctx + } + + cross_test!(test_peer_address, { + #[derive(Deserialize, Serialize)] + struct PeerAddressTest { + peer_address: PeerAddress, + } + + let address_str = "12D3KooWEtuv7kmgGCC7oAQ31hB7AR5KkhT3eEWB2bP2roo3M7rY"; + let json_content = format!("{{\"peer_address\": \"{address_str}\"}}"); + let address_struct: PeerAddressTest = serde_json::from_str(&json_content).unwrap(); + + let actual_peer_id = mm2_libp2p::PeerId::from_str(address_str).unwrap(); + let deserialized_peer_id: mm2_libp2p::PeerId = address_struct.peer_address.into(); + + assert_eq!(deserialized_peer_id, actual_peer_id); + }); + + cross_test!(test_valid_message, { + let ctx = ctx(); + let message = HealthcheckMessage::generate_message(&ctx, false).unwrap(); + message.is_received_message_valid().unwrap(); + }); + + cross_test!(test_corrupted_messages, { + let ctx = ctx(); + + let mut message = HealthcheckMessage::generate_message(&ctx, false).unwrap(); + message.data.expires_at_secs += healthcheck_message_exp_secs() * 3; + assert_eq!( + discriminant(&message.is_received_message_valid().err().unwrap()), + discriminant(&SignValidationError::LifetimeOverflow { + max_allowed_expiration_secs: 0, + remaining_expiration_secs: 0 + }) + ); + + let mut message = HealthcheckMessage::generate_message(&ctx, false).unwrap(); + message.data.is_a_reply = !message.data.is_a_reply; + assert_eq!( + discriminant(&message.is_received_message_valid().err().unwrap()), + discriminant(&SignValidationError::FakeSignature) + ); + + let mut message = HealthcheckMessage::generate_message(&ctx, false).unwrap(); + message.data.sender_public_key.push(0); + assert_eq!( + discriminant(&message.is_received_message_valid().err().unwrap()), + discriminant(&SignValidationError::InvalidPublicKey) + ); + }); + + cross_test!(test_expired_message, { + let ctx = ctx(); + let message = HealthcheckMessage::generate_message(&ctx, false).unwrap(); + common::executor::Timer::sleep(3.).await; + assert_eq!( + discriminant(&message.is_received_message_valid().err().unwrap()), + discriminant(&SignValidationError::Expired { + now_secs: 0, + expires_at_secs: 0 + }) + ); + }); + + cross_test!(test_encode_decode, { + let ctx = ctx(); + let original = HealthcheckMessage::generate_message(&ctx, false).unwrap(); + + let encoded = original.encode().unwrap(); + assert!(!encoded.is_empty()); + + let decoded = HealthcheckMessage::decode(&encoded).unwrap(); + assert_eq!(original, decoded); + }); +} diff --git a/mm2src/mm2_main/src/lp_init/init_context.rs b/mm2src/mm2_main/src/lp_init/init_context.rs index e1c045c098..8b03751b69 100644 --- a/mm2src/mm2_main/src/lp_init/init_context.rs +++ b/mm2src/mm2_main/src/lp_init/init_context.rs @@ -1,6 +1,6 @@ -use crate::mm2::lp_native_dex::init_hw::InitHwTaskManagerShared; +use crate::lp_native_dex::init_hw::InitHwTaskManagerShared; #[cfg(target_arch = "wasm32")] -use crate::mm2::lp_native_dex::init_metamask::InitMetamaskManagerShared; +use crate::lp_native_dex::init_metamask::InitMetamaskManagerShared; use mm2_core::mm_ctx::{from_ctx, MmArc}; use rpc_task::RpcTaskManager; use std::sync::Arc; diff --git a/mm2src/mm2_main/src/lp_init/init_hw.rs b/mm2src/mm2_main/src/lp_init/init_hw.rs index 18e97bc096..b9d0c67664 100644 --- a/mm2src/mm2_main/src/lp_init/init_hw.rs +++ b/mm2src/mm2_main/src/lp_init/init_hw.rs @@ -1,4 +1,4 @@ -use crate::mm2::lp_native_dex::init_context::MmInitContext; +use crate::lp_native_dex::init_context::MmInitContext; use async_trait::async_trait; use common::{HttpStatusCode, SuccessResponse}; use crypto::hw_rpc_task::{HwConnectStatuses, HwRpcTaskAwaitingStatus, HwRpcTaskUserAction, HwRpcTaskUserActionRequest, diff --git a/mm2src/mm2_main/src/lp_init/init_metamask.rs b/mm2src/mm2_main/src/lp_init/init_metamask.rs index f80afe5878..630f6a1796 100644 --- a/mm2src/mm2_main/src/lp_init/init_metamask.rs +++ b/mm2src/mm2_main/src/lp_init/init_metamask.rs @@ -1,4 +1,4 @@ -use crate::mm2::lp_native_dex::init_context::MmInitContext; +use crate::lp_native_dex::init_context::MmInitContext; use async_trait::async_trait; use common::{HttpStatusCode, SerdeInfallible, SuccessResponse}; use crypto::metamask::{from_metamask_error, MetamaskError, MetamaskRpcError, WithMetamaskRpcError}; diff --git a/mm2src/mm2_main/src/lp_message_service.rs b/mm2src/mm2_main/src/lp_message_service.rs index 018d6bf971..0804bfbfa8 100644 --- a/mm2src/mm2_main/src/lp_message_service.rs +++ b/mm2src/mm2_main/src/lp_message_service.rs @@ -1,6 +1,6 @@ #[path = "notification/telegram/telegram.rs"] pub mod telegram; -use crate::mm2::lp_message_service::telegram::{ChatIdRegistry, TelegramError, TgClient}; +use crate::lp_message_service::telegram::{ChatIdRegistry, TelegramError, TgClient}; use async_trait::async_trait; use derive_more::Display; use futures::lock::Mutex as AsyncMutex; @@ -121,8 +121,8 @@ pub async fn init_message_service(ctx: &MmArc) -> Result<(), MmError Vec { #[cfg(not(target_arch = "wasm32"))] fn default_seednodes(netid: u16) -> Vec { - use crate::mm2::lp_network::addr_to_ipv4_string; + use crate::lp_network::addr_to_ipv4_string; if netid == 8762 { DEFAULT_NETID_SEEDNODES .iter() @@ -499,9 +499,11 @@ pub async fn lp_init_continue(ctx: MmArc) -> MmInitResult<()> { pub async fn lp_init(ctx: MmArc, version: String, datetime: String) -> MmInitResult<()> { info!("Version: {} DT {}", version, datetime); + // Ensure the database root directory exists before initializing the wallet passphrase. + // This is necessary to store the encrypted wallet passphrase if needed. #[cfg(not(target_arch = "wasm32"))] { - let dbdir = ctx.dbdir(); + let dbdir = ctx.db_root(); fs::create_dir_all(&dbdir).map_to_mm(|e| MmInitError::ErrorCreatingDbDir { path: dbdir.clone(), error: e.to_string(), @@ -559,6 +561,20 @@ async fn kick_start(ctx: MmArc) -> MmInitResult<()> { Ok(()) } +fn get_p2p_key(ctx: &MmArc, i_am_seed: bool) -> P2PResult<[u8; 32]> { + // TODO: Use persistent peer ID regardless the node type. + if i_am_seed { + if let Ok(crypto_ctx) = CryptoCtx::from_ctx(ctx) { + let key = sha256(crypto_ctx.mm2_internal_privkey_slice()); + return Ok(key.take()); + } + } + + let mut p2p_key = [0; 32]; + common::os_rng(&mut p2p_key).map_err(|e| P2PInitError::Internal(e.to_string()))?; + Ok(p2p_key) +} + pub async fn init_p2p(ctx: MmArc) -> P2PResult<()> { let i_am_seed = ctx.conf["i_am_seed"].as_bool().unwrap_or(false); let netid = ctx.netid(); @@ -570,13 +586,8 @@ pub async fn init_p2p(ctx: MmArc) -> P2PResult<()> { let seednodes = seednodes(&ctx)?; let ctx_on_poll = ctx.clone(); - let force_p2p_key = if i_am_seed { - let crypto_ctx = CryptoCtx::from_ctx(&ctx).mm_err(|e| P2PInitError::Internal(e.to_string()))?; - let key = sha256(crypto_ctx.mm2_internal_privkey_slice()); - Some(key.take()) - } else { - None - }; + + let p2p_key = get_p2p_key(&ctx, i_am_seed)?; let node_type = if i_am_seed { relay_node_type(&ctx).await? @@ -591,9 +602,8 @@ pub async fn init_p2p(ctx: MmArc) -> P2PResult<()> { .try_into() .unwrap_or(usize::MAX); - let mut gossipsub_config = GossipsubConfig::new(netid, spawner, node_type); + let mut gossipsub_config = GossipsubConfig::new(netid, spawner, node_type, p2p_key); gossipsub_config.to_dial(seednodes); - gossipsub_config.force_key(force_p2p_key); gossipsub_config.max_num_streams(max_num_streams); let spawn_result = spawn_gossipsub(gossipsub_config, move |swarm| { @@ -626,14 +636,18 @@ pub async fn init_p2p(ctx: MmArc) -> P2PResult<()> { ); }) .await; + let (cmd_tx, event_rx, peer_id) = spawn_result?; - ctx.peer_id.pin(peer_id.to_string()).map_to_mm(P2PInitError::Internal)?; - let p2p_context = P2PContext::new(cmd_tx); + + let p2p_context = P2PContext::new(cmd_tx, generate_ed25519_keypair(p2p_key)); p2p_context.store_to_mm_arc(&ctx); let fut = p2p_event_process_loop(ctx.weak(), event_rx, i_am_seed); ctx.spawner().spawn(fut); + // Listen for health check messages. + subscribe_to_topic(&ctx, peer_healthcheck_topic(&peer_id.into())); + Ok(()) } diff --git a/mm2src/mm2_main/src/lp_network.rs b/mm2src/mm2_main/src/lp_network.rs index cd2b32c538..b2ef53f3fb 100644 --- a/mm2src/mm2_main/src/lp_network.rs +++ b/mm2src/mm2_main/src/lp_network.rs @@ -29,17 +29,17 @@ use instant::Instant; use keys::KeyPair; use mm2_core::mm_ctx::{MmArc, MmWeak}; use mm2_err_handle::prelude::*; +use mm2_libp2p::application::request_response::P2PRequest; +use mm2_libp2p::p2p_ctx::P2PContext; use mm2_libp2p::{decode_message, encode_message, DecodingError, GossipsubEvent, GossipsubMessage, Libp2pPublic, Libp2pSecpPublic, MessageId, NetworkPorts, PeerId, TOPIC_SEPARATOR}; use mm2_libp2p::{AdexBehaviourCmd, AdexBehaviourEvent, AdexEventRx, AdexResponse}; use mm2_libp2p::{PeerAddresses, RequestResponseBehaviourEvent}; use mm2_metrics::{mm_label, mm_timing}; -use mm2_net::p2p::P2PContext; use serde::de; use std::net::ToSocketAddrs; -use crate::mm2::lp_ordermatch; -use crate::mm2::{lp_stats, lp_swap}; +use crate::{lp_healthcheck, lp_ordermatch, lp_stats, lp_swap}; pub type P2PRequestResult = Result>; pub type P2PProcessResult = Result>; @@ -62,6 +62,7 @@ pub enum P2PRequestError { ResponseError(String), #[display(fmt = "Expected 1 response, found {}", _0)] ExpectedSingleResponseError(usize), + ValidationFailed(String), } /// Enum covering error cases that can happen during P2P message processing. @@ -88,12 +89,6 @@ impl From for P2PRequestError { fn from(e: rmp_serde::decode::Error) -> Self { P2PRequestError::DecodeError(e.to_string()) } } -#[derive(Eq, Debug, Deserialize, PartialEq, Serialize)] -pub enum P2PRequest { - Ordermatch(lp_ordermatch::OrdermatchRequest), - NetworkInfo(lp_stats::NetworkInfoRequest), -} - pub async fn p2p_event_process_loop(ctx: MmWeak, mut rx: AdexEventRx, i_am_relay: bool) { loop { let adex_event = rx.next().await; @@ -196,15 +191,16 @@ async fn process_p2p_message( to_propagate = true; }, Some(lp_swap::TX_HELPER_PREFIX) => { - if let Some(pair) = split.next() { - if let Ok(Some(coin)) = lp_coinfind(&ctx, pair).await { + if let Some(ticker) = split.next() { + if let Ok(Some(coin)) = lp_coinfind(&ctx, ticker).await { if let Err(e) = coin.tx_enum_from_bytes(&message.data) { log::error!("Message cannot continue the process due to: {:?}", e); return; }; - let fut = coin.send_raw_tx_bytes(&message.data); - ctx.spawner().spawn(async { + if coin.is_utxo_in_native_mode() { + let fut = coin.send_raw_tx_bytes(&message.data); + ctx.spawner().spawn(async { match fut.compat().await { Ok(id) => log::debug!("Transaction broadcasted successfully: {:?} ", id), // TODO (After https://github.com/KomodoPlatform/atomicDEX-API/pull/1433) @@ -213,8 +209,19 @@ async fn process_p2p_message( Err(e) => log::error!("Broadcast transaction failed (ignore this error if the transaction already sent by another seednode). {}", e), }; }) + } } + + to_propagate = true; + } + }, + Some(lp_healthcheck::PEER_HEALTHCHECK_PREFIX) => { + if let Err(e) = lp_healthcheck::process_p2p_healthcheck_message(&ctx, message).await { + log::error!("{}", e); + return; } + + to_propagate = true; }, None | Some(_) => (), } diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 86e2fb0491..620cb79bfb 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -21,7 +21,6 @@ // use async_trait::async_trait; -use best_orders::BestOrdersAction; use blake2::digest::{Update, VariableOutput}; use blake2::Blake2bVar; use coins::utxo::{compressed_pub_key_from_priv_raw, ChecksumType, UtxoAddressFormat}; @@ -42,6 +41,8 @@ use http::Response; use keys::{AddressFormat, KeyPair}; use mm2_core::mm_ctx::{from_ctx, MmArc, MmWeak}; use mm2_err_handle::prelude::*; +use mm2_libp2p::application::request_response::ordermatch::OrdermatchRequest; +use mm2_libp2p::application::request_response::P2PRequest; use mm2_libp2p::{decode_signed, encode_and_sign, encode_message, pub_sub_topic, PublicKey, TopicHash, TopicPrefix, TOPIC_SEPARATOR}; use mm2_metrics::mm_gauge; @@ -69,20 +70,19 @@ use std::time::Duration; use trie_db::NodeCodec as NodeCodecT; use uuid::Uuid; -use crate::mm2::lp_network::{broadcast_p2p_msg, request_any_relay, request_one_peer, subscribe_to_topic, P2PRequest, - P2PRequestError}; -use crate::mm2::lp_swap::maker_swap_v2::{self, MakerSwapStateMachine, MakerSwapStorage}; -use crate::mm2::lp_swap::taker_swap_v2::{self, TakerSwapStateMachine, TakerSwapStorage}; -use crate::mm2::lp_swap::{calc_max_maker_vol, check_balance_for_maker_swap, check_balance_for_taker_swap, - check_other_coin_balance_for_swap, detect_secret_hash_algo, dex_fee_amount_from_taker_coin, - generate_secret, get_max_maker_vol, insert_new_swap_to_db, is_pubkey_banned, - lp_atomic_locktime, p2p_keypair_and_peer_id_to_broadcast, - p2p_private_and_peer_id_to_broadcast, run_maker_swap, run_taker_swap, swap_v2_topic, - AtomicLocktimeVersion, CheckBalanceError, CheckBalanceResult, CoinVolumeInfo, MakerSwap, - RunMakerSwapInput, RunTakerSwapInput, SwapConfirmationsSettings, TakerSwap, LEGACY_SWAP_TYPE}; +use crate::lp_network::{broadcast_p2p_msg, request_any_relay, request_one_peer, subscribe_to_topic, P2PRequestError}; +use crate::lp_swap::maker_swap_v2::{self, MakerSwapStateMachine, MakerSwapStorage}; +use crate::lp_swap::taker_swap_v2::{self, TakerSwapStateMachine, TakerSwapStorage}; +use crate::lp_swap::{calc_max_maker_vol, check_balance_for_maker_swap, check_balance_for_taker_swap, + check_other_coin_balance_for_swap, detect_secret_hash_algo, dex_fee_amount_from_taker_coin, + generate_secret, get_max_maker_vol, insert_new_swap_to_db, is_pubkey_banned, lp_atomic_locktime, + p2p_keypair_and_peer_id_to_broadcast, p2p_private_and_peer_id_to_broadcast, run_maker_swap, + run_taker_swap, swap_v2_topic, AtomicLocktimeVersion, CheckBalanceError, CheckBalanceResult, + CoinVolumeInfo, MakerSwap, RunMakerSwapInput, RunTakerSwapInput, SwapConfirmationsSettings, + TakerSwap, LEGACY_SWAP_TYPE}; #[cfg(any(test, feature = "run-docker-tests"))] -use crate::mm2::lp_swap::taker_swap::FailAt; +use crate::lp_swap::taker_swap::FailAt; pub use best_orders::{best_orders_rpc, best_orders_rpc_v2}; pub use orderbook_depth::orderbook_depth_rpc; @@ -95,25 +95,21 @@ cfg_wasm32! { pub type OrdermatchDbLocked<'a> = DbLocked<'a, OrdermatchDb>; } -#[path = "lp_ordermatch/best_orders.rs"] mod best_orders; -#[path = "lp_ordermatch/lp_bot.rs"] mod lp_bot; +mod best_orders; +mod lp_bot; pub use lp_bot::{start_simple_market_maker_bot, stop_simple_market_maker_bot, StartSimpleMakerBotRequest, TradingBotEvent}; -#[path = "lp_ordermatch/my_orders_storage.rs"] mod my_orders_storage; -#[path = "lp_ordermatch/new_protocol.rs"] mod new_protocol; -#[path = "lp_ordermatch/order_requests_tracker.rs"] +mod new_protocol; mod order_requests_tracker; -#[path = "lp_ordermatch/orderbook_depth.rs"] mod orderbook_depth; -#[path = "lp_ordermatch/orderbook_rpc.rs"] mod orderbook_rpc; +mod orderbook_depth; +mod orderbook_rpc; #[cfg(all(test, not(target_arch = "wasm32")))] #[path = "ordermatch_tests.rs"] pub mod ordermatch_tests; -#[cfg(target_arch = "wasm32")] -#[path = "lp_ordermatch/ordermatch_wasm_db.rs"] -mod ordermatch_wasm_db; +#[cfg(target_arch = "wasm32")] mod ordermatch_wasm_db; pub const ORDERBOOK_PREFIX: TopicPrefix = "orbk"; #[cfg(not(test))] @@ -126,6 +122,7 @@ const TAKER_ORDER_TIMEOUT: u64 = 30; const ORDER_MATCH_TIMEOUT: u64 = 30; const ORDERBOOK_REQUESTING_TIMEOUT: u64 = MIN_ORDER_KEEP_ALIVE_INTERVAL * 2; const MAX_ORDERS_NUMBER_IN_ORDERBOOK_RESPONSE: usize = 1000; +const RECENTLY_CANCELLED_TIMEOUT: Duration = Duration::from_secs(120); #[cfg(not(test))] const TRIE_STATE_HISTORY_TIMEOUT: u64 = 14400; #[cfg(test)] @@ -335,6 +332,12 @@ async fn process_orders_keep_alive( Ok(()) } +#[inline] +fn process_maker_order_created(ctx: &MmArc, from_pubkey: String, created_msg: new_protocol::MakerOrderCreated) { + let order: OrderbookItem = (created_msg, from_pubkey).into(); + insert_or_update_order(ctx, order); +} + fn process_maker_order_updated( ctx: MmArc, from_pubkey: String, @@ -354,6 +357,22 @@ fn process_maker_order_updated( Ok(()) } +fn process_maker_order_cancelled(ctx: &MmArc, from_pubkey: String, cancelled_msg: new_protocol::MakerOrderCancelled) { + let uuid = Uuid::from(cancelled_msg.uuid); + let ordermatch_ctx = OrdermatchContext::from_ctx(ctx).expect("from_ctx failed"); + let mut orderbook = ordermatch_ctx.orderbook.lock(); + // Add the order to the recently cancelled list to ignore it if a new order with the same uuid + // is received within the `RECENTLY_CANCELLED_TIMEOUT` timeframe. + // We do this even if the order is in the order_set, because it could have been added through + // means other than the order creation message. + orderbook.recently_cancelled.insert(uuid, from_pubkey.clone()); + if let Some(order) = orderbook.order_set.get(&uuid) { + if order.pubkey == from_pubkey { + orderbook.remove_order_trie_update(uuid); + } + } +} + // fn verify_pubkey_orderbook(orderbook: &GetOrderbookPubkeyItem) -> Result<(), String> { // let keys: Vec<(_, _)> = orderbook // .orders @@ -460,16 +479,6 @@ fn insert_or_update_my_order(ctx: &MmArc, item: OrderbookItem, my_order: &MakerO } } -fn delete_order(ctx: &MmArc, pubkey: &str, uuid: Uuid) { - let ordermatch_ctx = OrdermatchContext::from_ctx(ctx).expect("from_ctx failed"); - let mut orderbook = ordermatch_ctx.orderbook.lock(); - if let Some(order) = orderbook.order_set.get(&uuid) { - if order.pubkey == pubkey { - orderbook.remove_order_trie_update(uuid); - } - } -} - fn delete_my_order(ctx: &MmArc, uuid: Uuid, p2p_privkey: Option) { let ordermatch_ctx: Arc = OrdermatchContext::from_ctx(ctx).expect("from_ctx failed"); let mut orderbook = ordermatch_ctx.orderbook.lock(); @@ -552,8 +561,7 @@ pub async fn process_msg(ctx: MmArc, from_peer: String, msg: &[u8], i_am_relay: log::debug!("received ordermatch message {:?}", message); match message { new_protocol::OrdermatchMessage::MakerOrderCreated(created_msg) => { - let order: OrderbookItem = (created_msg, hex::encode(pubkey.to_bytes().as_slice())).into(); - insert_or_update_order(&ctx, order); + process_maker_order_created(&ctx, pubkey.to_hex(), created_msg); Ok(()) }, new_protocol::OrdermatchMessage::PubkeyKeepAlive(keep_alive) => { @@ -580,7 +588,7 @@ pub async fn process_msg(ctx: MmArc, from_peer: String, msg: &[u8], i_am_relay: Ok(()) }, new_protocol::OrdermatchMessage::MakerOrderCancelled(cancelled_msg) => { - delete_order(&ctx, &pubkey.to_hex(), cancelled_msg.uuid.into()); + process_maker_order_cancelled(&ctx, pubkey.to_hex(), cancelled_msg); Ok(()) }, new_protocol::OrdermatchMessage::MakerOrderUpdated(updated_msg) => { @@ -592,34 +600,6 @@ pub async fn process_msg(ctx: MmArc, from_peer: String, msg: &[u8], i_am_relay: } } -#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] -pub enum OrdermatchRequest { - /// Get an orderbook for the given pair. - GetOrderbook { - base: String, - rel: String, - }, - /// Sync specific pubkey orderbook state if our known Patricia trie state doesn't match the latest keep alive message - SyncPubkeyOrderbookState { - pubkey: String, - /// Request using this condition - trie_roots: HashMap, - }, - BestOrders { - coin: String, - action: BestOrdersAction, - volume: BigRational, - }, - OrderbookDepth { - pairs: Vec<(String, String)>, - }, - BestOrdersByNumber { - coin: String, - action: BestOrdersAction, - number: usize, - }, -} - #[derive(Debug)] struct TryFromBytesError(String); @@ -1087,15 +1067,15 @@ fn maker_order_updated_p2p_notify( broadcast_p2p_msg(&ctx, topic, encoded_msg, peer_id); } -fn maker_order_cancelled_p2p_notify(ctx: MmArc, order: &MakerOrder) { +fn maker_order_cancelled_p2p_notify(ctx: &MmArc, order: &MakerOrder) { let message = new_protocol::OrdermatchMessage::MakerOrderCancelled(new_protocol::MakerOrderCancelled { uuid: order.uuid.into(), timestamp: now_sec(), pair_trie_root: H64::default(), }); - delete_my_order(&ctx, order.uuid, order.p2p_privkey); + delete_my_order(ctx, order.uuid, order.p2p_privkey); log::debug!("maker_order_cancelled_p2p_notify called, message {:?}", message); - broadcast_ordermatch_message(&ctx, order.orderbook_topic(), message, order.p2p_keypair()); + broadcast_ordermatch_message(ctx, order.orderbook_topic(), message, order.p2p_keypair()); } pub struct BalanceUpdateOrdermatchHandler { @@ -1145,7 +1125,7 @@ impl BalanceTradeFeeUpdatedHandler for BalanceUpdateOrdermatchHandler { // This checks that the order hasn't been removed by another process if removed_order_mutex.is_some() { // cancel the order - maker_order_cancelled_p2p_notify(ctx.clone(), &order); + maker_order_cancelled_p2p_notify(&ctx, &order); delete_my_maker_order( ctx.clone(), order.clone(), @@ -2481,7 +2461,6 @@ fn collect_orderbook_metrics(ctx: &MmArc, orderbook: &Orderbook) { mm_gauge!(ctx.metrics, "orderbook.memory_db", memory_db_size as f64); } -#[derive(Default)] struct Orderbook { /// A map from (base, rel). ordered: HashMap<(String, String), BTreeSet>, @@ -2494,12 +2473,34 @@ struct Orderbook { order_set: HashMap, /// a map of orderbook states of known maker pubkeys pubkeys_state: HashMap, + /// The `TimeCache` of recently canceled orders, mapping `Uuid` to the maker pubkey as `String`, + /// used to avoid order recreation in case of out-of-order p2p messages, + /// e.g., when receiving the order cancellation message before the order is created. + /// Entries are kept for `RECENTLY_CANCELLED_TIMEOUT` seconds. + recently_cancelled: TimeCache, topics_subscribed_to: HashMap, /// MemoryDB instance to store Patricia Tries data memory_db: MemoryDB, my_p2p_pubkeys: HashSet, } +impl Default for Orderbook { + fn default() -> Self { + Orderbook { + ordered: HashMap::default(), + pairs_existing_for_base: HashMap::default(), + pairs_existing_for_rel: HashMap::default(), + unordered: HashMap::default(), + order_set: HashMap::default(), + pubkeys_state: HashMap::default(), + recently_cancelled: TimeCache::new(RECENTLY_CANCELLED_TIMEOUT), + topics_subscribed_to: HashMap::default(), + memory_db: MemoryDB::default(), + my_p2p_pubkeys: HashSet::default(), + } + } +} + fn hashed_null_node() -> TrieHash { ::hashed_null_node() } impl Orderbook { @@ -2516,6 +2517,12 @@ impl Orderbook { fn find_order_by_uuid(&self, uuid: &Uuid) -> Option { self.order_set.get(uuid).cloned() } fn insert_or_update_order_update_trie(&mut self, order: OrderbookItem) { + // Ignore the order if it was recently cancelled + if self.recently_cancelled.get(&order.uuid) == Some(&order.pubkey) { + warn!("Maker order {} was recently cancelled, ignoring", order.uuid); + return; + } + let zero = BigRational::from_integer(0.into()); if order.max_volume <= zero || order.price <= zero || order.min_volume < zero { self.remove_order_trie_update(order.uuid); @@ -3318,6 +3325,7 @@ pub async fn lp_ordermatch_loop(ctx: MmArc) { // This checks that the order hasn't been removed by another process if let Some(order_mutex) = removed_order_mutex { let order = order_mutex.lock().await; + maker_order_cancelled_p2p_notify(&ctx, &order); delete_my_maker_order( ctx.clone(), order.clone(), @@ -3434,7 +3442,7 @@ async fn check_balance_for_maker_orders(ctx: MmArc, ordermatch_ctx: &OrdermatchC let removed_order_mutex = ordermatch_ctx.maker_orders_ctx.lock().remove_order(&uuid); // This checks that the order hasn't been removed by another process if removed_order_mutex.is_some() { - maker_order_cancelled_p2p_notify(ctx.clone(), &order); + maker_order_cancelled_p2p_notify(&ctx, &order); delete_my_maker_order(ctx.clone(), order.clone(), reason) .compat() .await @@ -4743,7 +4751,7 @@ async fn cancel_previous_maker_orders( let removed_order_mutex = ordermatch_ctx.maker_orders_ctx.lock().remove_order(&uuid); // This checks that the uuid, &order.base hasn't been removed by another process if removed_order_mutex.is_some() { - maker_order_cancelled_p2p_notify(ctx.clone(), &order); + maker_order_cancelled_p2p_notify(ctx, &order); delete_my_maker_order(ctx.clone(), order.clone(), MakerOrderCancellationReason::Cancelled) .compat() .await @@ -5131,6 +5139,7 @@ pub struct CancelOrderResponse { result: String, } +// TODO: This is a near copy of the function below, `cancel_order_rpc`. pub async fn cancel_order(ctx: MmArc, req: CancelOrderReq) -> Result> { let ordermatch_ctx = match OrdermatchContext::from_ctx(&ctx) { Ok(x) => x, @@ -5145,7 +5154,7 @@ pub async fn cancel_order(ctx: MmArc, req: CancelOrderReq) -> Result Result> let removed_order_mutex = ordermatch_ctx.maker_orders_ctx.lock().remove_order(&order.uuid); // This checks that the order hasn't been removed by another process if removed_order_mutex.is_some() { - maker_order_cancelled_p2p_notify(ctx.clone(), &order); + maker_order_cancelled_p2p_notify(&ctx, &order); delete_my_maker_order(ctx, order.clone(), MakerOrderCancellationReason::Cancelled) .compat() .await @@ -5543,7 +5552,7 @@ pub async fn cancel_orders_by(ctx: &MmArc, cancel_by: CancelBy) -> Result<(Vec { - MmError::err(OrderbookAddrErr::CoinIsNotSupported(coin.to_owned())) - }, CoinProtocol::ZHTLC { .. } => Ok(OrderbookAddress::Shielded), #[cfg(not(target_arch = "wasm32"))] // Todo: Shielded address is used for lightning for now, the lightning node public key can be used for the orderbook entry pubkey diff --git a/mm2src/mm2_main/src/lp_ordermatch/best_orders.rs b/mm2src/mm2_main/src/lp_ordermatch/best_orders.rs index 7e20ed46bc..3bf684b66c 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/best_orders.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/best_orders.rs @@ -4,6 +4,8 @@ use derive_more::Display; use http::{Response, StatusCode}; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; +use mm2_libp2p::application::request_response::{ordermatch::{BestOrdersAction, OrdermatchRequest}, + P2PRequest}; use mm2_number::{BigRational, MmNumber}; use mm2_rpc::data::legacy::OrderConfirmationsSettings; use num_traits::Zero; @@ -12,15 +14,8 @@ use std::collections::{HashMap, HashSet}; use uuid::Uuid; use super::{addr_format_from_protocol_info, is_my_order, mm2_internal_pubkey_hex, orderbook_address, - BaseRelProtocolInfo, OrderbookP2PItemWithProof, OrdermatchContext, OrdermatchRequest, RpcOrderbookEntryV2}; -use crate::mm2::lp_network::{request_any_relay, P2PRequest}; - -#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "lowercase")] -pub enum BestOrdersAction { - Buy, - Sell, -} + BaseRelProtocolInfo, OrderbookP2PItemWithProof, OrdermatchContext, RpcOrderbookEntryV2}; +use crate::lp_network::request_any_relay; #[derive(Debug, Deserialize)] pub struct BestOrdersRequest { @@ -404,8 +399,8 @@ pub async fn best_orders_rpc_v2( #[cfg(all(test, not(target_arch = "wasm32")))] mod best_orders_test { use super::*; - use crate::mm2::lp_ordermatch::ordermatch_tests::make_random_orders; - use crate::mm2::lp_ordermatch::{OrderbookItem, TrieProof}; + use crate::lp_ordermatch::ordermatch_tests::make_random_orders; + use crate::lp_ordermatch::{OrderbookItem, TrieProof}; use common::new_uuid; use std::iter::FromIterator; diff --git a/mm2src/mm2_main/src/lp_ordermatch/lp_bot.rs b/mm2src/mm2_main/src/lp_ordermatch/lp_bot.rs index acdb6bd217..9c8095253b 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/lp_bot.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/lp_bot.rs @@ -16,11 +16,11 @@ use std::ops::Deref; use std::{collections::HashMap, sync::Arc}; #[path = "simple_market_maker.rs"] mod simple_market_maker_bot; -use crate::mm2::lp_dispatcher::{LpEvents, StopCtxEvent}; -use crate::mm2::lp_message_service::{MessageServiceContext, MAKER_BOT_ROOM_ID}; -use crate::mm2::lp_ordermatch::lp_bot::simple_market_maker_bot::{tear_down_bot, BOT_DEFAULT_REFRESH_RATE, - PRECISION_FOR_NOTIFICATION}; -use crate::mm2::lp_swap::MakerSwapStatusChanged; +use crate::lp_dispatcher::{LpEvents, StopCtxEvent}; +use crate::lp_message_service::{MessageServiceContext, MAKER_BOT_ROOM_ID}; +use crate::lp_ordermatch::lp_bot::simple_market_maker_bot::{tear_down_bot, BOT_DEFAULT_REFRESH_RATE, + PRECISION_FOR_NOTIFICATION}; +use crate::lp_swap::MakerSwapStatusChanged; pub use simple_market_maker_bot::{start_simple_market_maker_bot, stop_simple_market_maker_bot, StartSimpleMakerBotRequest}; diff --git a/mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs b/mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs index 677af4be71..05322325a5 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs @@ -205,11 +205,10 @@ pub trait MyOrdersFilteringHistory { #[cfg(not(target_arch = "wasm32"))] mod native_impl { use super::*; - use crate::mm2::database::my_orders::{insert_maker_order, insert_taker_order, select_orders_by_filter, - select_status_by_uuid, update_maker_order, update_order_status, - update_was_taker}; - use crate::mm2::lp_ordermatch::{my_maker_order_file_path, my_maker_orders_dir, my_order_history_file_path, - my_taker_order_file_path, my_taker_orders_dir}; + use crate::database::my_orders::{insert_maker_order, insert_taker_order, select_orders_by_filter, + select_status_by_uuid, update_maker_order, update_order_status, update_was_taker}; + use crate::lp_ordermatch::{my_maker_order_file_path, my_maker_orders_dir, my_order_history_file_path, + my_taker_order_file_path, my_taker_orders_dir}; use mm2_io::fs::{read_dir_json, read_json, remove_file_async, write_json, FsJsonError}; const USE_TMP_FILE: bool = true; @@ -350,10 +349,10 @@ mod native_impl { #[cfg(target_arch = "wasm32")] mod wasm_impl { use super::*; - use crate::mm2::lp_ordermatch::ordermatch_wasm_db::{DbTransactionError, InitDbError, MyActiveMakerOrdersTable, - MyActiveTakerOrdersTable, MyFilteringHistoryOrdersTable, - MyHistoryOrdersTable}; - use crate::mm2::lp_ordermatch::OrdermatchContext; + use crate::lp_ordermatch::ordermatch_wasm_db::{DbTransactionError, InitDbError, MyActiveMakerOrdersTable, + MyActiveTakerOrdersTable, MyFilteringHistoryOrdersTable, + MyHistoryOrdersTable}; + use crate::lp_ordermatch::OrdermatchContext; use common::log::warn; use mm2_rpc::data::legacy::TakerAction; use num_traits::ToPrimitive; @@ -694,8 +693,8 @@ mod wasm_impl { mod tests { use super::wasm_impl::{maker_order_to_filtering_history_item, taker_order_to_filtering_history_item}; use super::*; - use crate::mm2::lp_ordermatch::ordermatch_wasm_db::{ItemId, MyFilteringHistoryOrdersTable}; - use crate::mm2::lp_ordermatch::{OrdermatchContext, TakerRequest}; + use crate::lp_ordermatch::ordermatch_wasm_db::{ItemId, MyFilteringHistoryOrdersTable}; + use crate::lp_ordermatch::{OrdermatchContext, TakerRequest}; use common::{new_uuid, now_ms}; use futures::compat::Future01CompatExt; use itertools::Itertools; diff --git a/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs b/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs index b15f5e6d38..72aa53597d 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs @@ -5,7 +5,7 @@ use mm2_rpc::data::legacy::{MatchBy as SuperMatchBy, OrderConfirmationsSettings, use std::collections::{HashMap, HashSet}; use uuid::Uuid; -use crate::mm2::lp_ordermatch::{AlbOrderedOrderbookPair, H64}; +use crate::lp_ordermatch::{AlbOrderedOrderbookPair, H64}; #[derive(Debug, Deserialize, Serialize)] #[allow(clippy::large_enum_variant)] diff --git a/mm2src/mm2_main/src/lp_ordermatch/orderbook_depth.rs b/mm2src/mm2_main/src/lp_ordermatch/orderbook_depth.rs index dcc991a361..1772acbe61 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/orderbook_depth.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/orderbook_depth.rs @@ -1,9 +1,10 @@ -use super::{orderbook_topic_from_base_rel, OrdermatchContext, OrdermatchRequest}; -use crate::mm2::lp_network::{request_any_relay, P2PRequest}; +use super::{orderbook_topic_from_base_rel, OrdermatchContext}; +use crate::lp_network::request_any_relay; use coins::is_wallet_only_ticker; use common::log; use http::Response; use mm2_core::mm_ctx::MmArc; +use mm2_libp2p::application::request_response::{ordermatch::OrdermatchRequest, P2PRequest}; use serde_json::{self as json, Value as Json}; use std::collections::HashMap; diff --git a/mm2src/mm2_main/src/lp_ordermatch/ordermatch_wasm_db.rs b/mm2src/mm2_main/src/lp_ordermatch/ordermatch_wasm_db.rs index 3e6c1665c0..d64e0b40df 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/ordermatch_wasm_db.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/ordermatch_wasm_db.rs @@ -40,7 +40,7 @@ impl Deref for OrdermatchDb { pub mod tables { use super::*; - use crate::mm2::lp_ordermatch::{MakerOrder, Order, TakerOrder}; + use crate::lp_ordermatch::{MakerOrder, Order, TakerOrder}; use serde_json::Value as Json; #[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] diff --git a/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs b/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs index 728f177176..14551167d6 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs @@ -1,14 +1,14 @@ -use crate::mm2::lp_dispatcher::{dispatch_lp_event, DispatcherContext}; -use crate::mm2::lp_ordermatch::lp_bot::{RunningState, StoppedState, StoppingState, TradingBotStarted, - TradingBotStopped, TradingBotStopping, VolumeSettings}; -use crate::mm2::lp_ordermatch::{cancel_all_orders, CancelBy, TradingBotEvent}; -use crate::mm2::lp_swap::SavedSwap; -use crate::mm2::{lp_ordermatch::{cancel_order, create_maker_order, - lp_bot::{SimpleCoinMarketMakerCfg, SimpleMakerBotRegistry, TradingBotContext, - TradingBotState}, - update_maker_order, CancelOrderReq, MakerOrder, MakerOrderUpdateReq, - OrdermatchContext, SetPriceReq}, - lp_swap::{latest_swaps_for_pair, LatestSwapsErr}}; +use crate::lp_dispatcher::{dispatch_lp_event, DispatcherContext}; +use crate::lp_ordermatch::lp_bot::{RunningState, StoppedState, StoppingState, TradingBotStarted, TradingBotStopped, + TradingBotStopping, VolumeSettings}; +use crate::lp_ordermatch::{cancel_all_orders, CancelBy, TradingBotEvent}; +use crate::lp_swap::SavedSwap; +use crate::{lp_ordermatch::{cancel_order, create_maker_order, + lp_bot::{SimpleCoinMarketMakerCfg, SimpleMakerBotRegistry, TradingBotContext, + TradingBotState}, + update_maker_order, CancelOrderReq, MakerOrder, MakerOrderUpdateReq, OrdermatchContext, + SetPriceReq}, + lp_swap::{latest_swaps_for_pair, LatestSwapsErr}}; use coins::lp_price::{fetch_price_tickers, Provider, RateInfos, PRICE_ENDPOINTS}; use coins::{lp_coinfind, GetNonZeroBalance}; use common::{executor::{SpawnFuture, Timer}, diff --git a/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker_tests.rs b/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker_tests.rs index 8928656415..7e12bcf5f3 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker_tests.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker_tests.rs @@ -1,6 +1,6 @@ -use crate::mm2::{lp_ordermatch::lp_bot::simple_market_maker_bot::vwap, - lp_ordermatch::lp_bot::SimpleCoinMarketMakerCfg, - lp_swap::{MakerSavedSwap, SavedSwap}}; +use crate::{lp_ordermatch::lp_bot::simple_market_maker_bot::vwap, + lp_ordermatch::lp_bot::SimpleCoinMarketMakerCfg, + lp_swap::{MakerSavedSwap, SavedSwap}}; use common::{block_on, log::UnifiedLoggerBuilder}; use mm2_number::MmNumber; diff --git a/mm2src/mm2_main/src/lp_stats.rs b/mm2src/mm2_main/src/lp_stats.rs index 77e8e091ed..185996ecd1 100644 --- a/mm2src/mm2_main/src/lp_stats.rs +++ b/mm2src/mm2_main/src/lp_stats.rs @@ -7,13 +7,14 @@ use futures::lock::Mutex as AsyncMutex; use http::StatusCode; use mm2_core::mm_ctx::{from_ctx, MmArc}; use mm2_err_handle::prelude::*; +use mm2_libp2p::application::request_response::network_info::NetworkInfoRequest; use mm2_libp2p::{encode_message, NetworkInfo, PeerId, RelayAddress, RelayAddressError}; use serde_json::{self as json, Value as Json}; use std::collections::{HashMap, HashSet}; use std::sync::Arc; -use crate::mm2::lp_network::{add_reserved_peer_addresses, lp_network_ports, request_peers, NetIdError, P2PRequest, - ParseAddressError, PeerDecodedResponse}; +use crate::lp_network::{add_reserved_peer_addresses, lp_network_ports, request_peers, NetIdError, ParseAddressError, + PeerDecodedResponse}; use std::str::FromStr; pub type NodeVersionResult = Result>; @@ -90,7 +91,7 @@ fn insert_node_info_to_db(_ctx: &MmArc, _node_info: &NodeInfo) -> Result<(), Str #[cfg(not(target_arch = "wasm32"))] fn insert_node_info_to_db(ctx: &MmArc, node_info: &NodeInfo) -> Result<(), String> { - crate::mm2::database::stats_nodes::insert_node_info(ctx, node_info).map_err(|e| e.to_string()) + crate::database::stats_nodes::insert_node_info(ctx, node_info).map_err(|e| e.to_string()) } #[cfg(target_arch = "wasm32")] @@ -98,7 +99,7 @@ fn insert_node_version_stat_to_db(_ctx: &MmArc, _node_version_stat: NodeVersionS #[cfg(not(target_arch = "wasm32"))] fn insert_node_version_stat_to_db(ctx: &MmArc, node_version_stat: NodeVersionStat) -> Result<(), String> { - crate::mm2::database::stats_nodes::insert_node_version_stat(ctx, node_version_stat).map_err(|e| e.to_string()) + crate::database::stats_nodes::insert_node_version_stat(ctx, node_version_stat).map_err(|e| e.to_string()) } #[cfg(target_arch = "wasm32")] @@ -106,7 +107,7 @@ fn delete_node_info_from_db(_ctx: &MmArc, _name: String) -> Result<(), String> { #[cfg(not(target_arch = "wasm32"))] fn delete_node_info_from_db(ctx: &MmArc, name: String) -> Result<(), String> { - crate::mm2::database::stats_nodes::delete_node_info(ctx, name).map_err(|e| e.to_string()) + crate::database::stats_nodes::delete_node_info(ctx, name).map_err(|e| e.to_string()) } #[cfg(target_arch = "wasm32")] @@ -114,7 +115,7 @@ fn select_peers_addresses_from_db(_ctx: &MmArc) -> Result, #[cfg(not(target_arch = "wasm32"))] fn select_peers_addresses_from_db(ctx: &MmArc) -> Result, String> { - crate::mm2::database::stats_nodes::select_peers_addresses(ctx).map_err(|e| e.to_string()) + crate::database::stats_nodes::select_peers_addresses(ctx).map_err(|e| e.to_string()) } #[cfg(target_arch = "wasm32")] @@ -125,7 +126,7 @@ pub async fn add_node_to_version_stat(_ctx: MmArc, _req: Json) -> NodeVersionRes /// Adds node info. to db to be used later for stats collection #[cfg(not(target_arch = "wasm32"))] pub async fn add_node_to_version_stat(ctx: MmArc, req: Json) -> NodeVersionResult { - use crate::mm2::lp_network::addr_to_ipv4_string; + use crate::lp_network::addr_to_ipv4_string; let node_info: NodeInfo = json::from_value(req)?; @@ -169,12 +170,6 @@ struct Mm2VersionRes { nodes: HashMap, } -#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] -pub enum NetworkInfoRequest { - /// Get MM2 version of nodes added to stats collection - GetMm2Version, -} - fn process_get_version_request(ctx: MmArc) -> Result>, String> { let response = ctx.mm_version().to_string(); let encoded = try_s!(encode_message(&response)); @@ -264,8 +259,9 @@ pub async fn start_version_stat_collection(ctx: MmArc, req: Json) -> NodeVersion #[cfg(not(target_arch = "wasm32"))] async fn stat_collection_loop(ctx: MmArc, interval: f64) { use common::now_sec; + use mm2_libp2p::application::request_response::P2PRequest; - use crate::mm2::database::stats_nodes::select_peers_names; + use crate::database::stats_nodes::select_peers_names; let mut interval = interval; loop { diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 272b53c0dc..0acb7fc443 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -58,9 +58,9 @@ // use super::lp_network::P2PRequestResult; -use crate::mm2::lp_network::{broadcast_p2p_msg, Libp2pPeerId, P2PProcessError, P2PProcessResult, P2PRequestError}; -use crate::mm2::lp_swap::maker_swap_v2::{MakerSwapStateMachine, MakerSwapStorage}; -use crate::mm2::lp_swap::taker_swap_v2::{TakerSwapStateMachine, TakerSwapStorage}; +use crate::lp_network::{broadcast_p2p_msg, Libp2pPeerId, P2PProcessError, P2PProcessResult, P2PRequestError}; +use crate::lp_swap::maker_swap_v2::{MakerSwapStateMachine, MakerSwapStorage}; +use crate::lp_swap::taker_swap_v2::{TakerSwapStateMachine, TakerSwapStorage}; use bitcrypto::{dhash160, sha256}; use coins::{lp_coinfind, lp_coinfind_or_err, CoinFindError, DexFee, MmCoin, MmCoinEnum, TradeFee, TransactionEnum}; use common::log::{debug, warn}; @@ -94,30 +94,27 @@ use uuid::Uuid; #[cfg(feature = "custom-swap-locktime")] use std::sync::atomic::{AtomicU64, Ordering}; -#[path = "lp_swap/check_balance.rs"] mod check_balance; -#[path = "lp_swap/maker_swap.rs"] mod maker_swap; -#[path = "lp_swap/maker_swap_v2.rs"] pub mod maker_swap_v2; -#[path = "lp_swap/max_maker_vol_rpc.rs"] mod max_maker_vol_rpc; -#[path = "lp_swap/my_swaps_storage.rs"] mod my_swaps_storage; -#[path = "lp_swap/pubkey_banning.rs"] mod pubkey_banning; -#[path = "lp_swap/recreate_swap_data.rs"] mod recreate_swap_data; -#[path = "lp_swap/saved_swap.rs"] mod saved_swap; -#[path = "lp_swap/swap_lock.rs"] mod swap_lock; +mod check_balance; +mod maker_swap; +pub mod maker_swap_v2; +mod max_maker_vol_rpc; +mod my_swaps_storage; +mod pubkey_banning; +mod recreate_swap_data; +mod saved_swap; +mod swap_lock; #[path = "lp_swap/komodefi.swap_v2.pb.rs"] #[rustfmt::skip] mod swap_v2_pb; -#[path = "lp_swap/swap_v2_common.rs"] mod swap_v2_common; -#[path = "lp_swap/swap_v2_rpcs.rs"] pub(crate) mod swap_v2_rpcs; -#[path = "lp_swap/swap_watcher.rs"] pub(crate) mod swap_watcher; -#[path = "lp_swap/taker_restart.rs"] +mod swap_v2_common; +pub(crate) mod swap_v2_rpcs; +pub(crate) mod swap_watcher; pub(crate) mod taker_restart; -#[path = "lp_swap/taker_swap.rs"] pub(crate) mod taker_swap; -#[path = "lp_swap/taker_swap_v2.rs"] pub mod taker_swap_v2; -#[path = "lp_swap/trade_preimage.rs"] mod trade_preimage; +pub(crate) mod taker_swap; +pub mod taker_swap_v2; +mod trade_preimage; -#[cfg(target_arch = "wasm32")] -#[path = "lp_swap/swap_wasm_db.rs"] -mod swap_wasm_db; +#[cfg(target_arch = "wasm32")] mod swap_wasm_db; pub use check_balance::{check_other_coin_balance_for_swap, CheckBalanceError, CheckBalanceResult}; use coins::utxo::utxo_standard::UtxoStandardCoin; @@ -154,8 +151,11 @@ pub const TX_HELPER_PREFIX: TopicPrefix = "txhlp"; pub(crate) const LEGACY_SWAP_TYPE: u8 = 0; pub(crate) const MAKER_SWAP_V2_TYPE: u8 = 1; pub(crate) const TAKER_SWAP_V2_TYPE: u8 = 2; -const MAX_STARTED_AT_DIFF: u64 = 60; +pub(crate) const TAKER_FEE_VALIDATION_ATTEMPTS: usize = 6; +pub(crate) const TAKER_FEE_VALIDATION_RETRY_DELAY_SECS: f64 = 10.; + +const MAX_STARTED_AT_DIFF: u64 = 60; const NEGOTIATE_SEND_INTERVAL: f64 = 30.; /// If a certain P2P message is not received, swap will be aborted after this time expires. @@ -1019,7 +1019,7 @@ pub async fn insert_new_swap_to_db( #[cfg(not(target_arch = "wasm32"))] fn add_swap_to_db_index(ctx: &MmArc, swap: &SavedSwap) { if let Some(conn) = ctx.sqlite_conn_opt() { - crate::mm2::database::stats_swaps::add_swap_to_index(&conn, swap) + crate::database::stats_swaps::add_swap_to_index(&conn, swap) } } @@ -1668,12 +1668,8 @@ pub fn detect_secret_hash_algo(maker_coin: &MmCoinEnum, taker_coin: &MmCoinEnum) (MmCoinEnum::Tendermint(_) | MmCoinEnum::TendermintToken(_) | MmCoinEnum::LightningCoin(_), _) => { SecretHashAlgo::SHA256 }, - #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] - (MmCoinEnum::SolanaCoin(_), _) => SecretHashAlgo::SHA256, // If taker is lightning coin the SHA256 of the secret will be sent as part of the maker signed invoice (_, MmCoinEnum::Tendermint(_) | MmCoinEnum::TendermintToken(_)) => SecretHashAlgo::SHA256, - #[cfg(all(feature = "enable-solana", not(target_arch = "wasm32")))] - (_, MmCoinEnum::SolanaCoin(_)) => SecretHashAlgo::SHA256, (_, _) => SecretHashAlgo::DHASH160, } } @@ -1840,9 +1836,9 @@ pub(crate) const NO_REFUND_FEE: bool = false; #[cfg(all(test, not(target_arch = "wasm32")))] mod lp_swap_tests { use super::*; - use crate::mm2::lp_native_dex::{fix_directories, init_p2p}; + use crate::lp_native_dex::{fix_directories, init_p2p}; use coins::hd_wallet::HDPathAccountToAddressId; - use coins::utxo::rpc_clients::ElectrumRpcRequest; + use coins::utxo::rpc_clients::ElectrumConnectionSettings; use coins::utxo::utxo_standard::utxo_standard_coin_with_priv_key; use coins::utxo::{UtxoActivationParams, UtxoRpcMode}; use coins::MarketCoinOps; @@ -2224,12 +2220,15 @@ mod lp_swap_tests { mode: UtxoRpcMode::Electrum { servers: electrums .iter() - .map(|url| ElectrumRpcRequest { + .map(|url| ElectrumConnectionSettings { url: url.to_string(), protocol: Default::default(), disable_cert_verification: false, + timeout_sec: None, }) .collect(), + min_connected: None, + max_connected: None, }, utxo_merge_params: None, tx_history: false, diff --git a/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs b/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs index 130fb2e3e1..38c6e07106 100644 --- a/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs +++ b/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs @@ -1,3 +1,4 @@ +// This file is @generated by prost-build. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct SignedMessage { diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 8d00318485..0eb72b8a71 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -9,12 +9,13 @@ use super::{broadcast_my_swap_status, broadcast_p2p_tx_msg, broadcast_swap_msg_e wait_for_maker_payment_conf_until, AtomicSwap, LockedAmount, MySwapInfo, NegotiationDataMsg, NegotiationDataV2, NegotiationDataV3, RecoveredSwap, RecoveredSwapAction, SavedSwap, SavedSwapIo, SavedTradeFee, SecretHashAlgo, SwapConfirmationsSettings, SwapError, SwapMsg, SwapPubkeys, SwapTxDataMsg, - SwapsContext, TransactionIdentifier, INCLUDE_REFUND_FEE, NO_REFUND_FEE, WAIT_CONFIRM_INTERVAL_SEC}; -use crate::mm2::lp_dispatcher::{DispatcherContext, LpEvents}; -use crate::mm2::lp_network::subscribe_to_topic; -use crate::mm2::lp_ordermatch::MakerOrderBuilder; -use crate::mm2::lp_swap::swap_v2_common::mark_swap_as_finished; -use crate::mm2::lp_swap::{broadcast_swap_message, taker_payment_spend_duration, MAX_STARTED_AT_DIFF}; + SwapsContext, TransactionIdentifier, INCLUDE_REFUND_FEE, NO_REFUND_FEE, TAKER_FEE_VALIDATION_ATTEMPTS, + TAKER_FEE_VALIDATION_RETRY_DELAY_SECS, WAIT_CONFIRM_INTERVAL_SEC}; +use crate::lp_dispatcher::{DispatcherContext, LpEvents}; +use crate::lp_network::subscribe_to_topic; +use crate::lp_ordermatch::MakerOrderBuilder; +use crate::lp_swap::swap_v2_common::mark_swap_as_finished; +use crate::lp_swap::{broadcast_swap_message, taker_payment_spend_duration, MAX_STARTED_AT_DIFF}; use coins::lp_price::fetch_swap_coins_price; use coins::{CanRefundHtlc, CheckIfMyPaymentSentArgs, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, MmCoin, MmCoinEnum, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, @@ -767,18 +768,17 @@ impl MakerSwap { min_block_number: taker_coin_start_block, uuid: self.uuid.as_bytes(), }) - .compat() .await { Ok(_) => break, Err(err) => { - if attempts >= 6 { + if attempts >= TAKER_FEE_VALIDATION_ATTEMPTS { return Ok((Some(MakerSwapCommand::Finish), vec![ MakerSwapEvent::TakerFeeValidateFailed(ERRL!("{}", err).into()), ])); } else { attempts += 1; - Timer::sleep(10.).await; + Timer::sleep(TAKER_FEE_VALIDATION_RETRY_DELAY_SECS).await; } }, }; @@ -794,7 +794,8 @@ impl MakerSwap { } async fn maker_payment(&self) -> Result<(Option, Vec), String> { - let timeout = self.r().data.started_at + self.r().data.lock_duration / 3; + let lock_duration = self.r().data.lock_duration; + let timeout = self.r().data.started_at + lock_duration / 3; let now = now_sec(); if now > timeout { return Ok((Some(MakerSwapCommand::Finish), vec![ @@ -802,24 +803,24 @@ impl MakerSwap { ])); } + let maker_payment_lock = self.r().data.maker_payment_lock; + let other_maker_coin_htlc_pub = self.r().other_maker_coin_htlc_pub; let secret_hash = self.secret_hash(); + let maker_coin_swap_contract_address = self.r().data.maker_coin_swap_contract_address.clone(); let unique_data = self.unique_swap_data(); - let transaction_f = self - .maker_coin - .check_if_my_payment_sent(CheckIfMyPaymentSentArgs { - time_lock: self.r().data.maker_payment_lock, - other_pub: &*self.r().other_maker_coin_htlc_pub, - secret_hash: secret_hash.as_slice(), - search_from_block: self.r().data.maker_coin_start_block, - swap_contract_address: &self.r().data.maker_coin_swap_contract_address, - swap_unique_data: &unique_data, - amount: &self.maker_amount, - payment_instructions: &self.r().payment_instructions, - }) - .compat(); + let payment_instructions = self.r().payment_instructions.clone(); + let transaction_f = self.maker_coin.check_if_my_payment_sent(CheckIfMyPaymentSentArgs { + time_lock: maker_payment_lock, + other_pub: &*other_maker_coin_htlc_pub, + secret_hash: secret_hash.as_slice(), + search_from_block: self.r().data.maker_coin_start_block, + swap_contract_address: &maker_coin_swap_contract_address, + swap_unique_data: &unique_data, + amount: &self.maker_amount, + payment_instructions: &payment_instructions, + }); - let wait_maker_payment_until = - wait_for_maker_payment_conf_until(self.r().data.started_at, self.r().data.lock_duration); + let wait_maker_payment_until = wait_for_maker_payment_conf_until(self.r().data.started_at, lock_duration); let watcher_reward = if self.r().watcher_reward { match self .maker_coin @@ -841,20 +842,23 @@ impl MakerSwap { Ok(res) => match res { Some(tx) => tx, None => { - let payment_fut = self.maker_coin.send_maker_payment(SendPaymentArgs { - time_lock_duration: self.r().data.lock_duration, - time_lock: self.r().data.maker_payment_lock, - other_pubkey: &*self.r().other_maker_coin_htlc_pub, - secret_hash: secret_hash.as_slice(), - amount: self.maker_amount.clone(), - swap_contract_address: &self.r().data.maker_coin_swap_contract_address, - swap_unique_data: &unique_data, - payment_instructions: &self.r().payment_instructions, - watcher_reward, - wait_for_confirmation_until: wait_maker_payment_until, - }); - - match payment_fut.compat().await { + let payment = self + .maker_coin + .send_maker_payment(SendPaymentArgs { + time_lock_duration: lock_duration, + time_lock: maker_payment_lock, + other_pubkey: &*other_maker_coin_htlc_pub, + secret_hash: secret_hash.as_slice(), + amount: self.maker_amount.clone(), + swap_contract_address: &maker_coin_swap_contract_address, + swap_unique_data: &unique_data, + payment_instructions: &payment_instructions, + watcher_reward, + wait_for_confirmation_until: wait_maker_payment_until, + }) + .await; + + match payment { Ok(t) => t, Err(err) => { return Ok((Some(MakerSwapCommand::Finish), vec![ @@ -1141,12 +1145,10 @@ impl MakerSwap { } async fn confirm_taker_payment_spend(&self) -> Result<(Option, Vec), String> { - // we should wait for only one confirmation to make sure our spend transaction is not failed - let confirmations = std::cmp::min(1, self.r().data.taker_payment_confirmations); let requires_nota = false; let confirm_taker_payment_spend_input = ConfirmPaymentInput { payment_tx: self.r().taker_payment_spend.clone().unwrap().tx_hex.0, - confirmations, + confirmations: self.r().data.taker_payment_confirmations, requires_nota, wait_until: self.wait_refund_until(), check_every: WAIT_CONFIRM_INTERVAL_SEC, @@ -1210,7 +1212,7 @@ impl MakerSwap { } loop { - match self.maker_coin.can_refund_htlc(locktime).compat().await { + match self.maker_coin.can_refund_htlc(locktime).await { Ok(CanRefundHtlc::CanRefundNow) => break, Ok(CanRefundHtlc::HaveToWait(to_sleep)) => Timer::sleep(to_sleep as f64).await, Err(e) => { @@ -1480,7 +1482,6 @@ impl MakerSwap { amount: &self.maker_amount, payment_instructions: &payment_instructions, }) - .compat() .await ); match maybe_maker_payment { @@ -1523,7 +1524,7 @@ impl MakerSwap { return ERR!("Maker payment will be refunded automatically!"); } - let can_refund_htlc = try_s!(self.maker_coin.can_refund_htlc(maker_payment_lock).compat().await); + let can_refund_htlc = try_s!(self.maker_coin.can_refund_htlc(maker_payment_lock).await); if let CanRefundHtlc::HaveToWait(seconds_to_wait) = can_refund_htlc { return ERR!("Too early to refund, wait until {}", wait_until_sec(seconds_to_wait)); } @@ -1679,7 +1680,7 @@ impl MakerSwapEvent { }, MakerSwapEvent::TakerPaymentSpent(_) => "Taker payment spent...".to_owned(), MakerSwapEvent::TakerPaymentSpendFailed(_) => "Taker payment spend failed...".to_owned(), - MakerSwapEvent::TakerPaymentSpendConfirmStarted => "Taker payment send wait confirm started...".to_owned(), + MakerSwapEvent::TakerPaymentSpendConfirmStarted => "Taker payment spend confirm started...".to_owned(), MakerSwapEvent::TakerPaymentSpendConfirmed => "Taker payment spend confirmed...".to_owned(), MakerSwapEvent::TakerPaymentSpendConfirmFailed(_) => "Taker payment spend confirm failed...".to_owned(), MakerSwapEvent::MakerPaymentWaitRefundStarted { wait_until } => { @@ -2394,12 +2395,12 @@ mod maker_swap_tests { TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); TestCoin::can_refund_htlc - .mock_safe(|_, _| MockResult::Return(Box::new(futures01::future::ok(CanRefundHtlc::CanRefundNow)))); + .mock_safe(|_, _| MockResult::Return(Box::pin(futures::future::ok(CanRefundHtlc::CanRefundNow)))); static mut MY_PAYMENT_SENT_CALLED: bool = false; TestCoin::check_if_my_payment_sent.mock_safe(|_, _| { unsafe { MY_PAYMENT_SENT_CALLED = true }; - MockResult::Return(Box::new(futures01::future::ok(Some(eth_tx_for_test().into())))) + MockResult::Return(Box::pin(futures::future::ok(Some(eth_tx_for_test().into())))) }); static mut MAKER_REFUND_CALLED: bool = false; @@ -2434,7 +2435,7 @@ mod maker_swap_tests { TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); TestCoin::can_refund_htlc - .mock_safe(|_, _| MockResult::Return(Box::new(futures01::future::ok(CanRefundHtlc::CanRefundNow)))); + .mock_safe(|_, _| MockResult::Return(Box::pin(futures::future::ok(CanRefundHtlc::CanRefundNow)))); static mut MAKER_REFUND_CALLED: bool = false; TestCoin::send_maker_refunds_payment.mock_safe(|_, _| { @@ -2529,10 +2530,10 @@ mod maker_swap_tests { static mut MY_PAYMENT_SENT_CALLED: bool = false; TestCoin::check_if_my_payment_sent.mock_safe(|_, _| { unsafe { MY_PAYMENT_SENT_CALLED = true }; - MockResult::Return(Box::new(futures01::future::ok(Some(eth_tx_for_test().into())))) + MockResult::Return(Box::pin(futures::future::ok(Some(eth_tx_for_test().into())))) }); TestCoin::can_refund_htlc - .mock_safe(|_, _| MockResult::Return(Box::new(futures01::future::ok(CanRefundHtlc::HaveToWait(1000))))); + .mock_safe(|_, _| MockResult::Return(Box::pin(futures::future::ok(CanRefundHtlc::HaveToWait(1000))))); TestCoin::search_for_swap_tx_spend_my .mock_safe(|_, _| MockResult::Return(Box::pin(futures::future::ready(Ok(None))))); let maker_coin = MmCoinEnum::Test(TestCoin::default()); @@ -2558,7 +2559,7 @@ mod maker_swap_tests { static mut MY_PAYMENT_SENT_CALLED: bool = false; TestCoin::check_if_my_payment_sent.mock_safe(|_, _| { unsafe { MY_PAYMENT_SENT_CALLED = true }; - MockResult::Return(Box::new(futures01::future::ok(None))) + MockResult::Return(Box::pin(futures::future::ok(None))) }); let maker_coin = MmCoinEnum::Test(TestCoin::default()); let taker_coin = MmCoinEnum::Test(TestCoin::default()); @@ -2724,7 +2725,7 @@ mod maker_swap_tests { #[test] fn swap_must_not_lock_funds_by_default() { - use crate::mm2::lp_swap::get_locked_amount; + use crate::lp_swap::get_locked_amount; let ctx = mm_ctx_with_iguana(PASSPHRASE); diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index 6e9555c10f..d0e667a752 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -1,16 +1,16 @@ use super::swap_v2_common::*; use super::{swap_v2_topic, LockedAmount, LockedAmountInfo, SavedTradeFee, SwapsContext, NEGOTIATE_SEND_INTERVAL, NEGOTIATION_TIMEOUT_SEC}; -use crate::mm2::lp_swap::maker_swap::MakerSwapPreparedParams; -use crate::mm2::lp_swap::swap_lock::SwapLock; -use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_maker_swap, recv_swap_v2_msg, SecretHashAlgo, - SwapConfirmationsSettings, TransactionIdentifier, MAKER_SWAP_V2_TYPE, MAX_STARTED_AT_DIFF}; -use crate::mm2::lp_swap::{swap_v2_pb::*, NO_REFUND_FEE}; +use crate::lp_swap::maker_swap::MakerSwapPreparedParams; +use crate::lp_swap::swap_lock::SwapLock; +use crate::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_maker_swap, recv_swap_v2_msg, SecretHashAlgo, + SwapConfirmationsSettings, TransactionIdentifier, MAKER_SWAP_V2_TYPE, MAX_STARTED_AT_DIFF}; +use crate::lp_swap::{swap_v2_pb::*, NO_REFUND_FEE}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use coins::{CanRefundHtlc, ConfirmPaymentInput, DexFee, FeeApproxStage, FundingTxSpend, GenTakerFundingSpendArgs, - GenTakerPaymentSpendArgs, MakerCoinSwapOpsV2, MmCoin, ParseCoinAssocTypes, RefundMakerPaymentArgs, - RefundPaymentArgs, SearchForFundingSpendErr, SendMakerPaymentArgs, SwapTxTypeWithSecretHash, + GenTakerPaymentSpendArgs, MakerCoinSwapOpsV2, MmCoin, ParseCoinAssocTypes, RefundMakerPaymentSecretArgs, + RefundMakerPaymentTimelockArgs, SearchForFundingSpendErr, SendMakerPaymentArgs, SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, ToBytes, TradePreimageValue, Transaction, TxPreimageWithSig, ValidateTakerFundingArgs}; use common::executor::abortable_queue::AbortableQueue; use common::executor::{AbortableSystem, Timer}; @@ -32,14 +32,14 @@ use std::marker::PhantomData; use uuid::Uuid; cfg_native!( - use crate::mm2::database::my_swaps::{insert_new_swap_v2, SELECT_MY_SWAP_V2_BY_UUID}; + use crate::database::my_swaps::{insert_new_swap_v2, SELECT_MY_SWAP_V2_BY_UUID}; use common::async_blocking; use db_common::sqlite::rusqlite::{named_params, Error as SqlError, Result as SqlResult, Row}; use db_common::sqlite::rusqlite::types::Type as SqlType; ); cfg_wasm32!( - use crate::mm2::lp_swap::swap_wasm_db::{MySwapsFiltersTable, SavedSwapTable}; + use crate::lp_swap::swap_wasm_db::{MySwapsFiltersTable, SavedSwapTable}; ); // This is needed to have Debug on messages @@ -353,7 +353,8 @@ pub struct MakerSwapStateMachine { + | e @ SearchForFundingSpendErr::FromBlockConversionErr(_) + | e @ SearchForFundingSpendErr::Internal(_) => { let next_state = MakerPaymentRefundRequired { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, @@ -1446,7 +1451,7 @@ impl break, @@ -1490,21 +1495,21 @@ impl for MySwapsError { @@ -111,9 +111,9 @@ mod native_impl { #[cfg(target_arch = "wasm32")] mod wasm_impl { use super::*; - use crate::mm2::lp_swap::swap_wasm_db::cursor_prelude::*; - use crate::mm2::lp_swap::swap_wasm_db::{DbTransactionError, InitDbError, MySwapsFiltersTable}; - use crate::mm2::lp_swap::{SwapsContext, LEGACY_SWAP_TYPE}; + use crate::lp_swap::swap_wasm_db::cursor_prelude::*; + use crate::lp_swap::swap_wasm_db::{DbTransactionError, InitDbError, MySwapsFiltersTable}; + use crate::lp_swap::{SwapsContext, LEGACY_SWAP_TYPE}; use std::collections::BTreeSet; use uuid::Uuid; @@ -328,7 +328,7 @@ mod wasm_impl { mod wasm_tests { use super::wasm_impl::*; use super::*; - use crate::mm2::lp_swap::{LEGACY_SWAP_TYPE, MAKER_SWAP_V2_TYPE, TAKER_SWAP_V2_TYPE}; + use crate::lp_swap::{LEGACY_SWAP_TYPE, MAKER_SWAP_V2_TYPE, TAKER_SWAP_V2_TYPE}; use common::log::wasm_log::register_wasm_log; use common::new_uuid; use mm2_core::mm_ctx::MmCtxBuilder; diff --git a/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs b/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs index 56e7877b70..e4e430c71c 100644 --- a/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs +++ b/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs @@ -1,9 +1,9 @@ -use crate::mm2::lp_swap::maker_swap::{MakerSwapData, MakerSwapEvent, TakerNegotiationData, MAKER_ERROR_EVENTS, - MAKER_SUCCESS_EVENTS}; -use crate::mm2::lp_swap::taker_swap::{MakerNegotiationData, TakerPaymentSpentData, TakerSavedEvent, TakerSwapData, - TakerSwapEvent, TAKER_ERROR_EVENTS, TAKER_SUCCESS_EVENTS}; -use crate::mm2::lp_swap::{wait_for_maker_payment_conf_until, MakerSavedEvent, MakerSavedSwap, SavedSwap, SwapError, - TakerSavedSwap}; +use crate::lp_swap::maker_swap::{MakerSwapData, MakerSwapEvent, TakerNegotiationData, MAKER_ERROR_EVENTS, + MAKER_SUCCESS_EVENTS}; +use crate::lp_swap::taker_swap::{MakerNegotiationData, TakerPaymentSpentData, TakerSavedEvent, TakerSwapData, + TakerSwapEvent, TAKER_ERROR_EVENTS, TAKER_SUCCESS_EVENTS}; +use crate::lp_swap::{wait_for_maker_payment_conf_until, MakerSavedEvent, MakerSavedSwap, SavedSwap, SwapError, + TakerSavedSwap}; use coins::{lp_coinfind, MmCoinEnum}; use common::{HttpStatusCode, StatusCode}; use derive_more::Display; @@ -264,6 +264,8 @@ fn convert_taker_to_maker_events( | TakerSwapEvent::MakerPaymentWaitConfirmStarted | TakerSwapEvent::MakerPaymentValidatedAndConfirmed | TakerSwapEvent::MakerPaymentSpent(_) + | TakerSwapEvent::MakerPaymentSpendConfirmed + | TakerSwapEvent::MakerPaymentSpendConfirmFailed(_) | TakerSwapEvent::MakerPaymentSpentByWatcher(_) | TakerSwapEvent::MakerPaymentSpendFailed(_) // We don't know the reason at the moment, so we rely on the errors handling above. diff --git a/mm2src/mm2_main/src/lp_swap/saved_swap.rs b/mm2src/mm2_main/src/lp_swap/saved_swap.rs index 1531ceb28b..c471f7eacb 100644 --- a/mm2src/mm2_main/src/lp_swap/saved_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/saved_swap.rs @@ -1,6 +1,6 @@ -use crate::mm2::lp_swap::maker_swap::{MakerSavedSwap, MakerSwap, MakerSwapEvent}; -use crate::mm2::lp_swap::taker_swap::{TakerSavedSwap, TakerSwap, TakerSwapEvent}; -use crate::mm2::lp_swap::{MySwapInfo, RecoveredSwap}; +use crate::lp_swap::maker_swap::{MakerSavedSwap, MakerSwap, MakerSwapEvent}; +use crate::lp_swap::taker_swap::{TakerSavedSwap, TakerSwap, TakerSwapEvent}; +use crate::lp_swap::{MySwapInfo, RecoveredSwap}; use async_trait::async_trait; use coins::lp_coinfind; use derive_more::Display; @@ -184,9 +184,9 @@ pub trait SavedSwapIo { #[cfg(not(target_arch = "wasm32"))] mod native_impl { use super::*; - use crate::mm2::lp_swap::maker_swap::{stats_maker_swap_dir, stats_maker_swap_file_path}; - use crate::mm2::lp_swap::taker_swap::{stats_taker_swap_dir, stats_taker_swap_file_path}; - use crate::mm2::lp_swap::{my_swap_file_path, my_swaps_dir}; + use crate::lp_swap::maker_swap::{stats_maker_swap_dir, stats_maker_swap_file_path}; + use crate::lp_swap::taker_swap::{stats_taker_swap_dir, stats_taker_swap_file_path}; + use crate::lp_swap::{my_swap_file_path, my_swaps_dir}; use mm2_io::fs::{read_dir_json, read_json, write_json, FsJsonError}; const USE_TMP_FILE: bool = true; @@ -262,9 +262,9 @@ mod native_impl { #[cfg(target_arch = "wasm32")] mod wasm_impl { use super::*; - use crate::mm2::lp_swap::swap_wasm_db::{DbTransactionError, InitDbError, MySwapsFiltersTable, SavedSwapTable, - SwapsMigrationTable}; - use crate::mm2::lp_swap::{SwapsContext, LEGACY_SWAP_TYPE}; + use crate::lp_swap::swap_wasm_db::{DbTransactionError, InitDbError, MySwapsFiltersTable, SavedSwapTable, + SwapsMigrationTable}; + use crate::lp_swap::{SwapsContext, LEGACY_SWAP_TYPE}; use bytes::Buf; use common::log::{info, warn}; use mm2_db::indexed_db::cursor_prelude::CursorError; @@ -278,13 +278,13 @@ mod wasm_impl { .cursor_builder() .bound("migration", 0, u32::MAX) .reverse() + .where_first() .open_cursor("migration") .await? - // TODO refactor when "closure invoked recursively or after being dropped" is fixed - .collect() + .next() .await?; - Ok(migrations.first().map(|(_, m)| m.migration).unwrap_or_default()) + Ok(migrations.map(|(_, m)| m.migration).unwrap_or_default()) } pub async fn migrate_swaps_data(ctx: &MmArc) -> MmResult<(), SavedSwapError> { @@ -426,8 +426,8 @@ mod wasm_impl { #[cfg(target_arch = "wasm32")] mod tests { use super::*; - use crate::mm2::lp_swap::swap_wasm_db::{ItemId, MySwapsFiltersTable, SavedSwapTable, SwapsMigrationTable}; - use crate::mm2::lp_swap::{SwapsContext, LEGACY_SWAP_TYPE}; + use crate::lp_swap::swap_wasm_db::{ItemId, MySwapsFiltersTable, SavedSwapTable, SwapsMigrationTable}; + use crate::lp_swap::{SwapsContext, LEGACY_SWAP_TYPE}; use mm2_core::mm_ctx::MmCtxBuilder; use serde_json as json; use wasm_bindgen_test::*; diff --git a/mm2src/mm2_main/src/lp_swap/swap_lock.rs b/mm2src/mm2_main/src/lp_swap/swap_lock.rs index 25e752d14b..f4347dde3b 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_lock.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_lock.rs @@ -32,7 +32,7 @@ pub trait SwapLockOps: Sized { #[cfg(not(target_arch = "wasm32"))] mod native_lock { use super::*; - use crate::mm2::lp_swap::my_swaps_dir; + use crate::lp_swap::my_swaps_dir; use mm2_io::file_lock::{FileLock, FileLockError}; use std::path::PathBuf; @@ -70,8 +70,8 @@ mod native_lock { #[cfg(target_arch = "wasm32")] mod wasm_lock { use super::*; - use crate::mm2::lp_swap::swap_wasm_db::{DbTransactionError, InitDbError, ItemId, SwapLockTable}; - use crate::mm2::lp_swap::SwapsContext; + use crate::lp_swap::swap_wasm_db::{DbTransactionError, InitDbError, ItemId, SwapLockTable}; + use crate::lp_swap::SwapsContext; use common::executor::SpawnFuture; use common::log::{debug, error}; use common::{now_float, now_ms}; @@ -194,8 +194,8 @@ mod wasm_lock { mod tests { use super::wasm_lock::*; use super::*; - use crate::mm2::lp_swap::swap_wasm_db::SwapLockTable; - use crate::mm2::lp_swap::SwapsContext; + use crate::lp_swap::swap_wasm_db::SwapLockTable; + use crate::lp_swap::SwapsContext; use common::executor::Timer; use common::new_uuid; use common::now_ms; diff --git a/mm2src/mm2_main/src/lp_swap/swap_v2_common.rs b/mm2src/mm2_main/src/lp_swap/swap_v2_common.rs index ec87e9b79b..686d263d5a 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_v2_common.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_v2_common.rs @@ -1,6 +1,6 @@ -use crate::mm2::lp_network::{subscribe_to_topic, unsubscribe_from_topic}; -use crate::mm2::lp_swap::swap_lock::{SwapLock, SwapLockError, SwapLockOps}; -use crate::mm2::lp_swap::{swap_v2_topic, SwapsContext}; +use crate::lp_network::{subscribe_to_topic, unsubscribe_from_topic}; +use crate::lp_swap::swap_lock::{SwapLock, SwapLockError, SwapLockOps}; +use crate::lp_swap::{swap_v2_topic, SwapsContext}; use coins::utxo::utxo_standard::UtxoStandardCoin; use coins::{lp_coinfind, MmCoinEnum}; use common::executor::abortable_queue::AbortableQueue; @@ -18,13 +18,13 @@ use uuid::Uuid; cfg_native!( use common::async_blocking; - use crate::mm2::database::my_swaps::{does_swap_exist, get_swap_events, update_swap_events, - select_unfinished_swaps_uuids, set_swap_is_finished}; + use crate::database::my_swaps::{does_swap_exist, get_swap_events, update_swap_events, select_unfinished_swaps_uuids, + set_swap_is_finished}; ); cfg_wasm32!( use common::bool_as_int::BoolAsInt; - use crate::mm2::lp_swap::swap_wasm_db::{IS_FINISHED_SWAP_TYPE_INDEX, MySwapsFiltersTable, SavedSwapTable}; + use crate::lp_swap::swap_wasm_db::{IS_FINISHED_SWAP_TYPE_INDEX, MySwapsFiltersTable, SavedSwapTable}; use mm2_db::indexed_db::{DbTransactionError, InitDbError, MultiIndex}; ); diff --git a/mm2src/mm2_main/src/lp_swap/swap_v2_rpcs.rs b/mm2src/mm2_main/src/lp_swap/swap_v2_rpcs.rs index d5c50c6534..e3f57e8400 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_v2_rpcs.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_v2_rpcs.rs @@ -18,7 +18,7 @@ use std::num::NonZeroUsize; use uuid::Uuid; cfg_native!( - use crate::mm2::database::my_swaps::SELECT_MY_SWAP_V2_FOR_RPC_BY_UUID; + use crate::database::my_swaps::SELECT_MY_SWAP_V2_FOR_RPC_BY_UUID; use common::async_blocking; use db_common::sqlite::query_single_row; use db_common::sqlite::rusqlite::{Result as SqlResult, Row, Error as SqlError}; @@ -29,7 +29,7 @@ cfg_wasm32!( use super::SwapsContext; use super::maker_swap_v2::MakerSwapDbRepr; use super::taker_swap_v2::TakerSwapDbRepr; - use crate::mm2::lp_swap::swap_wasm_db::{MySwapsFiltersTable, SavedSwapTable}; + use crate::lp_swap::swap_wasm_db::{MySwapsFiltersTable, SavedSwapTable}; use mm2_db::indexed_db::{DbTransactionError, DbTransactionResult, InitDbError}; ); @@ -77,7 +77,7 @@ impl From for SwapV2DbError { #[cfg(target_arch = "wasm32")] pub(super) async fn get_swap_type(ctx: &MmArc, uuid: &Uuid) -> MmResult, SwapV2DbError> { - use crate::mm2::lp_swap::swap_wasm_db::MySwapsFiltersTable; + use crate::lp_swap::swap_wasm_db::MySwapsFiltersTable; let swaps_ctx = SwapsContext::from_ctx(ctx).unwrap(); let db = swaps_ctx.swap_db().await?; diff --git a/mm2src/mm2_main/src/lp_swap/swap_watcher.rs b/mm2src/mm2_main/src/lp_swap/swap_watcher.rs index c88b34f76b..312f62e5c5 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_watcher.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_watcher.rs @@ -1,8 +1,9 @@ use super::{broadcast_p2p_tx_msg, get_payment_locktime, lp_coinfind, taker_payment_spend_deadline, tx_helper_topic, - H256Json, SwapsContext, WAIT_CONFIRM_INTERVAL_SEC}; -use crate::mm2::lp_network::{P2PRequestError, P2PRequestResult}; + H256Json, SwapsContext, TAKER_FEE_VALIDATION_ATTEMPTS, TAKER_FEE_VALIDATION_RETRY_DELAY_SECS, + WAIT_CONFIRM_INTERVAL_SEC}; +use crate::lp_network::{P2PRequestError, P2PRequestResult}; -use crate::mm2::MmError; +use crate::MmError; use async_trait::async_trait; use coins::{CanRefundHtlc, ConfirmPaymentInput, FoundSwapTxSpend, MmCoinEnum, RefundPaymentArgs, SendMakerPaymentSpendPreimageInput, SwapTxTypeWithSecretHash, WaitForHTLCTxSpendArgs, @@ -181,24 +182,31 @@ impl State for ValidateTakerFee { async fn on_changed(self: Box, watcher_ctx: &mut WatcherStateMachine) -> StateResult { debug!("Watcher validate taker fee"); - let validated_f = watcher_ctx - .taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: watcher_ctx.data.taker_fee_hash.clone(), - sender_pubkey: watcher_ctx.verified_pub.clone(), - min_block_number: watcher_ctx.data.taker_coin_start_block, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.clone(), - lock_duration: watcher_ctx.data.lock_duration, - }) - .compat(); - - if let Err(err) = validated_f.await { - return Self::change_state(Stopped::from_reason(StopReason::Error( - WatcherError::InvalidTakerFee(format!("{:?}", err)).into(), - ))); - }; - Self::change_state(ValidateTakerPayment {}) + let validation_result = retry_on_err!(async { + watcher_ctx + .taker_coin + .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: watcher_ctx.data.taker_fee_hash.clone(), + sender_pubkey: watcher_ctx.verified_pub.clone(), + min_block_number: watcher_ctx.data.taker_coin_start_block, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.clone(), + lock_duration: watcher_ctx.data.lock_duration, + }) + .compat() + .await + }) + .repeat_every_secs(TAKER_FEE_VALIDATION_RETRY_DELAY_SECS) + .attempts(TAKER_FEE_VALIDATION_ATTEMPTS) + .inspect_err(|e| error!("Error validating taker fee: {}", e)) + .await; + + match validation_result { + Ok(_) => Self::change_state(ValidateTakerPayment {}), + Err(repeat_err) => Self::change_state(Stopped::from_reason(StopReason::Error( + WatcherError::InvalidTakerFee(repeat_err.to_string()).into(), + ))), + } } } @@ -346,17 +354,20 @@ impl State for WaitForTakerPaymentSpend { }, }; - let f = watcher_ctx.maker_coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { - tx_bytes: &maker_payment_hex, - secret_hash: &watcher_ctx.data.secret_hash, - wait_until, - from_block: watcher_ctx.data.maker_coin_start_block, - swap_contract_address: &None, - check_every: payment_search_interval, - watcher_reward: watcher_ctx.watcher_reward, - }); - - if f.compat().await.is_ok() { + if watcher_ctx + .maker_coin + .wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { + tx_bytes: &maker_payment_hex, + secret_hash: &watcher_ctx.data.secret_hash, + wait_until, + from_block: watcher_ctx.data.maker_coin_start_block, + swap_contract_address: &None, + check_every: payment_search_interval, + watcher_reward: watcher_ctx.watcher_reward, + }) + .await + .is_ok() + { info!("{}", MAKER_PAYMENT_SPEND_FOUND_LOG); return Self::change_state(Stopped::from_reason(StopReason::Finished( WatcherSuccess::MakerPaymentSpentByTaker, @@ -445,7 +456,6 @@ impl State for RefundTakerPayment { match watcher_ctx .taker_coin .can_refund_htlc(watcher_ctx.taker_locktime()) - .compat() .await { Ok(CanRefundHtlc::CanRefundNow) => break, diff --git a/mm2src/mm2_main/src/lp_swap/taker_restart.rs b/mm2src/mm2_main/src/lp_swap/taker_restart.rs index 8d4d5a2bc5..d934b6b11e 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_restart.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_restart.rs @@ -1,7 +1,7 @@ use super::taker_swap::TakerSwapCommand; use super::{AtomicSwap, TakerSavedSwap, TakerSwap}; -use crate::mm2::lp_swap::taker_swap::{TakerPaymentSpentData, TakerSavedEvent, TakerSwapEvent}; -use crate::mm2::lp_swap::{SavedSwap, SavedSwapIo, TransactionIdentifier, MAKER_PAYMENT_SPENT_BY_WATCHER_LOG}; +use crate::lp_swap::taker_swap::{TakerPaymentSpentData, TakerSavedEvent, TakerSwapEvent}; +use crate::lp_swap::{SavedSwap, SavedSwapIo, TransactionIdentifier, MAKER_PAYMENT_SPENT_BY_WATCHER_LOG}; use coins::{FoundSwapTxSpend, SearchForSwapTxSpendInput, TransactionEnum, ValidateWatcherSpendInput, WatcherSpendType}; use common::log::info; use common::{now_ms, Future01CompatExt}; @@ -49,6 +49,7 @@ pub async fn get_command_based_on_maker_or_watcher_activity( Err(e) => ERR!("Error {} when trying to find taker payment spend", e), }, TakerSwapCommand::SpendMakerPayment => check_maker_payment_spend_and_add_event(ctx, swap, saved).await, + TakerSwapCommand::ConfirmMakerPaymentSpend => Ok(command), TakerSwapCommand::PrepareForTakerPaymentRefund | TakerSwapCommand::RefundTakerPayment => { #[cfg(not(any(test, feature = "run-docker-tests")))] { @@ -141,7 +142,7 @@ pub async fn check_maker_payment_spend_and_add_event( let new_swap = SavedSwap::Taker(saved); try_s!(new_swap.save_to_db(ctx).await); info!("{}", MAKER_PAYMENT_SPENT_BY_WATCHER_LOG); - Ok(TakerSwapCommand::Finish) + Ok(TakerSwapCommand::ConfirmMakerPaymentSpend) } pub async fn check_taker_payment_spend(swap: &TakerSwap) -> Result, String> { diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index 4e087ad527..c7b1cf59a9 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -10,12 +10,12 @@ use super::{broadcast_my_swap_status, broadcast_swap_message, broadcast_swap_msg NegotiationDataMsg, NegotiationDataV2, NegotiationDataV3, RecoveredSwap, RecoveredSwapAction, SavedSwap, SavedSwapIo, SavedTradeFee, SwapConfirmationsSettings, SwapError, SwapMsg, SwapPubkeys, SwapTxDataMsg, SwapsContext, TransactionIdentifier, INCLUDE_REFUND_FEE, NO_REFUND_FEE, WAIT_CONFIRM_INTERVAL_SEC}; -use crate::mm2::lp_network::subscribe_to_topic; -use crate::mm2::lp_ordermatch::TakerOrderBuilder; -use crate::mm2::lp_swap::swap_v2_common::mark_swap_as_finished; -use crate::mm2::lp_swap::taker_restart::get_command_based_on_maker_or_watcher_activity; -use crate::mm2::lp_swap::{broadcast_p2p_tx_msg, broadcast_swap_msg_every_delayed, tx_helper_topic, - wait_for_maker_payment_conf_duration, TakerSwapWatcherData, MAX_STARTED_AT_DIFF}; +use crate::lp_network::subscribe_to_topic; +use crate::lp_ordermatch::TakerOrderBuilder; +use crate::lp_swap::swap_v2_common::mark_swap_as_finished; +use crate::lp_swap::taker_restart::get_command_based_on_maker_or_watcher_activity; +use crate::lp_swap::{broadcast_p2p_tx_msg, broadcast_swap_msg_every_delayed, tx_helper_topic, + wait_for_maker_payment_conf_duration, TakerSwapWatcherData, MAX_STARTED_AT_DIFF}; use coins::lp_price::fetch_swap_coins_price; use coins::{lp_coinfind, CanRefundHtlc, CheckIfMyPaymentSentArgs, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, MmCoin, MmCoinEnum, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, @@ -44,7 +44,7 @@ use uuid::Uuid; const TAKER_PAYMENT_SPEND_SEARCH_INTERVAL: f64 = 10.; -pub const TAKER_SUCCESS_EVENTS: [&str; 11] = [ +pub const TAKER_SUCCESS_EVENTS: [&str; 12] = [ "Started", "Negotiated", "TakerFeeSent", @@ -55,10 +55,11 @@ pub const TAKER_SUCCESS_EVENTS: [&str; 11] = [ "TakerPaymentSent", "TakerPaymentSpent", "MakerPaymentSpent", + "MakerPaymentSpendConfirmed", "Finished", ]; -pub const TAKER_USING_WATCHERS_SUCCESS_EVENTS: [&str; 13] = [ +pub const TAKER_USING_WATCHERS_SUCCESS_EVENTS: [&str; 14] = [ "Started", "Negotiated", "TakerFeeSent", @@ -71,10 +72,11 @@ pub const TAKER_USING_WATCHERS_SUCCESS_EVENTS: [&str; 13] = [ "TakerPaymentSpent", "MakerPaymentSpent", "MakerPaymentSpentByWatcher", + "MakerPaymentSpendConfirmed", "Finished", ]; -pub const TAKER_ERROR_EVENTS: [&str; 16] = [ +pub const TAKER_ERROR_EVENTS: [&str; 17] = [ "StartFailed", "NegotiateFailed", "TakerFeeSendFailed", @@ -85,6 +87,7 @@ pub const TAKER_ERROR_EVENTS: [&str; 16] = [ "TakerPaymentDataSendFailed", "TakerPaymentWaitForSpendFailed", "MakerPaymentSpendFailed", + "MakerPaymentSpendConfirmFailed", "TakerPaymentWaitRefundStarted", "TakerPaymentRefundStarted", "TakerPaymentRefunded", @@ -178,8 +181,10 @@ impl TakerSavedEvent { TakerSwapEvent::TakerPaymentSpent(_) => Some(TakerSwapCommand::SpendMakerPayment), TakerSwapEvent::TakerPaymentWaitForSpendFailed(_) => Some(TakerSwapCommand::PrepareForTakerPaymentRefund), TakerSwapEvent::TakerPaymentWaitConfirmFailed(_) => Some(TakerSwapCommand::PrepareForTakerPaymentRefund), - TakerSwapEvent::MakerPaymentSpent(_) => Some(TakerSwapCommand::Finish), - TakerSwapEvent::MakerPaymentSpentByWatcher(_) => Some(TakerSwapCommand::Finish), + TakerSwapEvent::MakerPaymentSpent(_) => Some(TakerSwapCommand::ConfirmMakerPaymentSpend), + TakerSwapEvent::MakerPaymentSpendConfirmed => Some(TakerSwapCommand::Finish), + TakerSwapEvent::MakerPaymentSpendConfirmFailed(_) => Some(TakerSwapCommand::PrepareForTakerPaymentRefund), + TakerSwapEvent::MakerPaymentSpentByWatcher(_) => Some(TakerSwapCommand::ConfirmMakerPaymentSpend), TakerSwapEvent::MakerPaymentSpendFailed(_) => Some(TakerSwapCommand::PrepareForTakerPaymentRefund), TakerSwapEvent::TakerPaymentWaitRefundStarted { .. } => { Some(TakerSwapCommand::PrepareForTakerPaymentRefund) @@ -259,6 +264,9 @@ impl TakerSavedSwap { if !self.is_finished() { return false; }; + let mut maker_payment_spent = false; + let mut maker_payment_spent_by_watcher = false; + let mut maker_payment_spend_confirmed_failed = false; for event in self.events.iter() { match event.event { TakerSwapEvent::StartFailed(_) @@ -267,15 +275,26 @@ impl TakerSavedSwap { | TakerSwapEvent::MakerPaymentValidateFailed(_) | TakerSwapEvent::TakerPaymentRefunded(_) | TakerSwapEvent::TakerPaymentRefundedByWatcher(_) - | TakerSwapEvent::MakerPaymentSpent(_) - | TakerSwapEvent::MakerPaymentSpentByWatcher(_) + | TakerSwapEvent::MakerPaymentSpendConfirmed | TakerSwapEvent::MakerPaymentWaitConfirmFailed(_) => { return false; }, + TakerSwapEvent::MakerPaymentSpent(_) => { + maker_payment_spent = true; + }, + TakerSwapEvent::MakerPaymentSpentByWatcher(_) => { + maker_payment_spent_by_watcher = true; + }, + TakerSwapEvent::MakerPaymentSpendConfirmFailed(_) => { + maker_payment_spend_confirmed_failed = true; + }, _ => (), } } - true + // MakerPaymentSpent or MakerPaymentSpentByWatcher were the last success events but a new step `MakerPaymentSpendConfirmed` was added after them. + // For backward compatibility (old saved swaps) we need to check for MakerPaymentSpent or MakerPaymentSpentByWatcher + // and if there is no MakerPaymentSpendConfirmFailed. + maker_payment_spend_confirmed_failed || (!maker_payment_spent && !maker_payment_spent_by_watcher) } pub fn swap_data(&self) -> Result<&TakerSwapData, String> { @@ -554,6 +573,7 @@ pub struct TakerSwapMut { pub maker_payment: Option, pub taker_payment: Option, maker_payment_spend: Option, + maker_payment_spend_confirmed: bool, taker_payment_spend: Option, maker_payment_spend_preimage: Option>, taker_payment_refund_preimage: Option>, @@ -660,6 +680,8 @@ pub enum TakerSwapEvent { TakerPaymentSpent(TakerPaymentSpentData), TakerPaymentWaitForSpendFailed(SwapError), MakerPaymentSpent(TransactionIdentifier), + MakerPaymentSpendConfirmed, + MakerPaymentSpendConfirmFailed(SwapError), MakerPaymentSpentByWatcher(TransactionIdentifier), MakerPaymentSpendFailed(SwapError), TakerPaymentWaitRefundStarted { wait_until: u64 }, @@ -698,6 +720,8 @@ impl TakerSwapEvent { TakerSwapEvent::TakerPaymentSpent(_) => "Taker payment spent...".to_owned(), TakerSwapEvent::TakerPaymentWaitForSpendFailed(_) => "Taker payment wait for spend failed...".to_owned(), TakerSwapEvent::MakerPaymentSpent(_) => "Maker payment spent...".to_owned(), + TakerSwapEvent::MakerPaymentSpendConfirmed => "Maker payment spent confirmed...".to_owned(), + TakerSwapEvent::MakerPaymentSpendConfirmFailed(_) => "Maker payment spend confirm failed...".to_owned(), TakerSwapEvent::MakerPaymentSpentByWatcher(_) => "Maker payment spent by watcher...".to_owned(), TakerSwapEvent::MakerPaymentSpendFailed(_) => "Maker payment spend failed...".to_owned(), TakerSwapEvent::TakerPaymentWaitRefundStarted { wait_until } => { @@ -733,6 +757,7 @@ impl TakerSwapEvent { | TakerSwapEvent::TakerPaymentSent(_) | TakerSwapEvent::TakerPaymentSpent(_) | TakerSwapEvent::MakerPaymentSpent(_) + | TakerSwapEvent::MakerPaymentSpendConfirmed | TakerSwapEvent::MakerPaymentSpentByWatcher(_) | TakerSwapEvent::Finished ) @@ -751,6 +776,7 @@ pub enum TakerSwapCommand { SendTakerPayment, WaitForTakerPaymentSpend, SpendMakerPayment, + ConfirmMakerPaymentSpend, PrepareForTakerPaymentRefund, RefundTakerPayment, FinalizeTakerPaymentRefund, @@ -837,6 +863,8 @@ impl TakerSwap { }, TakerSwapEvent::TakerPaymentWaitForSpendFailed(err) => self.errors.lock().push(err), TakerSwapEvent::MakerPaymentSpent(tx) => self.w().maker_payment_spend = Some(tx), + TakerSwapEvent::MakerPaymentSpendConfirmed => self.w().maker_payment_spend_confirmed = true, + TakerSwapEvent::MakerPaymentSpendConfirmFailed(err) => self.errors.lock().push(err), TakerSwapEvent::MakerPaymentSpentByWatcher(tx) => self.w().maker_payment_spend = Some(tx), TakerSwapEvent::MakerPaymentSpendFailed(err) => self.errors.lock().push(err), TakerSwapEvent::TakerPaymentWaitRefundStarted { .. } => (), @@ -862,6 +890,7 @@ impl TakerSwap { TakerSwapCommand::SendTakerPayment => self.send_taker_payment().await, TakerSwapCommand::WaitForTakerPaymentSpend => self.wait_for_taker_payment_spend().await, TakerSwapCommand::SpendMakerPayment => self.spend_maker_payment().await, + TakerSwapCommand::ConfirmMakerPaymentSpend => self.confirm_maker_payment_spend().await, TakerSwapCommand::PrepareForTakerPaymentRefund => self.prepare_for_taker_payment_refund().await, TakerSwapCommand::RefundTakerPayment => self.refund_taker_payment().await, TakerSwapCommand::FinalizeTakerPaymentRefund => self.finalize_taker_payment_refund().await, @@ -907,6 +936,7 @@ impl TakerSwap { other_taker_coin_htlc_pub: H264::default(), taker_fee: None, maker_payment: None, + maker_payment_spend_confirmed: false, taker_payment: None, taker_payment_spend: None, maker_payment_spend_preimage: None, @@ -1277,7 +1307,6 @@ impl TakerSwap { let fee_tx = self .taker_coin .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount, self.uuid.as_bytes(), expire_at) - .compat() .await; let transaction = match fee_tx { Ok(t) => t, @@ -1493,20 +1522,26 @@ impl TakerSwap { ])); } + let taker_payment_lock = self.r().data.taker_payment_lock; + let other_taker_coin_htlc_pub = self.r().other_taker_coin_htlc_pub; + let secret_hash = self.r().secret_hash.clone(); + let taker_coin_swap_contract_address = self.r().data.taker_coin_swap_contract_address.clone(); let unique_data = self.unique_swap_data(); + let taker_amount_decimal = self.taker_amount.to_decimal(); + let payment_instructions = self.r().payment_instructions.clone(); let f = self.taker_coin.check_if_my_payment_sent(CheckIfMyPaymentSentArgs { - time_lock: self.r().data.taker_payment_lock, - other_pub: self.r().other_taker_coin_htlc_pub.as_slice(), - secret_hash: &self.r().secret_hash.0, + time_lock: taker_payment_lock, + other_pub: other_taker_coin_htlc_pub.as_slice(), + secret_hash: &secret_hash.0, search_from_block: self.r().data.taker_coin_start_block, - swap_contract_address: &self.r().data.taker_coin_swap_contract_address, + swap_contract_address: &taker_coin_swap_contract_address, swap_unique_data: &unique_data, - amount: &self.taker_amount.to_decimal(), - payment_instructions: &self.r().payment_instructions, + amount: &taker_amount_decimal, + payment_instructions: &payment_instructions, }); let reward_amount = self.r().reward_amount.clone(); - let wait_until = self.r().data.taker_payment_lock; + let wait_until = taker_payment_lock; let watcher_reward = if self.r().watcher_reward { match self .taker_coin @@ -1532,28 +1567,32 @@ impl TakerSwap { None }; - let transaction = match f.compat().await { + let transaction = match f.await { Ok(res) => match res { Some(tx) => tx, None => { let time_lock = match std::env::var("USE_TEST_LOCKTIME") { Ok(_) => self.r().data.started_at, - Err(_) => self.r().data.taker_payment_lock, + Err(_) => taker_payment_lock, }; - let payment_fut = self.taker_coin.send_taker_payment(SendPaymentArgs { - time_lock_duration: self.r().data.lock_duration, - time_lock, - other_pubkey: &*self.r().other_taker_coin_htlc_pub, - secret_hash: &self.r().secret_hash.0, - amount: self.taker_amount.to_decimal(), - swap_contract_address: &self.r().data.taker_coin_swap_contract_address, - swap_unique_data: &unique_data, - payment_instructions: &self.r().payment_instructions, - watcher_reward, - wait_for_confirmation_until: self.r().data.taker_payment_lock, - }); + let lock_duration = self.r().data.lock_duration; + let payment = self + .taker_coin + .send_taker_payment(SendPaymentArgs { + time_lock_duration: lock_duration, + time_lock, + other_pubkey: &*other_taker_coin_htlc_pub, + secret_hash: &secret_hash.0, + amount: taker_amount_decimal, + swap_contract_address: &taker_coin_swap_contract_address, + swap_unique_data: &unique_data, + payment_instructions: &payment_instructions, + watcher_reward, + wait_for_confirmation_until: taker_payment_lock, + }) + .await; - match payment_fut.compat().await { + match payment { Ok(t) => t, Err(err) => { return Ok((Some(TakerSwapCommand::Finish), vec![ @@ -1646,7 +1685,7 @@ impl TakerSwap { async fn wait_for_taker_payment_spend(&self) -> Result<(Option, Vec), String> { const BROADCAST_MSG_INTERVAL_SEC: f64 = 600.; - let tx_hex = self.r().taker_payment.as_ref().unwrap().tx_hex.0.clone(); + let tx_hex = self.r().taker_payment.as_ref().unwrap().tx_hex.clone(); let mut watcher_broadcast_abort_handle = None; // Watchers cannot be used for lightning swaps for now // Todo: Check if watchers can work in some cases with lightning and implement it if it's possible, this part will probably work if only the taker is lightning since the preimage is available @@ -1676,7 +1715,7 @@ impl TakerSwap { } // Todo: taker_payment should be a message on lightning network not a swap message - let msg = SwapMsg::TakerPayment(tx_hex); + let msg = SwapMsg::TakerPayment(tx_hex.0.clone()); let send_abort_handle = broadcast_swap_msg_every( self.ctx.clone(), swap_topic(&self.uuid), @@ -1685,50 +1724,34 @@ impl TakerSwap { self.p2p_privkey, ); - let confirm_taker_payment_input = ConfirmPaymentInput { - payment_tx: self.r().taker_payment.clone().unwrap().tx_hex.0, - confirmations: self.r().data.taker_payment_confirmations, - requires_nota: self.r().data.taker_payment_requires_nota.unwrap_or(false), - wait_until: self.r().data.taker_payment_lock, - check_every: WAIT_CONFIRM_INTERVAL_SEC, - }; - let wait_f = self - .taker_coin - .wait_for_confirmations(confirm_taker_payment_input) - .compat(); - if let Err(err) = wait_f.await { - return Ok((Some(TakerSwapCommand::PrepareForTakerPaymentRefund), vec![ - TakerSwapEvent::TakerPaymentWaitConfirmFailed( - ERRL!("!taker_coin.wait_for_confirmations: {}", err).into(), - ), - TakerSwapEvent::TakerPaymentWaitRefundStarted { - wait_until: self.wait_refund_until(), - }, - ])); - } - #[cfg(any(test, feature = "run-docker-tests"))] if self.fail_at == Some(FailAt::WaitForTakerPaymentSpendPanic) { + // Wait for 5 seconds before panicking to ensure the message is sent + Timer::sleep(5.).await; panic!("Taker panicked unexpectedly at wait for taker payment spend"); } - info!("Taker payment confirmed"); + info!("Waiting for maker to spend taker payment!"); let wait_until = match std::env::var("USE_TEST_LOCKTIME") { Ok(_) => self.r().data.started_at, Err(_) => self.r().data.taker_payment_lock, }; + let secret_hash = self.r().secret_hash.clone(); + let taker_coin_start_block = self.r().data.taker_coin_start_block; + let taker_coin_swap_contract_address = self.r().data.taker_coin_swap_contract_address.clone(); + let watcher_reward = self.r().watcher_reward; let f = self.taker_coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { - tx_bytes: &self.r().taker_payment.clone().unwrap().tx_hex, - secret_hash: &self.r().secret_hash.0, + tx_bytes: &tx_hex, + secret_hash: &secret_hash.0, wait_until, - from_block: self.r().data.taker_coin_start_block, - swap_contract_address: &self.r().data.taker_coin_swap_contract_address, + from_block: taker_coin_start_block, + swap_contract_address: &taker_coin_swap_contract_address, check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, - watcher_reward: self.r().watcher_reward, + watcher_reward, }); - let tx = match f.compat().await { + let tx = match f.await { Ok(t) => t, Err(err) => { return Ok((Some(TakerSwapCommand::PrepareForTakerPaymentRefund), vec![ @@ -1748,8 +1771,6 @@ impl TakerSwap { tx_hash, }; - let secret_hash = self.r().secret_hash.clone(); - let watcher_reward = self.r().watcher_reward; let secret = match self .taker_coin .extract_secret(&secret_hash.0, &tx_ident.tx_hex, watcher_reward) @@ -1829,9 +1850,36 @@ impl TakerSwap { tx_hash, }; - Ok((Some(TakerSwapCommand::Finish), vec![TakerSwapEvent::MakerPaymentSpent( - tx_ident, - )])) + Ok((Some(TakerSwapCommand::ConfirmMakerPaymentSpend), vec![ + TakerSwapEvent::MakerPaymentSpent(tx_ident), + ])) + } + + async fn confirm_maker_payment_spend(&self) -> Result<(Option, Vec), String> { + let confirm_maker_payment_spend_input = ConfirmPaymentInput { + payment_tx: self.r().maker_payment_spend.clone().unwrap().tx_hex.0, + confirmations: self.r().data.maker_payment_confirmations, + requires_nota: false, + wait_until: self.wait_refund_until(), + check_every: WAIT_CONFIRM_INTERVAL_SEC, + }; + let wait_fut = self + .maker_coin + .wait_for_confirmations(confirm_maker_payment_spend_input); + if let Err(err) = wait_fut.compat().await { + return Ok((Some(TakerSwapCommand::PrepareForTakerPaymentRefund), vec![ + TakerSwapEvent::MakerPaymentSpendConfirmFailed( + ERRL!("!wait for maker payment spend confirmations: {}", err).into(), + ), + TakerSwapEvent::TakerPaymentWaitRefundStarted { + wait_until: self.wait_refund_until(), + }, + ])); + } + info!("Maker payment spend confirmed"); + Ok((Some(TakerSwapCommand::Finish), vec![ + TakerSwapEvent::MakerPaymentSpendConfirmed, + ])) } async fn prepare_for_taker_payment_refund( @@ -1873,7 +1921,7 @@ impl TakerSwap { } loop { - match self.taker_coin.can_refund_htlc(locktime).compat().await { + match self.taker_coin.can_refund_htlc(locktime).await { Ok(CanRefundHtlc::CanRefundNow) => break, Ok(CanRefundHtlc::HaveToWait(to_sleep)) => Timer::sleep(to_sleep as f64).await, Err(e) => { @@ -2057,8 +2105,8 @@ impl TakerSwap { return ERR!("Taker payment is refunded, swap is not recoverable"); } - if self.r().maker_payment_spend.is_some() { - return ERR!("Maker payment is spent, swap is not recoverable"); + if self.r().maker_payment_spend.is_some() && self.r().maker_payment_spend_confirmed { + return ERR!("Maker payment is spent and confirmed, swap is not recoverable"); } let maker_payment = match &self.r().maker_payment { @@ -2133,7 +2181,6 @@ impl TakerSwap { amount: &self.taker_amount.to_decimal(), payment_instructions: &payment_instructions, }) - .compat() .await ); match maybe_sent { @@ -2260,7 +2307,7 @@ impl TakerSwap { return ERR!("Taker payment will be refunded automatically!"); } - let can_refund = try_s!(self.taker_coin.can_refund_htlc(taker_payment_lock).compat().await); + let can_refund = try_s!(self.taker_coin.can_refund_htlc(taker_payment_lock).await); if let CanRefundHtlc::HaveToWait(seconds_to_wait) = can_refund { return ERR!("Too early to refund, wait until {}", wait_until_sec(seconds_to_wait)); } @@ -2676,7 +2723,7 @@ pub fn max_taker_vol_from_available( #[cfg(all(test, not(target_arch = "wasm32")))] mod taker_swap_tests { use super::*; - use crate::mm2::lp_swap::{dex_fee_amount, get_locked_amount_by_other_swaps}; + use crate::lp_swap::{dex_fee_amount, get_locked_amount_by_other_swaps}; use coins::eth::{addr_from_str, signed_eth_tx_from_bytes, SignedEthTx}; use coins::utxo::UtxoTx; use coins::{FoundSwapTxSpend, MarketCoinOps, MmCoin, SwapOps, TestCoin}; @@ -2708,7 +2755,7 @@ mod taker_swap_tests { fn test_recover_funds_taker_swap_maker_payment_spend_errored() { let ctx = mm_ctx_with_iguana(PASSPHRASE); - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.12596566232185483","maker_coin":"KMD","maker_coin_start_block":1458035,"maker_payment_confirmations":1,"maker_payment_wait":1564053079,"my_persistent_pub":"0326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0a","started_at":1564050479,"taker_amount":"50.000000000000001504212457800000","taker_coin":"DOGE","taker_coin_start_block":2823448,"taker_payment_confirmations":1,"taker_payment_lock":1564058279,"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"},"type":"Started"},"timestamp":1564050480269},{"event":{"data":{"maker_payment_locktime":1564066080,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"3669eb83a007a3c507448d79f45a9f06ec2f36a8"},"type":"Negotiated"},"timestamp":1564050540991},{"event":{"data":{"tx_hash":"bdde828b492d6d1cc25cd2322fd592dafd722fcc7d8b0fedce4d3bb4a1a8c8ff","tx_hex":"0100000002c7efa995c8b7be0a8b6c2d526c6c444c1634d65584e9ee89904e9d8675eac88c010000006a473044022051f34d5e3b7d0b9098d5e35333f3550f9cb9e57df83d5e4635b7a8d2986d6d5602200288c98da05de6950e01229a637110a1800ba643e75cfec59d4eb1021ad9b40801210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffffae6c233989efa7c7d2aa6534adc96078917ff395b7f09f734a147b2f44ade164000000006a4730440220393a784c2da74d0e2a28ec4f7df6c8f9d8b2af6ae6957f1e68346d744223a8fd02201b7a96954ac06815a43a6c7668d829ae9cbb5de76fa77189ddfd9e3038df662c01210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff02115f5800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac41a84641020000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac6d84395d"},"type":"TakerFeeSent"},"timestamp":1564050545296},{"event":{"data":{"tx_hash":"0a0f11fa82802c2c30862c50ab2162185dae8de7f7235f32c506f814c142b382","tx_hex":"0400008085202f8902ace337db2dd4c56b0697f58fb8cfb6bd1cd6f469d925fc0376d1dcfb7581bf82000000006b483045022100d1f95be235c5c8880f5d703ace287e2768548792c58c5dbd27f5578881b30ea70220030596106e21c7e0057ee0dab283f9a1fe273f15208cba80870c447bd559ef0d0121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff9f339752567c404427fd77f2b35cecdb4c21489edc64e25e729fdb281785e423000000006a47304402203179e95877dbc107123a417f1e648e3ff13d384890f1e4a67b6dd5087235152e0220102a8ab799fadb26b5d89ceb9c7bc721a7e0c2a0d0d7e46bbe0cf3d130010d430121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff025635c0000000000017a91480a95d366d65e34a465ab17b0c9eb1d5a33bae08876cbfce05000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8d7c395d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1564050588176},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1564050588178},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1564050693585},{"event":{"data":{"tx_hash":"539cb6dbdc25465bbccc575554f05d1bb04c70efce4316e41194e747375c3659","tx_hex":"0100000001ffc8a8a1b43b4dceed0f8b7dcc2f72fdda92d52f32d25cc21c6d2d498b82debd010000006a47304402203967b7f9f5532fa47116585c7d1bcba51861ea2059cca00409f34660db18e33a0220640991911852533a12fdfeb039fb9c8ca2c45482c6993bd84636af3670d49c1501210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff0200f2052a0100000017a914f2fa08ae416b576779ae5da975e5442663215fce87415173f9000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac0585395d"},"type":"TakerPaymentSent"},"timestamp":1564050695611},{"event":{"data":{"secret":"1b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093","transaction":{"tx_hash":"cc5af1cf68d246419fee49c3d74c0cd173599d115b86efe274368a614951bc47","tx_hex":"010000000159365c3747e79411e41643ceef704cb01b5df0545557ccbc5b4625dcdbb69c5300000000d747304402200e78e27d2f1c18676f98ca3dfa4e4a9eeaa8209b55f57b4dd5d9e1abdf034cfa0220623b5c22b62234cec230342aa306c497e43494b44ec2425b84e236b1bf01257001201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b6304a7a2395db175210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a88821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68ffffffff01008d380c010000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8c77395d"}},"type":"TakerPaymentSpent"},"timestamp":1564051092890},{"event":{"data":{"error":"lp_swap:1981] utxo:891] rpc_clients:738] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"67\", method: \"blockchain.transaction.broadcast\", params: [String(\"0400008085202f890182b342c114f806c5325f23f7e78dae5d186221ab502c86302c2c8082fa110f0a00000000d7473044022035791ea5548f87484065c9e1f0bdca9ebc699f2c7f51182c84f360102e32dc3d02200612ed53bca52d9c2568437f087598531534badf26229fe0f652ea72ddf03ca501201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b630420c1395db17521031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a888210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac68ffffffff01460ec000000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac967e395d000000000000000000000000000000\")] }, error: Transport(\"rpc_clients:668] All electrums are currently disconnected\") }"},"type":"MakerPaymentSpendFailed"},"timestamp":1564051092897},{"event":{"type":"Finished"},"timestamp":1564051092900}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.12596566232185483","maker_coin":"KMD","maker_coin_start_block":1458035,"maker_payment_confirmations":1,"maker_payment_wait":1564053079,"my_persistent_pub":"0326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0a","started_at":1564050479,"taker_amount":"50.000000000000001504212457800000","taker_coin":"DOGE","taker_coin_start_block":2823448,"taker_payment_confirmations":1,"taker_payment_lock":1564058279,"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"},"type":"Started"},"timestamp":1564050480269},{"event":{"data":{"maker_payment_locktime":1564066080,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"3669eb83a007a3c507448d79f45a9f06ec2f36a8"},"type":"Negotiated"},"timestamp":1564050540991},{"event":{"data":{"tx_hash":"bdde828b492d6d1cc25cd2322fd592dafd722fcc7d8b0fedce4d3bb4a1a8c8ff","tx_hex":"0100000002c7efa995c8b7be0a8b6c2d526c6c444c1634d65584e9ee89904e9d8675eac88c010000006a473044022051f34d5e3b7d0b9098d5e35333f3550f9cb9e57df83d5e4635b7a8d2986d6d5602200288c98da05de6950e01229a637110a1800ba643e75cfec59d4eb1021ad9b40801210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffffae6c233989efa7c7d2aa6534adc96078917ff395b7f09f734a147b2f44ade164000000006a4730440220393a784c2da74d0e2a28ec4f7df6c8f9d8b2af6ae6957f1e68346d744223a8fd02201b7a96954ac06815a43a6c7668d829ae9cbb5de76fa77189ddfd9e3038df662c01210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff02115f5800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac41a84641020000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac6d84395d"},"type":"TakerFeeSent"},"timestamp":1564050545296},{"event":{"data":{"tx_hash":"0a0f11fa82802c2c30862c50ab2162185dae8de7f7235f32c506f814c142b382","tx_hex":"0400008085202f8902ace337db2dd4c56b0697f58fb8cfb6bd1cd6f469d925fc0376d1dcfb7581bf82000000006b483045022100d1f95be235c5c8880f5d703ace287e2768548792c58c5dbd27f5578881b30ea70220030596106e21c7e0057ee0dab283f9a1fe273f15208cba80870c447bd559ef0d0121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff9f339752567c404427fd77f2b35cecdb4c21489edc64e25e729fdb281785e423000000006a47304402203179e95877dbc107123a417f1e648e3ff13d384890f1e4a67b6dd5087235152e0220102a8ab799fadb26b5d89ceb9c7bc721a7e0c2a0d0d7e46bbe0cf3d130010d430121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff025635c0000000000017a91480a95d366d65e34a465ab17b0c9eb1d5a33bae08876cbfce05000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8d7c395d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1564050588176},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1564050588178},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1564050693585},{"event":{"data":{"tx_hash":"539cb6dbdc25465bbccc575554f05d1bb04c70efce4316e41194e747375c3659","tx_hex":"0100000001ffc8a8a1b43b4dceed0f8b7dcc2f72fdda92d52f32d25cc21c6d2d498b82debd010000006a47304402203967b7f9f5532fa47116585c7d1bcba51861ea2059cca00409f34660db18e33a0220640991911852533a12fdfeb039fb9c8ca2c45482c6993bd84636af3670d49c1501210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff0200f2052a0100000017a914f2fa08ae416b576779ae5da975e5442663215fce87415173f9000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac0585395d"},"type":"TakerPaymentSent"},"timestamp":1564050695611},{"event":{"data":{"secret":"1b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093","transaction":{"tx_hash":"cc5af1cf68d246419fee49c3d74c0cd173599d115b86efe274368a614951bc47","tx_hex":"010000000159365c3747e79411e41643ceef704cb01b5df0545557ccbc5b4625dcdbb69c5300000000d747304402200e78e27d2f1c18676f98ca3dfa4e4a9eeaa8209b55f57b4dd5d9e1abdf034cfa0220623b5c22b62234cec230342aa306c497e43494b44ec2425b84e236b1bf01257001201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b6304a7a2395db175210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a88821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68ffffffff01008d380c010000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8c77395d"}},"type":"TakerPaymentSpent"},"timestamp":1564051092890},{"event":{"data":{"error":"lp_swap:1981] utxo:891] rpc_clients:738] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"67\", method: \"blockchain.transaction.broadcast\", params: [String(\"0400008085202f890182b342c114f806c5325f23f7e78dae5d186221ab502c86302c2c8082fa110f0a00000000d7473044022035791ea5548f87484065c9e1f0bdca9ebc699f2c7f51182c84f360102e32dc3d02200612ed53bca52d9c2568437f087598531534badf26229fe0f652ea72ddf03ca501201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b630420c1395db17521031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a888210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac68ffffffff01460ec000000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac967e395d000000000000000000000000000000\")] }, error: Transport(\"rpc_clients:668] All electrums are currently disconnected\") }"},"type":"MakerPaymentSpendFailed"},"timestamp":1564051092897},{"event":{"type":"Finished"},"timestamp":1564051092900}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); @@ -2744,18 +2791,18 @@ mod taker_swap_tests { fn test_recover_funds_taker_swap_taker_payment_errored_but_sent_not_spent() { let ctx = mm_ctx_with_iguana(PASSPHRASE); - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"3.54932734","maker_coin":"KMD","maker_coin_start_block":1452970,"maker_payment_confirmations":1,"maker_payment_wait":1563746537,"my_persistent_pub":"03101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9","started_at":1563743937,"taker_amount":"0.02004833998671660000000000","taker_coin":"ETH","taker_coin_start_block":8196380,"taker_payment_confirmations":1,"taker_payment_lock":1563751737,"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"},"type":"Started"},"timestamp":1563743937741},{"event":{"data":{"maker_payment_locktime":1563759539,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"432c8272ac59b47dea2d299b5cf1ee64ea1917b9"},"type":"Negotiated"},"timestamp":1563744003530},{"event":{"data":{"tx_hash":"a59203eb2328827de00bed699a29389792906e4f39fdea145eb40dc6b3821bd6","tx_hex":"f8690284ee6b280082520894d8997941dd1346e9231118d5685d866294f59e5b865af3107a4000801ca0743d2b7c9fad65805d882179062012261be328d7628ae12ee08eff8d7657d993a07eecbd051f49d35279416778faa4664962726d516ce65e18755c9b9406a9c2fd"},"type":"TakerFeeSent"},"timestamp":1563744020598},{"event":{"data":{"tx_hash":"0cf4acbcefde53645851c5c6053ea61fe0cbb5f828a906d69eb809e0b071a03b","tx_hex":"0400008085202f89025d5ae3e8c87418c9b735f8f2f7d29e26820c33c9f30d53f2d31f8b99ea9b1490010000006a47304402201185c06ca575261c539b287175751b7de642eb7466c59128639a19b4c2dd2f9b02201c8c4167d581864bedd4d1deb5596472e6e3ce29fe9e7996907a7b59c905d5490121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff06dbf9971c8dfd4a0c8c49f4f15c51de59ba13b2efa702682e26869843af9a87000000006a473044022012b47c12c7f6ad7d8b778fc4b5dcfd56a39325daf302f56e7b84753ba5216cfa022076bf571cf9e20facf70d2f134e8ed2de67aa08581a27ff3128bf93a9b594ac770121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff02fed727150000000017a914d5268b31131a652f9b6ddf57db62f02285cdfad1874e1d7835000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac37cf345d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563744071778},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563744071781},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563744118073},{"event":{"data":{"error":"lp_swap:1888] eth:654] RPC error: Error { code: ServerError(-32010), message: \"Transaction with the same hash was already imported.\", data: None }"},"type":"TakerPaymentTransactionFailed"},"timestamp":1563744118577},{"event":{"type":"Finished"},"timestamp":1563744118580}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"3.54932734","maker_coin":"KMD","maker_coin_start_block":1452970,"maker_payment_confirmations":1,"maker_payment_wait":1563746537,"my_persistent_pub":"03101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9","started_at":1563743937,"taker_amount":"0.02004833998671660000000000","taker_coin":"ETH","taker_coin_start_block":8196380,"taker_payment_confirmations":1,"taker_payment_lock":1563751737,"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"},"type":"Started"},"timestamp":1563743937741},{"event":{"data":{"maker_payment_locktime":1563759539,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"432c8272ac59b47dea2d299b5cf1ee64ea1917b9"},"type":"Negotiated"},"timestamp":1563744003530},{"event":{"data":{"tx_hash":"a59203eb2328827de00bed699a29389792906e4f39fdea145eb40dc6b3821bd6","tx_hex":"f8690284ee6b280082520894d8997941dd1346e9231118d5685d866294f59e5b865af3107a4000801ca0743d2b7c9fad65805d882179062012261be328d7628ae12ee08eff8d7657d993a07eecbd051f49d35279416778faa4664962726d516ce65e18755c9b9406a9c2fd"},"type":"TakerFeeSent"},"timestamp":1563744020598},{"event":{"data":{"tx_hash":"0cf4acbcefde53645851c5c6053ea61fe0cbb5f828a906d69eb809e0b071a03b","tx_hex":"0400008085202f89025d5ae3e8c87418c9b735f8f2f7d29e26820c33c9f30d53f2d31f8b99ea9b1490010000006a47304402201185c06ca575261c539b287175751b7de642eb7466c59128639a19b4c2dd2f9b02201c8c4167d581864bedd4d1deb5596472e6e3ce29fe9e7996907a7b59c905d5490121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff06dbf9971c8dfd4a0c8c49f4f15c51de59ba13b2efa702682e26869843af9a87000000006a473044022012b47c12c7f6ad7d8b778fc4b5dcfd56a39325daf302f56e7b84753ba5216cfa022076bf571cf9e20facf70d2f134e8ed2de67aa08581a27ff3128bf93a9b594ac770121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff02fed727150000000017a914d5268b31131a652f9b6ddf57db62f02285cdfad1874e1d7835000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac37cf345d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563744071778},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563744071781},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563744118073},{"event":{"data":{"error":"lp_swap:1888] eth:654] RPC error: Error { code: ServerError(-32010), message: \"Transaction with the same hash was already imported.\", data: None }"},"type":"TakerPaymentTransactionFailed"},"timestamp":1563744118577},{"event":{"type":"Finished"},"timestamp":1563744118580}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); TestCoin::can_refund_htlc - .mock_safe(|_, _| MockResult::Return(Box::new(futures01::future::ok(CanRefundHtlc::CanRefundNow)))); + .mock_safe(|_, _| MockResult::Return(Box::pin(futures::future::ok(CanRefundHtlc::CanRefundNow)))); static mut MY_PAYMENT_SENT_CALLED: bool = false; TestCoin::check_if_my_payment_sent.mock_safe(|_, _| { unsafe { MY_PAYMENT_SENT_CALLED = true }; - MockResult::Return(Box::new(futures01::future::ok(Some(eth_tx_for_test().into())))) + MockResult::Return(Box::pin(futures::future::ok(Some(eth_tx_for_test().into())))) }); static mut TX_SPEND_CALLED: bool = false; @@ -2794,7 +2841,7 @@ mod taker_swap_tests { fn test_recover_funds_taker_swap_taker_payment_errored_but_sent_and_spent_by_maker() { let ctx = mm_ctx_with_iguana(PASSPHRASE); - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"3.54932734","maker_coin":"KMD","maker_coin_start_block":1452970,"maker_payment_confirmations":1,"maker_payment_wait":1563746537,"my_persistent_pub":"03101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9","started_at":1563743937,"taker_amount":"0.02004833998671660000000000","taker_coin":"ETH","taker_coin_start_block":8196380,"taker_payment_confirmations":1,"taker_payment_lock":1563751737,"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"},"type":"Started"},"timestamp":1563743937741},{"event":{"data":{"maker_payment_locktime":1563759539,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"432c8272ac59b47dea2d299b5cf1ee64ea1917b9"},"type":"Negotiated"},"timestamp":1563744003530},{"event":{"data":{"tx_hash":"a59203eb2328827de00bed699a29389792906e4f39fdea145eb40dc6b3821bd6","tx_hex":"f8690284ee6b280082520894d8997941dd1346e9231118d5685d866294f59e5b865af3107a4000801ca0743d2b7c9fad65805d882179062012261be328d7628ae12ee08eff8d7657d993a07eecbd051f49d35279416778faa4664962726d516ce65e18755c9b9406a9c2fd"},"type":"TakerFeeSent"},"timestamp":1563744020598},{"event":{"data":{"tx_hash":"0cf4acbcefde53645851c5c6053ea61fe0cbb5f828a906d69eb809e0b071a03b","tx_hex":"0400008085202f89025d5ae3e8c87418c9b735f8f2f7d29e26820c33c9f30d53f2d31f8b99ea9b1490010000006a47304402201185c06ca575261c539b287175751b7de642eb7466c59128639a19b4c2dd2f9b02201c8c4167d581864bedd4d1deb5596472e6e3ce29fe9e7996907a7b59c905d5490121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff06dbf9971c8dfd4a0c8c49f4f15c51de59ba13b2efa702682e26869843af9a87000000006a473044022012b47c12c7f6ad7d8b778fc4b5dcfd56a39325daf302f56e7b84753ba5216cfa022076bf571cf9e20facf70d2f134e8ed2de67aa08581a27ff3128bf93a9b594ac770121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff02fed727150000000017a914d5268b31131a652f9b6ddf57db62f02285cdfad1874e1d7835000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac37cf345d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563744071778},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563744071781},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563744118073},{"event":{"data":{"error":"lp_swap:1888] eth:654] RPC error: Error { code: ServerError(-32010), message: \"Transaction with the same hash was already imported.\", data: None }"},"type":"TakerPaymentTransactionFailed"},"timestamp":1563744118577},{"event":{"type":"Finished"},"timestamp":1563744118580}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"3.54932734","maker_coin":"KMD","maker_coin_start_block":1452970,"maker_payment_confirmations":1,"maker_payment_wait":1563746537,"my_persistent_pub":"03101ace6b08605b9424b0582b5cce044b70a3c8d8d10cb2965e039b0967ae92b9","started_at":1563743937,"taker_amount":"0.02004833998671660000000000","taker_coin":"ETH","taker_coin_start_block":8196380,"taker_payment_confirmations":1,"taker_payment_lock":1563751737,"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"},"type":"Started"},"timestamp":1563743937741},{"event":{"data":{"maker_payment_locktime":1563759539,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"432c8272ac59b47dea2d299b5cf1ee64ea1917b9"},"type":"Negotiated"},"timestamp":1563744003530},{"event":{"data":{"tx_hash":"a59203eb2328827de00bed699a29389792906e4f39fdea145eb40dc6b3821bd6","tx_hex":"f8690284ee6b280082520894d8997941dd1346e9231118d5685d866294f59e5b865af3107a4000801ca0743d2b7c9fad65805d882179062012261be328d7628ae12ee08eff8d7657d993a07eecbd051f49d35279416778faa4664962726d516ce65e18755c9b9406a9c2fd"},"type":"TakerFeeSent"},"timestamp":1563744020598},{"event":{"data":{"tx_hash":"0cf4acbcefde53645851c5c6053ea61fe0cbb5f828a906d69eb809e0b071a03b","tx_hex":"0400008085202f89025d5ae3e8c87418c9b735f8f2f7d29e26820c33c9f30d53f2d31f8b99ea9b1490010000006a47304402201185c06ca575261c539b287175751b7de642eb7466c59128639a19b4c2dd2f9b02201c8c4167d581864bedd4d1deb5596472e6e3ce29fe9e7996907a7b59c905d5490121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff06dbf9971c8dfd4a0c8c49f4f15c51de59ba13b2efa702682e26869843af9a87000000006a473044022012b47c12c7f6ad7d8b778fc4b5dcfd56a39325daf302f56e7b84753ba5216cfa022076bf571cf9e20facf70d2f134e8ed2de67aa08581a27ff3128bf93a9b594ac770121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff02fed727150000000017a914d5268b31131a652f9b6ddf57db62f02285cdfad1874e1d7835000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac37cf345d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563744071778},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563744071781},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563744118073},{"event":{"data":{"error":"lp_swap:1888] eth:654] RPC error: Error { code: ServerError(-32010), message: \"Transaction with the same hash was already imported.\", data: None }"},"type":"TakerPaymentTransactionFailed"},"timestamp":1563744118577},{"event":{"type":"Finished"},"timestamp":1563744118580}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"uuid":"3447b727-fe93-4357-8e5a-8cf2699b7e86"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); @@ -2804,7 +2851,7 @@ mod taker_swap_tests { static mut MY_PAYMENT_SENT_CALLED: bool = false; TestCoin::check_if_my_payment_sent.mock_safe(|_, _| { unsafe { MY_PAYMENT_SENT_CALLED = true }; - MockResult::Return(Box::new(futures01::future::ok(Some(eth_tx_for_test().into())))) + MockResult::Return(Box::pin(futures::future::ok(Some(eth_tx_for_test().into())))) }); static mut SEARCH_TX_SPEND_CALLED: bool = false; @@ -2847,13 +2894,13 @@ mod taker_swap_tests { fn test_recover_funds_taker_swap_taker_payment_refund_failed_not_spent() { let ctx = mm_ctx_with_iguana(PASSPHRASE); - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); TestCoin::can_refund_htlc - .mock_safe(|_, _| MockResult::Return(Box::new(futures01::future::ok(CanRefundHtlc::CanRefundNow)))); + .mock_safe(|_, _| MockResult::Return(Box::pin(futures::future::ok(CanRefundHtlc::CanRefundNow)))); static mut SEARCH_TX_SPEND_CALLED: bool = false; TestCoin::search_for_swap_tx_spend_my.mock_safe(|_, _| { @@ -2890,13 +2937,13 @@ mod taker_swap_tests { fn test_recover_funds_taker_swap_taker_payment_refund_failed_not_spent_too_early_to_refund() { let ctx = mm_ctx_with_iguana(PASSPHRASE); - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); TestCoin::can_refund_htlc - .mock_safe(|_, _| MockResult::Return(Box::new(futures01::future::ok(CanRefundHtlc::HaveToWait(1000))))); + .mock_safe(|_, _| MockResult::Return(Box::pin(futures::future::ok(CanRefundHtlc::HaveToWait(1000))))); static mut SEARCH_TX_SPEND_CALLED: bool = false; TestCoin::search_for_swap_tx_spend_my.mock_safe(|_, _| { @@ -2921,7 +2968,7 @@ mod taker_swap_tests { fn test_recover_funds_taker_swap_taker_payment_refund_failed_spent_by_maker() { let ctx = mm_ctx_with_iguana(PASSPHRASE); - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); @@ -2968,7 +3015,7 @@ mod taker_swap_tests { let ctx = mm_ctx_with_iguana(PASSPHRASE); // the json doesn't have Finished event at the end - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.12596566232185483","maker_coin":"KMD","maker_coin_start_block":1458035,"maker_payment_confirmations":1,"maker_payment_wait":1564053079,"my_persistent_pub":"0326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0a","started_at":1564050479,"taker_amount":"50.000000000000001504212457800000","taker_coin":"DOGE","taker_coin_start_block":2823448,"taker_payment_confirmations":1,"taker_payment_lock":1564058279,"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"},"type":"Started"},"timestamp":1564050480269},{"event":{"data":{"maker_payment_locktime":1564066080,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"3669eb83a007a3c507448d79f45a9f06ec2f36a8"},"type":"Negotiated"},"timestamp":1564050540991},{"event":{"data":{"tx_hash":"bdde828b492d6d1cc25cd2322fd592dafd722fcc7d8b0fedce4d3bb4a1a8c8ff","tx_hex":"0100000002c7efa995c8b7be0a8b6c2d526c6c444c1634d65584e9ee89904e9d8675eac88c010000006a473044022051f34d5e3b7d0b9098d5e35333f3550f9cb9e57df83d5e4635b7a8d2986d6d5602200288c98da05de6950e01229a637110a1800ba643e75cfec59d4eb1021ad9b40801210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffffae6c233989efa7c7d2aa6534adc96078917ff395b7f09f734a147b2f44ade164000000006a4730440220393a784c2da74d0e2a28ec4f7df6c8f9d8b2af6ae6957f1e68346d744223a8fd02201b7a96954ac06815a43a6c7668d829ae9cbb5de76fa77189ddfd9e3038df662c01210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff02115f5800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac41a84641020000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac6d84395d"},"type":"TakerFeeSent"},"timestamp":1564050545296},{"event":{"data":{"tx_hash":"0a0f11fa82802c2c30862c50ab2162185dae8de7f7235f32c506f814c142b382","tx_hex":"0400008085202f8902ace337db2dd4c56b0697f58fb8cfb6bd1cd6f469d925fc0376d1dcfb7581bf82000000006b483045022100d1f95be235c5c8880f5d703ace287e2768548792c58c5dbd27f5578881b30ea70220030596106e21c7e0057ee0dab283f9a1fe273f15208cba80870c447bd559ef0d0121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff9f339752567c404427fd77f2b35cecdb4c21489edc64e25e729fdb281785e423000000006a47304402203179e95877dbc107123a417f1e648e3ff13d384890f1e4a67b6dd5087235152e0220102a8ab799fadb26b5d89ceb9c7bc721a7e0c2a0d0d7e46bbe0cf3d130010d430121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff025635c0000000000017a91480a95d366d65e34a465ab17b0c9eb1d5a33bae08876cbfce05000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8d7c395d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1564050588176},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1564050588178},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1564050693585},{"event":{"data":{"tx_hash":"539cb6dbdc25465bbccc575554f05d1bb04c70efce4316e41194e747375c3659","tx_hex":"0100000001ffc8a8a1b43b4dceed0f8b7dcc2f72fdda92d52f32d25cc21c6d2d498b82debd010000006a47304402203967b7f9f5532fa47116585c7d1bcba51861ea2059cca00409f34660db18e33a0220640991911852533a12fdfeb039fb9c8ca2c45482c6993bd84636af3670d49c1501210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff0200f2052a0100000017a914f2fa08ae416b576779ae5da975e5442663215fce87415173f9000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac0585395d"},"type":"TakerPaymentSent"},"timestamp":1564050695611},{"event":{"data":{"secret":"1b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093","transaction":{"tx_hash":"cc5af1cf68d246419fee49c3d74c0cd173599d115b86efe274368a614951bc47","tx_hex":"010000000159365c3747e79411e41643ceef704cb01b5df0545557ccbc5b4625dcdbb69c5300000000d747304402200e78e27d2f1c18676f98ca3dfa4e4a9eeaa8209b55f57b4dd5d9e1abdf034cfa0220623b5c22b62234cec230342aa306c497e43494b44ec2425b84e236b1bf01257001201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b6304a7a2395db175210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a88821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68ffffffff01008d380c010000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8c77395d"}},"type":"TakerPaymentSpent"},"timestamp":1564051092890},{"event":{"data":{"error":"lp_swap:1981] utxo:891] rpc_clients:738] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"67\", method: \"blockchain.transaction.broadcast\", params: [String(\"0400008085202f890182b342c114f806c5325f23f7e78dae5d186221ab502c86302c2c8082fa110f0a00000000d7473044022035791ea5548f87484065c9e1f0bdca9ebc699f2c7f51182c84f360102e32dc3d02200612ed53bca52d9c2568437f087598531534badf26229fe0f652ea72ddf03ca501201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b630420c1395db17521031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a888210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac68ffffffff01460ec000000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac967e395d000000000000000000000000000000\")] }, error: Transport(\"rpc_clients:668] All electrums are currently disconnected\") }"},"type":"MakerPaymentSpendFailed"},"timestamp":1564051092897}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.12596566232185483","maker_coin":"KMD","maker_coin_start_block":1458035,"maker_payment_confirmations":1,"maker_payment_wait":1564053079,"my_persistent_pub":"0326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0a","started_at":1564050479,"taker_amount":"50.000000000000001504212457800000","taker_coin":"DOGE","taker_coin_start_block":2823448,"taker_payment_confirmations":1,"taker_payment_lock":1564058279,"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"},"type":"Started"},"timestamp":1564050480269},{"event":{"data":{"maker_payment_locktime":1564066080,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"3669eb83a007a3c507448d79f45a9f06ec2f36a8"},"type":"Negotiated"},"timestamp":1564050540991},{"event":{"data":{"tx_hash":"bdde828b492d6d1cc25cd2322fd592dafd722fcc7d8b0fedce4d3bb4a1a8c8ff","tx_hex":"0100000002c7efa995c8b7be0a8b6c2d526c6c444c1634d65584e9ee89904e9d8675eac88c010000006a473044022051f34d5e3b7d0b9098d5e35333f3550f9cb9e57df83d5e4635b7a8d2986d6d5602200288c98da05de6950e01229a637110a1800ba643e75cfec59d4eb1021ad9b40801210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffffae6c233989efa7c7d2aa6534adc96078917ff395b7f09f734a147b2f44ade164000000006a4730440220393a784c2da74d0e2a28ec4f7df6c8f9d8b2af6ae6957f1e68346d744223a8fd02201b7a96954ac06815a43a6c7668d829ae9cbb5de76fa77189ddfd9e3038df662c01210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff02115f5800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac41a84641020000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac6d84395d"},"type":"TakerFeeSent"},"timestamp":1564050545296},{"event":{"data":{"tx_hash":"0a0f11fa82802c2c30862c50ab2162185dae8de7f7235f32c506f814c142b382","tx_hex":"0400008085202f8902ace337db2dd4c56b0697f58fb8cfb6bd1cd6f469d925fc0376d1dcfb7581bf82000000006b483045022100d1f95be235c5c8880f5d703ace287e2768548792c58c5dbd27f5578881b30ea70220030596106e21c7e0057ee0dab283f9a1fe273f15208cba80870c447bd559ef0d0121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff9f339752567c404427fd77f2b35cecdb4c21489edc64e25e729fdb281785e423000000006a47304402203179e95877dbc107123a417f1e648e3ff13d384890f1e4a67b6dd5087235152e0220102a8ab799fadb26b5d89ceb9c7bc721a7e0c2a0d0d7e46bbe0cf3d130010d430121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff025635c0000000000017a91480a95d366d65e34a465ab17b0c9eb1d5a33bae08876cbfce05000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8d7c395d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1564050588176},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1564050588178},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1564050693585},{"event":{"data":{"tx_hash":"539cb6dbdc25465bbccc575554f05d1bb04c70efce4316e41194e747375c3659","tx_hex":"0100000001ffc8a8a1b43b4dceed0f8b7dcc2f72fdda92d52f32d25cc21c6d2d498b82debd010000006a47304402203967b7f9f5532fa47116585c7d1bcba51861ea2059cca00409f34660db18e33a0220640991911852533a12fdfeb039fb9c8ca2c45482c6993bd84636af3670d49c1501210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff0200f2052a0100000017a914f2fa08ae416b576779ae5da975e5442663215fce87415173f9000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac0585395d"},"type":"TakerPaymentSent"},"timestamp":1564050695611},{"event":{"data":{"secret":"1b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093","transaction":{"tx_hash":"cc5af1cf68d246419fee49c3d74c0cd173599d115b86efe274368a614951bc47","tx_hex":"010000000159365c3747e79411e41643ceef704cb01b5df0545557ccbc5b4625dcdbb69c5300000000d747304402200e78e27d2f1c18676f98ca3dfa4e4a9eeaa8209b55f57b4dd5d9e1abdf034cfa0220623b5c22b62234cec230342aa306c497e43494b44ec2425b84e236b1bf01257001201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b6304a7a2395db175210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a88821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68ffffffff01008d380c010000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8c77395d"}},"type":"TakerPaymentSpent"},"timestamp":1564051092890},{"event":{"data":{"error":"lp_swap:1981] utxo:891] rpc_clients:738] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"67\", method: \"blockchain.transaction.broadcast\", params: [String(\"0400008085202f890182b342c114f806c5325f23f7e78dae5d186221ab502c86302c2c8082fa110f0a00000000d7473044022035791ea5548f87484065c9e1f0bdca9ebc699f2c7f51182c84f360102e32dc3d02200612ed53bca52d9c2568437f087598531534badf26229fe0f652ea72ddf03ca501201b8886b8a2cdb62505699400b694ac20f04d7bd4abd80e1ab154aa8d861fc093004c6b630420c1395db17521031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac6782012088a9143669eb83a007a3c507448d79f45a9f06ec2f36a888210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0aac68ffffffff01460ec000000000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac967e395d000000000000000000000000000000\")] }, error: Transport(\"rpc_clients:668] All electrums are currently disconnected\") }"},"type":"MakerPaymentSpendFailed"},"timestamp":1564051092897}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); @@ -3005,7 +3052,7 @@ mod taker_swap_tests { let ctx = mm_ctx_with_iguana(PASSPHRASE); // swap file contains neither maker_coin_swap_contract_address nor taker_coin_swap_contract_address - let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; + let taker_saved_json = r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","TakerPaymentTransactionFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.58610590","maker_coin":"KMD","maker_coin_start_block":1450923,"maker_payment_confirmations":1,"maker_payment_wait":1563623475,"my_persistent_pub":"02713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91","started_at":1563620875,"taker_amount":"0.0077700000552410000000000","taker_coin":"LTC","taker_coin_start_block":1670837,"taker_payment_confirmations":1,"taker_payment_lock":1563628675,"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"},"type":"Started"},"timestamp":1563620875766},{"event":{"data":{"maker_payment_locktime":1563636475,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"7ed38daab6085c1a1e4426e61dc87a3c2c081a95"},"type":"Negotiated"},"timestamp":1563620955014},{"event":{"data":{"tx_hash":"6740136eaaa615d9d231969e3a9599d0fc59e53989237a8d31cd6fc86c160013","tx_hex":"0100000001a2586ea8294cedc55741bef625ba72c646399903391a7f6c604a58c6263135f2000000006b4830450221009c78c8ba4a7accab6b09f9a95da5bc59c81f4fc1e60b288ec3c5462b4d02ef01022056b63be1629cf17751d3cc5ffec51bcb1d7f9396e9ce9ca254d0f34104f7263a012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0210270000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac78aa1900000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac5bf6325d"},"type":"TakerFeeSent"},"timestamp":1563620958220},{"event":{"data":{"tx_hash":"d0f6e664cea9d89fe7b5cf8005fdca070d1ab1d05a482aaef95c08cdaecddf0a","tx_hex":"0400008085202f89019f1cbda354342cdf982046b331bbd3791f53b692efc6e4becc36be495b2977d9000000006b483045022100fa9d4557394141f6a8b9bfb8cd594a521fd8bcd1965dbf8bc4e04abc849ac66e0220589f521814c10a7561abfd5e432f7a2ee60d4875fe4604618af3207dae531ac00121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff029e537e030000000017a9145534898009f1467191065f6890b96914b39a1c018791857702000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac72ee325d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1563620999307},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1563620999310},{"event":{"type":"MakerPaymentValidatedAndConfirmed"},"timestamp":1563621244153},{"event":{"data":{"tx_hash":"1e883eb2f3991e84ba27f53651f89b7dda708678a5b9813d043577f222b9ca30","tx_hex":"01000000011300166cc86fcd318d7a238939e559fcd099953a9e9631d2d915a6aa6e134067010000006a47304402206781d5f2db2ff13d2ec7e266f774ea5630cc2dba4019e18e9716131b8b026051022006ebb33857b6d180f13aa6be2fc532f9734abde9d00ae14757e7d7ba3741c08c012102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ffffffff0228db0b000000000017a91483818667161bf94adda3964a81a231cbf6f5338187b0480c00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac7cf7325d"},"type":"TakerPaymentSent"},"timestamp":1563621246370},{"event":{"data":{"error":"utxo:1145] rpc_clients:782] Waited too long until 1563628675 for output TransactionOutput { value: 777000, script_pubkey: a91483818667161bf94adda3964a81a231cbf6f5338187 } to be spent "},"type":"TakerPaymentWaitForSpendFailed"},"timestamp":1563638060370},{"event":{"data":{"error":"lp_swap:2025] utxo:938] rpc_clients:719] JsonRpcError { request: JsonRpcRequest { jsonrpc: \"2.0\", id: \"9\", method: \"blockchain.transaction.broadcast\", params: [String(\"010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d\")] }, error: Response(Object({\"code\": Number(1), \"message\": String(\"the transaction was rejected by network rules.\\n\\nMissing inputs\\n[010000000130cab922f27735043d81b9a5788670da7d9bf85136f527ba841e99f3b23e881e00000000b6473044022058a0c1da6bcf8c1418899ff8475f3ab6dddbff918528451c1fe71c2f7dad176302204c2e0bcf8f9b5f09e02ccfeb9256e9b34fb355ea655a5704a8a3fa920079b91501514c6b63048314335db1752102713015d3fa4d30259e90be5f131beb593bf0131f3af2dcdb304e3322d8d52b91ac6782012088a9147ed38daab6085c1a1e4426e61dc87a3c2c081a958821031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ac68feffffff0188540a00000000001976a91406ccabfd5f9075ecd5e8d0d31c0e973a54d51e8288ac1c2b335d]\")})) }"},"type":"TakerPaymentRefundFailed"},"timestamp":1563638060583},{"event":{"type":"Finished"},"timestamp":1563638060585}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"uuid":"9db641f5-4300-4527-9fa6-f1c391d42c35"}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); @@ -3040,7 +3087,7 @@ mod taker_swap_tests { let ctx = mm_ctx_with_iguana(PASSPHRASE); // swap file contains only maker_coin_swap_contract_address - let taker_saved_json = r#"{"type":"Taker","uuid":"49c79ea4-e1eb-4fb2-a0ef-265bded0b77f","events":[{"timestamp":1608542326909,"event":{"type":"Started","data":{"taker_coin":"RICK","maker_coin":"ETH","maker":"c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed","my_persistent_pub":"02031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3","lock_duration":7800,"maker_amount":"0.1","taker_amount":"0.1","maker_payment_confirmations":1,"maker_payment_requires_nota":false,"taker_payment_confirmations":0,"taker_payment_requires_nota":false,"taker_payment_lock":1608550126,"uuid":"49c79ea4-e1eb-4fb2-a0ef-265bded0b77f","started_at":1608542326,"maker_payment_wait":1608545446,"maker_coin_start_block":14360,"taker_coin_start_block":723123,"maker_coin_swap_contract_address":"eA6D65434A15377081495a9E7C5893543E7c32cB"}}},{"timestamp":1608542327416,"event":{"type":"Negotiated","data":{"maker_payment_locktime":1608557926,"maker_pubkey":"03c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed","secret_hash":"8b0221f3b977c1c65dddf17c1c28e2bbced9e7b4"}}},{"timestamp":1608542332604,"event":{"type":"TakerFeeSent","data":{"tx_hex":"0400008085202f89011ca964f77200b73d64b481f47de84098041d3470d6256e44f2741f080e2b11cf020000006b4830450221008a064f5e51ef8281d43eb7bcd016fed7e560ea1eb7b0713ec977602c96d8f79b02205bfaa6655b849b9922c03276b938273f2edb8fb9ffcaa2a9212d7220560f6060012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff0246320000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac62752e27000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac7768e05f000000000000000000000000000000","tx_hash":"3793df28ed2aac6188d2c48ec65eff12eea301089d60da655fc96f598326d708"}}},{"timestamp":1608542334018,"event":{"type":"MakerPaymentReceived","data":{"tx_hex":"f8ef82021c80830249f094a09ad3cd7e96586ebd05a2607ee56b56fb2db8fd88016345785d8a0000b884152cf3af50aebafeaf827c62c2eed09e265fa5aa9e013c0f27f0a88259f1aaa1279f0c32000000000000000000000000bab36286672fbdc7b250804bf6d14be0df69fa298b0221f3b977c1c65dddf17c1c28e2bbced9e7b4000000000000000000000000000000000000000000000000000000000000000000000000000000005fe0a5661ba0f18a0c5c349462b51dacd1a0761e4997d4572a01e48480c4e310d69a40308ad3a04510513f01a79c59f22c9cb79952547c8dfc4c74785b630f512d64369323e0c1","tx_hash":"6782323490584a2bc768cd5199506bfa1ed91e7515b35bb72fa269604b7dc0aa"}}},{"timestamp":1608542334019,"event":{"type":"MakerPaymentWaitConfirmStarted"}},{"timestamp":1608542334825,"event":{"type":"MakerPaymentValidatedAndConfirmed"}},{"timestamp":1608542337671,"event":{"type":"TakerPaymentSent","data":{"tx_hex":"0400008085202f890108d72683596fc95f65da609d0801a3ee12ff5ec68ec4d28861ac2aed28df9337010000006b48304502210086a03db599438b243bee2b02af56e23447f85d09854416b51305536b9ca5890e02204b288acdea4cdc7ab1ffbd9766a7bdf95f5bd02d2917dfb7089dbf29032591b0012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff03809698000000000017a914888e9e1816214c3960eac7b55e35521ca4426b0c870000000000000000166a148b0221f3b977c1c65dddf17c1c28e2bbced9e7b4fada9526000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac7f68e05f000000000000000000000000000000","tx_hash":"44fa493757df5fdca823bbac05a8b8feb5862d799d4947fd544abcd129feceea"}}},{"timestamp":1608542348271,"event":{"type":"TakerPaymentSpent","data":{"transaction":{"tx_hex":"0400008085202f8901eacefe29d1bc4a54fd47499d792d86b5feb8a805acbb23a8dc5fdf573749fa4400000000d74730440220508c853cc4f1fcb9e6aa00e704eef99adaee9a4ea63a1fd6393bb7ff18da02c802200396bb5d52157bd77ff26ac521ed75aca388d3ec1e5e3ebb7b3aed73c3d33ec50120df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e004c6b6304ee86e05fb1752102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ac6782012088a9148b0221f3b977c1c65dddf17c1c28e2bbced9e7b4882103c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3edac68ffffffff0198929800000000001976a9146d9d2b554d768232320587df75c4338ecc8bf37d88ac725ae05f000000000000000000000000000000","tx_hash":"9376dde62249802a0aba8259f51def9bb2e509af85a5ec7df04b479a9da28a29"},"secret":"df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e"}}},{"timestamp":1608542349372,"event":{"type":"MakerPaymentSpent","data":{"tx_hex":"f90107821fb980830249f094a09ad3cd7e96586ebd05a2607ee56b56fb2db8fd80b8a402ed292b50aebafeaf827c62c2eed09e265fa5aa9e013c0f27f0a88259f1aaa1279f0c32000000000000000000000000000000000000000000000000016345785d8a0000df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b2d0d6c2c785217457b69b922a2a9cea98f71e91ca0ed6a4942a78c7ae6eb3c9dec496459a9ef68b34cb389acd939d13d3ecaf7e4aca021bb77e80fc60acf25a7a01cc1272b1b76594a521fb1abe1322d650e58a672c2","tx_hash":"c2d206e665aee159a5ab9aff60f76444e97bdad8f9152eccb6ca07d9204974ca"}}},{"timestamp":1608542349373,"event":{"type":"Finished"}}],"maker_amount":"0.1","maker_coin":"ETH","taker_amount":"0.1","taker_coin":"RICK","gui":"nogui","mm_version":"1a6082121","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","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"]}"#; + let taker_saved_json = r#"{"type":"Taker","uuid":"49c79ea4-e1eb-4fb2-a0ef-265bded0b77f","events":[{"timestamp":1608542326909,"event":{"type":"Started","data":{"taker_coin":"RICK","maker_coin":"ETH","maker":"c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed","my_persistent_pub":"02031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3","lock_duration":7800,"maker_amount":"0.1","taker_amount":"0.1","maker_payment_confirmations":1,"maker_payment_requires_nota":false,"taker_payment_confirmations":0,"taker_payment_requires_nota":false,"taker_payment_lock":1608550126,"uuid":"49c79ea4-e1eb-4fb2-a0ef-265bded0b77f","started_at":1608542326,"maker_payment_wait":1608545446,"maker_coin_start_block":14360,"taker_coin_start_block":723123,"maker_coin_swap_contract_address":"eA6D65434A15377081495a9E7C5893543E7c32cB"}}},{"timestamp":1608542327416,"event":{"type":"Negotiated","data":{"maker_payment_locktime":1608557926,"maker_pubkey":"03c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3ed","secret_hash":"8b0221f3b977c1c65dddf17c1c28e2bbced9e7b4"}}},{"timestamp":1608542332604,"event":{"type":"TakerFeeSent","data":{"tx_hex":"0400008085202f89011ca964f77200b73d64b481f47de84098041d3470d6256e44f2741f080e2b11cf020000006b4830450221008a064f5e51ef8281d43eb7bcd016fed7e560ea1eb7b0713ec977602c96d8f79b02205bfaa6655b849b9922c03276b938273f2edb8fb9ffcaa2a9212d7220560f6060012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff0246320000000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac62752e27000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac7768e05f000000000000000000000000000000","tx_hash":"3793df28ed2aac6188d2c48ec65eff12eea301089d60da655fc96f598326d708"}}},{"timestamp":1608542334018,"event":{"type":"MakerPaymentReceived","data":{"tx_hex":"f8ef82021c80830249f094a09ad3cd7e96586ebd05a2607ee56b56fb2db8fd88016345785d8a0000b884152cf3af50aebafeaf827c62c2eed09e265fa5aa9e013c0f27f0a88259f1aaa1279f0c32000000000000000000000000bab36286672fbdc7b250804bf6d14be0df69fa298b0221f3b977c1c65dddf17c1c28e2bbced9e7b4000000000000000000000000000000000000000000000000000000000000000000000000000000005fe0a5661ba0f18a0c5c349462b51dacd1a0761e4997d4572a01e48480c4e310d69a40308ad3a04510513f01a79c59f22c9cb79952547c8dfc4c74785b630f512d64369323e0c1","tx_hash":"6782323490584a2bc768cd5199506bfa1ed91e7515b35bb72fa269604b7dc0aa"}}},{"timestamp":1608542334019,"event":{"type":"MakerPaymentWaitConfirmStarted"}},{"timestamp":1608542334825,"event":{"type":"MakerPaymentValidatedAndConfirmed"}},{"timestamp":1608542337671,"event":{"type":"TakerPaymentSent","data":{"tx_hex":"0400008085202f890108d72683596fc95f65da609d0801a3ee12ff5ec68ec4d28861ac2aed28df9337010000006b48304502210086a03db599438b243bee2b02af56e23447f85d09854416b51305536b9ca5890e02204b288acdea4cdc7ab1ffbd9766a7bdf95f5bd02d2917dfb7089dbf29032591b0012102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ffffffff03809698000000000017a914888e9e1816214c3960eac7b55e35521ca4426b0c870000000000000000166a148b0221f3b977c1c65dddf17c1c28e2bbced9e7b4fada9526000000001976a91405aab5342166f8594baf17a7d9bef5d56744332788ac7f68e05f000000000000000000000000000000","tx_hash":"44fa493757df5fdca823bbac05a8b8feb5862d799d4947fd544abcd129feceea"}}},{"timestamp":1608542348271,"event":{"type":"TakerPaymentSpent","data":{"transaction":{"tx_hex":"0400008085202f8901eacefe29d1bc4a54fd47499d792d86b5feb8a805acbb23a8dc5fdf573749fa4400000000d74730440220508c853cc4f1fcb9e6aa00e704eef99adaee9a4ea63a1fd6393bb7ff18da02c802200396bb5d52157bd77ff26ac521ed75aca388d3ec1e5e3ebb7b3aed73c3d33ec50120df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e004c6b6304ee86e05fb1752102031d4256c4bc9f99ac88bf3dba21773132281f65f9bf23a59928bce08961e2f3ac6782012088a9148b0221f3b977c1c65dddf17c1c28e2bbced9e7b4882103c6a78589e18b482aea046975e6d0acbdea7bf7dbf04d9d5bd67fda917815e3edac68ffffffff0198929800000000001976a9146d9d2b554d768232320587df75c4338ecc8bf37d88ac725ae05f000000000000000000000000000000","tx_hash":"9376dde62249802a0aba8259f51def9bb2e509af85a5ec7df04b479a9da28a29"},"secret":"df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e"}}},{"timestamp":1608542349372,"event":{"type":"MakerPaymentSpent","data":{"tx_hex":"f90107821fb980830249f094a09ad3cd7e96586ebd05a2607ee56b56fb2db8fd80b8a402ed292b50aebafeaf827c62c2eed09e265fa5aa9e013c0f27f0a88259f1aaa1279f0c32000000000000000000000000000000000000000000000000016345785d8a0000df871242dcbcc4fe9ed4d3413e21b2f8ce606a3ee7128c9b2d2e31fcedc1848e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000004b2d0d6c2c785217457b69b922a2a9cea98f71e91ca0ed6a4942a78c7ae6eb3c9dec496459a9ef68b34cb389acd939d13d3ecaf7e4aca021bb77e80fc60acf25a7a01cc1272b1b76594a521fb1abe1322d650e58a672c2","tx_hash":"c2d206e665aee159a5ab9aff60f76444e97bdad8f9152eccb6ca07d9204974ca"}}},{"timestamp":1608542349373,"event":{"type":"Finished"}}],"maker_amount":"0.1","maker_coin":"ETH","taker_amount":"0.1","taker_coin":"RICK","gui":"nogui","mm_version":"1a6082121","success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"]}"#; let taker_saved_swap: TakerSavedSwap = json::from_str(taker_saved_json).unwrap(); TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); @@ -3074,7 +3121,7 @@ mod taker_swap_tests { fn test_recoverable() { // Swap ended with MakerPaymentWaitConfirmFailed event. // MM2 did not attempt to send the payment in this case so swap is not recoverable. - let swap: TakerSavedSwap = json::from_str(r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.12596566232185483","maker_coin":"KMD","maker_coin_start_block":1458035,"maker_payment_confirmations":1,"maker_payment_wait":1564053079,"my_persistent_pub":"0326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0a","started_at":1564050479,"taker_amount":"50.000000000000001504212457800000","taker_coin":"DOGE","taker_coin_start_block":2823448,"taker_payment_confirmations":1,"taker_payment_lock":1564058279,"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"},"type":"Started"},"timestamp":1564050480269},{"event":{"data":{"maker_payment_locktime":1564066080,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"3669eb83a007a3c507448d79f45a9f06ec2f36a8"},"type":"Negotiated"},"timestamp":1564050540991},{"event":{"data":{"tx_hash":"bdde828b492d6d1cc25cd2322fd592dafd722fcc7d8b0fedce4d3bb4a1a8c8ff","tx_hex":"0100000002c7efa995c8b7be0a8b6c2d526c6c444c1634d65584e9ee89904e9d8675eac88c010000006a473044022051f34d5e3b7d0b9098d5e35333f3550f9cb9e57df83d5e4635b7a8d2986d6d5602200288c98da05de6950e01229a637110a1800ba643e75cfec59d4eb1021ad9b40801210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffffae6c233989efa7c7d2aa6534adc96078917ff395b7f09f734a147b2f44ade164000000006a4730440220393a784c2da74d0e2a28ec4f7df6c8f9d8b2af6ae6957f1e68346d744223a8fd02201b7a96954ac06815a43a6c7668d829ae9cbb5de76fa77189ddfd9e3038df662c01210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff02115f5800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac41a84641020000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac6d84395d"},"type":"TakerFeeSent"},"timestamp":1564050545296},{"event":{"data":{"tx_hash":"0a0f11fa82802c2c30862c50ab2162185dae8de7f7235f32c506f814c142b382","tx_hex":"0400008085202f8902ace337db2dd4c56b0697f58fb8cfb6bd1cd6f469d925fc0376d1dcfb7581bf82000000006b483045022100d1f95be235c5c8880f5d703ace287e2768548792c58c5dbd27f5578881b30ea70220030596106e21c7e0057ee0dab283f9a1fe273f15208cba80870c447bd559ef0d0121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff9f339752567c404427fd77f2b35cecdb4c21489edc64e25e729fdb281785e423000000006a47304402203179e95877dbc107123a417f1e648e3ff13d384890f1e4a67b6dd5087235152e0220102a8ab799fadb26b5d89ceb9c7bc721a7e0c2a0d0d7e46bbe0cf3d130010d430121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff025635c0000000000017a91480a95d366d65e34a465ab17b0c9eb1d5a33bae08876cbfce05000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8d7c395d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1564050588176},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1564050588178},{"event":{"data":{"error":"error"},"type":"MakerPaymentWaitConfirmFailed"},"timestamp":1564051092897},{"event":{"type":"Finished"},"timestamp":1564051092900}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","Finished"],"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"}"#).unwrap(); + let swap: TakerSavedSwap = json::from_str(r#"{"error_events":["StartFailed","NegotiateFailed","TakerFeeSendFailed","MakerPaymentValidateFailed","MakerPaymentWaitConfirmFailed","TakerPaymentTransactionFailed","TakerPaymentWaitConfirmFailed","TakerPaymentDataSendFailed","TakerPaymentWaitForSpendFailed","MakerPaymentSpendFailed","MakerPaymentSpendConfirmFailed","TakerPaymentWaitRefundStarted","TakerPaymentRefunded","TakerPaymentRefundedByWatcher","TakerPaymentRefundFailed"],"events":[{"event":{"data":{"lock_duration":7800,"maker":"1bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","maker_amount":"0.12596566232185483","maker_coin":"KMD","maker_coin_start_block":1458035,"maker_payment_confirmations":1,"maker_payment_wait":1564053079,"my_persistent_pub":"0326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0a","started_at":1564050479,"taker_amount":"50.000000000000001504212457800000","taker_coin":"DOGE","taker_coin_start_block":2823448,"taker_payment_confirmations":1,"taker_payment_lock":1564058279,"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"},"type":"Started"},"timestamp":1564050480269},{"event":{"data":{"maker_payment_locktime":1564066080,"maker_pubkey":"031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8","secret_hash":"3669eb83a007a3c507448d79f45a9f06ec2f36a8"},"type":"Negotiated"},"timestamp":1564050540991},{"event":{"data":{"tx_hash":"bdde828b492d6d1cc25cd2322fd592dafd722fcc7d8b0fedce4d3bb4a1a8c8ff","tx_hex":"0100000002c7efa995c8b7be0a8b6c2d526c6c444c1634d65584e9ee89904e9d8675eac88c010000006a473044022051f34d5e3b7d0b9098d5e35333f3550f9cb9e57df83d5e4635b7a8d2986d6d5602200288c98da05de6950e01229a637110a1800ba643e75cfec59d4eb1021ad9b40801210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffffae6c233989efa7c7d2aa6534adc96078917ff395b7f09f734a147b2f44ade164000000006a4730440220393a784c2da74d0e2a28ec4f7df6c8f9d8b2af6ae6957f1e68346d744223a8fd02201b7a96954ac06815a43a6c7668d829ae9cbb5de76fa77189ddfd9e3038df662c01210326846707a52a233cfc49a61ef51b1698bbe6aa78fa8b8d411c02743c09688f0affffffff02115f5800000000001976a914ca1e04745e8ca0c60d8c5881531d51bec470743f88ac41a84641020000001976a914444f0e1099709ba4d742454a7d98a5c9c162ceab88ac6d84395d"},"type":"TakerFeeSent"},"timestamp":1564050545296},{"event":{"data":{"tx_hash":"0a0f11fa82802c2c30862c50ab2162185dae8de7f7235f32c506f814c142b382","tx_hex":"0400008085202f8902ace337db2dd4c56b0697f58fb8cfb6bd1cd6f469d925fc0376d1dcfb7581bf82000000006b483045022100d1f95be235c5c8880f5d703ace287e2768548792c58c5dbd27f5578881b30ea70220030596106e21c7e0057ee0dab283f9a1fe273f15208cba80870c447bd559ef0d0121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff9f339752567c404427fd77f2b35cecdb4c21489edc64e25e729fdb281785e423000000006a47304402203179e95877dbc107123a417f1e648e3ff13d384890f1e4a67b6dd5087235152e0220102a8ab799fadb26b5d89ceb9c7bc721a7e0c2a0d0d7e46bbe0cf3d130010d430121031bb83b58ec130e28e0a6d5d2acf2eb01b0d3f1670e021d47d31db8a858219da8ffffffff025635c0000000000017a91480a95d366d65e34a465ab17b0c9eb1d5a33bae08876cbfce05000000001976a914c3f710deb7320b0efa6edb14e3ebeeb9155fa90d88ac8d7c395d000000000000000000000000000000"},"type":"MakerPaymentReceived"},"timestamp":1564050588176},{"event":{"type":"MakerPaymentWaitConfirmStarted"},"timestamp":1564050588178},{"event":{"data":{"error":"error"},"type":"MakerPaymentWaitConfirmFailed"},"timestamp":1564051092897},{"event":{"type":"Finished"},"timestamp":1564051092900}],"success_events":["Started","Negotiated","TakerFeeSent","MakerPaymentReceived","MakerPaymentWaitConfirmStarted","MakerPaymentValidatedAndConfirmed","TakerPaymentSent","TakerPaymentSpent","MakerPaymentSpent","MakerPaymentSpendConfirmed","Finished"],"uuid":"41383f43-46a5-478c-9386-3b2cce0aca20"}"#).unwrap(); assert!(!swap.is_recoverable()); } @@ -3153,7 +3200,7 @@ mod taker_swap_tests { #[test] fn locked_amount_should_not_use_paid_from_trading_vol_fee() { - use crate::mm2::lp_swap::get_locked_amount; + use crate::lp_swap::get_locked_amount; let taker_saved_json = r#"{ "type": "Taker", diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index 256503350b..29f3d07277 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -1,16 +1,16 @@ use super::swap_v2_common::*; use super::{LockedAmount, LockedAmountInfo, SavedTradeFee, SwapsContext, TakerSwapPreparedParams, NEGOTIATE_SEND_INTERVAL, NEGOTIATION_TIMEOUT_SEC}; -use crate::mm2::lp_swap::swap_lock::SwapLock; -use crate::mm2::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_taker_swap, recv_swap_v2_msg, swap_v2_topic, - SecretHashAlgo, SwapConfirmationsSettings, TransactionIdentifier, MAX_STARTED_AT_DIFF, - TAKER_SWAP_V2_TYPE}; -use crate::mm2::lp_swap::{swap_v2_pb::*, NO_REFUND_FEE}; +use crate::lp_swap::swap_lock::SwapLock; +use crate::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_taker_swap, recv_swap_v2_msg, swap_v2_topic, + SecretHashAlgo, SwapConfirmationsSettings, TransactionIdentifier, MAX_STARTED_AT_DIFF, + TAKER_SWAP_V2_TYPE}; +use crate::lp_swap::{swap_v2_pb::*, NO_REFUND_FEE}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use coins::{CanRefundHtlc, ConfirmPaymentInput, DexFee, FeeApproxStage, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, MakerCoinSwapOpsV2, MmCoin, ParseCoinAssocTypes, RefundFundingSecretArgs, - RefundPaymentArgs, SendTakerFundingArgs, SpendMakerPaymentArgs, SwapTxTypeWithSecretHash, + RefundTakerPaymentArgs, SendTakerFundingArgs, SpendMakerPaymentArgs, SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, ToBytes, TradeFee, TradePreimageValue, Transaction, TxPreimageWithSig, ValidateMakerPaymentArgs}; use common::executor::abortable_queue::AbortableQueue; @@ -33,14 +33,14 @@ use std::marker::PhantomData; use uuid::Uuid; cfg_native!( - use crate::mm2::database::my_swaps::{insert_new_swap_v2, SELECT_MY_SWAP_V2_BY_UUID}; + use crate::database::my_swaps::{insert_new_swap_v2, SELECT_MY_SWAP_V2_BY_UUID}; use common::async_blocking; use db_common::sqlite::rusqlite::{named_params, Error as SqlError, Result as SqlResult, Row}; use db_common::sqlite::rusqlite::types::Type as SqlType; ); cfg_wasm32!( - use crate::mm2::lp_swap::swap_wasm_db::{MySwapsFiltersTable, SavedSwapTable}; + use crate::lp_swap::swap_wasm_db::{MySwapsFiltersTable, SavedSwapTable}; ); // This is needed to have Debug on messages @@ -389,11 +389,13 @@ pub struct TakerSwapStateMachine, state_machine: &mut Self::StateMachine) -> StateResult { let args = SendTakerFundingArgs { - time_lock: state_machine.taker_funding_locktime(), + funding_time_lock: state_machine.taker_funding_locktime(), + payment_time_lock: state_machine.taker_payment_locktime(), taker_secret_hash: &state_machine.taker_secret_hash(), + maker_secret_hash: &self.negotiation_data.maker_secret_hash, maker_pub: &self.negotiation_data.taker_coin_htlc_pub_from_maker.to_bytes(), dex_fee: &state_machine.dex_fee, premium_amount: state_machine.taker_premium.to_decimal(), @@ -1569,7 +1573,7 @@ impl break, @@ -1889,18 +1896,21 @@ impl tx, diff --git a/mm2src/mm2_main/src/lp_swap/trade_preimage.rs b/mm2src/mm2_main/src/lp_swap/trade_preimage.rs index 7f64b052db..cb31ed63e9 100644 --- a/mm2src/mm2_main/src/lp_swap/trade_preimage.rs +++ b/mm2src/mm2_main/src/lp_swap/trade_preimage.rs @@ -1,6 +1,6 @@ use super::check_balance::CheckBalanceError; use super::{maker_swap_trade_preimage, taker_swap_trade_preimage, MakerTradePreimage, TakerTradePreimage}; -use crate::mm2::lp_ordermatch::{MakerOrderBuildError, TakerOrderBuildError}; +use crate::lp_ordermatch::{MakerOrderBuildError, TakerOrderBuildError}; use coins::{is_wallet_only_ticker, lp_coinfind_or_err, BalanceError, CoinFindError, TradeFee, TradePreimageError}; use common::HttpStatusCode; use crypto::CryptoCtxError; diff --git a/mm2src/mm2_main/src/lp_wallet.rs b/mm2src/mm2_main/src/lp_wallet.rs index e84ea8e98c..559821a26a 100644 --- a/mm2src/mm2_main/src/lp_wallet.rs +++ b/mm2src/mm2_main/src/lp_wallet.rs @@ -2,31 +2,28 @@ use common::HttpStatusCode; use crypto::{decrypt_mnemonic, encrypt_mnemonic, generate_mnemonic, CryptoCtx, CryptoInitError, EncryptedData, MnemonicError}; use http::StatusCode; +use itertools::Itertools; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use serde::de::DeserializeOwned; -use serde_json::{self as json}; +use serde_json::{self as json, Value as Json}; cfg_wasm32! { - use crate::mm2::lp_wallet::mnemonics_wasm_db::{WalletsDb, WalletsDBError}; + use crate::lp_wallet::mnemonics_wasm_db::{WalletsDb, WalletsDBError}; use mm2_core::mm_ctx::from_ctx; use mm2_db::indexed_db::{ConstructibleDb, DbLocked, InitDbResult}; - use mnemonics_wasm_db::{read_encrypted_passphrase_if_available, save_encrypted_passphrase}; + use mnemonics_wasm_db::{read_all_wallet_names, read_encrypted_passphrase_if_available, save_encrypted_passphrase}; use std::sync::Arc; type WalletsDbLocked<'a> = DbLocked<'a, WalletsDb>; } cfg_native! { - use mnemonics_storage::{read_encrypted_passphrase_if_available, save_encrypted_passphrase, WalletsStorageError}; + use mnemonics_storage::{read_all_wallet_names, read_encrypted_passphrase_if_available, save_encrypted_passphrase, WalletsStorageError}; } -#[cfg(not(target_arch = "wasm32"))] -#[path = "lp_wallet/mnemonics_storage.rs"] -mod mnemonics_storage; -#[cfg(target_arch = "wasm32")] -#[path = "lp_wallet/mnemonics_wasm_db.rs"] -mod mnemonics_wasm_db; +#[cfg(not(target_arch = "wasm32"))] mod mnemonics_storage; +#[cfg(target_arch = "wasm32")] mod mnemonics_wasm_db; type WalletInitResult = Result>; @@ -503,3 +500,53 @@ pub async fn get_mnemonic_rpc(ctx: MmArc, req: GetMnemonicRequest) -> MmResult, + activated_wallet: Option, +} + +#[derive(Debug, Display, Serialize, SerializeErrorType)] +#[serde(tag = "error_type", content = "error_data")] +pub enum GetWalletsError { + #[display(fmt = "Wallets storage error: {}", _0)] + WalletsStorageError(String), + #[display(fmt = "Internal error: {}", _0)] + Internal(String), +} + +impl HttpStatusCode for GetWalletsError { + fn status_code(&self) -> StatusCode { + match self { + GetWalletsError::WalletsStorageError(_) | GetWalletsError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl From for GetWalletsError { + fn from(e: WalletsStorageError) -> Self { GetWalletsError::WalletsStorageError(e.to_string()) } +} + +#[cfg(target_arch = "wasm32")] +impl From for GetWalletsError { + fn from(e: WalletsDBError) -> Self { GetWalletsError::WalletsStorageError(e.to_string()) } +} + +/// Retrieves all created wallets and the currently activated wallet. +pub async fn get_wallet_names_rpc(ctx: MmArc, _req: Json) -> MmResult { + // We want to return wallet names in the same order for both native and wasm32 targets. + let wallets = read_all_wallet_names(&ctx).await?.sorted().collect(); + // Note: `ok_or` is used here on `Constructible>` to handle the case where the wallet name is not set. + // `wallet_name` can be `None` in the case of no-login mode. + let activated_wallet = ctx.wallet_name.ok_or(GetWalletsError::Internal( + "`wallet_name` not initialized yet!".to_string(), + ))?; + + Ok(GetWalletNamesResponse { + wallet_names: wallets, + activated_wallet: activated_wallet.clone(), + }) +} diff --git a/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs b/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs index 3cf40e61fb..c873b2d5ff 100644 --- a/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs +++ b/mm2src/mm2_main/src/lp_wallet/mnemonics_storage.rs @@ -1,7 +1,7 @@ use crypto::EncryptedData; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; -use mm2_io::fs::ensure_file_is_writable; +use mm2_io::fs::{ensure_file_is_writable, list_files_by_extension}; type WalletsStorageResult = Result>; @@ -61,3 +61,10 @@ pub(super) async fn read_encrypted_passphrase_if_available(ctx: &MmArc) -> Walle )) }) } + +pub(super) async fn read_all_wallet_names(ctx: &MmArc) -> WalletsStorageResult> { + let wallet_names = list_files_by_extension(&ctx.db_root(), "dat", false) + .await + .mm_err(|e| WalletsStorageError::FsReadError(format!("Error reading wallets directory: {}", e)))?; + Ok(wallet_names) +} diff --git a/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs b/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs index f7bf2674e4..a815bfcca1 100644 --- a/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs +++ b/mm2src/mm2_main/src/lp_wallet/mnemonics_wasm_db.rs @@ -1,4 +1,4 @@ -use crate::mm2::lp_wallet::WalletsContext; +use crate::lp_wallet::WalletsContext; use async_trait::async_trait; use crypto::EncryptedData; use mm2_core::mm_ctx::MmArc; @@ -144,3 +144,16 @@ pub(super) async fn read_encrypted_passphrase_if_available(ctx: &MmArc) -> Walle }) .transpose() } + +pub(super) async fn read_all_wallet_names(ctx: &MmArc) -> WalletsDBResult> { + let wallets_ctx = WalletsContext::from_ctx(ctx).map_to_mm(WalletsDBError::Internal)?; + + let db = wallets_ctx.wallets_db().await?; + let transaction = db.transaction().await?; + let table = transaction.table::().await?; + + let all_items = table.get_all_items().await?; + let wallet_names = all_items.into_iter().map(|(_, item)| item.wallet_name); + + Ok(wallet_names) +} diff --git a/mm2src/mm2_main/src/mm2.rs b/mm2src/mm2_main/src/mm2.rs index 52412b6ec7..dd7c7bed27 100644 --- a/mm2src/mm2_main/src/mm2.rs +++ b/mm2src/mm2_main/src/mm2.rs @@ -21,12 +21,28 @@ // Copyright © 2023 Pampex LTD and TillyHK LTD. All rights reserved. // +#![feature(hash_raw_entry)] +// `mockable` implementation uses these +#![allow( + forgetting_references, + forgetting_copy_types, + clippy::swap_ptr_to_ref, + clippy::forget_non_drop, + clippy::let_unit_value +)] #![cfg_attr(target_arch = "wasm32", allow(dead_code))] #![cfg_attr(target_arch = "wasm32", allow(unused_imports))] +#[macro_use] extern crate common; +#[macro_use] extern crate gstuff; +#[macro_use] extern crate serde_json; +#[macro_use] extern crate serde_derive; +#[macro_use] extern crate ser_error_derive; +#[cfg(test)] extern crate mm2_test_helpers; + #[cfg(not(target_arch = "wasm32"))] use common::block_on; use common::crash_reports::init_crash_reports; -use common::double_panic_crash; +use common::log; use common::log::LogLevel; use common::password_policy::password_policy; use mm2_core::mm_ctx::MmCtxBuilder; @@ -38,7 +54,6 @@ use lp_swap::PAYMENT_LOCKTIME; use std::sync::atomic::Ordering; use gstuff::slurp; - use serde::ser::Serialize; use serde_json::{self as json, Value as Json}; @@ -48,25 +63,25 @@ use std::process::exit; use std::ptr::null; use std::str; -#[path = "lp_native_dex.rs"] mod lp_native_dex; pub use self::lp_native_dex::init_hw; pub use self::lp_native_dex::lp_init; use coins::update_coins_config; use mm2_err_handle::prelude::*; -#[cfg(not(target_arch = "wasm32"))] -#[path = "database.rs"] -pub mod database; - -#[path = "heartbeat_event.rs"] pub mod heartbeat_event; -#[path = "lp_dispatcher.rs"] pub mod lp_dispatcher; -#[path = "lp_message_service.rs"] pub mod lp_message_service; -#[path = "lp_network.rs"] pub mod lp_network; -#[path = "lp_ordermatch.rs"] pub mod lp_ordermatch; -#[path = "lp_stats.rs"] pub mod lp_stats; -#[path = "lp_swap.rs"] pub mod lp_swap; -#[path = "lp_wallet.rs"] pub mod lp_wallet; -#[path = "rpc.rs"] pub mod rpc; +#[cfg(not(target_arch = "wasm32"))] pub mod database; + +pub mod heartbeat_event; +pub mod lp_dispatcher; +pub mod lp_healthcheck; +pub mod lp_message_service; +mod lp_native_dex; +pub mod lp_network; +pub mod lp_ordermatch; +pub mod lp_stats; +pub mod lp_swap; +pub mod lp_wallet; +pub mod rpc; +#[cfg(all(target_arch = "wasm32", test))] mod wasm_tests; pub const PASSWORD_MAXIMUM_CONSECUTIVE_CHARACTERS: usize = 3; @@ -145,10 +160,33 @@ pub async fn lp_main( .with_datetime(datetime.clone()) .into_mm_arc(); ctx_cb(try_s!(ctx.ffi_handle())); + + #[cfg(not(target_arch = "wasm32"))] + spawn_ctrl_c_handler(ctx.clone()); + try_s!(lp_init(ctx, version, datetime).await); Ok(()) } +/// Handles CTRL-C signals and shutdowns the KDF runtime gracefully. +/// +/// It's important to spawn this task as soon as `Ctx` is in the correct state. +#[cfg(not(target_arch = "wasm32"))] +fn spawn_ctrl_c_handler(ctx: mm2_core::mm_ctx::MmArc) { + use crate::lp_dispatcher::{dispatch_lp_event, StopCtxEvent}; + + common::executor::spawn(async move { + tokio::signal::ctrl_c() + .await + .expect("Couldn't listen for the CTRL-C signal."); + + log::info!("Wrapping things up and shutting down..."); + + dispatch_lp_event(ctx.clone(), StopCtxEvent.into()).await; + ctx.stop().await.expect("Couldn't stop the KDF runtime."); + }); +} + fn help() { const HELP_MSG: &str = r#"Command-line options. The first command-line argument is special and designates the mode. @@ -166,7 +204,6 @@ Some (but not all) of the JSON configuration parameters (* - required): {"coins": [{"name": "dash", "coin": "DASH", ...}, ...], ...}. coins .. Information about the currencies: their ticker symbols, names, ports, addresses, etc. If the field isn't present on the command line then we try loading it from the 'coins' file. - crash .. Simulate a crash to check how the crash handling works. dbdir .. MM database path. 'DB' by default. gui .. The information about GUI app using KDF instance. Included in swap statuses shared with network. .. It's recommended to put essential info to this field (application name, OS, version, etc). @@ -175,7 +212,6 @@ Some (but not all) of the JSON configuration parameters (* - required): netid .. Subnetwork. Affects ports and keys. passphrase * .. Wallet seed. Compressed WIFs and hexadecimal ECDSA keys (prefixed with 0x) are also accepted. - panic .. Simulate a panic to see if backtrace works. rpccors .. Access-Control-Allow-Origin header value to be used in all the RPC responses. Default is currently 'http://localhost:3000' rpcip .. IP address to bind to for RPC server. Overrides the 127.0.0.1 default @@ -188,7 +224,6 @@ Some (but not all) of the JSON configuration parameters (* - required): Defaults to `false`. seednodes .. Seednode IPs that node will use. At least one seed IP must be present if the node is not a seed itself. - stderr .. Print a message to stderr and exit. wif .. `1` to add WIFs to the information we provide about a coin. Environment variables: @@ -233,16 +268,6 @@ pub fn mm2_main(version: String, datetime: String) { // we're not checking them for the mode switches in order not to risk [untrusted] data being mistaken for a mode switch. let first_arg = args_os.get(1).and_then(|arg| arg.to_str()); - if first_arg == Some("panic") { - panic!("panic message") - } - if first_arg == Some("crash") { - double_panic_crash() - } - if first_arg == Some("stderr") { - eprintln!("This goes to stderr"); - return; - } if first_arg == Some("update_config") { match on_update_config(&args_os) { Ok(_) => println!("Success"), diff --git a/mm2src/mm2_main/src/notification/telegram/telegram.rs b/mm2src/mm2_main/src/notification/telegram/telegram.rs index edfc5c15f7..3879920767 100644 --- a/mm2src/mm2_main/src/notification/telegram/telegram.rs +++ b/mm2src/mm2_main/src/notification/telegram/telegram.rs @@ -1,4 +1,4 @@ -use crate::mm2::lp_message_service::{MessageResult, MessageServiceTraits}; +use crate::lp_message_service::{MessageResult, MessageServiceTraits}; use async_trait::async_trait; use derive_more::Display; use mm2_net::transport::{post_json, SlurpError}; @@ -58,8 +58,8 @@ impl MessageServiceTraits for TgClient { #[cfg(all(test, not(target_arch = "wasm32")))] mod telegram_tests { - use crate::mm2::lp_message_service::telegram::{ChatIdRegistry, TgClient}; - use crate::mm2::lp_message_service::MessageServiceTraits; + use crate::lp_message_service::telegram::{ChatIdRegistry, TgClient}; + use crate::lp_message_service::MessageServiceTraits; use common::block_on; use std::env::var; diff --git a/mm2src/mm2_main/src/ordermatch_tests.rs b/mm2src/mm2_main/src/ordermatch_tests.rs index b0657b0d00..1ac83697af 100644 --- a/mm2src/mm2_main/src/ordermatch_tests.rs +++ b/mm2src/mm2_main/src/ordermatch_tests.rs @@ -1,14 +1,17 @@ use super::*; -use crate::mm2::lp_ordermatch::new_protocol::{MakerOrderUpdated, PubkeyKeepAlive}; +use crate::lp_ordermatch::new_protocol::{MakerOrderUpdated, PubkeyKeepAlive}; use coins::{MmCoin, TestCoin}; use common::{block_on, executor::spawn}; use crypto::privkey::key_pair_from_seed; use db_common::sqlite::rusqlite::Connection; use futures::{channel::mpsc, StreamExt}; use mm2_core::mm_ctx::{MmArc, MmCtx}; +use mm2_libp2p::application::request_response::ordermatch::OrdermatchRequest; +use mm2_libp2p::application::request_response::P2PRequest; +use mm2_libp2p::behaviours::atomicdex::generate_ed25519_keypair; +use mm2_libp2p::p2p_ctx::P2PContext; use mm2_libp2p::AdexBehaviourCmd; use mm2_libp2p::{decode_message, PeerId}; -use mm2_net::p2p::P2PContext; use mm2_test_helpers::for_tests::mm_ctx_with_iguana; use mocktopus::mocking::*; use rand::{seq::SliceRandom, thread_rng, Rng}; @@ -938,7 +941,14 @@ fn test_taker_order_cancellable() { fn prepare_for_cancel_by(ctx: &MmArc) -> mpsc::Receiver { let (tx, rx) = mpsc::channel(10); - let p2p_ctx = P2PContext::new(tx); + + let p2p_key = { + let crypto_ctx = CryptoCtx::from_ctx(ctx).unwrap(); + let key = bitcrypto::sha256(crypto_ctx.mm2_internal_privkey_slice()); + key.take() + }; + + let p2p_ctx = P2PContext::new(tx, generate_ed25519_keypair(p2p_key)); p2p_ctx.store_to_mm_arc(ctx); let ordermatch_ctx = OrdermatchContext::from_ctx(ctx).unwrap(); @@ -1671,7 +1681,14 @@ fn pubkey_and_secret_for_test(passphrase: &str) -> (String, [u8; 32]) { fn init_p2p_context(ctx: &MmArc) -> (mpsc::Sender, mpsc::Receiver) { let (cmd_tx, cmd_rx) = mpsc::channel(10); - let p2p_context = P2PContext::new(cmd_tx.clone()); + + let p2p_key = { + let crypto_ctx = CryptoCtx::from_ctx(ctx).unwrap(); + let key = bitcrypto::sha256(crypto_ctx.mm2_internal_privkey_slice()); + key.take() + }; + + let p2p_context = P2PContext::new(cmd_tx.clone(), generate_ed25519_keypair(p2p_key)); p2p_context.store_to_mm_arc(ctx); (cmd_tx, cmd_rx) } diff --git a/mm2src/mm2_main/src/rpc.rs b/mm2src/mm2_main/src/rpc.rs index 92422738f9..1f0afd3234 100644 --- a/mm2src/mm2_main/src/rpc.rs +++ b/mm2src/mm2_main/src/rpc.rs @@ -20,7 +20,7 @@ // Copyright © 2023 Pampex LTD and TillyHK LTD. All rights reserved. // -use crate::mm2::rpc::rate_limiter::RateLimitError; +use crate::rpc::rate_limiter::RateLimitError; use common::log::{error, info}; use common::{err_to_rpc_json_string, err_tp_rpc_json, HttpStatusCode, APPLICATION_JSON}; use derive_more::Display; @@ -28,28 +28,24 @@ use futures::future::{join_all, FutureExt}; use http::header::{HeaderValue, ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE}; use http::request::Parts; use http::{Method, Request, Response, StatusCode}; -use lazy_static::lazy_static; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use mm2_rpc::mm_protocol::{MmRpcBuilder, MmRpcResponse, MmRpcVersion}; -use regex::Regex; use serde::Serialize; use serde_json::{self as json, Value as Json}; -use std::borrow::Cow; use std::net::SocketAddr; cfg_native! { use hyper::{self, Body, Server}; + use futures::channel::oneshot; use mm2_net::sse_handler::{handle_sse, SSE_ENDPOINT}; } #[path = "rpc/dispatcher/dispatcher.rs"] mod dispatcher; #[path = "rpc/dispatcher/dispatcher_legacy.rs"] mod dispatcher_legacy; -#[path = "rpc/lp_commands/lp_commands.rs"] pub mod lp_commands; -#[path = "rpc/lp_commands/lp_commands_legacy.rs"] -pub mod lp_commands_legacy; -#[path = "rpc/rate_limiter.rs"] mod rate_limiter; +pub mod lp_commands; +mod rate_limiter; /// Lists the RPC method not requiring the "userpass" authentication. /// None is also public to skip auth and display proper error in case of method is missing @@ -178,35 +174,6 @@ fn response_from_dispatcher_error( response.serialize_http_response() } -pub fn escape_answer<'a, S: Into>>(input: S) -> Cow<'a, str> { - lazy_static! { - static ref REGEX: Regex = Regex::new("[<>&]").unwrap(); - } - - let input = input.into(); - let mut last_match = 0; - - if REGEX.is_match(&input) { - let matches = REGEX.find_iter(&input); - let mut output = String::with_capacity(input.len()); - for mat in matches { - let (begin, end) = (mat.start(), mat.end()); - output.push_str(&input[last_match..begin]); - match &input[begin..end] { - "<" => output.push_str("<"), - ">" => output.push_str(">"), - "&" => output.push_str("&"), - _ => unreachable!(), - } - last_match = end; - } - output.push_str(&input[last_match..]); - Cow::Owned(output) - } else { - input - } -} - async fn process_single_request(ctx: MmArc, req: Json, client: SocketAddr) -> Result>, String> { let local_only = ctx.conf["rpc_local_only"].as_bool().unwrap_or(true); if req["mmrpc"].is_null() { @@ -236,6 +203,8 @@ async fn process_single_request(ctx: MmArc, req: Json, client: SocketAddr) -> Re #[cfg(not(target_arch = "wasm32"))] async fn rpc_service(req: Request, ctx_h: u32, client: SocketAddr) -> Response { + const NON_ALLOWED_CHARS: &[char] = &['<', '>', '&']; + /// Unwraps a result or propagates its error 500 response with the specified headers (if they are present). macro_rules! try_sf { ($value: expr $(, $header_key:expr => $header_val:expr)*) => { @@ -269,40 +238,52 @@ async fn rpc_service(req: Request, ctx_h: u32, client: SocketAddr) -> Resp // https://github.com/artemii235/SuperNET/issues/219 let rpc_cors = match ctx.conf["rpccors"].as_str() { Some(s) => try_sf!(HeaderValue::from_str(s)), - None => HeaderValue::from_static("http://localhost:3000"), + None => { + if ctx.is_https() { + HeaderValue::from_static("https://localhost:3000") + } else { + HeaderValue::from_static("http://localhost:3000") + } + }, }; // Convert the native Hyper stream into a portable stream of `Bytes`. let (req, req_body) = req.into_parts(); - let req_bytes = try_sf!(hyper::body::to_bytes(req_body).await, ACCESS_CONTROL_ALLOW_ORIGIN => rpc_cors); - let req_str = String::from_utf8_lossy(req_bytes.as_ref()); - let is_invalid_input = req_str.chars().any(|c| c == '<' || c == '>' || c == '&'); - if is_invalid_input { + + if req.method == Method::OPTIONS { return Response::builder() - .status(500) - .header(CONTENT_TYPE, APPLICATION_JSON) - .body(Body::from(err_to_rpc_json_string("Invalid input"))) + .status(StatusCode::OK) + .header(ACCESS_CONTROL_ALLOW_ORIGIN, rpc_cors) + .header("Access-Control-Allow-Methods", "POST, OPTIONS") + .header("Access-Control-Allow-Headers", "Content-Type") + .header("Access-Control-Max-Age", "3600") + .body(Body::empty()) .unwrap(); } - let req_json: Json = try_sf!(json::from_slice(&req_bytes), ACCESS_CONTROL_ALLOW_ORIGIN => rpc_cors); - let res = try_sf!(process_rpc_request(ctx, req, req_json, client).await, ACCESS_CONTROL_ALLOW_ORIGIN => rpc_cors); - let (mut parts, body) = res.into_parts(); - parts.headers.insert(ACCESS_CONTROL_ALLOW_ORIGIN, rpc_cors); - let body_escaped = match std::str::from_utf8(&body) { - Ok(body_utf8) => { - let escaped = escape_answer(body_utf8); - escaped.as_bytes().to_vec() - }, - Err(_) => { + let req_json = { + let req_bytes = try_sf!(hyper::body::to_bytes(req_body).await, ACCESS_CONTROL_ALLOW_ORIGIN => rpc_cors); + let req_str = String::from_utf8_lossy(req_bytes.as_ref()); + let is_invalid_input = req_str.chars().any(|c| NON_ALLOWED_CHARS.contains(&c)); + if is_invalid_input { return Response::builder() .status(500) + .header(ACCESS_CONTROL_ALLOW_ORIGIN, rpc_cors) .header(CONTENT_TYPE, APPLICATION_JSON) - .body(Body::from(err_to_rpc_json_string("Non UTF-8 output"))) + .body(Body::from(err_to_rpc_json_string(&format!( + "Invalid input: contains one or more of the following non-allowed characters: {:?}", + NON_ALLOWED_CHARS + )))) .unwrap(); - }, + } + try_sf!(json::from_slice(&req_bytes), ACCESS_CONTROL_ALLOW_ORIGIN => rpc_cors) }; - Response::from_parts(parts, Body::from(body_escaped)) + + let res = try_sf!(process_rpc_request(ctx, req, req_json, client).await, ACCESS_CONTROL_ALLOW_ORIGIN => rpc_cors); + let (mut parts, body) = res.into_parts(); + parts.headers.insert(ACCESS_CONTROL_ALLOW_ORIGIN, rpc_cors); + + Response::from_parts(parts, Body::from(body)) } // TODO: This should exclude TCP internals, as including them results in having to @@ -351,6 +332,34 @@ pub extern "C" fn spawn_rpc(ctx_h: u32) { Ok((cert_chain, privkey)) } + // Handles incoming HTTP requests. + async fn handle_request( + req: Request, + remote_addr: SocketAddr, + ctx_h: u32, + is_event_stream_enabled: bool, + ) -> Result, Infallible> { + let (tx, rx) = oneshot::channel(); + // We execute the request in a separate task to avoid it being left uncompleted if the client disconnects. + // So what's inside the spawn here will complete till completion (or panic). + common::executor::spawn(async move { + if is_event_stream_enabled && req.uri().path() == SSE_ENDPOINT { + tx.send(handle_sse(req, ctx_h).await).ok(); + } else { + tx.send(rpc_service(req, ctx_h, remote_addr).await).ok(); + } + }); + // On the other hand, this `.await` might be aborted if the client disconnects. + match rx.await { + Ok(res) => Ok(res), + Err(_) => { + let err = "The RPC service aborted without responding."; + error!("{}", err); + Ok(Response::builder().status(500).body(Body::from(err)).unwrap()) + }, + } + } + // NB: We need to manually handle the incoming connections in order to get the remote IP address, // cf. https://github.com/hyperium/hyper/issues/1410#issuecomment-419510220. // Although if the ability to access the remote IP address is solved by the Hyper in the future @@ -358,28 +367,19 @@ pub extern "C" fn spawn_rpc(ctx_h: u32) { // cf. https://github.com/hyperium/hyper/pull/1640. let ctx = MmArc::from_ffi_handle(ctx_h).expect("No context"); - let is_event_stream_enabled = ctx.event_stream_configuration.is_some(); - let make_svc_fut = move |remote_addr: SocketAddr| async move { - Ok::<_, Infallible>(service_fn(move |req: Request| async move { - if is_event_stream_enabled && req.uri().path() == SSE_ENDPOINT { - let res = handle_sse(req, ctx_h).await?; - return Ok::<_, Infallible>(res); - } - - let res = rpc_service(req, ctx_h, remote_addr).await; - Ok::<_, Infallible>(res) - })) - }; - //The `make_svc` macro creates a `make_service_fn` for a specified socket type. // `$socket_type`: The socket type with a `remote_addr` method that returns a `SocketAddr`. macro_rules! make_svc { ($socket_type:ty) => { make_service_fn(move |socket: &$socket_type| { let remote_addr = socket.remote_addr(); - make_svc_fut(remote_addr) + async move { + Ok::<_, Infallible>(service_fn(move |req: Request| { + handle_request(req, remote_addr, ctx_h, is_event_stream_enabled) + })) + } }) }; } diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index 271fbf48cc..6b68666d6b 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -1,16 +1,20 @@ use super::{DispatcherError, DispatcherResult, PUBLIC_METHODS}; -use crate::mm2::lp_native_dex::init_hw::{cancel_init_trezor, init_trezor, init_trezor_status, init_trezor_user_action}; +use crate::lp_healthcheck::peer_connection_healthcheck_rpc; +use crate::lp_native_dex::init_hw::{cancel_init_trezor, init_trezor, init_trezor_status, init_trezor_user_action}; #[cfg(target_arch = "wasm32")] -use crate::mm2::lp_native_dex::init_metamask::{cancel_connect_metamask, connect_metamask, connect_metamask_status}; -use crate::mm2::lp_ordermatch::{best_orders_rpc_v2, orderbook_rpc_v2, start_simple_market_maker_bot, - stop_simple_market_maker_bot}; -use crate::mm2::lp_swap::swap_v2_rpcs::{active_swaps_rpc, my_recent_swaps_rpc, my_swap_status_rpc}; -use crate::mm2::lp_wallet::get_mnemonic_rpc; -use crate::mm2::rpc::rate_limiter::{process_rate_limit, RateLimitContext}; -use crate::{mm2::lp_stats::{add_node_to_version_stat, remove_node_from_version_stat, start_version_stat_collection, - stop_version_stat_collection, update_version_stat_collection}, - mm2::lp_swap::{get_locked_amount_rpc, max_maker_vol, recreate_swap_data, trade_preimage_rpc}, - mm2::rpc::lp_commands::{get_public_key, get_public_key_hash, get_shared_db_id, trezor_connection_status}}; +use crate::lp_native_dex::init_metamask::{cancel_connect_metamask, connect_metamask, connect_metamask_status}; +use crate::lp_ordermatch::{best_orders_rpc_v2, orderbook_rpc_v2, start_simple_market_maker_bot, + stop_simple_market_maker_bot}; +use crate::lp_stats::{add_node_to_version_stat, remove_node_from_version_stat, start_version_stat_collection, + stop_version_stat_collection, update_version_stat_collection}; +use crate::lp_swap::swap_v2_rpcs::{active_swaps_rpc, my_recent_swaps_rpc, my_swap_status_rpc}; +use crate::lp_swap::{get_locked_amount_rpc, max_maker_vol, recreate_swap_data, trade_preimage_rpc}; +use crate::lp_wallet::{get_mnemonic_rpc, get_wallet_names_rpc}; +use crate::rpc::lp_commands::db_id::get_shared_db_id; +use crate::rpc::lp_commands::pubkey::*; +use crate::rpc::lp_commands::tokens::get_token_info; +use crate::rpc::lp_commands::trezor::trezor_connection_status; +use crate::rpc::rate_limiter::{process_rate_limit, RateLimitContext}; use coins::eth::EthCoin; use coins::my_tx_history_v2::my_tx_history_v2_rpc; use coins::rpc_command::tendermint::{ibc_chains, ibc_transfer_channels}; @@ -28,7 +32,7 @@ use coins::rpc_command::{account_balance::account_balance, init_scan_for_new_addresses::{cancel_scan_for_new_addresses, init_scan_for_new_addresses, init_scan_for_new_addresses_status}, init_withdraw::{cancel_withdraw, init_withdraw, withdraw_status, withdraw_user_action}}; -#[cfg(feature = "enable-sia")] use coins::sia::SiaCoin; +#[cfg(feature = "enable-sia")] use coins::siacoin::SiaCoin; use coins::tendermint::{TendermintCoin, TendermintToken}; use coins::utxo::bch::BchCoin; use coins::utxo::qtum::QtumCoin; @@ -38,13 +42,6 @@ use coins::z_coin::ZCoin; use coins::{add_delegation, get_my_address, get_raw_transaction, get_staking_infos, get_swap_transaction_fee_policy, nft, remove_delegation, set_swap_transaction_fee_policy, sign_message, sign_raw_transaction, verify_message, withdraw}; -#[cfg(all( - feature = "enable-solana", - not(target_os = "ios"), - not(target_os = "android"), - not(target_arch = "wasm32") -))] -use coins::{SolanaCoin, SplToken}; use coins_activation::{cancel_init_l2, cancel_init_platform_coin_with_tokens, cancel_init_standalone_coin, cancel_init_token, enable_platform_coin_with_tokens, enable_token, init_l2, init_l2_status, init_l2_user_action, init_platform_coin_with_tokens, init_platform_coin_with_tokens_status, @@ -190,6 +187,8 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, get_raw_transaction).await, "get_shared_db_id" => handle_mmrpc(ctx, request, get_shared_db_id).await, "get_staking_infos" => handle_mmrpc(ctx, request, get_staking_infos).await, + "get_token_info" => handle_mmrpc(ctx, request, get_token_info).await, + "get_wallet_names" => handle_mmrpc(ctx, request, get_wallet_names_rpc).await, "max_maker_vol" => handle_mmrpc(ctx, request, max_maker_vol).await, "my_recent_swaps" => handle_mmrpc(ctx, request, my_recent_swaps_rpc).await, "my_swap_status" => handle_mmrpc(ctx, request, my_swap_status_rpc).await, @@ -213,6 +212,7 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, withdraw).await, "ibc_chains" => handle_mmrpc(ctx, request, ibc_chains).await, "ibc_transfer_channels" => handle_mmrpc(ctx, request, ibc_transfer_channels).await, + "peer_connection_healthcheck" => handle_mmrpc(ctx, request, peer_connection_healthcheck_rpc).await, "withdraw_nft" => handle_mmrpc(ctx, request, withdraw_nft).await, "start_eth_fee_estimator" => handle_mmrpc(ctx, request, start_eth_fee_estimator).await, "stop_eth_fee_estimator" => handle_mmrpc(ctx, request, stop_eth_fee_estimator).await, @@ -221,17 +221,6 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, set_swap_transaction_fee_policy).await, "send_asked_data" => handle_mmrpc(ctx, request, send_asked_data_rpc).await, "z_coin_tx_history" => handle_mmrpc(ctx, request, coins::my_tx_history_v2::z_coin_tx_history_rpc).await, - #[cfg(not(target_arch = "wasm32"))] - native_only_methods => match native_only_methods { - #[cfg(all(feature = "enable-solana", not(target_os = "ios"), not(target_os = "android")))] - "enable_solana_with_tokens" => { - handle_mmrpc(ctx, request, enable_platform_coin_with_tokens::).await - }, - #[cfg(all(feature = "enable-solana", not(target_os = "ios"), not(target_os = "android")))] - "enable_spl" => handle_mmrpc(ctx, request, enable_token::).await, - _ => MmError::err(DispatcherError::NoSuchMethod), - }, - #[cfg(target_arch = "wasm32")] _ => MmError::err(DispatcherError::NoSuchMethod), } } diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher_legacy.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher_legacy.rs index 795b76cee0..5f4b14f8b4 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher_legacy.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher_legacy.rs @@ -7,14 +7,14 @@ use mm2_core::mm_ctx::MmArc; use serde_json::{self as json, Value as Json}; use std::net::SocketAddr; -use super::lp_commands_legacy::*; -use crate::mm2::lp_ordermatch::{best_orders_rpc, buy, cancel_all_orders_rpc, cancel_order_rpc, my_orders, - order_status, orderbook_depth_rpc, orderbook_rpc, orders_history_by_filter, sell, - set_price, update_maker_order_rpc}; -use crate::mm2::lp_swap::{active_swaps_rpc, all_swaps_uuids_by_filter, ban_pubkey_rpc, coins_needed_for_kick_start, - import_swaps, list_banned_pubkeys_rpc, max_taker_vol, my_recent_swaps_rpc, my_swap_status, - recover_funds_of_swap, stats_swap_status, unban_pubkeys_rpc}; -use crate::mm2::rpc::rate_limiter::{process_rate_limit, RateLimitContext}; +use super::lp_commands::legacy::*; +use crate::lp_ordermatch::{best_orders_rpc, buy, cancel_all_orders_rpc, cancel_order_rpc, my_orders, order_status, + orderbook_depth_rpc, orderbook_rpc, orders_history_by_filter, sell, set_price, + update_maker_order_rpc}; +use crate::lp_swap::{active_swaps_rpc, all_swaps_uuids_by_filter, ban_pubkey_rpc, coins_needed_for_kick_start, + import_swaps, list_banned_pubkeys_rpc, max_taker_vol, my_recent_swaps_rpc, my_swap_status, + recover_funds_of_swap, stats_swap_status, unban_pubkeys_rpc}; +use crate::rpc::rate_limiter::{process_rate_limit, RateLimitContext}; use coins::{convert_address, convert_utxo_address, get_enabled_coins, get_trade_fee, kmd_rewards_info, my_tx_history, send_raw_transaction, set_required_confirmations, set_requires_notarization, show_priv_key, validate_address}; @@ -73,11 +73,11 @@ pub fn dispatcher(req: Json, ctx: MmArc) -> DispatcherRes { "electrum" => hyres(electrum(ctx, req)), "enable" => hyres(enable(ctx, req)), "get_enabled_coins" => hyres(get_enabled_coins(ctx)), + "get_directly_connected_peers" => hyres(get_directly_connected_peers(ctx)), "get_gossip_mesh" => hyres(get_gossip_mesh(ctx)), "get_gossip_peer_topics" => hyres(get_gossip_peer_topics(ctx)), "get_gossip_topic_peers" => hyres(get_gossip_topic_peers(ctx)), "get_my_peer_id" => hyres(get_my_peer_id(ctx)), - "get_peers_info" => hyres(get_peers_info(ctx)), "get_relay_mesh" => hyres(get_relay_mesh(ctx)), "get_trade_fee" => hyres(get_trade_fee(ctx, req)), // "fundvalue" => lp_fundvalue (ctx, req, false), @@ -98,7 +98,6 @@ pub fn dispatcher(req: Json, ctx: MmArc) -> DispatcherRes { "order_status" => hyres(order_status(ctx, req)), "orderbook" => hyres(orderbook_rpc(ctx, req)), "orderbook_depth" => hyres(orderbook_depth_rpc(ctx, req)), - "sim_panic" => hyres(sim_panic(req)), "recover_funds_of_swap" => hyres(recover_funds_of_swap(ctx, req)), "sell" => hyres(sell(ctx, req)), "show_priv_key" => hyres(show_priv_key(ctx, req)), @@ -144,7 +143,7 @@ pub async fn process_single_request( /// The set of functions that convert the result of the updated handlers into the legacy format. mod into_legacy { use super::*; - use crate::mm2::lp_swap; + use crate::lp_swap; pub async fn withdraw(ctx: MmArc, req: Json) -> Result>, String> { let params = try_s!(json::from_value(req)); diff --git a/mm2src/mm2_main/src/rpc/lp_commands/db_id.rs b/mm2src/mm2_main/src/rpc/lp_commands/db_id.rs new file mode 100644 index 0000000000..29fa399bd0 --- /dev/null +++ b/mm2src/mm2_main/src/rpc/lp_commands/db_id.rs @@ -0,0 +1,18 @@ +use crate::rpc::lp_commands::pubkey::GetPublicKeyError; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::mm_error::MmError; +use rpc::v1::types::H160 as H160Json; +use serde_json::Value as Json; + +pub type GetSharedDbIdResult = Result>; +pub type GetSharedDbIdError = GetPublicKeyError; + +#[derive(Serialize)] +pub struct GetSharedDbIdResponse { + shared_db_id: H160Json, +} + +pub async fn get_shared_db_id(ctx: MmArc, _req: Json) -> GetSharedDbIdResult { + let shared_db_id = ctx.shared_db_id().to_owned().into(); + Ok(GetSharedDbIdResponse { shared_db_id }) +} diff --git a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs b/mm2src/mm2_main/src/rpc/lp_commands/legacy.rs similarity index 87% rename from mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs rename to mm2src/mm2_main/src/rpc/lp_commands/legacy.rs index 7e61b8c642..828e145f23 100644 --- a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands_legacy.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/legacy.rs @@ -20,25 +20,24 @@ // use coins::{lp_coinfind, lp_coinfind_any, lp_coininit, CoinsContext, MmCoinEnum}; +use common::custom_futures::timeout::FutureTimerExt; use common::executor::Timer; -use common::log::error; use common::{rpc_err_response, rpc_response, HyRes}; use futures::compat::Future01CompatExt; use http::Response; use mm2_core::mm_ctx::MmArc; +use mm2_libp2p::p2p_ctx::P2PContext; use mm2_metrics::MetricsOps; -use mm2_net::p2p::P2PContext; use mm2_number::construct_detailed; use mm2_rpc::data::legacy::{BalanceResponse, CoinInitResponse, Mm2RpcResult, MmVersionResponse, Status}; use serde_json::{self as json, Value as Json}; -use std::borrow::Cow; use std::collections::HashSet; use uuid::Uuid; -use crate::mm2::lp_dispatcher::{dispatch_lp_event, StopCtxEvent}; -use crate::mm2::lp_network::subscribe_to_topic; -use crate::mm2::lp_ordermatch::{cancel_orders_by, get_matching_orders, CancelBy}; -use crate::mm2::lp_swap::{active_swaps_using_coins, tx_helper_topic, watcher_topic}; +use crate::lp_dispatcher::{dispatch_lp_event, StopCtxEvent}; +use crate::lp_network::subscribe_to_topic; +use crate::lp_ordermatch::{cancel_orders_by, get_matching_orders, CancelBy}; +use crate::lp_swap::{active_swaps_using_coins, tx_helper_topic, watcher_topic}; const INTERNAL_SERVER_ERROR_CODE: u16 = 500; const RESPONSE_OK_STATUS_CODE: u16 = 200; @@ -139,7 +138,16 @@ pub async fn disable_coin(ctx: MmArc, req: Json) -> Result>, St pub async fn electrum(ctx: MmArc, req: Json) -> Result>, String> { let ticker = try_s!(req["coin"].as_str().ok_or("No 'coin' field")).to_owned(); let coin: MmCoinEnum = try_s!(lp_coininit(&ctx, &ticker, &req).await); - let balance = try_s!(coin.my_balance().compat().await); + let balance = match coin.my_balance().compat().timeout_secs(5.).await { + Ok(Ok(balance)) => balance, + // If the coin was activated successfully but the balance query failed (most probably due to faulty + // electrum servers), remove the coin as the whole request is a failure now from the POV of the GUI. + err => { + let coins_ctx = try_s!(CoinsContext::from_ctx(&ctx)); + coins_ctx.remove_coin(coin).await; + return Err(ERRL!("Deactivated coin due to error in balance querying: {:?}", err)); + }, + }; let res = CoinInitResponse { result: "success".into(), address: try_s!(coin.my_address()), @@ -242,29 +250,13 @@ pub async fn my_balance(ctx: MmArc, req: Json) -> Result>, Stri Ok(try_s!(Response::builder().body(res))) } -#[cfg(not(target_arch = "wasm32"))] -async fn close_async_connection(ctx: &MmArc) { - if let Some(async_conn) = ctx.async_sqlite_connection.as_option() { - let mut conn = async_conn.lock().await; - if let Err(e) = conn.close().await { - error!("Error stopping AsyncConnection: {}", e); - } - } -} - pub async fn stop(ctx: MmArc) -> Result>, String> { dispatch_lp_event(ctx.clone(), StopCtxEvent.into()).await; // Should delay the shutdown a bit in order not to trip the "stop" RPC call in unit tests. // Stopping immediately leads to the "stop" RPC call failing with the "errno 10054" sometimes. let fut = async move { Timer::sleep(0.05).await; - - #[cfg(not(target_arch = "wasm32"))] - close_async_connection(&ctx).await; - - if let Err(e) = ctx.stop() { - error!("Error stopping MmCtx: {}", e); - } + ctx.stop().await.expect("Couldn't stop the KDF runtime."); }; // Please note we shouldn't use `MmCtx::spawner` to spawn this future, @@ -276,36 +268,6 @@ pub async fn stop(ctx: MmArc) -> Result>, String> { Ok(try_s!(Response::builder().body(res))) } -pub async fn sim_panic(req: Json) -> Result>, String> { - #[derive(Deserialize)] - struct Req { - #[serde(default)] - mode: String, - } - let req: Req = try_s!(json::from_value(req)); - - #[derive(Serialize)] - struct Ret<'a> { - /// Supported panic modes. - #[serde(skip_serializing_if = "Vec::is_empty")] - modes: Vec>, - } - let ret: Ret; - - if req.mode.is_empty() { - ret = Ret { - modes: vec!["simple".into()], - } - } else if req.mode == "simple" { - panic!("sim_panic: simple") - } else { - return ERR!("No such mode: {}", req.mode); - } - - let js = try_s!(json::to_vec(&ret)); - Ok(try_s!(Response::builder().body(js))) -} - pub fn version(ctx: MmArc) -> HyRes { match json::to_vec(&MmVersionResponse { result: ctx.mm_version.clone(), @@ -316,10 +278,10 @@ pub fn version(ctx: MmArc) -> HyRes { } } -pub async fn get_peers_info(ctx: MmArc) -> Result>, String> { +pub async fn get_directly_connected_peers(ctx: MmArc) -> Result>, String> { let ctx = P2PContext::fetch_from_mm_arc(&ctx); let cmd_tx = ctx.cmd_tx.lock().clone(); - let result = mm2_libp2p::get_peers_info(cmd_tx).await; + let result = mm2_libp2p::get_directly_connected_peers(cmd_tx).await; let result = json!({ "result": result, }); @@ -372,7 +334,9 @@ pub async fn get_relay_mesh(ctx: MmArc) -> Result>, String> { } pub async fn get_my_peer_id(ctx: MmArc) -> Result>, String> { - let peer_id = try_s!(ctx.peer_id.ok_or("Peer ID is not initialized")); + let p2p_ctx = P2PContext::fetch_from_mm_arc(&ctx); + let peer_id = p2p_ctx.peer_id().to_string(); + let result = json!({ "result": peer_id, }); diff --git a/mm2src/mm2_main/src/rpc/lp_commands/mod.rs b/mm2src/mm2_main/src/rpc/lp_commands/mod.rs new file mode 100644 index 0000000000..002066c836 --- /dev/null +++ b/mm2src/mm2_main/src/rpc/lp_commands/mod.rs @@ -0,0 +1,5 @@ +pub(crate) mod db_id; +pub mod legacy; +pub(crate) mod pubkey; +pub(crate) mod tokens; +pub(crate) mod trezor; diff --git a/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs b/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs new file mode 100644 index 0000000000..f5a5a95063 --- /dev/null +++ b/mm2src/mm2_main/src/rpc/lp_commands/pubkey.rs @@ -0,0 +1,48 @@ +use common::HttpStatusCode; +use crypto::{CryptoCtx, CryptoCtxError}; +use derive_more::Display; +use http::StatusCode; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::*; +use rpc::v1::types::H160 as H160Json; +use serde_json::Value as Json; + +pub type GetPublicKeyRpcResult = Result>; + +#[derive(Serialize, Display, SerializeErrorType)] +#[serde(tag = "error_type", content = "error_data")] +pub enum GetPublicKeyError { + Internal(String), +} + +impl From for GetPublicKeyError { + fn from(_: CryptoCtxError) -> Self { GetPublicKeyError::Internal("public_key not available".to_string()) } +} + +#[derive(Serialize)] +pub struct GetPublicKeyResponse { + public_key: String, +} + +impl HttpStatusCode for GetPublicKeyError { + fn status_code(&self) -> StatusCode { + match self { + GetPublicKeyError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +pub async fn get_public_key(ctx: MmArc, _req: Json) -> GetPublicKeyRpcResult { + let public_key = CryptoCtx::from_ctx(&ctx)?.mm2_internal_pubkey().to_string(); + Ok(GetPublicKeyResponse { public_key }) +} + +#[derive(Serialize)] +pub struct GetPublicKeyHashResponse { + public_key_hash: H160Json, +} + +pub async fn get_public_key_hash(ctx: MmArc, _req: Json) -> GetPublicKeyRpcResult { + let public_key_hash = ctx.rmd160().to_owned().into(); + Ok(GetPublicKeyHashResponse { public_key_hash }) +} diff --git a/mm2src/mm2_main/src/rpc/lp_commands/tokens.rs b/mm2src/mm2_main/src/rpc/lp_commands/tokens.rs new file mode 100644 index 0000000000..c72e772a81 --- /dev/null +++ b/mm2src/mm2_main/src/rpc/lp_commands/tokens.rs @@ -0,0 +1,95 @@ +use coins::eth::erc20::{get_erc20_ticker_by_contract_address, get_erc20_token_info, Erc20TokenInfo}; +use coins::eth::valid_addr_from_str; +use coins::{lp_coinfind_or_err, CoinFindError, CoinProtocol, MmCoinEnum}; +use common::HttpStatusCode; +use http::StatusCode; +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::*; + +#[derive(Deserialize)] +pub struct TokenInfoRequest { + protocol: CoinProtocol, +} + +#[derive(Serialize)] +#[serde(tag = "type", content = "info")] +pub enum TokenInfo { + ERC20(Erc20TokenInfo), +} + +#[derive(Serialize)] +pub struct TokenInfoResponse { + #[serde(skip_serializing_if = "Option::is_none")] + config_ticker: Option, + #[serde(flatten)] + info: TokenInfo, +} + +#[derive(Debug, Deserialize, Display, Serialize, SerializeErrorType)] +#[serde(tag = "error_type", content = "error_data")] +pub enum TokenInfoError { + #[display(fmt = "No such coin {}", coin)] + NoSuchCoin { coin: String }, + #[display(fmt = "Custom tokens are not supported for {} protocol yet!", protocol)] + UnsupportedTokenProtocol { protocol: String }, + #[display(fmt = "Invalid request {}", _0)] + InvalidRequest(String), + #[display(fmt = "Error retrieving token info {}", _0)] + RetrieveInfoError(String), +} + +impl HttpStatusCode for TokenInfoError { + fn status_code(&self) -> StatusCode { + match self { + TokenInfoError::NoSuchCoin { .. } => StatusCode::NOT_FOUND, + TokenInfoError::UnsupportedTokenProtocol { .. } | TokenInfoError::InvalidRequest(_) => { + StatusCode::BAD_REQUEST + }, + TokenInfoError::RetrieveInfoError(_) => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +impl From for TokenInfoError { + fn from(e: CoinFindError) -> Self { + match e { + CoinFindError::NoSuchCoin { coin } => TokenInfoError::NoSuchCoin { coin }, + } + } +} + +pub async fn get_token_info(ctx: MmArc, req: TokenInfoRequest) -> MmResult { + // Check that the protocol is a token protocol + let platform = req.protocol.platform().ok_or(TokenInfoError::InvalidRequest(format!( + "Protocol '{:?}' is not a token protocol", + req.protocol + )))?; + // Platform coin should be activated + let platform_coin = lp_coinfind_or_err(&ctx, platform).await?; + match platform_coin { + MmCoinEnum::EthCoin(eth_coin) => { + let contract_address_str = + req.protocol + .contract_address() + .ok_or(TokenInfoError::UnsupportedTokenProtocol { + protocol: platform.to_string(), + })?; + let contract_address = valid_addr_from_str(contract_address_str).map_to_mm(|e| { + let error = format!("Invalid contract address: {}", e); + TokenInfoError::InvalidRequest(error) + })?; + + let config_ticker = get_erc20_ticker_by_contract_address(&ctx, platform, contract_address_str); + let token_info = get_erc20_token_info(ð_coin, contract_address) + .await + .map_to_mm(TokenInfoError::RetrieveInfoError)?; + Ok(TokenInfoResponse { + config_ticker, + info: TokenInfo::ERC20(token_info), + }) + }, + _ => MmError::err(TokenInfoError::UnsupportedTokenProtocol { + protocol: platform.to_string(), + }), + } +} diff --git a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands.rs b/mm2src/mm2_main/src/rpc/lp_commands/trezor.rs similarity index 52% rename from mm2src/mm2_main/src/rpc/lp_commands/lp_commands.rs rename to mm2src/mm2_main/src/rpc/lp_commands/trezor.rs index ae992c6d3e..16698eb3cc 100644 --- a/mm2src/mm2_main/src/rpc/lp_commands/lp_commands.rs +++ b/mm2src/mm2_main/src/rpc/lp_commands/trezor.rs @@ -1,63 +1,9 @@ use common::HttpStatusCode; use crypto::{CryptoCtx, CryptoCtxError, HwConnectionStatus, HwPubkey}; -use derive_more::Display; use http::StatusCode; use mm2_core::mm_ctx::MmArc; -use mm2_err_handle::prelude::*; -use rpc::v1::types::H160 as H160Json; -use serde_json::Value as Json; - -pub type GetPublicKeyRpcResult = Result>; -pub type GetSharedDbIdResult = Result>; -pub type GetSharedDbIdError = GetPublicKeyError; - -#[derive(Serialize, Display, SerializeErrorType)] -#[serde(tag = "error_type", content = "error_data")] -pub enum GetPublicKeyError { - Internal(String), -} - -impl From for GetPublicKeyError { - fn from(_: CryptoCtxError) -> Self { GetPublicKeyError::Internal("public_key not available".to_string()) } -} - -#[derive(Serialize)] -pub struct GetPublicKeyResponse { - public_key: String, -} - -impl HttpStatusCode for GetPublicKeyError { - fn status_code(&self) -> StatusCode { - match self { - GetPublicKeyError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, - } - } -} - -pub async fn get_public_key(ctx: MmArc, _req: Json) -> GetPublicKeyRpcResult { - let public_key = CryptoCtx::from_ctx(&ctx)?.mm2_internal_pubkey().to_string(); - Ok(GetPublicKeyResponse { public_key }) -} - -#[derive(Serialize)] -pub struct GetPublicKeyHashResponse { - public_key_hash: H160Json, -} - -pub async fn get_public_key_hash(ctx: MmArc, _req: Json) -> GetPublicKeyRpcResult { - let public_key_hash = ctx.rmd160().to_owned().into(); - Ok(GetPublicKeyHashResponse { public_key_hash }) -} - -#[derive(Serialize)] -pub struct GetSharedDbIdResponse { - shared_db_id: H160Json, -} - -pub async fn get_shared_db_id(ctx: MmArc, _req: Json) -> GetSharedDbIdResult { - let shared_db_id = ctx.shared_db_id().to_owned().into(); - Ok(GetSharedDbIdResponse { shared_db_id }) -} +use mm2_err_handle::mm_error::{MmError, MmResult}; +use mm2_err_handle::or_mm_error::OrMmError; #[derive(Serialize, Display, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] diff --git a/mm2src/mm2_main/src/rpc/rate_limiter.rs b/mm2src/mm2_main/src/rpc/rate_limiter.rs index 9f07be2b46..aa68cee4a8 100644 --- a/mm2src/mm2_main/src/rpc/rate_limiter.rs +++ b/mm2src/mm2_main/src/rpc/rate_limiter.rs @@ -1,4 +1,4 @@ -use crate::mm2::rpc::DispatcherError; +use crate::rpc::DispatcherError; use derive_more::Display; use futures::lock::Mutex as AsyncMutex; use mm2_core::mm_ctx::from_ctx; diff --git a/mm2src/mm2_main/src/wasm_tests.rs b/mm2src/mm2_main/src/wasm_tests.rs index a43b948ac8..bd24bf4a4c 100644 --- a/mm2src/mm2_main/src/wasm_tests.rs +++ b/mm2src/mm2_main/src/wasm_tests.rs @@ -1,14 +1,14 @@ -use crate::mm2::lp_init; -use common::executor::{spawn, Timer}; +use crate::lp_init; +use common::executor::{spawn, spawn_abortable, spawn_local_abortable, AbortOnDropHandle, Timer}; use common::log::wasm_log::register_wasm_log; use mm2_core::mm_ctx::MmArc; use mm2_number::BigDecimal; use mm2_rpc::data::legacy::OrderbookResponse; use mm2_test_helpers::electrums::{doc_electrums, marty_electrums}; use mm2_test_helpers::for_tests::{check_recent_swaps, enable_electrum_json, enable_utxo_v2_electrum, - enable_z_coin_light, morty_conf, pirate_conf, rick_conf, start_swaps, - test_qrc20_history_impl, wait_for_swaps_finish_and_check_status, MarketMakerIt, - Mm2InitPrivKeyPolicy, Mm2TestConf, Mm2TestConfForSwap, ARRR, MORTY, + enable_z_coin_light, get_wallet_names, morty_conf, pirate_conf, rick_conf, + start_swaps, test_qrc20_history_impl, wait_for_swaps_finish_and_check_status, + MarketMakerIt, Mm2InitPrivKeyPolicy, Mm2TestConf, Mm2TestConfForSwap, ARRR, MORTY, PIRATE_ELECTRUMS, PIRATE_LIGHTWALLETD_URLS, RICK}; use mm2_test_helpers::get_passphrase; use mm2_test_helpers::structs::{Bip44Chain, EnableCoinBalance, HDAccountAddressId}; @@ -16,6 +16,7 @@ use serde_json::json; use wasm_bindgen_test::wasm_bindgen_test; const PIRATE_TEST_BALANCE_SEED: &str = "pirate test seed"; +const STOP_TIMEOUT_MS: u64 = 1000; /// Starts the WASM version of MM. fn wasm_start(ctx: MmArc) { @@ -26,13 +27,7 @@ fn wasm_start(ctx: MmArc) { /// This function runs Alice and Bob nodes, activates coins, starts swaps, /// and then immediately stops the nodes to check if `MmArc` is dropped in a short period. -async fn test_mm2_stops_impl( - pairs: &[(&'static str, &'static str)], - maker_price: f64, - taker_price: f64, - volume: f64, - stop_timeout_ms: u64, -) { +async fn test_mm2_stops_impl(pairs: &[(&'static str, &'static str)], maker_price: f64, taker_price: f64, volume: f64) { let coins = json!([rick_conf(), morty_conf()]); let bob_passphrase = get_passphrase!(".env.seed", "BOB_PASSPHRASE").unwrap(); @@ -69,20 +64,18 @@ async fn test_mm2_stops_impl( start_swaps(&mut mm_bob, &mut mm_alice, pairs, maker_price, taker_price, volume).await; mm_alice - .stop_and_wait_for_ctx_is_dropped(stop_timeout_ms) + .stop_and_wait_for_ctx_is_dropped(STOP_TIMEOUT_MS) .await .unwrap(); - mm_bob.stop_and_wait_for_ctx_is_dropped(stop_timeout_ms).await.unwrap(); + mm_bob.stop_and_wait_for_ctx_is_dropped(STOP_TIMEOUT_MS).await.unwrap(); } #[wasm_bindgen_test] async fn test_mm2_stops_immediately() { - const STOP_TIMEOUT_MS: u64 = 1000; - register_wasm_log(); let pairs: &[_] = &[("RICK", "MORTY")]; - test_mm2_stops_impl(pairs, 1., 1., 0.0001, STOP_TIMEOUT_MS).await; + test_mm2_stops_impl(pairs, 1., 1., 0.0001).await; } #[wasm_bindgen_test] @@ -147,8 +140,6 @@ async fn trade_base_rel_electrum( assert_eq!(0, bob_orderbook.asks.len(), "{} {} asks must be empty", base, rel); } - const STOP_TIMEOUT_MS: u64 = 1000; - mm_bob.stop_and_wait_for_ctx_is_dropped(STOP_TIMEOUT_MS).await.unwrap(); mm_alice .stop_and_wait_for_ctx_is_dropped(STOP_TIMEOUT_MS) @@ -266,3 +257,46 @@ async fn activate_z_coin_light() { }; assert_eq!(balance.balance.spendable, BigDecimal::default()); } + +#[wasm_bindgen_test] +async fn test_get_wallet_names() { + const DB_NAMESPACE_NUM: u64 = 1; + + let coins = json!([]); + + // Initialize the first wallet with a specific name + let wallet_1 = Mm2TestConf::seednode_with_wallet_name(&coins, "wallet_1", "pass"); + let mm_wallet_1 = + MarketMakerIt::start_with_db(wallet_1.conf, wallet_1.rpc_password, Some(wasm_start), DB_NAMESPACE_NUM) + .await + .unwrap(); + + // Retrieve and verify the wallet names for the first wallet + let get_wallet_names_1 = get_wallet_names(&mm_wallet_1).await; + assert_eq!(get_wallet_names_1.wallet_names, vec!["wallet_1"]); + assert_eq!(get_wallet_names_1.activated_wallet.unwrap(), "wallet_1"); + + // Stop the first wallet before starting the second one + mm_wallet_1 + .stop_and_wait_for_ctx_is_dropped(STOP_TIMEOUT_MS) + .await + .unwrap(); + + // Initialize the second wallet with a different name + let wallet_2 = Mm2TestConf::seednode_with_wallet_name(&coins, "wallet_2", "pass"); + let mm_wallet_2 = + MarketMakerIt::start_with_db(wallet_2.conf, wallet_2.rpc_password, Some(wasm_start), DB_NAMESPACE_NUM) + .await + .unwrap(); + + // Retrieve and verify the wallet names for the second wallet + let get_wallet_names_2 = get_wallet_names(&mm_wallet_2).await; + assert_eq!(get_wallet_names_2.wallet_names, vec!["wallet_1", "wallet_2"]); + assert_eq!(get_wallet_names_2.activated_wallet.unwrap(), "wallet_2"); + + // Stop the second wallet + mm_wallet_2 + .stop_and_wait_for_ctx_is_dropped(STOP_TIMEOUT_MS) + .await + .unwrap(); +} diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs index 0066f00139..3050f22826 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs @@ -1,4 +1,4 @@ -pub use common::{block_on, now_ms, now_sec, wait_until_ms, wait_until_sec}; +pub use common::{block_on, block_on_f01, now_ms, now_sec, wait_until_ms, wait_until_sec, DEX_FEE_ADDR_RAW_PUBKEY}; pub use mm2_number::MmNumber; use mm2_rpc::data::legacy::BalanceResponse; pub use mm2_test_helpers::for_tests::{check_my_swap_status, check_recent_swaps, enable_eth_coin, enable_native, @@ -7,8 +7,7 @@ pub use mm2_test_helpers::for_tests::{check_my_swap_status, check_recent_swaps, MAKER_ERROR_EVENTS, MAKER_SUCCESS_EVENTS, TAKER_ERROR_EVENTS, TAKER_SUCCESS_EVENTS}; -use super::eth_docker_tests::{erc20_contract_checksum, fill_eth, fill_eth_erc20_with_private_key, geth_account, - swap_contract}; +use super::eth_docker_tests::{erc20_contract_checksum, fill_eth, fill_eth_erc20_with_private_key, swap_contract}; use bitcrypto::{dhash160, ChecksumType}; use chain::TransactionOutput; use coins::eth::addr_from_raw_pubkey; @@ -28,7 +27,6 @@ use crypto::Secp256k1Secret; use ethabi::Token; use ethereum_types::{H160 as H160Eth, U256}; use futures::TryFutureExt; -use futures01::Future; use http::StatusCode; use keys::{Address, AddressBuilder, AddressHashEnum, AddressPrefix, KeyPair, NetworkAddressPrefixes, NetworkPrefix as CashAddrPrefix}; @@ -41,21 +39,16 @@ use script::Builder; use secp256k1::Secp256k1; pub use secp256k1::{PublicKey, SecretKey}; use serde_json::{self as json, Value as Json}; -pub use std::env; -use std::path::PathBuf; -use std::process::Command; -use std::process::Stdio; +use std::process::{Command, Stdio}; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] use std::str::FromStr; -use std::sync::Mutex; -pub use std::thread; -use std::time::Duration; -use testcontainers::clients::Cli; -use testcontainers::core::WaitFor; -use testcontainers::{Container, GenericImage, RunnableImage}; -use web3::transports::Http; +pub use std::{env, thread}; +use std::{path::PathBuf, sync::Mutex, time::Duration}; +use testcontainers::{clients::Cli, core::WaitFor, Container, GenericImage, RunnableImage}; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] use web3::types::Address as EthAddress; use web3::types::{BlockId, BlockNumber, TransactionRequest}; -use web3::Web3; +use web3::{transports::Http, Web3}; lazy_static! { static ref MY_COIN_LOCK: Mutex<()> = Mutex::new(()); @@ -67,18 +60,23 @@ lazy_static! { // Due to the SLP protocol limitations only 19 outputs (18 + change) can be sent in one transaction, which is sufficient for now though. // Supply more privkeys when 18 will be not enough. pub static ref SLP_TOKEN_OWNERS: Mutex> = Mutex::new(Vec::with_capacity(18)); - pub static ref MM_CTX: MmArc = MmCtxBuilder::new().into_mm_arc(); + pub static ref MM_CTX: MmArc = MmCtxBuilder::new().with_conf(json!({"use_trading_proto_v2": true})).into_mm_arc(); /// We need a second `MmCtx` instance when we use the same private keys for Maker and Taker across various tests. /// When enabling coins for both Maker and Taker, two distinct coin instances are created. /// This means that different instances of the same coin should have separate global nonce locks. /// Utilizing different `MmCtx` instances allows us to assign Maker and Taker coins to separate `CoinsCtx`. /// This approach addresses the `replacement transaction` issue, which occurs when different transactions share the same nonce. - pub static ref MM_CTX1: MmArc = MmCtxBuilder::new().into_mm_arc(); + pub static ref MM_CTX1: MmArc = MmCtxBuilder::new().with_conf(json!({"use_trading_proto_v2": true})).into_mm_arc(); pub static ref GETH_WEB3: Web3 = Web3::new(Http::new(GETH_RPC_URL).unwrap()); - pub static ref SEPOLIA_WEB3: Web3 = Web3::new(Http::new(SEPOLIA_RPC_URL).unwrap()); // Mutex used to prevent nonce re-usage during funding addresses used in tests pub static ref GETH_NONCE_LOCK: Mutex<()> = Mutex::new(()); +} + +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +lazy_static! { + pub static ref SEPOLIA_WEB3: Web3 = Web3::new(Http::new(SEPOLIA_RPC_URL).unwrap()); pub static ref SEPOLIA_NONCE_LOCK: Mutex<()> = Mutex::new(()); + pub static ref SEPOLIA_TESTS_LOCK: Mutex<()> = Mutex::new(()); } pub static mut QICK_TOKEN_ADDRESS: Option = None; @@ -89,35 +87,45 @@ pub static mut QTUM_CONF_PATH: Option = None; pub static mut GETH_ACCOUNT: H160Eth = H160Eth::zero(); /// ERC20 token address on Geth dev node pub static mut GETH_ERC20_CONTRACT: H160Eth = H160Eth::zero(); +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +pub static mut SEPOLIA_ERC20_CONTRACT: H160Eth = H160Eth::zero(); /// Swap contract address on Geth dev node pub static mut GETH_SWAP_CONTRACT: H160Eth = H160Eth::zero(); +/// Maker Swap V2 contract address on Geth dev node +pub static mut GETH_MAKER_SWAP_V2: H160Eth = H160Eth::zero(); +/// Taker Swap V2 contract address on Geth dev node +pub static mut GETH_TAKER_SWAP_V2: H160Eth = H160Eth::zero(); +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +pub static mut SEPOLIA_TAKER_SWAP_V2: H160Eth = H160Eth::zero(); +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +pub static mut SEPOLIA_MAKER_SWAP_V2: H160Eth = H160Eth::zero(); /// Swap contract (with watchers support) address on Geth dev node pub static mut GETH_WATCHERS_SWAP_CONTRACT: H160Eth = H160Eth::zero(); /// ERC721 token address on Geth dev node pub static mut GETH_ERC721_CONTRACT: H160Eth = H160Eth::zero(); /// ERC1155 token address on Geth dev node pub static mut GETH_ERC1155_CONTRACT: H160Eth = H160Eth::zero(); -/// Nft Swap contract address on Geth dev node -pub static mut GETH_NFT_SWAP_CONTRACT: H160Eth = H160Eth::zero(); /// NFT Maker Swap V2 contract address on Geth dev node pub static mut GETH_NFT_MAKER_SWAP_V2: H160Eth = H160Eth::zero(); +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] /// NFT Maker Swap V2 contract address on Sepolia testnet pub static mut SEPOLIA_ETOMIC_MAKER_NFT_SWAP_V2: H160Eth = H160Eth::zero(); -/// ERC721 token address on Sepolia testnet -pub static mut SEPOLIA_ERC721_CONTRACT: H160Eth = H160Eth::zero(); -/// ERC1155 token address on Sepolia testnet -pub static mut SEPOLIA_ERC1155_CONTRACT: H160Eth = H160Eth::zero(); pub static GETH_RPC_URL: &str = "http://127.0.0.1:8545"; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] pub static SEPOLIA_RPC_URL: &str = "https://ethereum-sepolia-rpc.publicnode.com"; pub const UTXO_ASSET_DOCKER_IMAGE: &str = "docker.io/artempikulin/testblockchain"; pub const UTXO_ASSET_DOCKER_IMAGE_WITH_TAG: &str = "docker.io/artempikulin/testblockchain:multiarch"; pub const GETH_DOCKER_IMAGE: &str = "docker.io/ethereum/client-go"; pub const GETH_DOCKER_IMAGE_WITH_TAG: &str = "docker.io/ethereum/client-go:stable"; +#[allow(dead_code)] +pub const SIA_DOCKER_IMAGE: &str = "docker.io/alrighttt/walletd-komodo"; +#[allow(dead_code)] +pub const SIA_DOCKER_IMAGE_WITH_TAG: &str = "docker.io/alrighttt/walletd-komodo:latest"; pub const NUCLEUS_IMAGE: &str = "docker.io/komodoofficial/nucleusd"; -pub const ATOM_IMAGE: &str = "docker.io/komodoofficial/gaiad"; -pub const IBC_RELAYER_IMAGE: &str = "docker.io/komodoofficial/ibc-relayer"; +pub const ATOM_IMAGE_WITH_TAG: &str = "docker.io/komodoofficial/gaiad:kdf-ci"; +pub const IBC_RELAYER_IMAGE_WITH_TAG: &str = "docker.io/komodoofficial/ibc-relayer:kdf-ci"; pub const QTUM_ADDRESS_LABEL: &str = "MM2_ADDRESS_LABEL"; @@ -131,13 +139,21 @@ pub const MYCOIN: &str = "MYCOIN"; /// Ticker of MYCOIN1 dockerized blockchain. pub const MYCOIN1: &str = "MYCOIN1"; -pub const ERC20_TOKEN_BYTES: &str = "6080604052600860ff16600a0a633b9aca000260005534801561002157600080fd5b50600054600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610c69806100776000396000f3006080604052600436106100a4576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100a9578063095ea7b31461013957806318160ddd1461019e57806323b872dd146101c9578063313ce5671461024e5780635a3b7e421461027f57806370a082311461030f57806395d89b4114610366578063a9059cbb146103f6578063dd62ed3e1461045b575b600080fd5b3480156100b557600080fd5b506100be6104d2565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100fe5780820151818401526020810190506100e3565b50505050905090810190601f16801561012b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561014557600080fd5b50610184600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061050b565b604051808215151515815260200191505060405180910390f35b3480156101aa57600080fd5b506101b36106bb565b6040518082815260200191505060405180910390f35b3480156101d557600080fd5b50610234600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506106c1565b604051808215151515815260200191505060405180910390f35b34801561025a57600080fd5b506102636109a1565b604051808260ff1660ff16815260200191505060405180910390f35b34801561028b57600080fd5b506102946109a6565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156102d45780820151818401526020810190506102b9565b50505050905090810190601f1680156103015780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561031b57600080fd5b50610350600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506109df565b6040518082815260200191505060405180910390f35b34801561037257600080fd5b5061037b6109f7565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103bb5780820151818401526020810190506103a0565b50505050905090810190601f1680156103e85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561040257600080fd5b50610441600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610a30565b604051808215151515815260200191505060405180910390f35b34801561046757600080fd5b506104bc600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610be1565b6040518082815260200191505060405180910390f35b6040805190810160405280600881526020017f515243205445535400000000000000000000000000000000000000000000000081525081565b60008260008173ffffffffffffffffffffffffffffffffffffffff161415151561053457600080fd5b60008314806105bf57506000600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054145b15156105ca57600080fd5b82600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508373ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925856040518082815260200191505060405180910390a3600191505092915050565b60005481565b60008360008173ffffffffffffffffffffffffffffffffffffffff16141515156106ea57600080fd5b8360008173ffffffffffffffffffffffffffffffffffffffff161415151561071157600080fd5b610797600260008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205485610c06565b600260008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610860600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205485610c06565b600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506108ec600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205485610c1f565b600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508473ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef866040518082815260200191505060405180910390a36001925050509392505050565b600881565b6040805190810160405280600981526020017f546f6b656e20302e31000000000000000000000000000000000000000000000081525081565b60016020528060005260406000206000915090505481565b6040805190810160405280600381526020017f515443000000000000000000000000000000000000000000000000000000000081525081565b60008260008173ffffffffffffffffffffffffffffffffffffffff1614151515610a5957600080fd5b610aa2600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205484610c06565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610b2e600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205484610c1f565b600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508373ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef856040518082815260200191505060405180910390a3600191505092915050565b6002602052816000526040600020602052806000526040600020600091509150505481565b6000818310151515610c1457fe5b818303905092915050565b6000808284019050838110151515610c3357fe5b80915050929150505600a165627a7a723058207f2e5248b61b80365ea08a0f6d11ac0b47374c4dfd538de76bc2f19591bbbba40029"; -pub const SWAP_CONTRACT_BYTES: &str = "608060405234801561001057600080fd5b50611437806100206000396000f3fe60806040526004361061004a5760003560e01c806302ed292b1461004f5780630716326d146100de578063152cf3af1461017b57806346fc0294146101f65780639b415b2a14610294575b600080fd5b34801561005b57600080fd5b506100dc600480360360a081101561007257600080fd5b81019080803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610339565b005b3480156100ea57600080fd5b506101176004803603602081101561010157600080fd5b8101908080359060200190929190505050610867565b60405180846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526020018367ffffffffffffffff1667ffffffffffffffff16815260200182600381111561016557fe5b60ff168152602001935050505060405180910390f35b6101f46004803603608081101561019157600080fd5b8101908080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080356bffffffffffffffffffffffff19169060200190929190803567ffffffffffffffff1690602001909291905050506108bf565b005b34801561020257600080fd5b50610292600480360360a081101561021957600080fd5b81019080803590602001909291908035906020019092919080356bffffffffffffffffffffffff19169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610bd9565b005b610337600480360360c08110156102aa57600080fd5b810190808035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080356bffffffffffffffffffffffff19169060200190929190803567ffffffffffffffff169060200190929190505050610fe2565b005b6001600381111561034657fe5b600080878152602001908152602001600020600001601c9054906101000a900460ff16600381111561037457fe5b1461037e57600080fd5b6000600333836003600288604051602001808281526020019150506040516020818303038152906040526040518082805190602001908083835b602083106103db57805182526020820191506020810190506020830392506103b8565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa15801561041d573d6000803e3d6000fd5b5050506040513d602081101561043257600080fd5b8101908080519060200190929190505050604051602001808281526020019150506040516020818303038152906040526040518082805190602001908083835b602083106104955780518252602082019150602081019050602083039250610472565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa1580156104d7573d6000803e3d6000fd5b5050506040515160601b8689604051602001808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526014018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001955050505050506040516020818303038152906040526040518082805190602001908083835b602083106105fc57805182526020820191506020810190506020830392506105d9565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa15801561063e573d6000803e3d6000fd5b5050506040515160601b905060008087815260200190815260200160002060000160009054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461069657600080fd5b6002600080888152602001908152602001600020600001601c6101000a81548160ff021916908360038111156106c857fe5b0217905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561074e573373ffffffffffffffffffffffffffffffffffffffff166108fc869081150290604051600060405180830381858888f19350505050158015610748573d6000803e3d6000fd5b50610820565b60008390508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33886040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b1580156107da57600080fd5b505af11580156107ee573d6000803e3d6000fd5b505050506040513d602081101561080457600080fd5b810190808051906020019092919050505061081e57600080fd5b505b7f36c177bcb01c6d568244f05261e2946c8c977fa50822f3fa098c470770ee1f3e8685604051808381526020018281526020019250505060405180910390a1505050505050565b60006020528060005260406000206000915090508060000160009054906101000a900460601b908060000160149054906101000a900467ffffffffffffffff169080600001601c9054906101000a900460ff16905083565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141580156108fc5750600034115b801561094057506000600381111561091057fe5b600080868152602001908152602001600020600001601c9054906101000a900460ff16600381111561093e57fe5b145b61094957600080fd5b60006003843385600034604051602001808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526014018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001955050505050506040516020818303038152906040526040518082805190602001908083835b60208310610a6c5780518252602082019150602081019050602083039250610a49565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa158015610aae573d6000803e3d6000fd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018367ffffffffffffffff16815260200160016003811115610af757fe5b81525060008087815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c021790555060208201518160000160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550604082015181600001601c6101000a81548160ff02191690836003811115610b9357fe5b02179055509050507fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad57856040518082815260200191505060405180910390a15050505050565b60016003811115610be657fe5b600080878152602001908152602001600020600001601c9054906101000a900460ff166003811115610c1457fe5b14610c1e57600080fd5b600060038233868689604051602001808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526014018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001955050505050506040516020818303038152906040526040518082805190602001908083835b60208310610d405780518252602082019150602081019050602083039250610d1d565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa158015610d82573d6000803e3d6000fd5b5050506040515160601b905060008087815260200190815260200160002060000160009054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916148015610e10575060008087815260200190815260200160002060000160149054906101000a900467ffffffffffffffff1667ffffffffffffffff164210155b610e1957600080fd5b6003600080888152602001908152602001600020600001601c6101000a81548160ff02191690836003811115610e4b57fe5b0217905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610ed1573373ffffffffffffffffffffffffffffffffffffffff166108fc869081150290604051600060405180830381858888f19350505050158015610ecb573d6000803e3d6000fd5b50610fa3565b60008390508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33886040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b158015610f5d57600080fd5b505af1158015610f71573d6000803e3d6000fd5b505050506040513d6020811015610f8757600080fd5b8101908080519060200190929190505050610fa157600080fd5b505b7f1797d500133f8e427eb9da9523aa4a25cb40f50ebc7dbda3c7c81778973f35ba866040518082815260200191505060405180910390a1505050505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415801561101f5750600085115b801561106357506000600381111561103357fe5b600080888152602001908152602001600020600001601c9054906101000a900460ff16600381111561106157fe5b145b61106c57600080fd5b60006003843385888a604051602001808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526014018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001955050505050506040516020818303038152906040526040518082805190602001908083835b6020831061118e578051825260208201915060208101905060208303925061116b565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa1580156111d0573d6000803e3d6000fd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018367ffffffffffffffff1681526020016001600381111561121957fe5b81525060008089815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c021790555060208201518160000160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550604082015181600001601c6101000a81548160ff021916908360038111156112b557fe5b021790555090505060008590508073ffffffffffffffffffffffffffffffffffffffff166323b872dd33308a6040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019350505050602060405180830381600087803b15801561137d57600080fd5b505af1158015611391573d6000803e3d6000fd5b505050506040513d60208110156113a757600080fd5b81019080805190602001909291905050506113c157600080fd5b7fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad57886040518082815260200191505060405180910390a1505050505050505056fea265627a7a723158208c83db436905afce0b7be1012be64818c49323c12d451fe2ab6bce76ff6421c964736f6c63430005110032"; -pub const WATCHERS_SWAP_CONTRACT_BYTES: &str = "608060405234801561000f575f80fd5b50612aa48061001d5f395ff3fe608060405260043610610085575f3560e01c806346fc02941161005857806346fc0294146101275780636a3227861461014f5780639b415b2a1461016b578063b5985c4d14610193578063cd1dde34146101bb57610085565b806302ed292b146100895780630716326d146100b15780630971fd54146100ef578063152cf3af1461010b575b5f80fd5b348015610094575f80fd5b506100af60048036038101906100aa9190611e1d565b6101e3565b005b3480156100bc575f80fd5b506100d760048036038101906100d29190611e94565b610518565b6040516100e693929190611f8e565b60405180910390f35b6101096004803603810190610104919061206f565b610568565b005b6101256004803603810190610120919061210c565b610787565b005b348015610132575f80fd5b5061014d60048036038101906101489190612170565b61099d565b005b610169600480360381019061016491906121e7565b610c4d565b005b348015610176575f80fd5b50610191600480360381019061018c91906122ab565b610f61565b005b34801561019e575f80fd5b506101b960048036038101906101b49190612334565b611203565b005b3480156101c6575f80fd5b506101e160048036038101906101dc91906123f8565b611887565b005b600160038111156101f7576101f6611f1b565b5b5f808781526020019081526020015f205f01601c9054906101000a900460ff16600381111561022957610228611f1b565b5b14610232575f80fd5b5f60033383600360028860405160200161024c91906124dc565b6040516020818303038152906040526040516102689190612562565b602060405180830381855afa158015610283573d5f803e3d5ffd5b5050506040513d601f19601f820116820180604052508101906102a6919061258c565b6040516020016102b691906124dc565b6040516020818303038152906040526040516102d29190612562565b602060405180830381855afa1580156102ed573d5f803e3d5ffd5b5050506040515160601b868960405160200161030d95949392919061263c565b6040516020818303038152906040526040516103299190612562565b602060405180830381855afa158015610344573d5f803e3d5ffd5b5050506040515160601b90505f808781526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610397575f80fd5b60025f808881526020019081526020015f205f01601c6101000a81548160ff021916908360038111156103cd576103cc611f1b565b5b02179055505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361044e573373ffffffffffffffffffffffffffffffffffffffff166108fc8690811502906040515f60405180830381858888f19350505050158015610448573d5f803e3d5ffd5b506104d7565b5f8390508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33886040518363ffffffff1660e01b815260040161048d9291906126b8565b6020604051808303815f875af11580156104a9573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104cd91906126f3565b6104d5575f80fd5b505b7f36c177bcb01c6d568244f05261e2946c8c977fa50822f3fa098c470770ee1f3e868560405161050892919061272d565b60405180910390a1505050505050565b5f602052805f5260405f205f91509050805f015f9054906101000a900460601b90805f0160149054906101000a900467ffffffffffffffff1690805f01601c9054906101000a900460ff16905083565b5f73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff16141580156105a357505f34115b80156105f157505f60038111156105bd576105bc611f1b565b5b5f808981526020019081526020015f205f01601c9054906101000a900460ff1660038111156105ef576105ee611f1b565b5b145b6105f9575f80fd5b5f60038733885f3489898960405160200161061b9897969594939291906127e7565b6040516020818303038152906040526040516106379190612562565b602060405180830381855afa158015610652573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018667ffffffffffffffff168152602001600160038111156106a2576106a1611f1b565b5b8152505f808a81526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506040820151815f01601c6101000a81548160ff0219169083600381111561073e5761073d611f1b565b5b02179055509050507fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad57886040516107759190612878565b60405180910390a15050505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141580156107c257505f34115b801561081057505f60038111156107dc576107db611f1b565b5b5f808681526020019081526020015f205f01601c9054906101000a900460ff16600381111561080e5761080d611f1b565b5b145b610818575f80fd5b5f60038433855f3460405160200161083495949392919061263c565b6040516020818303038152906040526040516108509190612562565b602060405180830381855afa15801561086b573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018367ffffffffffffffff168152602001600160038111156108bb576108ba611f1b565b5b8152505f808781526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506040820151815f01601c6101000a81548160ff0219169083600381111561095757610956611f1b565b5b02179055509050507fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad578560405161098e9190612878565b60405180910390a15050505050565b600160038111156109b1576109b0611f1b565b5b5f808781526020019081526020015f205f01601c9054906101000a900460ff1660038111156109e3576109e2611f1b565b5b146109ec575f80fd5b5f60038233868689604051602001610a0895949392919061263c565b604051602081830303815290604052604051610a249190612562565b602060405180830381855afa158015610a3f573d5f803e3d5ffd5b5050506040515160601b90505f808781526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916148015610ac657505f808781526020019081526020015f205f0160149054906101000a900467ffffffffffffffff1667ffffffffffffffff164210155b610ace575f80fd5b60035f808881526020019081526020015f205f01601c6101000a81548160ff02191690836003811115610b0457610b03611f1b565b5b02179055505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610b85573373ffffffffffffffffffffffffffffffffffffffff166108fc8690811502906040515f60405180830381858888f19350505050158015610b7f573d5f803e3d5ffd5b50610c0e565b5f8390508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33886040518363ffffffff1660e01b8152600401610bc49291906126b8565b6020604051808303815f875af1158015610be0573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c0491906126f3565b610c0c575f80fd5b505b7f1797d500133f8e427eb9da9523aa4a25cb40f50ebc7dbda3c7c81778973f35ba86604051610c3d9190612878565b60405180910390a1505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff1614158015610c8857505f88115b8015610cd657505f6003811115610ca257610ca1611f1b565b5b5f808b81526020019081526020015f205f01601c9054906101000a900460ff166003811115610cd457610cd3611f1b565b5b145b610cde575f80fd5b5f6003811115610cf157610cf0611f1b565b5b836003811115610d0457610d03611f1b565b5b14158015610d365750600380811115610d2057610d1f611f1b565b5b836003811115610d3357610d32611f1b565b5b14155b15610d4757803414610d46575f80fd5b5b5f60038733888b8d898989604051602001610d699897969594939291906127e7565b604051602081830303815290604052604051610d859190612562565b602060405180830381855afa158015610da0573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018667ffffffffffffffff16815260200160016003811115610df057610def611f1b565b5b8152505f808c81526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506040820151815f01601c6101000a81548160ff02191690836003811115610e8c57610e8b611f1b565b5b02179055509050505f8890508073ffffffffffffffffffffffffffffffffffffffff166323b872dd33308d6040518463ffffffff1660e01b8152600401610ed593929190612891565b6020604051808303815f875af1158015610ef1573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f1591906126f3565b610f1d575f80fd5b7fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad578b604051610f4c9190612878565b60405180910390a15050505050505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614158015610f9c57505f85115b8015610fea57505f6003811115610fb657610fb5611f1b565b5b5f808881526020019081526020015f205f01601c9054906101000a900460ff166003811115610fe857610fe7611f1b565b5b145b610ff2575f80fd5b5f6003843385888a60405160200161100e95949392919061263c565b60405160208183030381529060405260405161102a9190612562565b602060405180830381855afa158015611045573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018367ffffffffffffffff1681526020016001600381111561109557611094611f1b565b5b8152505f808981526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506040820151815f01601c6101000a81548160ff0219169083600381111561113157611130611f1b565b5b02179055509050505f8590508073ffffffffffffffffffffffffffffffffffffffff166323b872dd33308a6040518463ffffffff1660e01b815260040161117a93929190612891565b6020604051808303815f875af1158015611196573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111ba91906126f3565b6111c2575f80fd5b7fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad57886040516111f19190612878565b60405180910390a15050505050505050565b6001600381111561121757611216611f1b565b5b5f808b81526020019081526020015f205f01601c9054906101000a900460ff16600381111561124957611248611f1b565b5b14611289576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161128090612920565b60405180910390fd5b5f60038587600360028c6040516020016112a391906124dc565b6040516020818303038152906040526040516112bf9190612562565b602060405180830381855afa1580156112da573d5f803e3d5ffd5b5050506040513d601f19601f820116820180604052508101906112fd919061258c565b60405160200161130d91906124dc565b6040516020818303038152906040526040516113299190612562565b602060405180830381855afa158015611344573d5f803e3d5ffd5b5050506040515160601b8a8d89898960405160200161136a9897969594939291906127e7565b6040516020818303038152906040526040516113869190612562565b602060405180830381855afa1580156113a1573d5f803e3d5ffd5b5050506040515160601b90505f808b81526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461142b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161142290612988565b60405180910390fd5b60025f808c81526020019081526020015f205f01601c6101000a81548160ff0219169083600381111561146157611460611f1b565b5b02179055505f73ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff160361159e575f8060038111156114ad576114ac611f1b565b5b8560038111156114c0576114bf611f1b565b5b1480156114cb575083155b6114e057828a6114db91906129d3565b6114e2565b895b90508573ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f19350505050158015611527573d5f803e3d5ffd5b5060038081111561153b5761153a611f1b565b5b85600381111561154e5761154d611f1b565b5b03611598573373ffffffffffffffffffffffffffffffffffffffff166108fc8490811502906040515f60405180830381858888f19350505050158015611596573d5f803e3d5ffd5b505b50611786565b5f6003808111156115b2576115b1611f1b565b5b8560038111156115c5576115c4611f1b565b5b146115d057896115dd565b828a6115dc91906129d3565b5b90505f8890508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb88846040518363ffffffff1660e01b815260040161161e9291906126b8565b6020604051808303815f875af115801561163a573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061165e91906126f3565b61169d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161169490612a50565b60405180910390fd5b6003808111156116b0576116af611f1b565b5b8660038111156116c3576116c2611f1b565b5b03611783578073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33866040518363ffffffff1660e01b81526004016117039291906126b8565b6020604051808303815f875af115801561171f573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061174391906126f3565b611782576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161177990612a50565b60405180910390fd5b5b50505b6002600381111561179a57611799611f1b565b5b8460038111156117ad576117ac611f1b565b5b036117f7578573ffffffffffffffffffffffffffffffffffffffff166108fc8390811502906040515f60405180830381858888f193505050501580156117f5573d5f803e3d5ffd5b505b8215611842573373ffffffffffffffffffffffffffffffffffffffff166108fc8390811502906040515f60405180830381858888f19350505050158015611840573d5f803e3d5ffd5b505b7f36c177bcb01c6d568244f05261e2946c8c977fa50822f3fa098c470770ee1f3e8a8960405161187392919061272d565b60405180910390a150505050505050505050565b6001600381111561189b5761189a611f1b565b5b5f808b81526020019081526020015f205f01601c9054906101000a900460ff1660038111156118cd576118cc611f1b565b5b146118d6575f80fd5b5f600385878a8a8d8989896040516020016118f89897969594939291906127e7565b6040516020818303038152906040526040516119149190612562565b602060405180830381855afa15801561192f573d5f803e3d5ffd5b5050506040515160601b90505f808b81526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161480156119b657505f808b81526020019081526020015f205f0160149054906101000a900467ffffffffffffffff1667ffffffffffffffff164210155b6119be575f80fd5b60035f808c81526020019081526020015f205f01601c6101000a81548160ff021916908360038111156119f4576119f3611f1b565b5b02179055505f73ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff1603611b27575f806003811115611a4057611a3f611f1b565b5b856003811115611a5357611a52611f1b565b5b14611a6957828a611a6491906129d3565b611a6b565b895b90508673ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f19350505050158015611ab0573d5f803e3d5ffd5b505f6003811115611ac457611ac3611f1b565b5b856003811115611ad757611ad6611f1b565b5b14611b21573373ffffffffffffffffffffffffffffffffffffffff166108fc8490811502906040515f60405180830381858888f19350505050158015611b1f573d5f803e3d5ffd5b505b50611d16565b5f600380811115611b3b57611b3a611f1b565b5b856003811115611b4e57611b4d611f1b565b5b14611b595789611b66565b828a611b6591906129d3565b5b90505f8890508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb89846040518363ffffffff1660e01b8152600401611ba79291906126b8565b6020604051808303815f875af1158015611bc3573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611be791906126f3565b611bef575f80fd5b600380811115611c0257611c01611f1b565b5b866003811115611c1557611c14611f1b565b5b03611ca2578073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33866040518363ffffffff1660e01b8152600401611c559291906126b8565b6020604051808303815f875af1158015611c71573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c9591906126f3565b611c9d575f80fd5b611d13565b5f6003811115611cb557611cb4611f1b565b5b866003811115611cc857611cc7611f1b565b5b14611d12573373ffffffffffffffffffffffffffffffffffffffff166108fc8590811502906040515f60405180830381858888f19350505050158015611d10573d5f803e3d5ffd5b505b5b50505b7f1797d500133f8e427eb9da9523aa4a25cb40f50ebc7dbda3c7c81778973f35ba8a604051611d459190612878565b60405180910390a150505050505050505050565b5f80fd5b5f819050919050565b611d6f81611d5d565b8114611d79575f80fd5b50565b5f81359050611d8a81611d66565b92915050565b5f819050919050565b611da281611d90565b8114611dac575f80fd5b50565b5f81359050611dbd81611d99565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f611dec82611dc3565b9050919050565b611dfc81611de2565b8114611e06575f80fd5b50565b5f81359050611e1781611df3565b92915050565b5f805f805f60a08688031215611e3657611e35611d59565b5b5f611e4388828901611d7c565b9550506020611e5488828901611daf565b9450506040611e6588828901611d7c565b9350506060611e7688828901611e09565b9250506080611e8788828901611e09565b9150509295509295909350565b5f60208284031215611ea957611ea8611d59565b5b5f611eb684828501611d7c565b91505092915050565b5f7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000082169050919050565b611ef381611ebf565b82525050565b5f67ffffffffffffffff82169050919050565b611f1581611ef9565b82525050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b60048110611f5957611f58611f1b565b5b50565b5f819050611f6982611f48565b919050565b5f611f7882611f5c565b9050919050565b611f8881611f6e565b82525050565b5f606082019050611fa15f830186611eea565b611fae6020830185611f0c565b611fbb6040830184611f7f565b949350505050565b611fcc81611ebf565b8114611fd6575f80fd5b50565b5f81359050611fe781611fc3565b92915050565b611ff681611ef9565b8114612000575f80fd5b50565b5f8135905061201181611fed565b92915050565b60048110612023575f80fd5b50565b5f8135905061203481612017565b92915050565b5f8115159050919050565b61204e8161203a565b8114612058575f80fd5b50565b5f8135905061206981612045565b92915050565b5f805f805f805f60e0888a03121561208a57612089611d59565b5b5f6120978a828b01611d7c565b97505060206120a88a828b01611e09565b96505060406120b98a828b01611fd9565b95505060606120ca8a828b01612003565b94505060806120db8a828b01612026565b93505060a06120ec8a828b0161205b565b92505060c06120fd8a828b01611daf565b91505092959891949750929550565b5f805f806080858703121561212457612123611d59565b5b5f61213187828801611d7c565b945050602061214287828801611e09565b935050604061215387828801611fd9565b925050606061216487828801612003565b91505092959194509250565b5f805f805f60a0868803121561218957612188611d59565b5b5f61219688828901611d7c565b95505060206121a788828901611daf565b94505060406121b888828901611fd9565b93505060606121c988828901611e09565b92505060806121da88828901611e09565b9150509295509295909350565b5f805f805f805f805f6101208a8c03121561220557612204611d59565b5b5f6122128c828d01611d7c565b99505060206122238c828d01611daf565b98505060406122348c828d01611e09565b97505060606122458c828d01611e09565b96505060806122568c828d01611fd9565b95505060a06122678c828d01612003565b94505060c06122788c828d01612026565b93505060e06122898c828d0161205b565b92505061010061229b8c828d01611daf565b9150509295985092959850929598565b5f805f805f8060c087890312156122c5576122c4611d59565b5b5f6122d289828a01611d7c565b96505060206122e389828a01611daf565b95505060406122f489828a01611e09565b945050606061230589828a01611e09565b935050608061231689828a01611fd9565b92505060a061232789828a01612003565b9150509295509295509295565b5f805f805f805f805f6101208a8c03121561235257612351611d59565b5b5f61235f8c828d01611d7c565b99505060206123708c828d01611daf565b98505060406123818c828d01611d7c565b97505060606123928c828d01611e09565b96505060806123a38c828d01611e09565b95505060a06123b48c828d01611e09565b94505060c06123c58c828d01612026565b93505060e06123d68c828d0161205b565b9250506101006123e88c828d01611daf565b9150509295985092959850929598565b5f805f805f805f805f6101208a8c03121561241657612415611d59565b5b5f6124238c828d01611d7c565b99505060206124348c828d01611daf565b98505060406124458c828d01611fd9565b97505060606124568c828d01611e09565b96505060806124678c828d01611e09565b95505060a06124788c828d01611e09565b94505060c06124898c828d01612026565b93505060e061249a8c828d0161205b565b9250506101006124ac8c828d01611daf565b9150509295985092959850929598565b5f819050919050565b6124d66124d182611d5d565b6124bc565b82525050565b5f6124e782846124c5565b60208201915081905092915050565b5f81519050919050565b5f81905092915050565b5f5b8381101561252757808201518184015260208101905061250c565b5f8484015250505050565b5f61253c826124f6565b6125468185612500565b935061255681856020860161250a565b80840191505092915050565b5f61256d8284612532565b915081905092915050565b5f8151905061258681611d66565b92915050565b5f602082840312156125a1576125a0611d59565b5b5f6125ae84828501612578565b91505092915050565b5f8160601b9050919050565b5f6125cd826125b7565b9050919050565b5f6125de826125c3565b9050919050565b6125f66125f182611de2565b6125d4565b82525050565b5f819050919050565b61261661261182611ebf565b6125fc565b82525050565b5f819050919050565b61263661263182611d90565b61261c565b82525050565b5f61264782886125e5565b60148201915061265782876125e5565b6014820191506126678286612605565b60148201915061267782856125e5565b6014820191506126878284612625565b6020820191508190509695505050505050565b6126a381611de2565b82525050565b6126b281611d90565b82525050565b5f6040820190506126cb5f83018561269a565b6126d860208301846126a9565b9392505050565b5f815190506126ed81612045565b92915050565b5f6020828403121561270857612707611d59565b5b5f612715848285016126df565b91505092915050565b61272781611d5d565b82525050565b5f6040820190506127405f83018561271e565b61274d602083018461271e565b9392505050565b6004811061276557612764611f1b565b5b50565b5f81905061277582612754565b919050565b5f61278482612768565b9050919050565b5f8160f81b9050919050565b5f6127a18261278b565b9050919050565b6127b96127b48261277a565b612797565b82525050565b5f6127c982612797565b9050919050565b6127e16127dc8261203a565b6127bf565b82525050565b5f6127f2828b6125e5565b601482019150612802828a6125e5565b6014820191506128128289612605565b60148201915061282282886125e5565b6014820191506128328287612625565b60208201915061284282866127a8565b60018201915061285282856127d0565b6001820191506128628284612625565b6020820191508190509998505050505050505050565b5f60208201905061288b5f83018461271e565b92915050565b5f6060820190506128a45f83018661269a565b6128b1602083018561269a565b6128be60408301846126a9565b949350505050565b5f82825260208201905092915050565b7f5061796d656e7420776173206e6f742073656e740000000000000000000000005f82015250565b5f61290a6014836128c6565b9150612915826128d6565b602082019050919050565b5f6020820190508181035f830152612937816128fe565b9050919050565b7f496e76616c6964207061796d656e7420686173680000000000000000000000005f82015250565b5f6129726014836128c6565b915061297d8261293e565b602082019050919050565b5f6020820190508181035f83015261299f81612966565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6129dd82611d90565b91506129e883611d90565b9250828203905081811115612a00576129ff6129a6565b5b92915050565b7f546f6b656e207472616e73666572206661696c656400000000000000000000005f82015250565b5f612a3a6015836128c6565b9150612a4582612a06565b602082019050919050565b5f6020820190508181035f830152612a6781612a2e565b905091905056fea26469706673582212203106867e1b147b377237cde0aba42d82faf0282b83d7b6d62cca039d0b7f840564736f6c63430008160033"; -pub const ERC721_TEST_TOKEN_BYTES: &str = "608060405234801562000010575f80fd5b50604051620022ac380380620022ac8339818101604052810190620000369190620001ea565b8181815f9081620000489190620004a4565b5080600190816200005a9190620004a4565b505050505062000588565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b620000c6826200007e565b810181811067ffffffffffffffff82111715620000e857620000e76200008e565b5b80604052505050565b5f620000fc62000065565b90506200010a8282620000bb565b919050565b5f67ffffffffffffffff8211156200012c576200012b6200008e565b5b62000137826200007e565b9050602081019050919050565b5f5b838110156200016357808201518184015260208101905062000146565b5f8484015250505050565b5f620001846200017e846200010f565b620000f1565b905082815260208101848484011115620001a357620001a26200007a565b5b620001b084828562000144565b509392505050565b5f82601f830112620001cf57620001ce62000076565b5b8151620001e18482602086016200016e565b91505092915050565b5f80604083850312156200020357620002026200006e565b5b5f83015167ffffffffffffffff81111562000223576200022262000072565b5b6200023185828601620001b8565b925050602083015167ffffffffffffffff81111562000255576200025462000072565b5b6200026385828601620001b8565b9150509250929050565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680620002bc57607f821691505b602082108103620002d257620002d162000277565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302620003367fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82620002f9565b620003428683620002f9565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f6200038c6200038662000380846200035a565b62000363565b6200035a565b9050919050565b5f819050919050565b620003a7836200036c565b620003bf620003b68262000393565b84845462000305565b825550505050565b5f90565b620003d5620003c7565b620003e28184846200039c565b505050565b5b818110156200040957620003fd5f82620003cb565b600181019050620003e8565b5050565b601f82111562000458576200042281620002d8565b6200042d84620002ea565b810160208510156200043d578190505b620004556200044c85620002ea565b830182620003e7565b50505b505050565b5f82821c905092915050565b5f6200047a5f19846008026200045d565b1980831691505092915050565b5f62000494838362000469565b9150826002028217905092915050565b620004af826200026d565b67ffffffffffffffff811115620004cb57620004ca6200008e565b5b620004d78254620002a4565b620004e48282856200040d565b5f60209050601f8311600181146200051a575f841562000505578287015190505b62000511858262000487565b86555062000580565b601f1984166200052a86620002d8565b5f5b8281101562000553578489015182556001820191506020850194506020810190506200052c565b868310156200057357848901516200056f601f89168262000469565b8355505b6001600288020188555050505b505050505050565b611d1680620005965f395ff3fe608060405234801561000f575f80fd5b50600436106100e8575f3560e01c80636352211e1161008a578063a22cb46511610064578063a22cb46514610258578063b88d4fde14610274578063c87b56dd14610290578063e985e9c5146102c0576100e8565b80636352211e146101da57806370a082311461020a57806395d89b411461023a576100e8565b8063095ea7b3116100c6578063095ea7b31461016a57806323b872dd1461018657806340c10f19146101a257806342842e0e146101be576100e8565b806301ffc9a7146100ec57806306fdde031461011c578063081812fc1461013a575b5f80fd5b610106600480360381019061010191906115a7565b6102f0565b60405161011391906115ec565b60405180910390f35b6101246103d1565b604051610131919061168f565b60405180910390f35b610154600480360381019061014f91906116e2565b610460565b604051610161919061174c565b60405180910390f35b610184600480360381019061017f919061178f565b61047b565b005b6101a0600480360381019061019b91906117cd565b610491565b005b6101bc60048036038101906101b7919061178f565b610590565b005b6101d860048036038101906101d391906117cd565b61059e565b005b6101f460048036038101906101ef91906116e2565b6105bd565b604051610201919061174c565b60405180910390f35b610224600480360381019061021f919061181d565b6105ce565b6040516102319190611857565b60405180910390f35b610242610684565b60405161024f919061168f565b60405180910390f35b610272600480360381019061026d919061189a565b610714565b005b61028e60048036038101906102899190611a04565b61072a565b005b6102aa60048036038101906102a591906116e2565b610747565b6040516102b7919061168f565b60405180910390f35b6102da60048036038101906102d59190611a84565b6107ad565b6040516102e791906115ec565b60405180910390f35b5f7f80ac58cd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806103ba57507f5b5e139f000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b806103ca57506103c98261083b565b5b9050919050565b60605f80546103df90611aef565b80601f016020809104026020016040519081016040528092919081815260200182805461040b90611aef565b80156104565780601f1061042d57610100808354040283529160200191610456565b820191905f5260205f20905b81548152906001019060200180831161043957829003601f168201915b5050505050905090565b5f61046a826108a4565b506104748261092a565b9050919050565b61048d8282610488610963565b61096a565b5050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610501575f6040517f64a0ae920000000000000000000000000000000000000000000000000000000081526004016104f8919061174c565b60405180910390fd5b5f610514838361050f610963565b61097c565b90508373ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161461058a578382826040517f64283d7b00000000000000000000000000000000000000000000000000000000815260040161058193929190611b1f565b60405180910390fd5b50505050565b61059a8282610b87565b5050565b6105b883838360405180602001604052805f81525061072a565b505050565b5f6105c7826108a4565b9050919050565b5f8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361063f575f6040517f89c62b64000000000000000000000000000000000000000000000000000000008152600401610636919061174c565b60405180910390fd5b60035f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b60606001805461069390611aef565b80601f01602080910402602001604051908101604052809291908181526020018280546106bf90611aef565b801561070a5780601f106106e15761010080835404028352916020019161070a565b820191905f5260205f20905b8154815290600101906020018083116106ed57829003601f168201915b5050505050905090565b61072661071f610963565b8383610c7a565b5050565b610735848484610491565b61074184848484610de3565b50505050565b6060610752826108a4565b505f61075c610f95565b90505f81511161077a5760405180602001604052805f8152506107a5565b8061078484610fab565b604051602001610795929190611b8e565b6040516020818303038152906040525b915050919050565b5f60055f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16905092915050565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b5f806108af83611075565b90505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361092157826040517f7e2732890000000000000000000000000000000000000000000000000000000081526004016109189190611857565b60405180910390fd5b80915050919050565b5f60045f8381526020019081526020015f205f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b5f33905090565b61097783838360016110ae565b505050565b5f8061098784611075565b90505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16146109c8576109c781848661126d565b5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614610a5357610a075f855f806110ae565b600160035f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825403925050819055505b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614610ad257600160035f8773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8460025f8681526020019081526020015f205f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550838573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4809150509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610bf7575f6040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401610bee919061174c565b60405180910390fd5b5f610c0383835f61097c565b90505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614610c75575f6040517f73c6ac6e000000000000000000000000000000000000000000000000000000008152600401610c6c919061174c565b60405180910390fd5b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610cea57816040517f5b08ba18000000000000000000000000000000000000000000000000000000008152600401610ce1919061174c565b60405180910390fd5b8060055f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3183604051610dd691906115ec565b60405180910390a3505050565b5f8373ffffffffffffffffffffffffffffffffffffffff163b1115610f8f578273ffffffffffffffffffffffffffffffffffffffff1663150b7a02610e26610963565b8685856040518563ffffffff1660e01b8152600401610e489493929190611c03565b6020604051808303815f875af1925050508015610e8357506040513d601f19601f82011682018060405250810190610e809190611c61565b60015b610f04573d805f8114610eb1576040519150601f19603f3d011682016040523d82523d5f602084013e610eb6565b606091505b505f815103610efc57836040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401610ef3919061174c565b60405180910390fd5b805181602001fd5b63150b7a0260e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614610f8d57836040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401610f84919061174c565b60405180910390fd5b505b50505050565b606060405180602001604052805f815250905090565b60605f6001610fb984611330565b0190505f8167ffffffffffffffff811115610fd757610fd66118e0565b5b6040519080825280601f01601f1916602001820160405280156110095781602001600182028036833780820191505090505b5090505f82602001820190505b60011561106a578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a858161105f5761105e611c8c565b5b0494505f8503611016575b819350505050919050565b5f60025f8381526020019081526020015f205f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b80806110e657505f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b15611218575f6110f5846108a4565b90505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415801561115f57508273ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b8015611172575061117081846107ad565b155b156111b457826040517fa9fbf51f0000000000000000000000000000000000000000000000000000000081526004016111ab919061174c565b60405180910390fd5b811561121657838573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45b505b8360045f8581526020019081526020015f205f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505050565b611278838383611481565b61132b575f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036112ec57806040517f7e2732890000000000000000000000000000000000000000000000000000000081526004016112e39190611857565b60405180910390fd5b81816040517f177e802f000000000000000000000000000000000000000000000000000000008152600401611322929190611cb9565b60405180910390fd5b505050565b5f805f90507a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000831061138c577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000838161138257611381611c8c565b5b0492506040810190505b6d04ee2d6d415b85acef810000000083106113c9576d04ee2d6d415b85acef810000000083816113bf576113be611c8c565b5b0492506020810190505b662386f26fc1000083106113f857662386f26fc1000083816113ee576113ed611c8c565b5b0492506010810190505b6305f5e1008310611421576305f5e100838161141757611416611c8c565b5b0492506008810190505b612710831061144657612710838161143c5761143b611c8c565b5b0492506004810190505b60648310611469576064838161145f5761145e611c8c565b5b0492506002810190505b600a8310611478576001810190505b80915050919050565b5f8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415801561153857508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1614806114f957506114f884846107ad565b5b8061153757508273ffffffffffffffffffffffffffffffffffffffff1661151f8361092a565b73ffffffffffffffffffffffffffffffffffffffff16145b5b90509392505050565b5f604051905090565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b61158681611552565b8114611590575f80fd5b50565b5f813590506115a18161157d565b92915050565b5f602082840312156115bc576115bb61154a565b5b5f6115c984828501611593565b91505092915050565b5f8115159050919050565b6115e6816115d2565b82525050565b5f6020820190506115ff5f8301846115dd565b92915050565b5f81519050919050565b5f82825260208201905092915050565b5f5b8381101561163c578082015181840152602081019050611621565b5f8484015250505050565b5f601f19601f8301169050919050565b5f61166182611605565b61166b818561160f565b935061167b81856020860161161f565b61168481611647565b840191505092915050565b5f6020820190508181035f8301526116a78184611657565b905092915050565b5f819050919050565b6116c1816116af565b81146116cb575f80fd5b50565b5f813590506116dc816116b8565b92915050565b5f602082840312156116f7576116f661154a565b5b5f611704848285016116ce565b91505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6117368261170d565b9050919050565b6117468161172c565b82525050565b5f60208201905061175f5f83018461173d565b92915050565b61176e8161172c565b8114611778575f80fd5b50565b5f8135905061178981611765565b92915050565b5f80604083850312156117a5576117a461154a565b5b5f6117b28582860161177b565b92505060206117c3858286016116ce565b9150509250929050565b5f805f606084860312156117e4576117e361154a565b5b5f6117f18682870161177b565b93505060206118028682870161177b565b9250506040611813868287016116ce565b9150509250925092565b5f602082840312156118325761183161154a565b5b5f61183f8482850161177b565b91505092915050565b611851816116af565b82525050565b5f60208201905061186a5f830184611848565b92915050565b611879816115d2565b8114611883575f80fd5b50565b5f8135905061189481611870565b92915050565b5f80604083850312156118b0576118af61154a565b5b5f6118bd8582860161177b565b92505060206118ce85828601611886565b9150509250929050565b5f80fd5b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61191682611647565b810181811067ffffffffffffffff82111715611935576119346118e0565b5b80604052505050565b5f611947611541565b9050611953828261190d565b919050565b5f67ffffffffffffffff821115611972576119716118e0565b5b61197b82611647565b9050602081019050919050565b828183375f83830152505050565b5f6119a86119a384611958565b61193e565b9050828152602081018484840111156119c4576119c36118dc565b5b6119cf848285611988565b509392505050565b5f82601f8301126119eb576119ea6118d8565b5b81356119fb848260208601611996565b91505092915050565b5f805f8060808587031215611a1c57611a1b61154a565b5b5f611a298782880161177b565b9450506020611a3a8782880161177b565b9350506040611a4b878288016116ce565b925050606085013567ffffffffffffffff811115611a6c57611a6b61154e565b5b611a78878288016119d7565b91505092959194509250565b5f8060408385031215611a9a57611a9961154a565b5b5f611aa78582860161177b565b9250506020611ab88582860161177b565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680611b0657607f821691505b602082108103611b1957611b18611ac2565b5b50919050565b5f606082019050611b325f83018661173d565b611b3f6020830185611848565b611b4c604083018461173d565b949350505050565b5f81905092915050565b5f611b6882611605565b611b728185611b54565b9350611b8281856020860161161f565b80840191505092915050565b5f611b998285611b5e565b9150611ba58284611b5e565b91508190509392505050565b5f81519050919050565b5f82825260208201905092915050565b5f611bd582611bb1565b611bdf8185611bbb565b9350611bef81856020860161161f565b611bf881611647565b840191505092915050565b5f608082019050611c165f83018761173d565b611c23602083018661173d565b611c306040830185611848565b8181036060830152611c428184611bcb565b905095945050505050565b5f81519050611c5b8161157d565b92915050565b5f60208284031215611c7657611c7561154a565b5b5f611c8384828501611c4d565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f604082019050611ccc5f83018561173d565b611cd96020830184611848565b939250505056fea26469706673582212207439b47c2a9a1624955997732075917bbf1da26949d000c778f561eb5687576164736f6c63430008180033"; -pub const ERC1155_TEST_TOKEN_BYTES: &str = "608060405234801562000010575f80fd5b50604051620024eb380380620024eb8339818101604052810190620000369190620001ea565b8062000048816200005060201b60201c565b505062000554565b806002908162000061919062000470565b5050565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b620000c6826200007e565b810181811067ffffffffffffffff82111715620000e857620000e76200008e565b5b80604052505050565b5f620000fc62000065565b90506200010a8282620000bb565b919050565b5f67ffffffffffffffff8211156200012c576200012b6200008e565b5b62000137826200007e565b9050602081019050919050565b5f5b838110156200016357808201518184015260208101905062000146565b5f8484015250505050565b5f620001846200017e846200010f565b620000f1565b905082815260208101848484011115620001a357620001a26200007a565b5b620001b084828562000144565b509392505050565b5f82601f830112620001cf57620001ce62000076565b5b8151620001e18482602086016200016e565b91505092915050565b5f602082840312156200020257620002016200006e565b5b5f82015167ffffffffffffffff81111562000222576200022162000072565b5b6200023084828501620001b8565b91505092915050565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200028857607f821691505b6020821081036200029e576200029d62000243565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302620003027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82620002c5565b6200030e8683620002c5565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f62000358620003526200034c8462000326565b6200032f565b62000326565b9050919050565b5f819050919050565b620003738362000338565b6200038b62000382826200035f565b848454620002d1565b825550505050565b5f90565b620003a162000393565b620003ae81848462000368565b505050565b5b81811015620003d557620003c95f8262000397565b600181019050620003b4565b5050565b601f8211156200042457620003ee81620002a4565b620003f984620002b6565b8101602085101562000409578190505b620004216200041885620002b6565b830182620003b3565b50505b505050565b5f82821c905092915050565b5f620004465f198460080262000429565b1980831691505092915050565b5f62000460838362000435565b9150826002028217905092915050565b6200047b8262000239565b67ffffffffffffffff8111156200049757620004966200008e565b5b620004a3825462000270565b620004b0828285620003d9565b5f60209050601f831160018114620004e6575f8415620004d1578287015190505b620004dd858262000453565b8655506200054c565b601f198416620004f686620002a4565b5f5b828110156200051f57848901518255600182019150602085019450602081019050620004f8565b868310156200053f57848901516200053b601f89168262000435565b8355505b6001600288020188555050505b505050505050565b611f8980620005625f395ff3fe608060405234801561000f575f80fd5b5060043610610090575f3560e01c80634e1273f4116100645780634e1273f414610140578063731133e914610170578063a22cb4651461018c578063e985e9c5146101a8578063f242432a146101d857610090565b8062fdd58e1461009457806301ffc9a7146100c45780630e89341c146100f45780632eb2c2d614610124575b5f80fd5b6100ae60048036038101906100a991906113bd565b6101f4565b6040516100bb919061140a565b60405180910390f35b6100de60048036038101906100d99190611478565b610249565b6040516100eb91906114bd565b60405180910390f35b61010e600480360381019061010991906114d6565b61032a565b60405161011b919061158b565b60405180910390f35b61013e6004803603810190610139919061179b565b6103bc565b005b61015a60048036038101906101559190611926565b610463565b6040516101679190611a53565b60405180910390f35b61018a60048036038101906101859190611a73565b61056a565b005b6101a660048036038101906101a19190611b1d565b61057c565b005b6101c260048036038101906101bd9190611b5b565b610592565b6040516101cf91906114bd565b60405180910390f35b6101f260048036038101906101ed9190611b99565b610620565b005b5f805f8381526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f7fd9b67a26000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061031357507f0e89341c000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b806103235750610322826106c7565b5b9050919050565b60606002805461033990611c59565b80601f016020809104026020016040519081016040528092919081815260200182805461036590611c59565b80156103b05780601f10610387576101008083540402835291602001916103b0565b820191905f5260205f20905b81548152906001019060200180831161039357829003601f168201915b50505050509050919050565b5f6103c5610730565b90508073ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff161415801561040a57506104088682610592565b155b1561044e5780866040517fe237d922000000000000000000000000000000000000000000000000000000008152600401610445929190611c98565b60405180910390fd5b61045b8686868686610737565b505050505050565b606081518351146104af57815183516040517f5b0599910000000000000000000000000000000000000000000000000000000081526004016104a6929190611cbf565b60405180910390fd5b5f835167ffffffffffffffff8111156104cb576104ca6115af565b5b6040519080825280602002602001820160405280156104f95781602001602082028036833780820191505090505b5090505f5b845181101561055f5761053561051d828761082b90919063ffffffff16565b610530838761083e90919063ffffffff16565b6101f4565b82828151811061054857610547611ce6565b5b6020026020010181815250508060010190506104fe565b508091505092915050565b61057684848484610851565b50505050565b61058e610587610730565b83836108e6565b5050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16905092915050565b5f610629610730565b90508073ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff161415801561066e575061066c8682610592565b155b156106b25780866040517fe237d9220000000000000000000000000000000000000000000000000000000081526004016106a9929190611c98565b60405180910390fd5b6106bf8686868686610a4f565b505050505050565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b5f33905090565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036107a7575f6040517f57f447ce00000000000000000000000000000000000000000000000000000000815260040161079e9190611d13565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1603610817575f6040517f01a8351400000000000000000000000000000000000000000000000000000000815260040161080e9190611d13565b60405180910390fd5b6108248585858585610b55565b5050505050565b5f60208202602084010151905092915050565b5f60208202602084010151905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036108c1575f6040517f57f447ce0000000000000000000000000000000000000000000000000000000081526004016108b89190611d13565b60405180910390fd5b5f806108cd8585610c01565b915091506108de5f87848487610b55565b505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610956575f6040517fced3e10000000000000000000000000000000000000000000000000000000000815260040161094d9190611d13565b60405180910390fd5b8060015f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3183604051610a4291906114bd565b60405180910390a3505050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610abf575f6040517f57f447ce000000000000000000000000000000000000000000000000000000008152600401610ab69190611d13565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1603610b2f575f6040517f01a83514000000000000000000000000000000000000000000000000000000008152600401610b269190611d13565b60405180910390fd5b5f80610b3b8585610c01565b91509150610b4c8787848487610b55565b50505050505050565b610b6185858585610c31565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1614610bfa575f610b9d610730565b90506001845103610be9575f610bbc5f8661083e90919063ffffffff16565b90505f610bd25f8661083e90919063ffffffff16565b9050610be2838989858589610fc1565b5050610bf8565b610bf7818787878787611170565b5b505b5050505050565b60608060405191506001825283602083015260408201905060018152826020820152604081016040529250929050565b8051825114610c7b57815181516040517f5b059991000000000000000000000000000000000000000000000000000000008152600401610c72929190611cbf565b60405180910390fd5b5f610c84610730565b90505f5b8351811015610e80575f610ca5828661083e90919063ffffffff16565b90505f610cbb838661083e90919063ffffffff16565b90505f73ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614610dde575f805f8481526020019081526020015f205f8a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905081811015610d8a57888183856040517f03dee4c5000000000000000000000000000000000000000000000000000000008152600401610d819493929190611d2c565b60405180910390fd5b8181035f808581526020019081526020015f205f8b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff1614610e7357805f808481526020019081526020015f205f8973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f828254610e6b9190611d9c565b925050819055505b5050806001019050610c88565b506001835103610f3b575f610e9e5f8561083e90919063ffffffff16565b90505f610eb45f8561083e90919063ffffffff16565b90508573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f628585604051610f2c929190611cbf565b60405180910390a45050610fba565b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8686604051610fb1929190611dcf565b60405180910390a45b5050505050565b5f8473ffffffffffffffffffffffffffffffffffffffff163b1115611168578373ffffffffffffffffffffffffffffffffffffffff1663f23a6e6187878686866040518663ffffffff1660e01b8152600401611021959493929190611e56565b6020604051808303815f875af192505050801561105c57506040513d601f19601f820116820180604052508101906110599190611ec2565b60015b6110dd573d805f811461108a576040519150601f19603f3d011682016040523d82523d5f602084013e61108f565b606091505b505f8151036110d557846040517f57f447ce0000000000000000000000000000000000000000000000000000000081526004016110cc9190611d13565b60405180910390fd5b805181602001fd5b63f23a6e6160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461116657846040517f57f447ce00000000000000000000000000000000000000000000000000000000815260040161115d9190611d13565b60405180910390fd5b505b505050505050565b5f8473ffffffffffffffffffffffffffffffffffffffff163b1115611317578373ffffffffffffffffffffffffffffffffffffffff1663bc197c8187878686866040518663ffffffff1660e01b81526004016111d0959493929190611eed565b6020604051808303815f875af192505050801561120b57506040513d601f19601f820116820180604052508101906112089190611ec2565b60015b61128c573d805f8114611239576040519150601f19603f3d011682016040523d82523d5f602084013e61123e565b606091505b505f81510361128457846040517f57f447ce00000000000000000000000000000000000000000000000000000000815260040161127b9190611d13565b60405180910390fd5b805181602001fd5b63bc197c8160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461131557846040517f57f447ce00000000000000000000000000000000000000000000000000000000815260040161130c9190611d13565b60405180910390fd5b505b505050505050565b5f604051905090565b5f80fd5b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61135982611330565b9050919050565b6113698161134f565b8114611373575f80fd5b50565b5f8135905061138481611360565b92915050565b5f819050919050565b61139c8161138a565b81146113a6575f80fd5b50565b5f813590506113b781611393565b92915050565b5f80604083850312156113d3576113d2611328565b5b5f6113e085828601611376565b92505060206113f1858286016113a9565b9150509250929050565b6114048161138a565b82525050565b5f60208201905061141d5f8301846113fb565b92915050565b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b61145781611423565b8114611461575f80fd5b50565b5f813590506114728161144e565b92915050565b5f6020828403121561148d5761148c611328565b5b5f61149a84828501611464565b91505092915050565b5f8115159050919050565b6114b7816114a3565b82525050565b5f6020820190506114d05f8301846114ae565b92915050565b5f602082840312156114eb576114ea611328565b5b5f6114f8848285016113a9565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f5b8381101561153857808201518184015260208101905061151d565b5f8484015250505050565b5f601f19601f8301169050919050565b5f61155d82611501565b611567818561150b565b935061157781856020860161151b565b61158081611543565b840191505092915050565b5f6020820190508181035f8301526115a38184611553565b905092915050565b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6115e582611543565b810181811067ffffffffffffffff82111715611604576116036115af565b5b80604052505050565b5f61161661131f565b905061162282826115dc565b919050565b5f67ffffffffffffffff821115611641576116406115af565b5b602082029050602081019050919050565b5f80fd5b5f61166861166384611627565b61160d565b9050808382526020820190506020840283018581111561168b5761168a611652565b5b835b818110156116b457806116a088826113a9565b84526020840193505060208101905061168d565b5050509392505050565b5f82601f8301126116d2576116d16115ab565b5b81356116e2848260208601611656565b91505092915050565b5f80fd5b5f67ffffffffffffffff821115611709576117086115af565b5b61171282611543565b9050602081019050919050565b828183375f83830152505050565b5f61173f61173a846116ef565b61160d565b90508281526020810184848401111561175b5761175a6116eb565b5b61176684828561171f565b509392505050565b5f82601f830112611782576117816115ab565b5b813561179284826020860161172d565b91505092915050565b5f805f805f60a086880312156117b4576117b3611328565b5b5f6117c188828901611376565b95505060206117d288828901611376565b945050604086013567ffffffffffffffff8111156117f3576117f261132c565b5b6117ff888289016116be565b935050606086013567ffffffffffffffff8111156118205761181f61132c565b5b61182c888289016116be565b925050608086013567ffffffffffffffff81111561184d5761184c61132c565b5b6118598882890161176e565b9150509295509295909350565b5f67ffffffffffffffff8211156118805761187f6115af565b5b602082029050602081019050919050565b5f6118a361189e84611866565b61160d565b905080838252602082019050602084028301858111156118c6576118c5611652565b5b835b818110156118ef57806118db8882611376565b8452602084019350506020810190506118c8565b5050509392505050565b5f82601f83011261190d5761190c6115ab565b5b813561191d848260208601611891565b91505092915050565b5f806040838503121561193c5761193b611328565b5b5f83013567ffffffffffffffff8111156119595761195861132c565b5b611965858286016118f9565b925050602083013567ffffffffffffffff8111156119865761198561132c565b5b611992858286016116be565b9150509250929050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b6119ce8161138a565b82525050565b5f6119df83836119c5565b60208301905092915050565b5f602082019050919050565b5f611a018261199c565b611a0b81856119a6565b9350611a16836119b6565b805f5b83811015611a46578151611a2d88826119d4565b9750611a38836119eb565b925050600181019050611a19565b5085935050505092915050565b5f6020820190508181035f830152611a6b81846119f7565b905092915050565b5f805f8060808587031215611a8b57611a8a611328565b5b5f611a9887828801611376565b9450506020611aa9878288016113a9565b9350506040611aba878288016113a9565b925050606085013567ffffffffffffffff811115611adb57611ada61132c565b5b611ae78782880161176e565b91505092959194509250565b611afc816114a3565b8114611b06575f80fd5b50565b5f81359050611b1781611af3565b92915050565b5f8060408385031215611b3357611b32611328565b5b5f611b4085828601611376565b9250506020611b5185828601611b09565b9150509250929050565b5f8060408385031215611b7157611b70611328565b5b5f611b7e85828601611376565b9250506020611b8f85828601611376565b9150509250929050565b5f805f805f60a08688031215611bb257611bb1611328565b5b5f611bbf88828901611376565b9550506020611bd088828901611376565b9450506040611be1888289016113a9565b9350506060611bf2888289016113a9565b925050608086013567ffffffffffffffff811115611c1357611c1261132c565b5b611c1f8882890161176e565b9150509295509295909350565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680611c7057607f821691505b602082108103611c8357611c82611c2c565b5b50919050565b611c928161134f565b82525050565b5f604082019050611cab5f830185611c89565b611cb86020830184611c89565b9392505050565b5f604082019050611cd25f8301856113fb565b611cdf60208301846113fb565b9392505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f602082019050611d265f830184611c89565b92915050565b5f608082019050611d3f5f830187611c89565b611d4c60208301866113fb565b611d5960408301856113fb565b611d6660608301846113fb565b95945050505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f611da68261138a565b9150611db18361138a565b9250828201905080821115611dc957611dc8611d6f565b5b92915050565b5f6040820190508181035f830152611de781856119f7565b90508181036020830152611dfb81846119f7565b90509392505050565b5f81519050919050565b5f82825260208201905092915050565b5f611e2882611e04565b611e328185611e0e565b9350611e4281856020860161151b565b611e4b81611543565b840191505092915050565b5f60a082019050611e695f830188611c89565b611e766020830187611c89565b611e8360408301866113fb565b611e9060608301856113fb565b8181036080830152611ea28184611e1e565b90509695505050505050565b5f81519050611ebc8161144e565b92915050565b5f60208284031215611ed757611ed6611328565b5b5f611ee484828501611eae565b91505092915050565b5f60a082019050611f005f830188611c89565b611f0d6020830187611c89565b8181036040830152611f1f81866119f7565b90508181036060830152611f3381856119f7565b90508181036080830152611f478184611e1e565b9050969550505050505056fea26469706673582212203835581c6344b12728c44fa4d9e912cd60e64012c1b772bb703d1c36825c16fd64736f6c63430008180033"; -pub const NFT_SWAP_CONTRACT_BYTES: &str = "60a060405234801562000010575f80fd5b50604051620055a2380380620055a2833981810160405281019062000036919062000147565b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603620000a7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200009e90620001fb565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff1681525050506200021b565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6200011182620000e6565b9050919050565b620001238162000105565b81146200012e575f80fd5b50565b5f81519050620001418162000118565b92915050565b5f602082840312156200015f576200015e620000e2565b5b5f6200016e8482850162000131565b91505092915050565b5f82825260208201905092915050565b7f66656541646472657373206d757374206e6f74206265207a65726f20616464725f8201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b5f620001e360238362000177565b9150620001f08262000187565b604082019050919050565b5f6020820190508181035f8301526200021481620001d5565b9050919050565b608051615360620002425f395f8181612aef01528181612b8a0152612f4801526153605ff3fe608060405260043610610113575f3560e01c80639b4603f21161009f578063cc90c19911610063578063cc90c1991461038e578063d6a71eb4146103b6578063e06cf966146103de578063efccb9eb14610408578063f23a6e611461044657610113565b80639b4603f2146102be578063b27e46fb146102da578063bc197c8114610302578063c8d9009b1461033e578063c92cd12d1461036657610113565b8063150b7a02116100e6578063150b7a02146101cb5780633e6af5f21461020757806346b95ac71461022f57806365e266171461026e5780636e6bf6d21461029657610113565b806301ffc9a71461011757806305ec158d146101535780630f235fce1461017b578063146e5b24146101a3575b5f80fd5b348015610122575f80fd5b5061013d6004803603810190610138919061386f565b610482565b60405161014a91906138b4565b60405180910390f35b34801561015e575f80fd5b506101796004803603810190610174919061398d565b610563565b005b348015610186575f80fd5b506101a1600480360381019061019c9190613a2a565b610823565b005b3480156101ae575f80fd5b506101c960048036038101906101c49190613ab3565b610add565b005b3480156101d6575f80fd5b506101f160048036038101906101ec9190613bb1565b610cc3565b6040516101fe9190613c44565b60405180910390f35b348015610212575f80fd5b5061022d60048036038101906102289190613ab3565b611112565b005b34801561023a575f80fd5b5061025560048036038101906102509190613c5d565b611423565b6040516102659493929190613d53565b60405180910390f35b348015610279575f80fd5b50610294600480360381019061028f9190613ab3565b611485565b005b3480156102a1575f80fd5b506102bc60048036038101906102b79190613a2a565b6118e9565b005b6102d860048036038101906102d39190613dc0565b611ba4565b005b3480156102e5575f80fd5b5061030060048036038101906102fb919061398d565b611eda565b005b34801561030d575f80fd5b5061032860048036038101906103239190613eb2565b612199565b6040516103359190613c44565b60405180910390f35b348015610349575f80fd5b50610364600480360381019061035f9190613a2a565b6121d5565b005b348015610371575f80fd5b5061038c6004803603810190610387919061398d565b6124fe565b005b348015610399575f80fd5b506103b460048036038101906103af9190613ab3565b61282c565b005b3480156103c1575f80fd5b506103dc60048036038101906103d79190613f89565b612bdc565b005b3480156103e9575f80fd5b506103f2612f46565b6040516103ff919061405c565b60405180910390f35b348015610413575f80fd5b5061042e60048036038101906104299190613c5d565b612f6a565b60405161043d939291906140bb565b60405180910390f35b348015610451575f80fd5b5061046c600480360381019061046791906140f0565b612fb6565b6040516104799190613c44565b60405180910390f35b5f7f4e2312e0000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061054c57507f150b7a02000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b8061055c575061055b8261344a565b5b9050919050565b6001600381111561057757610576613ce0565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff1660038111156105a9576105a8613ce0565b5b146105e9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105e090614206565b60405180910390fd5b5f600387336002896040516020016106019190614244565b60405160208183030381529060405260405161061d91906142ca565b602060405180830381855afa158015610638573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061065b91906142f4565b888888886040516020016106759796959493929190614384565b60405160208183030381529060405260405161069191906142ca565b602060405180830381855afa1580156106ac573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610736576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161072d9061444e565b60405180910390fd5b60035f808a81526020019081526020015f205f0160186101000a81548160ff0219169083600381111561076c5761076b613ce0565b5b02179055507fac509cdcc7ddb189f81fff6f4824f5c95076e64c3bdce542c50feaa6779afd73886040516107a0919061447b565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b81526004016107eb94939291906144d6565b5f604051808303815f87803b158015610802575f80fd5b505af1158015610814573d5f803e3d5ffd5b50505050505050505050505050565b6001600381111561083757610836613ce0565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff16600381111561086957610868613ce0565b5b146108a9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108a090614206565b60405180910390fd5b5f60038633878787876040516020016108c79695949392919061452c565b6040516020818303038152906040526040516108e391906142ca565b602060405180830381855afa1580156108fe573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610988576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161097f9061444e565b60405180910390fd5b5f808881526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff164210156109f3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109ea9061460b565b60405180910390fd5b60035f808981526020019081526020015f205f0160186101000a81548160ff02191690836003811115610a2957610a28613ce0565b5b02179055507f5dedc4f52b757d9112d09ca0b2f022927104d54e3f54da091587e8ad1921907287604051610a5d919061447b565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b8152600401610aa693929190614629565b5f604051808303815f87803b158015610abd575f80fd5b505af1158015610acf573d5f803e3d5ffd5b505050505050505050505050565b60016004811115610af157610af0613ce0565b5b60015f8981526020019081526020015f205f01601c9054906101000a900460ff166004811115610b2457610b23613ce0565b5b14610b64576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b5b90614206565b60405180910390fd5b5f600387878733888888604051602001610b84979695949392919061465e565b604051602081830303815290604052604051610ba091906142ca565b602060405180830381855afa158015610bbb573d5f803e3d5ffd5b5050506040515160601b905060015f8981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610c46576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c3d9061444e565b60405180910390fd5b600260015f8a81526020019081526020015f205f01601c6101000a81548160ff02191690836004811115610c7d57610c7c613ce0565b5b02179055507f9c45e43e2ef051f70491ffd5221bf02ab37e1324128714ef9610df5f24fc9fb588604051610cb1919061447b565b60405180910390a15050505050505050565b5f808383810190610cd49190614807565b90505f6003811115610ce957610ce8613ce0565b5b5f80835f015181526020019081526020015f205f0160189054906101000a900460ff166003811115610d1e57610d1d613ce0565b5b14610d5e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d55906148a2565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816020015173ffffffffffffffffffffffffffffffffffffffff1603610dd0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610dc79061490a565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816040015173ffffffffffffffffffffffffffffffffffffffff1603610e42576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e3990614972565b60405180910390fd5b806040015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610eb4576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610eab90614a00565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff1614610f22576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f1990614a68565b60405180910390fd5b610f2f81602001516134b3565b15610f6f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f6690614ad0565b60405180910390fd5b5f60038260200151888460600151856080015186604001518b604051602001610f9d9695949392919061452c565b604051602081830303815290604052604051610fb991906142ca565b602060405180830381855afa158015610fd4573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018360a0015163ffffffff1681526020016001600381111561102457611023613ce0565b5b8152505f80845f015181526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548160ff021916908360038111156110bb576110ba613ce0565b5b02179055509050507ff1dc11bbb6d7542c4267ecf1d370ff4c7092518633ecae9939e8488f4e53d2ad825f01516040516110f5919061447b565b60405180910390a163150b7a0260e01b9250505095945050505050565b6001600481111561112657611125613ce0565b5b60015f8981526020019081526020015f205f01601c9054906101000a900460ff16600481111561115957611158613ce0565b5b14611199576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161119090614206565b60405180910390fd5b5f6003878787336002896040516020016111b39190614244565b6040516020818303038152906040526040516111cf91906142ca565b602060405180830381855afa1580156111ea573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061120d91906142f4565b8888604051602001611225979695949392919061465e565b60405160208183030381529060405260405161124191906142ca565b602060405180830381855afa15801561125c573d5f803e3d5ffd5b5050506040515160601b905060015f8981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916146112e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016112de9061444e565b60405180910390fd5b600460015f8a81526020019081526020015f205f01601c6101000a81548160ff0219169083600481111561131e5761131d613ce0565b5b02179055507f45169a52eef651b20a81474b50b8a5d83225225fcd097ef3cf7952d9ab304f278885604051611354929190614aee565b60405180910390a15f86886113699190614b42565b90505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036113e7573373ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f193505050501580156113e1573d5f803e3d5ffd5b50611418565b5f83905061141633838373ffffffffffffffffffffffffffffffffffffffff166134c49092919063ffffffff16565b505b505050505050505050565b6001602052805f5260405f205f91509050805f015f9054906101000a900460601b90805f0160149054906101000a900463ffffffff1690805f0160189054906101000a900463ffffffff1690805f01601c9054906101000a900460ff16905084565b6001600481111561149957611498613ce0565b5b60015f8981526020019081526020015f205f01601c9054906101000a900460ff1660048111156114cc576114cb613ce0565b5b148061151c5750600260048111156114e7576114e6613ce0565b5b60015f8981526020019081526020015f205f01601c9054906101000a900460ff16600481111561151a57611519613ce0565b5b145b61155b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161155290614be5565b60405180910390fd5b5f60038787873388888860405160200161157b979695949392919061465e565b60405160208183030381529060405260405161159791906142ca565b602060405180830381855afa1580156115b2573d5f803e3d5ffd5b5050506040515160601b905060015f8981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461163d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016116349061444e565b60405180910390fd5b6002600481111561165157611650613ce0565b5b60015f8a81526020019081526020015f205f01601c9054906101000a900460ff16600481111561168457611683613ce0565b5b036116f65760015f8981526020019081526020015f205f0160189054906101000a900463ffffffff1663ffffffff164210156116f5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016116ec9061460b565b60405180910390fd5b5b6001600481111561170a57611709613ce0565b5b60015f8a81526020019081526020015f205f01601c9054906101000a900460ff16600481111561173d5761173c613ce0565b5b036117af5760015f8981526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff164210156117ae576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016117a590614c73565b60405180910390fd5b5b600460015f8a81526020019081526020015f205f01601c6101000a81548160ff021916908360048111156117e6576117e5613ce0565b5b02179055507fbdd7a4be6d82798a500b59077706b12d3f45acf5504828919f92501307b2b9538860405161181a919061447b565b60405180910390a15f868861182f9190614b42565b90505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036118ad573373ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f193505050501580156118a7573d5f803e3d5ffd5b506118de565b5f8390506118dc33838373ffffffffffffffffffffffffffffffffffffffff166134c49092919063ffffffff16565b505b505050505050505050565b600160038111156118fd576118fc613ce0565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff16600381111561192f5761192e613ce0565b5b1461196f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161196690614206565b60405180910390fd5b5f600386336002886040516020016119879190614244565b6040516020818303038152906040526040516119a391906142ca565b602060405180830381855afa1580156119be573d5f803e3d5ffd5b5050506040513d601f19601f820116820180604052508101906119e191906142f4565b8787876040516020016119f99695949392919061452c565b604051602081830303815290604052604051611a1591906142ca565b602060405180830381855afa158015611a30573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614611aba576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611ab19061444e565b60405180910390fd5b60035f808981526020019081526020015f205f0160186101000a81548160ff02191690836003811115611af057611aef613ce0565b5b02179055507fac509cdcc7ddb189f81fff6f4824f5c95076e64c3bdce542c50feaa6779afd7387604051611b24919061447b565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b8152600401611b6d93929190614629565b5f604051808303815f87803b158015611b84575f80fd5b505af1158015611b96573d5f803e3d5ffd5b505050505050505050505050565b5f6004811115611bb757611bb6613ce0565b5b60015f8981526020019081526020015f205f01601c9054906101000a900460ff166004811115611bea57611be9613ce0565b5b14611c2a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c2190614d01565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1603611c98576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c8f90614d8f565b60405180910390fd5b5f3411611cda576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611cd190614e1d565b60405180910390fd5b853411611d1c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611d1390614eab565b60405180910390fd5b5f60038734611d2b9190614ec9565b88883389895f604051602001611d47979695949392919061465e565b604051602081830303815290604052604051611d6391906142ca565b602060405180830381855afa158015611d7e573d5f803e3d5ffd5b5050506040515160601b90506040518060800160405280826bffffffffffffffffffffffff191681526020018463ffffffff1681526020018363ffffffff16815260200160016004811115611dd657611dd5613ce0565b5b81525060015f8a81526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548163ffffffff021916908363ffffffff1602179055506060820151815f01601c6101000a81548160ff02191690836004811115611e9157611e90613ce0565b5b02179055509050507ffc6cdccd1d98ded12074a9ebc7f6ab74fed1814ff57f4fb5202464d8938bd93588604051611ec8919061447b565b60405180910390a15050505050505050565b60016003811115611eee57611eed613ce0565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff166003811115611f2057611f1f613ce0565b5b14611f60576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611f5790614206565b60405180910390fd5b5f600387338888888888604051602001611f809796959493929190614384565b604051602081830303815290604052604051611f9c91906142ca565b602060405180830381855afa158015611fb7573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614612041576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016120389061444e565b60405180910390fd5b5f808981526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff164210156120ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016120a39061460b565b60405180910390fd5b60035f808a81526020019081526020015f205f0160186101000a81548160ff021916908360038111156120e2576120e1613ce0565b5b02179055507f5dedc4f52b757d9112d09ca0b2f022927104d54e3f54da091587e8ad1921907288604051612116919061447b565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b815260040161216194939291906144d6565b5f604051808303815f87803b158015612178575f80fd5b505af115801561218a573d5f803e3d5ffd5b50505050505050505050505050565b5f6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016121cc90614f46565b60405180910390fd5b600160038111156121e9576121e8613ce0565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff16600381111561221b5761221a613ce0565b5b1461225b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161225290614206565b60405180910390fd5b3273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146122c9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016122c090614fae565b60405180910390fd5b5f60033387876002886040516020016122e29190614244565b6040516020818303038152906040526040516122fe91906142ca565b602060405180830381855afa158015612319573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061233c91906142f4565b87876040516020016123539695949392919061452c565b60405160208183030381529060405260405161236f91906142ca565b602060405180830381855afa15801561238a573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614612414576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161240b9061444e565b60405180910390fd5b60025f808981526020019081526020015f205f0160186101000a81548160ff0219169083600381111561244a57612449613ce0565b5b02179055507fad62ed075fe8969df63026f45152d6e996a0697a736a8de92ee85ae9c9958cf08760405161247e919061447b565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b81526004016124c793929190614629565b5f604051808303815f87803b1580156124de575f80fd5b505af11580156124f0573d5f803e3d5ffd5b505050505050505050505050565b6001600381111561251257612511613ce0565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff16600381111561254457612543613ce0565b5b14612584576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161257b90614206565b60405180910390fd5b3273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146125f2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016125e990614fae565b60405180910390fd5b5f600333888860028960405160200161260b9190614244565b60405160208183030381529060405260405161262791906142ca565b602060405180830381855afa158015612642573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061266591906142f4565b88888860405160200161267e9796959493929190614384565b60405160208183030381529060405260405161269a91906142ca565b602060405180830381855afa1580156126b5573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461273f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016127369061444e565b60405180910390fd5b60025f808a81526020019081526020015f205f0160186101000a81548160ff0219169083600381111561277557612774613ce0565b5b02179055507fad62ed075fe8969df63026f45152d6e996a0697a736a8de92ee85ae9c9958cf0886040516127a9919061447b565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b81526004016127f494939291906144d6565b5f604051808303815f87803b15801561280b575f80fd5b505af115801561281d573d5f803e3d5ffd5b50505050505050505050505050565b600260048111156128405761283f613ce0565b5b60015f8981526020019081526020015f205f01601c9054906101000a900460ff16600481111561287357612872613ce0565b5b146128b3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016128aa9061503c565b60405180910390fd5b5f600387873388886002896040516020016128ce9190614244565b6040516020818303038152906040526040516128ea91906142ca565b602060405180830381855afa158015612905573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061292891906142f4565b8860405160200161293f979695949392919061465e565b60405160208183030381529060405260405161295b91906142ca565b602060405180830381855afa158015612976573d5f803e3d5ffd5b5050506040515160601b905060015f8981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614612a01576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016129f89061444e565b60405180910390fd5b600360015f8a81526020019081526020015f205f01601c6101000a81548160ff02191690836004811115612a3857612a37613ce0565b5b02179055507f0d0da0df275f85bed3a5fe7ae79f3559341a3f9ccd8e010133438135bda00a878884604051612a6e929190614aee565b60405180910390a15f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603612b56573373ffffffffffffffffffffffffffffffffffffffff166108fc8890811502906040515f60405180830381858888f19350505050158015612aec573d5f803e3d5ffd5b507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166108fc8790811502906040515f60405180830381858888f19350505050158015612b50573d5f803e3d5ffd5b50612bd2565b5f829050612b8533898373ffffffffffffffffffffffffffffffffffffffff166134c49092919063ffffffff16565b612bd07f0000000000000000000000000000000000000000000000000000000000000000888373ffffffffffffffffffffffffffffffffffffffff166134c49092919063ffffffff16565b505b5050505050505050565b5f6004811115612bef57612bee613ce0565b5b60015f8b81526020019081526020015f205f01601c9054906101000a900460ff166004811115612c2257612c21613ce0565b5b14612c62576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612c59906150ca565b60405180910390fd5b5f8811612ca4576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612c9b90615132565b60405180910390fd5b5f8711612ce6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612cdd9061519a565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1603612d54576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612d4b90614d8f565b60405180910390fd5b5f60038989883389898d604051602001612d74979695949392919061465e565b604051602081830303815290604052604051612d9091906142ca565b602060405180830381855afa158015612dab573d5f803e3d5ffd5b5050506040515160601b90506040518060800160405280826bffffffffffffffffffffffff191681526020018463ffffffff1681526020018363ffffffff16815260200160016004811115612e0357612e02613ce0565b5b81525060015f8c81526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548163ffffffff021916908363ffffffff1602179055506060820151815f01601c6101000a81548160ff02191690836004811115612ebe57612ebd613ce0565b5b02179055509050507ffc6cdccd1d98ded12074a9ebc7f6ab74fed1814ff57f4fb5202464d8938bd9358a604051612ef5919061447b565b60405180910390a15f879050612f3933308b8d612f129190614b42565b8473ffffffffffffffffffffffffffffffffffffffff16613543909392919063ffffffff16565b5050505050505050505050565b7f000000000000000000000000000000000000000000000000000000000000000081565b5f602052805f5260405f205f91509050805f015f9054906101000a900460601b90805f0160149054906101000a900463ffffffff1690805f0160189054906101000a900460ff16905083565b5f808383810190612fc79190614807565b90505f6003811115612fdc57612fdb613ce0565b5b5f80835f015181526020019081526020015f205f0160189054906101000a900460ff16600381111561301157613010613ce0565b5b14613051576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161304890615228565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816020015173ffffffffffffffffffffffffffffffffffffffff16036130c3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016130ba9061490a565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816040015173ffffffffffffffffffffffffffffffffffffffff1603613135576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161312c90614972565b60405180910390fd5b806040015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146131a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161319e90614a00565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614613215576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161320c90614a68565b60405180910390fd5b5f8511613257576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161324e90615290565b60405180910390fd5b61326481602001516134b3565b156132a4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161329b90614ad0565b60405180910390fd5b5f60038260200151898460600151856080015186604001518c8c6040516020016132d49796959493929190614384565b6040516020818303038152906040526040516132f091906142ca565b602060405180830381855afa15801561330b573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018360a0015163ffffffff1681526020016001600381111561335b5761335a613ce0565b5b8152505f80845f015181526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548160ff021916908360038111156133f2576133f1613ce0565b5b02179055509050507ff1dc11bbb6d7542c4267ecf1d370ff4c7092518633ecae9939e8488f4e53d2ad825f015160405161342c919061447b565b60405180910390a163f23a6e6160e01b925050509695505050505050565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b5f80823b90505f8111915050919050565b61353e838473ffffffffffffffffffffffffffffffffffffffff1663a9059cbb85856040516024016134f79291906152ae565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506135c5565b505050565b6135bf848573ffffffffffffffffffffffffffffffffffffffff166323b872dd86868660405160240161357893929190614629565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506135c5565b50505050565b5f6135ef828473ffffffffffffffffffffffffffffffffffffffff1661365a90919063ffffffff16565b90505f81511415801561361357508080602001905181019061361191906152ff565b155b1561365557826040517f5274afe700000000000000000000000000000000000000000000000000000000815260040161364c919061405c565b60405180910390fd5b505050565b606061366783835f61366f565b905092915050565b6060814710156136b657306040517fcd7860590000000000000000000000000000000000000000000000000000000081526004016136ad919061405c565b60405180910390fd5b5f808573ffffffffffffffffffffffffffffffffffffffff1684866040516136de91906142ca565b5f6040518083038185875af1925050503d805f8114613718576040519150601f19603f3d011682016040523d82523d5f602084013e61371d565b606091505b509150915061372d868383613738565b925050509392505050565b60608261374d57613748826137c5565b6137bd565b5f825114801561377357505f8473ffffffffffffffffffffffffffffffffffffffff163b145b156137b557836040517f9996b3150000000000000000000000000000000000000000000000000000000081526004016137ac919061405c565b60405180910390fd5b8190506137be565b5b9392505050565b5f815111156137d75780518082602001fd5b6040517f1425ea4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f604051905090565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b61384e8161381a565b8114613858575f80fd5b50565b5f8135905061386981613845565b92915050565b5f6020828403121561388457613883613812565b5b5f6138918482850161385b565b91505092915050565b5f8115159050919050565b6138ae8161389a565b82525050565b5f6020820190506138c75f8301846138a5565b92915050565b5f819050919050565b6138df816138cd565b81146138e9575f80fd5b50565b5f813590506138fa816138d6565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61392982613900565b9050919050565b6139398161391f565b8114613943575f80fd5b50565b5f8135905061395481613930565b92915050565b5f819050919050565b61396c8161395a565b8114613976575f80fd5b50565b5f8135905061398781613963565b92915050565b5f805f805f805f60e0888a0312156139a8576139a7613812565b5b5f6139b58a828b016138ec565b97505060206139c68a828b01613946565b96505060406139d78a828b016138ec565b95505060606139e88a828b016138ec565b94505060806139f98a828b01613946565b93505060a0613a0a8a828b01613979565b92505060c0613a1b8a828b01613979565b91505092959891949750929550565b5f805f805f8060c08789031215613a4457613a43613812565b5b5f613a5189828a016138ec565b9650506020613a6289828a01613946565b9550506040613a7389828a016138ec565b9450506060613a8489828a016138ec565b9350506080613a9589828a01613946565b92505060a0613aa689828a01613979565b9150509295509295509295565b5f805f805f805f60e0888a031215613ace57613acd613812565b5b5f613adb8a828b016138ec565b9750506020613aec8a828b01613979565b9650506040613afd8a828b01613979565b9550506060613b0e8a828b01613946565b9450506080613b1f8a828b016138ec565b93505060a0613b308a828b016138ec565b92505060c0613b418a828b01613946565b91505092959891949750929550565b5f80fd5b5f80fd5b5f80fd5b5f8083601f840112613b7157613b70613b50565b5b8235905067ffffffffffffffff811115613b8e57613b8d613b54565b5b602083019150836001820283011115613baa57613ba9613b58565b5b9250929050565b5f805f805f60808688031215613bca57613bc9613812565b5b5f613bd788828901613946565b9550506020613be888828901613946565b9450506040613bf988828901613979565b935050606086013567ffffffffffffffff811115613c1a57613c19613816565b5b613c2688828901613b5c565b92509250509295509295909350565b613c3e8161381a565b82525050565b5f602082019050613c575f830184613c35565b92915050565b5f60208284031215613c7257613c71613812565b5b5f613c7f848285016138ec565b91505092915050565b5f7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000082169050919050565b613cbc81613c88565b82525050565b5f63ffffffff82169050919050565b613cda81613cc2565b82525050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b60058110613d1e57613d1d613ce0565b5b50565b5f819050613d2e82613d0d565b919050565b5f613d3d82613d21565b9050919050565b613d4d81613d33565b82525050565b5f608082019050613d665f830187613cb3565b613d736020830186613cd1565b613d806040830185613cd1565b613d8d6060830184613d44565b95945050505050565b613d9f81613cc2565b8114613da9575f80fd5b50565b5f81359050613dba81613d96565b92915050565b5f805f805f805f60e0888a031215613ddb57613dda613812565b5b5f613de88a828b016138ec565b9750506020613df98a828b01613979565b9650506040613e0a8a828b01613946565b9550506060613e1b8a828b016138ec565b9450506080613e2c8a828b016138ec565b93505060a0613e3d8a828b01613dac565b92505060c0613e4e8a828b01613dac565b91505092959891949750929550565b5f8083601f840112613e7257613e71613b50565b5b8235905067ffffffffffffffff811115613e8f57613e8e613b54565b5b602083019150836020820283011115613eab57613eaa613b58565b5b9250929050565b5f805f805f805f8060a0898b031215613ece57613ecd613812565b5b5f613edb8b828c01613946565b9850506020613eec8b828c01613946565b975050604089013567ffffffffffffffff811115613f0d57613f0c613816565b5b613f198b828c01613e5d565b9650965050606089013567ffffffffffffffff811115613f3c57613f3b613816565b5b613f488b828c01613e5d565b9450945050608089013567ffffffffffffffff811115613f6b57613f6a613816565b5b613f778b828c01613b5c565b92509250509295985092959890939650565b5f805f805f805f805f6101208a8c031215613fa757613fa6613812565b5b5f613fb48c828d016138ec565b9950506020613fc58c828d01613979565b9850506040613fd68c828d01613979565b9750506060613fe78c828d01613946565b9650506080613ff88c828d01613946565b95505060a06140098c828d016138ec565b94505060c061401a8c828d016138ec565b93505060e061402b8c828d01613dac565b92505061010061403d8c828d01613dac565b9150509295985092959850929598565b6140568161391f565b82525050565b5f60208201905061406f5f83018461404d565b92915050565b6004811061408657614085613ce0565b5b50565b5f81905061409682614075565b919050565b5f6140a582614089565b9050919050565b6140b58161409b565b82525050565b5f6060820190506140ce5f830186613cb3565b6140db6020830185613cd1565b6140e860408301846140ac565b949350505050565b5f805f805f8060a0878903121561410a57614109613812565b5b5f61411789828a01613946565b965050602061412889828a01613946565b955050604061413989828a01613979565b945050606061414a89828a01613979565b935050608087013567ffffffffffffffff81111561416b5761416a613816565b5b61417789828a01613b5c565b92509250509295509295509295565b5f82825260208201905092915050565b7f496e76616c6964207061796d656e742073746174652e204d75737420626520505f8201527f61796d656e7453656e7400000000000000000000000000000000000000000000602082015250565b5f6141f0602a83614186565b91506141fb82614196565b604082019050919050565b5f6020820190508181035f83015261421d816141e4565b9050919050565b5f819050919050565b61423e614239826138cd565b614224565b82525050565b5f61424f828461422d565b60208201915081905092915050565b5f81519050919050565b5f81905092915050565b5f5b8381101561428f578082015181840152602081019050614274565b5f8484015250505050565b5f6142a48261425e565b6142ae8185614268565b93506142be818560208601614272565b80840191505092915050565b5f6142d5828461429a565b915081905092915050565b5f815190506142ee816138d6565b92915050565b5f6020828403121561430957614308613812565b5b5f614316848285016142e0565b91505092915050565b5f8160601b9050919050565b5f6143358261431f565b9050919050565b5f6143468261432b565b9050919050565b61435e6143598261391f565b61433c565b82525050565b5f819050919050565b61437e6143798261395a565b614364565b82525050565b5f61438f828a61434d565b60148201915061439f828961434d565b6014820191506143af828861422d565b6020820191506143bf828761422d565b6020820191506143cf828661434d565b6014820191506143df828561436d565b6020820191506143ef828461436d565b60208201915081905098975050505050505050565b7f496e76616c6964207061796d656e7448617368000000000000000000000000005f82015250565b5f614438601383614186565b915061444382614404565b602082019050919050565b5f6020820190508181035f8301526144658161442c565b9050919050565b614475816138cd565b82525050565b5f60208201905061448e5f83018461446c565b92915050565b61449d8161395a565b82525050565b5f82825260208201905092915050565b50565b5f6144c15f836144a3565b91506144cc826144b3565b5f82019050919050565b5f60a0820190506144e95f83018761404d565b6144f6602083018661404d565b6145036040830185614494565b6145106060830184614494565b8181036080830152614521816144b6565b905095945050505050565b5f614537828961434d565b601482019150614547828861434d565b601482019150614557828761422d565b602082019150614567828661422d565b602082019150614577828561434d565b601482019150614587828461436d565b602082019150819050979650505050505050565b7f43757272656e742074696d657374616d70206469646e277420657863656564205f8201527f7061796d656e7420726566756e64206c6f636b2074696d650000000000000000602082015250565b5f6145f5603883614186565b91506146008261459b565b604082019050919050565b5f6020820190508181035f830152614622816145e9565b9050919050565b5f60608201905061463c5f83018661404d565b614649602083018561404d565b6146566040830184614494565b949350505050565b5f614669828a61436d565b602082019150614679828961436d565b602082019150614689828861434d565b601482019150614699828761434d565b6014820191506146a9828661422d565b6020820191506146b9828561422d565b6020820191506146c9828461434d565b60148201915081905098975050505050505050565b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b614728826146e2565b810181811067ffffffffffffffff82111715614747576147466146f2565b5b80604052505050565b5f614759613809565b9050614765828261471f565b919050565b5f60c0828403121561477f5761477e6146de565b5b61478960c0614750565b90505f614798848285016138ec565b5f8301525060206147ab84828501613946565b60208301525060406147bf84828501613946565b60408301525060606147d3848285016138ec565b60608301525060806147e7848285016138ec565b60808301525060a06147fb84828501613dac565b60a08301525092915050565b5f60c0828403121561481c5761481b613812565b5b5f6148298482850161476a565b91505092915050565b7f4d616b657220455243373231207061796d656e74206d75737420626520556e695f8201527f6e697469616c697a656400000000000000000000000000000000000000000000602082015250565b5f61488c602a83614186565b915061489782614832565b604082019050919050565b5f6020820190508181035f8301526148b981614880565b9050919050565b7f54616b6572206d757374206e6f74206265207a65726f206164647265737300005f82015250565b5f6148f4601e83614186565b91506148ff826148c0565b602082019050919050565b5f6020820190508181035f830152614921816148e8565b9050919050565b7f546f6b656e206d757374206e6f74206265207a65726f206164647265737300005f82015250565b5f61495c601e83614186565b915061496782614928565b602082019050919050565b5f6020820190508181035f83015261498981614950565b9050919050565b7f546f6b656e206164647265737320646f6573206e6f74206d617463682073656e5f8201527f6465720000000000000000000000000000000000000000000000000000000000602082015250565b5f6149ea602383614186565b91506149f582614990565b604082019050919050565b5f6020820190508181035f830152614a17816149de565b9050919050565b7f4f70657261746f72206d757374206265207468652073656e64657200000000005f82015250565b5f614a52601b83614186565b9150614a5d82614a1e565b602082019050919050565b5f6020820190508181035f830152614a7f81614a46565b9050919050565b7f54616b65722063616e6e6f74206265206120636f6e74726163740000000000005f82015250565b5f614aba601a83614186565b9150614ac582614a86565b602082019050919050565b5f6020820190508181035f830152614ae781614aae565b9050919050565b5f604082019050614b015f83018561446c565b614b0e602083018461446c565b9392505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f614b4c8261395a565b9150614b578361395a565b9250828201905080821115614b6f57614b6e614b15565b5b92915050565b7f496e76616c6964207061796d656e742073746174652e204d75737420626520505f8201527f61796d656e7453656e74206f722054616b6572417070726f7665640000000000602082015250565b5f614bcf603b83614186565b9150614bda82614b75565b604082019050919050565b5f6020820190508181035f830152614bfc81614bc3565b9050919050565b7f43757272656e742074696d657374616d70206469646e277420657863656564205f8201527f7061796d656e74207072652d617070726f7665206c6f636b2074696d65000000602082015250565b5f614c5d603d83614186565b9150614c6882614c03565b604082019050919050565b5f6020820190508181035f830152614c8a81614c51565b9050919050565b7f54616b6572207061796d656e7420697320616c726561647920696e697469616c5f8201527f697a656400000000000000000000000000000000000000000000000000000000602082015250565b5f614ceb602483614186565b9150614cf682614c91565b604082019050919050565b5f6020820190508181035f830152614d1881614cdf565b9050919050565b7f5265636569766572206d757374206e6f74206265207a65726f206164647265735f8201527f7300000000000000000000000000000000000000000000000000000000000000602082015250565b5f614d79602183614186565b9150614d8482614d1f565b604082019050919050565b5f6020820190508181035f830152614da681614d6d565b9050919050565b7f4554482076616c7565206d7573742062652067726561746572207468616e207a5f8201527f65726f0000000000000000000000000000000000000000000000000000000000602082015250565b5f614e07602383614186565b9150614e1282614dad565b604082019050919050565b5f6020820190508181035f830152614e3481614dfb565b9050919050565b7f4554482076616c7565206d7573742062652067726561746572207468616e20645f8201527f6578206665650000000000000000000000000000000000000000000000000000602082015250565b5f614e95602683614186565b9150614ea082614e3b565b604082019050919050565b5f6020820190508181035f830152614ec281614e89565b9050919050565b5f614ed38261395a565b9150614ede8361395a565b9250828203905081811115614ef657614ef5614b15565b5b92915050565b7f4261746368207472616e7366657273206e6f7420737570706f727465640000005f82015250565b5f614f30601d83614186565b9150614f3b82614efc565b602082019050919050565b5f6020820190508181035f830152614f5d81614f24565b9050919050565b7f43616c6c6572206d75737420626520616e20454f4100000000000000000000005f82015250565b5f614f98601583614186565b9150614fa382614f64565b602082019050919050565b5f6020820190508181035f830152614fc581614f8c565b9050919050565b7f496e76616c6964207061796d656e742073746174652e204d75737420626520545f8201527f616b6572417070726f7665640000000000000000000000000000000000000000602082015250565b5f615026602c83614186565b915061503182614fcc565b604082019050919050565b5f6020820190508181035f8301526150538161501a565b9050919050565b7f4552433230207632207061796d656e7420697320616c726561647920696e69745f8201527f69616c697a656400000000000000000000000000000000000000000000000000602082015250565b5f6150b4602783614186565b91506150bf8261505a565b604082019050919050565b5f6020820190508181035f8301526150e1816150a8565b9050919050565b7f416d6f756e74206d757374206e6f74206265207a65726f0000000000000000005f82015250565b5f61511c601783614186565b9150615127826150e8565b602082019050919050565b5f6020820190508181035f83015261514981615110565b9050919050565b7f44657820666565206d757374206e6f74206265207a65726f00000000000000005f82015250565b5f615184601883614186565b915061518f82615150565b602082019050919050565b5f6020820190508181035f8301526151b181615178565b9050919050565b7f4d616b65722045524331313535207061796d656e74206d75737420626520556e5f8201527f696e697469616c697a6564000000000000000000000000000000000000000000602082015250565b5f615212602b83614186565b915061521d826151b8565b604082019050919050565b5f6020820190508181035f83015261523f81615206565b9050919050565b7f56616c7565206d7573742062652067726561746572207468616e2030000000005f82015250565b5f61527a601c83614186565b915061528582615246565b602082019050919050565b5f6020820190508181035f8301526152a78161526e565b9050919050565b5f6040820190506152c15f83018561404d565b6152ce6020830184614494565b9392505050565b6152de8161389a565b81146152e8575f80fd5b50565b5f815190506152f9816152d5565b92915050565b5f6020828403121561531457615313613812565b5b5f615321848285016152eb565b9150509291505056fea26469706673582212200d86b0f6898fb823c55626c3b02a7098bc8622606b092e0f458df6c86ce2967864736f6c63430008180033"; -pub const NFT_MAKER_SWAP_V2_BYTES: &str = "6080604052348015600e575f80fd5b50612ffd8061001c5f395ff3fe608060405234801561000f575f80fd5b50600436106100a7575f3560e01c8063b27e46fb1161006f578063b27e46fb1461015f578063bc197c811461017b578063c8d9009b146101ab578063c92cd12d146101c7578063efccb9eb146101e3578063f23a6e6114610215576100a7565b806301ffc9a7146100ab57806305ec158d146100db5780630f235fce146100f7578063150b7a02146101135780636e6bf6d214610143575b5f80fd5b6100c560048036038101906100c09190611ebc565b610245565b6040516100d29190611f01565b60405180910390f35b6100f560048036038101906100f09190611fda565b610326565b005b610111600480360381019061010c9190612077565b6105e6565b005b61012d60048036038101906101289190612161565b6108a0565b60405161013a91906121f4565b60405180910390f35b61015d60048036038101906101589190612077565b610cef565b005b61017960048036038101906101749190611fda565b610faa565b005b61019560048036038101906101909190612262565b611269565b6040516101a291906121f4565b60405180910390f35b6101c560048036038101906101c09190612077565b6112a5565b005b6101e160048036038101906101dc9190611fda565b6115ce565b005b6101fd60048036038101906101f89190612339565b6118fc565b60405161020c9392919061242f565b60405180910390f35b61022f600480360381019061022a9190612464565b611948565b60405161023c91906121f4565b60405180910390f35b5f7f4e2312e0000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061030f57507f150b7a02000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b8061031f575061031e82611ddc565b5b9050919050565b6001600381111561033a576103396123bc565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff16600381111561036c5761036b6123bc565b5b146103ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103a39061257a565b60405180910390fd5b5f600387336002896040516020016103c491906125b8565b6040516020818303038152906040526040516103e09190612624565b602060405180830381855afa1580156103fb573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061041e919061264e565b8888888860405160200161043897969594939291906126de565b6040516020818303038152906040526040516104549190612624565b602060405180830381855afa15801561046f573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916146104f9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104f0906127a8565b60405180910390fd5b60035f808a81526020019081526020015f205f0160186101000a81548160ff0219169083600381111561052f5761052e6123bc565b5b02179055507fac509cdcc7ddb189f81fff6f4824f5c95076e64c3bdce542c50feaa6779afd738860405161056391906127d5565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b81526004016105ae949392919061283f565b5f604051808303815f87803b1580156105c5575f80fd5b505af11580156105d7573d5f803e3d5ffd5b50505050505050505050505050565b600160038111156105fa576105f96123bc565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff16600381111561062c5761062b6123bc565b5b1461066c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106639061257a565b60405180910390fd5b5f600386338787878760405160200161068a96959493929190612895565b6040516020818303038152906040526040516106a69190612624565b602060405180830381855afa1580156106c1573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461074b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610742906127a8565b60405180910390fd5b5f808881526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff164210156107b6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107ad90612974565b60405180910390fd5b60035f808981526020019081526020015f205f0160186101000a81548160ff021916908360038111156107ec576107eb6123bc565b5b02179055507f5dedc4f52b757d9112d09ca0b2f022927104d54e3f54da091587e8ad192190728760405161082091906127d5565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b815260040161086993929190612992565b5f604051808303815f87803b158015610880575f80fd5b505af1158015610892573d5f803e3d5ffd5b505050505050505050505050565b5f8083838101906108b19190612b1a565b90505f60038111156108c6576108c56123bc565b5b5f80835f015181526020019081526020015f205f0160189054906101000a900460ff1660038111156108fb576108fa6123bc565b5b1461093b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161093290612bb5565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816020015173ffffffffffffffffffffffffffffffffffffffff16036109ad576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109a490612c1d565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816040015173ffffffffffffffffffffffffffffffffffffffff1603610a1f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a1690612c85565b60405180910390fd5b806040015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610a91576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a8890612d13565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff1614610aff576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610af690612d7b565b60405180910390fd5b610b0c8160200151611e45565b15610b4c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b4390612de3565b60405180910390fd5b5f60038260200151888460600151856080015186604001518b604051602001610b7a96959493929190612895565b604051602081830303815290604052604051610b969190612624565b602060405180830381855afa158015610bb1573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018360a0015163ffffffff16815260200160016003811115610c0157610c006123bc565b5b8152505f80845f015181526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548160ff02191690836003811115610c9857610c976123bc565b5b02179055509050507ff1dc11bbb6d7542c4267ecf1d370ff4c7092518633ecae9939e8488f4e53d2ad825f0151604051610cd291906127d5565b60405180910390a163150b7a0260e01b9250505095945050505050565b60016003811115610d0357610d026123bc565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff166003811115610d3557610d346123bc565b5b14610d75576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d6c9061257a565b60405180910390fd5b5f60038633600288604051602001610d8d91906125b8565b604051602081830303815290604052604051610da99190612624565b602060405180830381855afa158015610dc4573d5f803e3d5ffd5b5050506040513d601f19601f82011682018060405250810190610de7919061264e565b878787604051602001610dff96959493929190612895565b604051602081830303815290604052604051610e1b9190612624565b602060405180830381855afa158015610e36573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610ec0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610eb7906127a8565b60405180910390fd5b60035f808981526020019081526020015f205f0160186101000a81548160ff02191690836003811115610ef657610ef56123bc565b5b02179055507fac509cdcc7ddb189f81fff6f4824f5c95076e64c3bdce542c50feaa6779afd7387604051610f2a91906127d5565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b8152600401610f7393929190612992565b5f604051808303815f87803b158015610f8a575f80fd5b505af1158015610f9c573d5f803e3d5ffd5b505050505050505050505050565b60016003811115610fbe57610fbd6123bc565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff166003811115610ff057610fef6123bc565b5b14611030576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110279061257a565b60405180910390fd5b5f60038733888888888860405160200161105097969594939291906126de565b60405160208183030381529060405260405161106c9190612624565b602060405180830381855afa158015611087573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614611111576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611108906127a8565b60405180910390fd5b5f808981526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff1642101561117c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161117390612974565b60405180910390fd5b60035f808a81526020019081526020015f205f0160186101000a81548160ff021916908360038111156111b2576111b16123bc565b5b02179055507f5dedc4f52b757d9112d09ca0b2f022927104d54e3f54da091587e8ad19219072886040516111e691906127d5565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b8152600401611231949392919061283f565b5f604051808303815f87803b158015611248575f80fd5b505af115801561125a573d5f803e3d5ffd5b50505050505050505050505050565b5f6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161129c90612e4b565b60405180910390fd5b600160038111156112b9576112b86123bc565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff1660038111156112eb576112ea6123bc565b5b1461132b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016113229061257a565b60405180910390fd5b3273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611399576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161139090612eb3565b60405180910390fd5b5f60033387876002886040516020016113b291906125b8565b6040516020818303038152906040526040516113ce9190612624565b602060405180830381855afa1580156113e9573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061140c919061264e565b878760405160200161142396959493929190612895565b60405160208183030381529060405260405161143f9190612624565b602060405180830381855afa15801561145a573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916146114e4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114db906127a8565b60405180910390fd5b60025f808981526020019081526020015f205f0160186101000a81548160ff0219169083600381111561151a576115196123bc565b5b02179055507fad62ed075fe8969df63026f45152d6e996a0697a736a8de92ee85ae9c9958cf08760405161154e91906127d5565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b815260040161159793929190612992565b5f604051808303815f87803b1580156115ae575f80fd5b505af11580156115c0573d5f803e3d5ffd5b505050505050505050505050565b600160038111156115e2576115e16123bc565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff166003811115611614576116136123bc565b5b14611654576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161164b9061257a565b60405180910390fd5b3273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146116c2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016116b990612eb3565b60405180910390fd5b5f60033388886002896040516020016116db91906125b8565b6040516020818303038152906040526040516116f79190612624565b602060405180830381855afa158015611712573d5f803e3d5ffd5b5050506040513d601f19601f82011682018060405250810190611735919061264e565b88888860405160200161174e97969594939291906126de565b60405160208183030381529060405260405161176a9190612624565b602060405180830381855afa158015611785573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461180f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611806906127a8565b60405180910390fd5b60025f808a81526020019081526020015f205f0160186101000a81548160ff02191690836003811115611845576118446123bc565b5b02179055507fad62ed075fe8969df63026f45152d6e996a0697a736a8de92ee85ae9c9958cf08860405161187991906127d5565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b81526004016118c4949392919061283f565b5f604051808303815f87803b1580156118db575f80fd5b505af11580156118ed573d5f803e3d5ffd5b50505050505050505050505050565b5f602052805f5260405f205f91509050805f015f9054906101000a900460601b90805f0160149054906101000a900463ffffffff1690805f0160189054906101000a900460ff16905083565b5f8083838101906119599190612b1a565b90505f600381111561196e5761196d6123bc565b5b5f80835f015181526020019081526020015f205f0160189054906101000a900460ff1660038111156119a3576119a26123bc565b5b146119e3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016119da90612f41565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816020015173ffffffffffffffffffffffffffffffffffffffff1603611a55576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611a4c90612c1d565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816040015173ffffffffffffffffffffffffffffffffffffffff1603611ac7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611abe90612c85565b60405180910390fd5b806040015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611b39576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b3090612d13565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614611ba7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b9e90612d7b565b60405180910390fd5b5f8511611be9576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611be090612fa9565b60405180910390fd5b611bf68160200151611e45565b15611c36576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c2d90612de3565b60405180910390fd5b5f60038260200151898460600151856080015186604001518c8c604051602001611c6697969594939291906126de565b604051602081830303815290604052604051611c829190612624565b602060405180830381855afa158015611c9d573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018360a0015163ffffffff16815260200160016003811115611ced57611cec6123bc565b5b8152505f80845f015181526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548160ff02191690836003811115611d8457611d836123bc565b5b02179055509050507ff1dc11bbb6d7542c4267ecf1d370ff4c7092518633ecae9939e8488f4e53d2ad825f0151604051611dbe91906127d5565b60405180910390a163f23a6e6160e01b925050509695505050505050565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b5f80823b90505f8111915050919050565b5f604051905090565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b611e9b81611e67565b8114611ea5575f80fd5b50565b5f81359050611eb681611e92565b92915050565b5f60208284031215611ed157611ed0611e5f565b5b5f611ede84828501611ea8565b91505092915050565b5f8115159050919050565b611efb81611ee7565b82525050565b5f602082019050611f145f830184611ef2565b92915050565b5f819050919050565b611f2c81611f1a565b8114611f36575f80fd5b50565b5f81359050611f4781611f23565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f611f7682611f4d565b9050919050565b611f8681611f6c565b8114611f90575f80fd5b50565b5f81359050611fa181611f7d565b92915050565b5f819050919050565b611fb981611fa7565b8114611fc3575f80fd5b50565b5f81359050611fd481611fb0565b92915050565b5f805f805f805f60e0888a031215611ff557611ff4611e5f565b5b5f6120028a828b01611f39565b97505060206120138a828b01611f93565b96505060406120248a828b01611f39565b95505060606120358a828b01611f39565b94505060806120468a828b01611f93565b93505060a06120578a828b01611fc6565b92505060c06120688a828b01611fc6565b91505092959891949750929550565b5f805f805f8060c0878903121561209157612090611e5f565b5b5f61209e89828a01611f39565b96505060206120af89828a01611f93565b95505060406120c089828a01611f39565b94505060606120d189828a01611f39565b93505060806120e289828a01611f93565b92505060a06120f389828a01611fc6565b9150509295509295509295565b5f80fd5b5f80fd5b5f80fd5b5f8083601f84011261212157612120612100565b5b8235905067ffffffffffffffff81111561213e5761213d612104565b5b60208301915083600182028301111561215a57612159612108565b5b9250929050565b5f805f805f6080868803121561217a57612179611e5f565b5b5f61218788828901611f93565b955050602061219888828901611f93565b94505060406121a988828901611fc6565b935050606086013567ffffffffffffffff8111156121ca576121c9611e63565b5b6121d68882890161210c565b92509250509295509295909350565b6121ee81611e67565b82525050565b5f6020820190506122075f8301846121e5565b92915050565b5f8083601f84011261222257612221612100565b5b8235905067ffffffffffffffff81111561223f5761223e612104565b5b60208301915083602082028301111561225b5761225a612108565b5b9250929050565b5f805f805f805f8060a0898b03121561227e5761227d611e5f565b5b5f61228b8b828c01611f93565b985050602061229c8b828c01611f93565b975050604089013567ffffffffffffffff8111156122bd576122bc611e63565b5b6122c98b828c0161220d565b9650965050606089013567ffffffffffffffff8111156122ec576122eb611e63565b5b6122f88b828c0161220d565b9450945050608089013567ffffffffffffffff81111561231b5761231a611e63565b5b6123278b828c0161210c565b92509250509295985092959890939650565b5f6020828403121561234e5761234d611e5f565b5b5f61235b84828501611f39565b91505092915050565b5f7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000082169050919050565b61239881612364565b82525050565b5f63ffffffff82169050919050565b6123b68161239e565b82525050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b600481106123fa576123f96123bc565b5b50565b5f81905061240a826123e9565b919050565b5f612419826123fd565b9050919050565b6124298161240f565b82525050565b5f6060820190506124425f83018661238f565b61244f60208301856123ad565b61245c6040830184612420565b949350505050565b5f805f805f8060a0878903121561247e5761247d611e5f565b5b5f61248b89828a01611f93565b965050602061249c89828a01611f93565b95505060406124ad89828a01611fc6565b94505060606124be89828a01611fc6565b935050608087013567ffffffffffffffff8111156124df576124de611e63565b5b6124eb89828a0161210c565b92509250509295509295509295565b5f82825260208201905092915050565b7f496e76616c6964207061796d656e742073746174652e204d75737420626520505f8201527f61796d656e7453656e7400000000000000000000000000000000000000000000602082015250565b5f612564602a836124fa565b915061256f8261250a565b604082019050919050565b5f6020820190508181035f83015261259181612558565b9050919050565b5f819050919050565b6125b26125ad82611f1a565b612598565b82525050565b5f6125c382846125a1565b60208201915081905092915050565b5f81519050919050565b5f81905092915050565b8281835e5f83830152505050565b5f6125fe826125d2565b61260881856125dc565b93506126188185602086016125e6565b80840191505092915050565b5f61262f82846125f4565b915081905092915050565b5f8151905061264881611f23565b92915050565b5f6020828403121561266357612662611e5f565b5b5f6126708482850161263a565b91505092915050565b5f8160601b9050919050565b5f61268f82612679565b9050919050565b5f6126a082612685565b9050919050565b6126b86126b382611f6c565b612696565b82525050565b5f819050919050565b6126d86126d382611fa7565b6126be565b82525050565b5f6126e9828a6126a7565b6014820191506126f982896126a7565b60148201915061270982886125a1565b60208201915061271982876125a1565b60208201915061272982866126a7565b60148201915061273982856126c7565b60208201915061274982846126c7565b60208201915081905098975050505050505050565b7f496e76616c6964207061796d656e7448617368000000000000000000000000005f82015250565b5f6127926013836124fa565b915061279d8261275e565b602082019050919050565b5f6020820190508181035f8301526127bf81612786565b9050919050565b6127cf81611f1a565b82525050565b5f6020820190506127e85f8301846127c6565b92915050565b6127f781611f6c565b82525050565b61280681611fa7565b82525050565b5f82825260208201905092915050565b50565b5f61282a5f8361280c565b91506128358261281c565b5f82019050919050565b5f60a0820190506128525f8301876127ee565b61285f60208301866127ee565b61286c60408301856127fd565b61287960608301846127fd565b818103608083015261288a8161281f565b905095945050505050565b5f6128a082896126a7565b6014820191506128b082886126a7565b6014820191506128c082876125a1565b6020820191506128d082866125a1565b6020820191506128e082856126a7565b6014820191506128f082846126c7565b602082019150819050979650505050505050565b7f43757272656e742074696d657374616d70206469646e277420657863656564205f8201527f7061796d656e7420726566756e64206c6f636b2074696d650000000000000000602082015250565b5f61295e6038836124fa565b915061296982612904565b604082019050919050565b5f6020820190508181035f83015261298b81612952565b9050919050565b5f6060820190506129a55f8301866127ee565b6129b260208301856127ee565b6129bf60408301846127fd565b949350505050565b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b612a11826129cb565b810181811067ffffffffffffffff82111715612a3057612a2f6129db565b5b80604052505050565b5f612a42611e56565b9050612a4e8282612a08565b919050565b612a5c8161239e565b8114612a66575f80fd5b50565b5f81359050612a7781612a53565b92915050565b5f60c08284031215612a9257612a916129c7565b5b612a9c60c0612a39565b90505f612aab84828501611f39565b5f830152506020612abe84828501611f93565b6020830152506040612ad284828501611f93565b6040830152506060612ae684828501611f39565b6060830152506080612afa84828501611f39565b60808301525060a0612b0e84828501612a69565b60a08301525092915050565b5f60c08284031215612b2f57612b2e611e5f565b5b5f612b3c84828501612a7d565b91505092915050565b7f4d616b657220455243373231207061796d656e74206d75737420626520556e695f8201527f6e697469616c697a656400000000000000000000000000000000000000000000602082015250565b5f612b9f602a836124fa565b9150612baa82612b45565b604082019050919050565b5f6020820190508181035f830152612bcc81612b93565b9050919050565b7f54616b6572206d757374206e6f74206265207a65726f206164647265737300005f82015250565b5f612c07601e836124fa565b9150612c1282612bd3565b602082019050919050565b5f6020820190508181035f830152612c3481612bfb565b9050919050565b7f546f6b656e206d757374206e6f74206265207a65726f206164647265737300005f82015250565b5f612c6f601e836124fa565b9150612c7a82612c3b565b602082019050919050565b5f6020820190508181035f830152612c9c81612c63565b9050919050565b7f546f6b656e206164647265737320646f6573206e6f74206d617463682073656e5f8201527f6465720000000000000000000000000000000000000000000000000000000000602082015250565b5f612cfd6023836124fa565b9150612d0882612ca3565b604082019050919050565b5f6020820190508181035f830152612d2a81612cf1565b9050919050565b7f4f70657261746f72206d757374206265207468652073656e64657200000000005f82015250565b5f612d65601b836124fa565b9150612d7082612d31565b602082019050919050565b5f6020820190508181035f830152612d9281612d59565b9050919050565b7f54616b65722063616e6e6f74206265206120636f6e74726163740000000000005f82015250565b5f612dcd601a836124fa565b9150612dd882612d99565b602082019050919050565b5f6020820190508181035f830152612dfa81612dc1565b9050919050565b7f4261746368207472616e7366657273206e6f7420737570706f727465640000005f82015250565b5f612e35601d836124fa565b9150612e4082612e01565b602082019050919050565b5f6020820190508181035f830152612e6281612e29565b9050919050565b7f43616c6c6572206d75737420626520616e20454f4100000000000000000000005f82015250565b5f612e9d6015836124fa565b9150612ea882612e69565b602082019050919050565b5f6020820190508181035f830152612eca81612e91565b9050919050565b7f4d616b65722045524331313535207061796d656e74206d75737420626520556e5f8201527f696e697469616c697a6564000000000000000000000000000000000000000000602082015250565b5f612f2b602b836124fa565b9150612f3682612ed1565b604082019050919050565b5f6020820190508181035f830152612f5881612f1f565b9050919050565b7f56616c7565206d7573742062652067726561746572207468616e2030000000005f82015250565b5f612f93601c836124fa565b9150612f9e82612f5f565b602082019050919050565b5f6020820190508181035f830152612fc081612f87565b905091905056fea26469706673582212204e239c256ffaf5624f6d55ae2e9f8afd626e0e129a36ff33221d4b2fe58f6b5a64736f6c63430008190033"; +pub const ERC20_TOKEN_BYTES: &str = include_str!("../../../mm2_test_helpers/contract_bytes/erc20_token_bytes"); +pub const SWAP_CONTRACT_BYTES: &str = include_str!("../../../mm2_test_helpers/contract_bytes/swap_contract_bytes"); +pub const WATCHERS_SWAP_CONTRACT_BYTES: &str = + include_str!("../../../mm2_test_helpers/contract_bytes/watchers_swap_contract_bytes"); +pub const ERC721_TEST_TOKEN_BYTES: &str = + include_str!("../../../mm2_test_helpers/contract_bytes/erc721_test_token_bytes"); +pub const ERC1155_TEST_TOKEN_BYTES: &str = + include_str!("../../../mm2_test_helpers/contract_bytes/erc1155_test_token_bytes"); +/// https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapMakerNftV2.sol +pub const NFT_MAKER_SWAP_V2_BYTES: &str = + include_str!("../../../mm2_test_helpers/contract_bytes/nft_maker_swap_v2_bytes"); +/// https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapMakerNftV2.sol +pub const MAKER_SWAP_V2_BYTES: &str = include_str!("../../../mm2_test_helpers/contract_bytes/maker_swap_v2_bytes"); +/// https://github.com/KomodoPlatform/etomic-swap/blob/5e15641cbf41766cd5b37b4d71842c270773f788/contracts/EtomicSwapTakerV2.sol +pub const TAKER_SWAP_V2_BYTES: &str = include_str!("../../../mm2_test_helpers/contract_bytes/taker_swap_v2_bytes"); pub trait CoinDockerOps { fn rpc_client(&self) -> &UtxoRpcClientEnum; @@ -152,13 +168,13 @@ pub trait CoinDockerOps { fn wait_ready(&self, expected_tx_version: i32) { let timeout = wait_until_ms(120000); loop { - match self.rpc_client().get_block_count().wait() { + match block_on_f01(self.rpc_client().get_block_count()) { Ok(n) => { if n > 1 { if let UtxoRpcClientEnum::Native(client) = self.rpc_client() { - let hash = client.get_block_hash(n).wait().unwrap(); - let block = client.get_block(hash).wait().unwrap(); - let coinbase = client.get_verbose_transaction(&block.tx[0]).wait().unwrap(); + let hash = block_on_f01(client.get_block_hash(n)).unwrap(); + let block = block_on_f01(client.get_block(hash)).unwrap(); + let coinbase = block_on_f01(client.get_verbose_transaction(&block.tx[0])).unwrap(); log!("Coinbase tx {:?} in block {}", coinbase, n); if coinbase.version == expected_tx_version { break; @@ -248,10 +264,11 @@ impl BchDockerOps { .build() .expect("valid address props"); - self.native_client() - .import_address(&address.to_string(), &address.to_string(), false) - .wait() - .unwrap(); + block_on_f01( + self.native_client() + .import_address(&address.to_string(), &address.to_string(), false), + ) + .unwrap(); let script_pubkey = Builder::build_p2pkh(&key_pair.public().address_hash().into()); @@ -267,9 +284,7 @@ impl BchDockerOps { slp_privkeys.push(*key_pair.private_ref()); } - let slp_genesis_tx = send_outputs_from_my_address(self.coin.clone(), bch_outputs) - .wait() - .unwrap(); + let slp_genesis_tx = block_on_f01(send_outputs_from_my_address(self.coin.clone(), bch_outputs)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: slp_genesis_tx.tx_hex(), confirmations: 1, @@ -277,7 +292,7 @@ impl BchDockerOps { wait_until: wait_until_sec(30), check_every: 1, }; - self.coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(self.coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let adex_slp = SlpToken::new( 8, @@ -296,7 +311,7 @@ impl BchDockerOps { wait_until: wait_until_sec(30), check_every: 1, }; - self.coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(self.coin.wait_for_confirmations(confirm_payment_input)).unwrap(); *SLP_TOKEN_OWNERS.lock().unwrap() = slp_privkeys; *SLP_TOKEN_ID.lock().unwrap() = slp_genesis_tx.tx_hash_as_bytes().as_slice().into(); } @@ -372,6 +387,22 @@ pub fn geth_docker_node<'a>(docker: &'a Cli, ticker: &'static str, port: u16) -> } } +#[allow(dead_code)] +pub fn sia_docker_node<'a>(docker: &'a Cli, ticker: &'static str, port: u16) -> DockerNode<'a> { + let image = + GenericImage::new(SIA_DOCKER_IMAGE, "latest").with_env_var("WALLETD_API_PASSWORD", "password".to_string()); + let args = vec![]; + let image = RunnableImage::from((image, args)) + .with_mapped_port((port, port)) + .with_container_name("sia-docker"); + let container = docker.run(image); + DockerNode { + container, + ticker: ticker.into(), + port, + } +} + pub fn nucleus_node(docker: &'_ Cli, runtime_dir: PathBuf) -> DockerNode<'_> { let nucleus_node_runtime_dir = runtime_dir.join("nucleus-testnet-data"); assert!(nucleus_node_runtime_dir.exists()); @@ -392,8 +423,8 @@ pub fn atom_node(docker: &'_ Cli, runtime_dir: PathBuf) -> DockerNode<'_> { let atom_node_runtime_dir = runtime_dir.join("atom-testnet-data"); assert!(atom_node_runtime_dir.exists()); - let image = - GenericImage::new(ATOM_IMAGE, "latest").with_volume(atom_node_runtime_dir.to_str().unwrap(), "/root/.gaia"); + let (image, tag) = ATOM_IMAGE_WITH_TAG.rsplit_once(':').unwrap(); + let image = GenericImage::new(image, tag).with_volume(atom_node_runtime_dir.to_str().unwrap(), "/root/.gaia"); let image = RunnableImage::from((image, vec![])).with_network("host"); let container = docker.run(image); @@ -408,8 +439,8 @@ pub fn ibc_relayer_node(docker: &'_ Cli, runtime_dir: PathBuf) -> DockerNode<'_> let relayer_node_runtime_dir = runtime_dir.join("ibc-relayer-data"); assert!(relayer_node_runtime_dir.exists()); - let image = GenericImage::new(IBC_RELAYER_IMAGE, "latest") - .with_volume(relayer_node_runtime_dir.to_str().unwrap(), "/home/relayer/.relayer"); + let (image, tag) = IBC_RELAYER_IMAGE_WITH_TAG.rsplit_once(':').unwrap(); + let image = GenericImage::new(image, tag).with_volume(relayer_node_runtime_dir.to_str().unwrap(), "/root/.relayer"); let image = RunnableImage::from((image, vec![])).with_network("host"); let container = docker.run(image); @@ -446,7 +477,7 @@ where match coin.as_ref().rpc_client { UtxoRpcClientEnum::Native(ref native) => { let my_address = coin.my_address().unwrap(); - native.import_address(&my_address, &my_address, false).wait().unwrap() + block_on_f01(native.import_address(&my_address, &my_address, false)).unwrap() }, UtxoRpcClientEnum::Electrum(_) => panic!("Expected NativeClient"), } @@ -566,9 +597,7 @@ where UtxoRpcClientEnum::Native(ref native) => native, UtxoRpcClientEnum::Electrum(_) => panic!("NativeClient expected"), }; - let mut addresses = native - .get_addresses_by_label(label) - .wait() + let mut addresses = block_on_f01(native.get_addresses_by_label(label)) .expect("!getaddressesbylabel") .into_iter(); match addresses.next() { @@ -590,22 +619,20 @@ pub fn fill_qrc20_address(coin: &Qrc20Coin, amount: BigDecimal, timeout: u64) { }; let from_addr = get_address_by_label(coin, QTUM_ADDRESS_LABEL); - let to_addr = coin.my_addr_as_contract_addr().compat().wait().unwrap(); + let to_addr = block_on_f01(coin.my_addr_as_contract_addr().compat()).unwrap(); let satoshis = sat_from_big_decimal(&amount, coin.as_ref().decimals).expect("!sat_from_big_decimal"); - let hash = client - .transfer_tokens( - &coin.contract_address, - &from_addr, - to_addr, - satoshis.into(), - coin.as_ref().decimals, - ) - .wait() - .expect("!transfer_tokens") - .txid; + let hash = block_on_f01(client.transfer_tokens( + &coin.contract_address, + &from_addr, + to_addr, + satoshis.into(), + coin.as_ref().decimals, + )) + .expect("!transfer_tokens") + .txid; - let tx_bytes = client.get_transaction_bytes(&hash).wait().unwrap(); + let tx_bytes = block_on_f01(client.get_transaction_bytes(&hash)).unwrap(); log!("{:02x}", tx_bytes); let confirm_payment_input = ConfirmPaymentInput { payment_tx: tx_bytes.0, @@ -614,7 +641,7 @@ pub fn fill_qrc20_address(coin: &Qrc20Coin, amount: BigDecimal, timeout: u64) { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); } /// Generate random privkey, create a QRC20 coin and fill it's address with the specified balance. @@ -722,9 +749,9 @@ where let timeout = wait_until_sec(timeout); if let UtxoRpcClientEnum::Native(client) = &coin.as_ref().rpc_client { - client.import_address(address, address, false).wait().unwrap(); - let hash = client.send_to_address(address, &amount).wait().unwrap(); - let tx_bytes = client.get_transaction_bytes(&hash).wait().unwrap(); + block_on_f01(client.import_address(address, address, false)).unwrap(); + let hash = block_on_f01(client.send_to_address(address, &amount)).unwrap(); + let tx_bytes = block_on_f01(client.get_transaction_bytes(&hash)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: tx_bytes.clone().0, confirmations: 1, @@ -732,13 +759,10 @@ where wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); log!("{:02x}", tx_bytes); loop { - let unspents = client - .list_unspent_impl(0, std::i32::MAX, vec![address.to_string()]) - .wait() - .unwrap(); + let unspents = block_on_f01(client.list_unspent_impl(0, std::i32::MAX, vec![address.to_string()])).unwrap(); if !unspents.is_empty() { break; } @@ -774,7 +798,7 @@ pub fn wait_for_estimate_smart_fee(timeout: u64) -> Result<(), String> { UtxoRpcClientEnum::Electrum(_) => panic!("Expected NativeClient"), }; while now_sec() < timeout { - if let Ok(res) = client.estimate_smart_fee(&None, 1).wait() { + if let Ok(res) = block_on_f01(client.estimate_smart_fee(&None, 1)) { if res.errors.is_empty() { *state = EstimateSmartFeeState::Ok; return Ok(()); @@ -1057,29 +1081,6 @@ pub fn slp_supplied_node() -> MarketMakerIt { .unwrap() } -pub fn _solana_supplied_node() -> MarketMakerIt { - let coins = json! ([ - {"coin": "SOL-DEVNET","name": "solana","fname": "Solana","rpcport": 80,"mm2": 1,"required_confirmations": 1,"avg_blocktime": 0.25,"protocol": {"type": "SOLANA"}}, - {"coin":"USDC-SOL-DEVNET","protocol":{"type":"SPLTOKEN","protocol_data":{"decimals":6,"token_contract_address":"4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU","platform":"SOL-DEVNET"}},"mm2": 1}, - {"coin":"ADEX-SOL-DEVNET","protocol":{"type":"SPLTOKEN","protocol_data":{"decimals":9,"token_contract_address":"5tSm6PqMosy1rz1AqV3kD28yYT5XqZW3QYmZommuFiPJ","platform":"SOL-DEVNET"}},"mm2": 1}, - ]); - - MarketMakerIt::start( - json! ({ - "gui": "nogui", - "netid": 9000, - "dht": "on", // Enable DHT without delay. - "passphrase": "federal stay trigger hour exist success game vapor become comfort action phone bright ill target wild nasty crumble dune close rare fabric hen iron", - "coins": coins, - "rpc_password": "pass", - "i_am_seed": true, - }), - "pass".to_string(), - None, - ) - .unwrap() -} - pub fn get_balance(mm: &MarketMakerIt, coin: &str) -> BalanceResponse { let rc = block_on(mm.rpc(&json!({ "userpass": mm.userpass, @@ -1140,6 +1141,23 @@ async fn get_current_gas_limit(web3: &Web3) { } } +pub fn prepare_ibc_channels(container_id: &str) { + let exec = |args: &[&str]| { + Command::new("docker") + .args(["exec", container_id]) + .args(args) + .output() + .unwrap(); + }; + + exec(&["rly", "transact", "clients", "nucleus-atom", "--override"]); + // It takes a couple of seconds for nodes to get into the right state after updating clients. + // Wait for 5 just to make sure. + thread::sleep(Duration::from_secs(5)); + + exec(&["rly", "transact", "link", "nucleus-atom"]); +} + pub fn wait_until_relayer_container_is_ready(container_id: &str) { const Q_RESULT: &str = "0: nucleus-atom -> chns(✔) clnts(✔) conn(✔) (nucleus-testnet<>cosmoshub-testnet)"; @@ -1248,6 +1266,105 @@ pub fn init_geth_node() { thread::sleep(Duration::from_millis(100)); } + let tx_request_deploy_maker_swap_contract_v2 = TransactionRequest { + from: GETH_ACCOUNT, + to: None, + gas: None, + gas_price: None, + value: None, + data: Some(hex::decode(MAKER_SWAP_V2_BYTES).unwrap().into()), + nonce: None, + condition: None, + transaction_type: None, + access_list: None, + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + }; + let deploy_maker_swap_v2_tx_hash = block_on( + GETH_WEB3 + .eth() + .send_transaction(tx_request_deploy_maker_swap_contract_v2), + ) + .unwrap(); + log!( + "Sent deploy maker swap v2 contract transaction {:?}", + deploy_maker_swap_v2_tx_hash + ); + + loop { + let deploy_maker_swap_v2_tx_receipt = + match block_on(GETH_WEB3.eth().transaction_receipt(deploy_maker_swap_v2_tx_hash)) { + Ok(receipt) => receipt, + Err(_) => { + thread::sleep(Duration::from_millis(100)); + continue; + }, + }; + + if let Some(receipt) = deploy_maker_swap_v2_tx_receipt { + GETH_MAKER_SWAP_V2 = receipt.contract_address.unwrap(); + log!( + "GETH_MAKER_SWAP_V2 contract address: {:?}, receipt.status: {:?}", + GETH_MAKER_SWAP_V2, + receipt.status + ); + break; + } + thread::sleep(Duration::from_millis(100)); + } + + let dex_fee_addr = addr_from_raw_pubkey(&DEX_FEE_ADDR_RAW_PUBKEY).unwrap(); + let dex_fee_addr = Token::Address(dex_fee_addr); + let params = ethabi::encode(&[dex_fee_addr]); + let taker_swap_v2_data = format!("{}{}", TAKER_SWAP_V2_BYTES, hex::encode(params)); + + let tx_request_deploy_taker_swap_contract_v2 = TransactionRequest { + from: GETH_ACCOUNT, + to: None, + gas: None, + gas_price: None, + value: None, + data: Some(hex::decode(taker_swap_v2_data).unwrap().into()), + nonce: None, + condition: None, + transaction_type: None, + access_list: None, + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + }; + let deploy_taker_swap_v2_tx_hash = block_on( + GETH_WEB3 + .eth() + .send_transaction(tx_request_deploy_taker_swap_contract_v2), + ) + .unwrap(); + log!( + "Sent deploy taker swap v2 contract transaction {:?}", + deploy_taker_swap_v2_tx_hash + ); + + loop { + let deploy_taker_swap_v2_tx_receipt = + match block_on(GETH_WEB3.eth().transaction_receipt(deploy_taker_swap_v2_tx_hash)) { + Ok(receipt) => receipt, + Err(_) => { + thread::sleep(Duration::from_millis(100)); + continue; + }, + }; + + if let Some(receipt) = deploy_taker_swap_v2_tx_receipt { + GETH_TAKER_SWAP_V2 = receipt.contract_address.unwrap(); + log!( + "GETH_TAKER_SWAP_V2 contract address: {:?}, receipt.status: {:?}", + GETH_TAKER_SWAP_V2, + receipt.status + ); + break; + } + thread::sleep(Duration::from_millis(100)); + } + let tx_request_deploy_watchers_swap_contract = TransactionRequest { from: GETH_ACCOUNT, to: None, @@ -1285,7 +1402,7 @@ pub fn init_geth_node() { if let Some(receipt) = deploy_watchers_swap_tx_receipt { GETH_WATCHERS_SWAP_CONTRACT = receipt.contract_address.unwrap(); - log!("GETH_WATCHERS_SWAP_CONTRACT {:?}", GETH_SWAP_CONTRACT); + log!("GETH_WATCHERS_SWAP_CONTRACT {:?}", GETH_WATCHERS_SWAP_CONTRACT); break; } thread::sleep(Duration::from_millis(100)); @@ -1329,7 +1446,7 @@ pub fn init_geth_node() { if let Some(receipt) = deploy_nft_maker_swap_v2_tx_receipt { GETH_NFT_MAKER_SWAP_V2 = receipt.contract_address.unwrap(); log!( - "GETH_NFT_MAKER_SWAP_V2 {:?}, receipt.status {:?}", + "GETH_NFT_MAKER_SWAP_V2 contact address: {:?}, receipt.status: {:?}", GETH_NFT_MAKER_SWAP_V2, receipt.status ); @@ -1338,17 +1455,13 @@ pub fn init_geth_node() { thread::sleep(Duration::from_millis(100)); } - let dex_fee_address = Token::Address(geth_account()); - let params = ethabi::encode(&[dex_fee_address]); - let nft_swap_data = format!("{}{}", NFT_SWAP_CONTRACT_BYTES, hex::encode(params)); - - let tx_request_deploy_nft_swap_contract = TransactionRequest { + let tx_request_deploy_nft_maker_swap_v2_contract = TransactionRequest { from: GETH_ACCOUNT, to: None, gas: None, gas_price: None, value: None, - data: Some(hex::decode(nft_swap_data).unwrap().into()), + data: Some(hex::decode(NFT_MAKER_SWAP_V2_BYTES).unwrap().into()), nonce: None, condition: None, transaction_type: None, @@ -1356,13 +1469,20 @@ pub fn init_geth_node() { max_fee_per_gas: None, max_priority_fee_per_gas: None, }; - let deploy_nft_swap_tx_hash = - block_on(GETH_WEB3.eth().send_transaction(tx_request_deploy_nft_swap_contract)).unwrap(); - log!("Sent deploy nft swap contract transaction {:?}", deploy_swap_tx_hash); + let deploy_nft_maker_swap_v2_tx_hash = block_on( + GETH_WEB3 + .eth() + .send_transaction(tx_request_deploy_nft_maker_swap_v2_contract), + ) + .unwrap(); + log!( + "Sent deploy nft maker swap v2 contract transaction {:?}", + deploy_nft_maker_swap_v2_tx_hash + ); loop { - let deploy_nft_swap_tx_receipt = - match block_on(GETH_WEB3.eth().transaction_receipt(deploy_nft_swap_tx_hash)) { + let deploy_nft_maker_swap_v2_tx_receipt = + match block_on(GETH_WEB3.eth().transaction_receipt(deploy_nft_maker_swap_v2_tx_hash)) { Ok(receipt) => receipt, Err(_) => { thread::sleep(Duration::from_millis(100)); @@ -1370,11 +1490,11 @@ pub fn init_geth_node() { }, }; - if let Some(receipt) = deploy_nft_swap_tx_receipt { - GETH_NFT_SWAP_CONTRACT = receipt.contract_address.unwrap(); + if let Some(receipt) = deploy_nft_maker_swap_v2_tx_receipt { + GETH_NFT_MAKER_SWAP_V2 = receipt.contract_address.unwrap(); log!( - "GETH_NFT_SWAP_CONTRACT {:?}, receipt.status {:?}", - GETH_NFT_SWAP_CONTRACT, + "GETH_NFT_MAKER_SWAP_V2 {:?}, receipt.status {:?}", + GETH_NFT_MAKER_SWAP_V2, receipt.status ); break; @@ -1460,10 +1580,15 @@ pub fn init_geth_node() { thread::sleep(Duration::from_millis(100)); } - SEPOLIA_ETOMIC_MAKER_NFT_SWAP_V2 = EthAddress::from_str("0x9eb88cd58605d8fb9b14652d6152727f7e95fb4d").unwrap(); - SEPOLIA_ERC721_CONTRACT = EthAddress::from_str("0xbac1c9f2087f39caaa4e93412c6412809186870e").unwrap(); - SEPOLIA_ERC1155_CONTRACT = EthAddress::from_str("0xfb53b8764be6033d89ceacafa36631b09d60a1d2").unwrap(); - + #[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] + { + SEPOLIA_ETOMIC_MAKER_NFT_SWAP_V2 = + EthAddress::from_str("0x9eb88cd58605d8fb9b14652d6152727f7e95fb4d").unwrap(); + SEPOLIA_ERC20_CONTRACT = EthAddress::from_str("0xF7b5F8E8555EF7A743f24D3E974E23A3C6cB6638").unwrap(); + SEPOLIA_TAKER_SWAP_V2 = EthAddress::from_str("0x7Cc9F2c1c3B797D09B9d1CCd7FDcD2539a4b3874").unwrap(); + // deploy tx https://sepolia.etherscan.io/tx/0x6f743d79ecb806f5899a6a801083e33eba9e6f10726af0873af9f39883db7f11 + SEPOLIA_MAKER_SWAP_V2 = EthAddress::from_str("0xf9000589c66Df3573645B59c10aa87594Edc318F").unwrap(); + } let alice_passphrase = get_passphrase!(".env.client", "ALICE_PASSPHRASE").unwrap(); let alice_keypair = key_pair_from_seed(&alice_passphrase).unwrap(); let alice_eth_addr = addr_from_raw_pubkey(alice_keypair.public()).unwrap(); diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index 330dec30de..1757f97d36 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -12,10 +12,9 @@ use coins::TxFeeDetails; use coins::{ConfirmPaymentInput, FoundSwapTxSpend, MarketCoinOps, MmCoin, RefundPaymentArgs, SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, SwapOps, SwapTxTypeWithSecretHash, TransactionEnum, WithdrawRequest}; -use common::{block_on, executor::Timer, get_utc_timestamp, now_sec, wait_until_sec}; +use common::{block_on, block_on_f01, executor::Timer, get_utc_timestamp, now_sec, wait_until_sec}; use crypto::privkey::key_pair_from_seed; use crypto::{CryptoCtx, DerivationPath, KeyPairPolicy}; -use futures01::Future; use http::StatusCode; use mm2_number::{BigDecimal, BigRational, MmNumber}; use mm2_test_helpers::for_tests::{check_my_swap_status_amounts, disable_coin, disable_coin_err, enable_eth_coin, @@ -51,7 +50,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_taker() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let tx = coin.send_taker_payment(taker_payment_args).wait().unwrap(); + let tx = block_on(coin.send_taker_payment(taker_payment_args)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: tx.tx_hex(), @@ -60,7 +59,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_taker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), time_lock, @@ -81,7 +80,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_taker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -113,7 +112,9 @@ fn test_for_non_existent_tx_hex_utxo() { wait_until: timeout, check_every: 1, }; - let actual = coin.wait_for_confirmations(confirm_payment_input).wait().err().unwrap(); + let actual = block_on_f01(coin.wait_for_confirmations(confirm_payment_input)) + .err() + .unwrap(); assert!(actual.contains( "Tx d342ff9da528a2e262bddf2b6f9a27d1beb7aeb03f0fc8d9eac2987266447e44 was not found on chain after 10 tries" )); @@ -138,7 +139,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_maker() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); + let tx = block_on(coin.send_maker_payment(maker_payment_args)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: tx.tx_hex(), @@ -147,7 +148,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_maker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), time_lock, @@ -168,7 +169,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_maker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -207,7 +208,7 @@ fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let tx = coin.send_taker_payment(taker_payment_args).wait().unwrap(); + let tx = block_on(coin.send_taker_payment(taker_payment_args)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: tx.tx_hex(), @@ -216,7 +217,7 @@ fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let maker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &tx.tx_hex(), time_lock, @@ -236,7 +237,7 @@ fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -275,7 +276,7 @@ fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); + let tx = block_on(coin.send_maker_payment(maker_payment_args)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: tx.tx_hex(), @@ -284,7 +285,7 @@ fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let taker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &tx.tx_hex(), time_lock, @@ -304,7 +305,7 @@ fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -346,7 +347,7 @@ fn test_one_hundred_maker_payments_in_a_row_native() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); + let tx = block_on(coin.send_maker_payment(maker_payment_args)).unwrap(); if let TransactionEnum::UtxoTx(tx) = tx { unspents.push(UnspentInfo { outpoint: OutPoint { @@ -2472,16 +2473,12 @@ fn test_maker_order_should_not_kick_start_and_appear_in_orderbook_if_balance_is_ bob_conf["log"] = mm_bob.folder.join("mm2_dup.log").to_str().unwrap().into(); block_on(mm_bob.stop()).unwrap(); - let withdraw = coin - .withdraw(WithdrawRequest::new_max( - "MYCOIN".to_string(), - "RRYmiZSDo3UdHHqj1rLKf8cbJroyv9NxXw".to_string(), - )) - .wait() - .unwrap(); - coin.send_raw_tx(&hex::encode(&withdraw.tx.tx_hex().unwrap().0)) - .wait() - .unwrap(); + let withdraw = block_on_f01(coin.withdraw(WithdrawRequest::new_max( + "MYCOIN".to_string(), + "RRYmiZSDo3UdHHqj1rLKf8cbJroyv9NxXw".to_string(), + ))) + .unwrap(); + block_on_f01(coin.send_raw_tx(&hex::encode(&withdraw.tx.tx_hex().unwrap().0))).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: withdraw.tx.tx_hex().unwrap().0.to_owned(), confirmations: 1, @@ -2489,7 +2486,7 @@ fn test_maker_order_should_not_kick_start_and_appear_in_orderbook_if_balance_is_ wait_until: wait_until_sec(10), check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let mm_bob_dup = MarketMakerIt::start(bob_conf, "pass".to_string(), None).unwrap(); let (_bob_dup_dump_log, _bob_dup_dump_dashboard) = mm_dump(&mm_bob_dup.log_path); @@ -5260,127 +5257,6 @@ fn test_sell_min_volume_dust() { assert!(!rc.0.is_success(), "!sell: {}", rc.1); } -#[test] -fn test_enable_eth_erc20_coins_with_enable_hd() { - const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; - - let coins = json!([eth_dev_conf(), erc20_dev_conf(&erc20_contract_checksum())]); - let swap_contract = format!("0x{}", hex::encode(swap_contract())); - - // Withdraw from HD account 0, change address 0, index 0 - let path_to_address = HDAccountAddressId::default(); - let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); - let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); - log!("Alice log path: {}", mm_hd.log_path.display()); - - let eth_enable = block_on(enable_eth_with_tokens_v2( - &mm_hd, - "ETH", - &["ERC20DEV"], - &swap_contract, - &[GETH_RPC_URL], - 60, - Some(path_to_address), - )); - let activation_result = match eth_enable { - EthWithTokensActivationResult::HD(hd) => hd, - _ => panic!("Expected EthWithTokensActivationResult::HD"), - }; - let balance = match activation_result.wallet_balance { - EnableCoinBalanceMap::HD(hd) => hd, - _ => panic!("Expected EnableCoinBalance::HD"), - }; - let account = balance.accounts.get(0).expect("Expected account at index 0"); - assert_eq!( - account.addresses[0].address, - "0x1737F1FaB40c6Fd3dc729B51C0F97DB3297CCA93" - ); - assert_eq!(account.addresses[0].balance.len(), 2); - assert!(account.addresses[0].balance.contains_key("ETH")); - assert!(account.addresses[0].balance.contains_key("ERC20DEV")); - - block_on(mm_hd.stop()).unwrap(); - - // Enable HD account 0, change address 0, index 1 - let path_to_address = HDAccountAddressId { - account_id: 0, - chain: Bip44Chain::External, - address_id: 1, - }; - let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); - let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); - log!("Alice log path: {}", mm_hd.log_path.display()); - - let eth_enable = block_on(enable_eth_with_tokens_v2( - &mm_hd, - "ETH", - &["ERC20DEV"], - &swap_contract, - &[GETH_RPC_URL], - 60, - Some(path_to_address), - )); - let activation_result = match eth_enable { - EthWithTokensActivationResult::HD(hd) => hd, - _ => panic!("Expected EthWithTokensActivationResult::HD"), - }; - let balance = match activation_result.wallet_balance { - EnableCoinBalanceMap::HD(hd) => hd, - _ => panic!("Expected EnableCoinBalance::HD"), - }; - let account = balance.accounts.get(0).expect("Expected account at index 0"); - assert_eq!( - account.addresses[1].address, - "0xDe841899aB4A22E23dB21634e54920aDec402397" - ); - assert_eq!(account.addresses[0].balance.len(), 2); - assert!(account.addresses[0].balance.contains_key("ETH")); - assert!(account.addresses[0].balance.contains_key("ERC20DEV")); - - block_on(mm_hd.stop()).unwrap(); - - // Enable HD account 77, change address 0, index 7 - let path_to_address = HDAccountAddressId { - account_id: 77, - chain: Bip44Chain::External, - address_id: 7, - }; - let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); - let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); - let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); - log!("Alice log path: {}", mm_hd.log_path.display()); - - let eth_enable = block_on(enable_eth_with_tokens_v2( - &mm_hd, - "ETH", - &["ERC20DEV"], - &swap_contract, - &[GETH_RPC_URL], - 60, - Some(path_to_address), - )); - let activation_result = match eth_enable { - EthWithTokensActivationResult::HD(hd) => hd, - _ => panic!("Expected EthWithTokensActivationResult::HD"), - }; - let balance = match activation_result.wallet_balance { - EnableCoinBalanceMap::HD(hd) => hd, - _ => panic!("Expected EnableCoinBalance::HD"), - }; - let account = balance.accounts.get(0).expect("Expected account at index 0"); - assert_eq!( - account.addresses[7].address, - "0xa420a4DBd8C50e6240014Db4587d2ec8D0cE0e6B" - ); - assert_eq!(account.addresses[0].balance.len(), 2); - assert!(account.addresses[0].balance.contains_key("ETH")); - assert!(account.addresses[0].balance.contains_key("ERC20DEV")); - - block_on(mm_hd.stop()).unwrap(); -} - fn request_and_check_orderbook_depth(mm_alice: &MarketMakerIt) { let rc = block_on(mm_alice.rpc(&json! ({ "userpass": mm_alice.userpass, diff --git a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs index a61c0f2690..7ea038a8f7 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -1,98 +1,114 @@ use super::docker_tests_common::{random_secp256k1_secret, ERC1155_TEST_ABI, ERC721_TEST_ABI, GETH_ACCOUNT, - GETH_ERC1155_CONTRACT, GETH_ERC20_CONTRACT, GETH_ERC721_CONTRACT, - GETH_NFT_MAKER_SWAP_V2, GETH_NFT_SWAP_CONTRACT, GETH_NONCE_LOCK, GETH_RPC_URL, - GETH_SWAP_CONTRACT, GETH_WATCHERS_SWAP_CONTRACT, GETH_WEB3, MM_CTX, MM_CTX1, - SEPOLIA_ERC1155_CONTRACT, SEPOLIA_ERC721_CONTRACT, SEPOLIA_ETOMIC_MAKER_NFT_SWAP_V2, - SEPOLIA_NONCE_LOCK, SEPOLIA_RPC_URL, SEPOLIA_WEB3}; + GETH_ERC1155_CONTRACT, GETH_ERC20_CONTRACT, GETH_ERC721_CONTRACT, GETH_MAKER_SWAP_V2, + GETH_NFT_MAKER_SWAP_V2, GETH_NONCE_LOCK, GETH_RPC_URL, GETH_SWAP_CONTRACT, + GETH_TAKER_SWAP_V2, GETH_WATCHERS_SWAP_CONTRACT, GETH_WEB3, MM_CTX, MM_CTX1}; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +use super::docker_tests_common::{SEPOLIA_ERC20_CONTRACT, SEPOLIA_ETOMIC_MAKER_NFT_SWAP_V2, SEPOLIA_MAKER_SWAP_V2, + SEPOLIA_NONCE_LOCK, SEPOLIA_RPC_URL, SEPOLIA_TAKER_SWAP_V2, SEPOLIA_TESTS_LOCK, + SEPOLIA_WEB3}; use crate::common::Future01CompatExt; use bitcrypto::{dhash160, sha256}; -use coins::eth::{checksum_address, eth_addr_to_hex, eth_coin_from_conf_and_request, EthCoin, SignedEthTx, ERC20_ABI}; +use coins::eth::gas_limit::ETH_MAX_TRADE_GAS; +use coins::eth::v2_activation::{eth_coin_from_conf_and_request_v2, EthActivationV2Request, EthNode}; +use coins::eth::{checksum_address, eth_addr_to_hex, eth_coin_from_conf_and_request, EthCoin, EthCoinType, + EthPrivKeyBuildPolicy, SignedEthTx, SwapV2Contracts, ERC20_ABI}; use coins::nft::nft_structs::{Chain, ContractType, NftInfo}; -use coins::{lp_coinfind, CoinProtocol, CoinWithDerivationMethod, CoinsContext, ConfirmPaymentInput, DerivationMethod, - Eip1559Ops, FoundSwapTxSpend, MakerNftSwapOpsV2, MarketCoinOps, MmCoinEnum, MmCoinStruct, NftSwapInfo, - ParseCoinAssocTypes, PrivKeyBuildPolicy, RefundPaymentArgs, SearchForSwapTxSpendInput, - SendNftMakerPaymentArgs, SendPaymentArgs, SpendNftMakerPaymentArgs, SpendPaymentArgs, SwapOps, - SwapTxFeePolicy, SwapTxTypeWithSecretHash, ToBytes, Transaction, ValidateNftMakerPaymentArgs}; -use common::{block_on, now_sec}; +use coins::{lp_coinfind, CoinProtocol, CoinWithDerivationMethod, CommonSwapOpsV2, ConfirmPaymentInput, + DerivationMethod, Eip1559Ops, FoundSwapTxSpend, MakerNftSwapOpsV2, MarketCoinOps, NftSwapInfo, + ParseCoinAssocTypes, ParseNftAssocTypes, PrivKeyBuildPolicy, RefundNftMakerPaymentArgs, RefundPaymentArgs, + SearchForSwapTxSpendInput, SendNftMakerPaymentArgs, SendPaymentArgs, SpendNftMakerPaymentArgs, + SpendPaymentArgs, SwapOps, SwapTxFeePolicy, SwapTxTypeWithSecretHash, ToBytes, Transaction, + ValidateNftMakerPaymentArgs}; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +use coins::{CoinsContext, DexFee, FundingTxSpend, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, + MakerCoinSwapOpsV2, MmCoinEnum, MmCoinStruct, RefundFundingSecretArgs, RefundMakerPaymentSecretArgs, + RefundMakerPaymentTimelockArgs, RefundTakerPaymentArgs, SendMakerPaymentArgs, SendTakerFundingArgs, + SpendMakerPaymentArgs, TakerCoinSwapOpsV2, TxPreimageWithSig, ValidateMakerPaymentArgs, + ValidateTakerFundingArgs}; +use common::{block_on, block_on_f01, now_sec}; use crypto::Secp256k1Secret; -use ethcore_transaction::Action; use ethereum_types::U256; -use futures01::Future; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] use mm2_core::mm_ctx::MmArc; use mm2_number::{BigDecimal, BigUint}; -use mm2_test_helpers::for_tests::{erc20_dev_conf, eth_dev_conf, nft_dev_conf, nft_sepolia_conf}; +use mm2_test_helpers::for_tests::{account_balance, disable_coin, enable_erc20_token_v2, enable_eth_with_tokens_v2, + erc20_dev_conf, eth_dev_conf, get_new_address, get_token_info, nft_dev_conf, + MarketMakerIt, Mm2TestConf}; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +use mm2_test_helpers::for_tests::{eth_sepolia_conf, sepolia_erc20_dev_conf}; +use mm2_test_helpers::structs::{Bip44Chain, EnableCoinBalanceMap, EthWithTokensActivationResult, HDAccountAddressId, + TokenInfo}; +use serde_json::Value as Json; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +use std::str::FromStr; use std::thread; use std::time::Duration; use web3::contract::{Contract, Options}; use web3::ethabi::Token; -use web3::types::{Address, BlockNumber, TransactionRequest, H256}; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +use web3::types::BlockNumber; +use web3::types::{Address, TransactionRequest, H256}; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] const SEPOLIA_MAKER_PRIV: &str = "6e2f3a6223b928a05a3a3622b0c3f3573d03663b704a61a6eb73326de0487928"; +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] const SEPOLIA_TAKER_PRIV: &str = "e0be82dca60ff7e4c6d6db339ac9e1ae249af081dba2110bddd281e711608f16"; const NFT_ETH: &str = "NFT_ETH"; +const ETH: &str = "ETH"; +const ERC20: &str = "ERC20DEV"; /// # Safety /// /// GETH_ACCOUNT is set once during initialization before tests start pub fn geth_account() -> Address { unsafe { GETH_ACCOUNT } } - /// # Safety /// /// GETH_SWAP_CONTRACT is set once during initialization before tests start pub fn swap_contract() -> Address { unsafe { GETH_SWAP_CONTRACT } } - -#[allow(dead_code)] /// # Safety /// -/// GETH_NFT_SWAP_CONTRACT is set once during initialization before tests start -pub fn nft_swap_contract() -> Address { unsafe { GETH_NFT_SWAP_CONTRACT } } - -#[allow(dead_code)] +/// GETH_MAKER_SWAP_V2 is set once during initialization before tests start +pub fn maker_swap_v2() -> Address { unsafe { GETH_MAKER_SWAP_V2 } } +/// # Safety +/// +/// GETH_TAKER_SWAP_V2 is set once during initialization before tests start +pub fn taker_swap_v2() -> Address { unsafe { GETH_TAKER_SWAP_V2 } } +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +pub fn sepolia_taker_swap_v2() -> Address { unsafe { SEPOLIA_TAKER_SWAP_V2 } } +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +pub fn sepolia_maker_swap_v2() -> Address { unsafe { SEPOLIA_MAKER_SWAP_V2 } } /// # Safety /// /// GETH_NFT_MAKER_SWAP_V2 is set once during initialization before tests start -pub fn nft_maker_swap_v2() -> Address { unsafe { GETH_NFT_MAKER_SWAP_V2 } } - +pub fn geth_nft_maker_swap_v2() -> Address { unsafe { GETH_NFT_MAKER_SWAP_V2 } } /// # Safety /// /// GETH_WATCHERS_SWAP_CONTRACT is set once during initialization before tests start pub fn watchers_swap_contract() -> Address { unsafe { GETH_WATCHERS_SWAP_CONTRACT } } - /// # Safety /// /// GETH_ERC20_CONTRACT is set once during initialization before tests start pub fn erc20_contract() -> Address { unsafe { GETH_ERC20_CONTRACT } } - +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +pub fn sepolia_erc20_contract() -> Address { unsafe { SEPOLIA_ERC20_CONTRACT } } /// Return ERC20 dev token contract address in checksum format pub fn erc20_contract_checksum() -> String { checksum_address(&format!("{:02x}", erc20_contract())) } - -#[allow(dead_code)] +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +pub fn sepolia_erc20_contract_checksum() -> String { checksum_address(&format!("{:02x}", sepolia_erc20_contract())) } /// # Safety /// /// GETH_ERC721_CONTRACT is set once during initialization before tests start -pub fn erc721_contract() -> Address { unsafe { GETH_ERC721_CONTRACT } } - -#[allow(dead_code)] +pub fn geth_erc721_contract() -> Address { unsafe { GETH_ERC721_CONTRACT } } /// # Safety /// /// GETH_ERC1155_CONTRACT is set once during initialization before tests start -pub fn erc1155_contract() -> Address { unsafe { GETH_ERC1155_CONTRACT } } - +pub fn geth_erc1155_contract() -> Address { unsafe { GETH_ERC1155_CONTRACT } } +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] /// # Safety /// /// SEPOLIA_ETOMIC_MAKER_NFT_SWAP_V2 address is set once during initialization before tests start pub fn sepolia_etomic_maker_nft() -> Address { unsafe { SEPOLIA_ETOMIC_MAKER_NFT_SWAP_V2 } } -/// # Safety -/// -/// SEPOLIA_ERC721_CONTRACT address is set once during initialization before tests start -pub fn sepolia_erc721() -> Address { unsafe { SEPOLIA_ERC721_CONTRACT } } - -/// # Safety -/// -/// SEPOLIA_ERC1155_CONTRACT address is set once during initialization before tests start -pub fn sepolia_erc1155() -> Address { unsafe { SEPOLIA_ERC1155_CONTRACT } } - fn wait_for_confirmation(tx_hash: H256) { thread::sleep(Duration::from_millis(2000)); loop { @@ -142,13 +158,13 @@ fn fill_erc20(to_addr: Address, amount: U256) { wait_for_confirmation(tx_hash); } -#[allow(dead_code)] fn mint_erc721(to_addr: Address, token_id: U256) { let _guard = GETH_NONCE_LOCK.lock().unwrap(); - let erc721_contract = Contract::from_json(GETH_WEB3.eth(), erc721_contract(), ERC721_TEST_ABI.as_bytes()).unwrap(); + let erc721_contract = + Contract::from_json(GETH_WEB3.eth(), geth_erc721_contract(), ERC721_TEST_ABI.as_bytes()).unwrap(); let options = Options { - gas: Some(U256::from(150_000)), + gas: Some(U256::from(ETH_MAX_TRADE_GAS)), ..Options::default() }; @@ -171,25 +187,24 @@ fn mint_erc721(to_addr: Address, token_id: U256) { ); } -fn erc712_owner(token_id: U256) -> Address { - let _guard = SEPOLIA_NONCE_LOCK.lock().unwrap(); +fn geth_erc712_owner(token_id: U256) -> Address { + let _guard = GETH_NONCE_LOCK.lock().unwrap(); let erc721_contract = - Contract::from_json(SEPOLIA_WEB3.eth(), sepolia_erc721(), ERC721_TEST_ABI.as_bytes()).unwrap(); + Contract::from_json(GETH_WEB3.eth(), geth_erc721_contract(), ERC721_TEST_ABI.as_bytes()).unwrap(); block_on(erc721_contract.query("ownerOf", Token::Uint(token_id), None, Options::default(), None)).unwrap() } -#[allow(dead_code)] -fn mint_erc1155(to_addr: Address, token_id: U256, amount: U256) { +fn mint_erc1155(to_addr: Address, token_id: U256, amount: u32) { let _guard = GETH_NONCE_LOCK.lock().unwrap(); let erc1155_contract = - Contract::from_json(GETH_WEB3.eth(), erc1155_contract(), ERC1155_TEST_ABI.as_bytes()).unwrap(); + Contract::from_json(GETH_WEB3.eth(), geth_erc1155_contract(), ERC1155_TEST_ABI.as_bytes()).unwrap(); let tx_hash = block_on(erc1155_contract.call( "mint", ( Token::Address(to_addr), Token::Uint(token_id), - Token::Uint(amount), + Token::Uint(U256::from(amount)), Token::Bytes("".into()), ), geth_account(), @@ -208,17 +223,22 @@ fn mint_erc1155(to_addr: Address, token_id: U256, amount: U256) { )) .unwrap(); + // check that "balanceOf" from ERC11155 returns the exact amount of token without any decimals or scaling factors + let balance_dec = balance.to_string().parse::().unwrap(); assert_eq!( - balance, amount, + balance_dec, + BigDecimal::from(amount), "The balance of tokenId {:?} for address {:?} does not match the expected amount {:?}.", - token_id, to_addr, amount + token_id, + to_addr, + amount ); } -fn erc1155_balance(wallet_addr: Address, token_id: U256) -> U256 { - let _guard = SEPOLIA_NONCE_LOCK.lock().unwrap(); +fn geth_erc1155_balance(wallet_addr: Address, token_id: U256) -> U256 { + let _guard = GETH_NONCE_LOCK.lock().unwrap(); let erc1155_contract = - Contract::from_json(SEPOLIA_WEB3.eth(), sepolia_erc1155(), ERC1155_TEST_ABI.as_bytes()).unwrap(); + Contract::from_json(GETH_WEB3.eth(), geth_erc1155_contract(), ERC1155_TEST_ABI.as_bytes()).unwrap(); block_on(erc1155_contract.query( "balanceOf", (Token::Address(wallet_addr), Token::Uint(token_id)), @@ -343,143 +363,139 @@ pub enum TestNftType { /// Generates a global NFT coin instance with a random private key and an initial 100 ETH balance. /// Optionally mints a specified NFT (either ERC721 or ERC1155) to the global NFT address, /// with details recorded in the `nfts_infos` field based on the provided `nft_type`. -#[allow(dead_code)] -pub fn global_nft_with_random_privkey(swap_contract_address: Address, nft_type: Option) -> EthCoin { - let nft_conf = nft_dev_conf(); - let req = json!({ - "method": "enable", - "coin": "NFT_ETH", - "urls": [GETH_RPC_URL], - "swap_contract_address": swap_contract_address, - }); - - let global_nft = block_on(eth_coin_from_conf_and_request( - &MM_CTX, - "NFT_ETH", - &nft_conf, - &req, - CoinProtocol::NFT { - platform: "ETH".to_string(), - }, - PrivKeyBuildPolicy::IguanaPrivKey(random_secp256k1_secret()), +fn global_nft_with_random_privkey( + swap_v2_contracts: SwapV2Contracts, + swap_contract_address: Address, + fallback_swap_contract_address: Address, + nft_type: Option, + nft_ticker: String, + platform_ticker: String, +) -> EthCoin { + let build_policy = EthPrivKeyBuildPolicy::IguanaPrivKey(random_secp256k1_secret()); + let node = EthNode { + url: GETH_RPC_URL.to_string(), + komodo_proxy: false, + }; + let platform_request = EthActivationV2Request { + nodes: vec![node], + rpc_mode: Default::default(), + swap_contract_address, + swap_v2_contracts: Some(swap_v2_contracts), + fallback_swap_contract: Some(fallback_swap_contract_address), + contract_supports_watchers: false, + mm2: None, + required_confirmations: None, + priv_key_policy: Default::default(), + enable_params: Default::default(), + path_to_address: Default::default(), + gap_limit: None, + }; + let coin = block_on(eth_coin_from_conf_and_request_v2( + &MM_CTX1, + nft_ticker.as_str(), + &nft_dev_conf(), + platform_request, + build_policy, )) .unwrap(); - let my_address = block_on(global_nft.my_addr()); + let coin_type = EthCoinType::Nft { + platform: platform_ticker, + }; + let global_nft = block_on(coin.set_coin_type(coin_type)); + let my_address = block_on(coin.my_addr()); fill_eth(my_address, U256::from(10).pow(U256::from(20))); if let Some(nft_type) = nft_type { match nft_type { TestNftType::Erc1155 { token_id, amount } => { - mint_erc1155(my_address, U256::from(token_id), U256::from(amount)); - block_on(fill_erc1155_info(&global_nft, erc1155_contract(), token_id, amount)); + mint_erc1155(my_address, U256::from(token_id), amount); + block_on(fill_erc1155_info( + &global_nft, + geth_erc1155_contract(), + token_id, + amount, + )); }, TestNftType::Erc721 { token_id } => { mint_erc721(my_address, U256::from(token_id)); - block_on(fill_erc721_info(&global_nft, erc721_contract(), token_id)); + block_on(fill_erc721_info(&global_nft, geth_erc721_contract(), token_id)); }, } } - global_nft } -fn global_nft_from_privkey( - ctx: &MmArc, - swap_contract_address: Address, - secret: &'static str, - nft_type: Option, -) -> EthCoin { - let nft_conf = nft_sepolia_conf(); - let req = json!({ - "method": "enable", - "coin": "NFT_ETH", - "urls": [SEPOLIA_RPC_URL], - "swap_contract_address": swap_contract_address, - }); +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +/// Can be used to generate coin from Sepolia Maker/Taker priv keys. +fn sepolia_coin_from_privkey(ctx: &MmArc, secret: &'static str, ticker: &str, conf: &Json, erc20: bool) -> EthCoin { + let swap_addr = SwapAddresses { + swap_v2_contracts: SwapV2Contracts { + maker_swap_v2_contract: sepolia_maker_swap_v2(), + taker_swap_v2_contract: sepolia_taker_swap_v2(), + nft_maker_swap_v2_contract: sepolia_etomic_maker_nft(), + }, + swap_contract_address: sepolia_taker_swap_v2(), + fallback_swap_contract_address: sepolia_taker_swap_v2(), + }; let priv_key = Secp256k1Secret::from(secret); - let global_nft = block_on(eth_coin_from_conf_and_request( + let build_policy = EthPrivKeyBuildPolicy::IguanaPrivKey(priv_key); + + let node = EthNode { + url: SEPOLIA_RPC_URL.to_string(), + komodo_proxy: false, + }; + let platform_request = EthActivationV2Request { + nodes: vec![node], + rpc_mode: Default::default(), + swap_contract_address: swap_addr.swap_contract_address, + swap_v2_contracts: Some(swap_addr.swap_v2_contracts), + fallback_swap_contract: Some(swap_addr.fallback_swap_contract_address), + contract_supports_watchers: false, + mm2: None, + required_confirmations: None, + priv_key_policy: Default::default(), + enable_params: Default::default(), + path_to_address: Default::default(), + gap_limit: None, + }; + let coin = block_on(eth_coin_from_conf_and_request_v2( ctx, - NFT_ETH, - &nft_conf, - &req, - CoinProtocol::NFT { - platform: "ETH".to_string(), - }, - PrivKeyBuildPolicy::IguanaPrivKey(priv_key), + ticker, + conf, + platform_request, + build_policy, )) .unwrap(); + let coin = if erc20 { + let coin_type = EthCoinType::Erc20 { + platform: ETH.to_string(), + token_addr: sepolia_erc20_contract(), + }; + block_on(coin.set_coin_type(coin_type)) + } else { + coin + }; let coins_ctx = CoinsContext::from_ctx(ctx).unwrap(); let mut coins = block_on(coins_ctx.lock_coins()); coins.insert( - global_nft.ticker().into(), - MmCoinStruct::new(MmCoinEnum::EthCoin(global_nft.clone())), + coin.ticker().into(), + MmCoinStruct::new(MmCoinEnum::EthCoin(coin.clone())), ); - - if let Some(nft_type) = nft_type { - match nft_type { - TestNftType::Erc1155 { token_id, amount } => { - block_on(fill_erc1155_info(&global_nft, sepolia_erc1155(), token_id, amount)); - }, - TestNftType::Erc721 { token_id } => { - block_on(fill_erc721_info(&global_nft, sepolia_erc721(), token_id)); - }, - } - } - - global_nft + coin } -fn send_safe_transfer_from( - global_nft: &EthCoin, - token_address: Address, - from_address: Address, - to_address: Address, - nft_type: TestNftType, -) -> web3::Result { - let _guard = GETH_NONCE_LOCK.lock().unwrap(); - - let contract = match nft_type { - TestNftType::Erc1155 { .. } => { - Contract::from_json(SEPOLIA_WEB3.eth(), token_address, ERC1155_TEST_ABI.as_bytes()).unwrap() - }, - TestNftType::Erc721 { .. } => { - Contract::from_json(SEPOLIA_WEB3.eth(), token_address, ERC721_TEST_ABI.as_bytes()).unwrap() +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] +fn get_or_create_sepolia_coin(ctx: &MmArc, priv_key: &'static str, ticker: &str, conf: &Json, erc20: bool) -> EthCoin { + match block_on(lp_coinfind(ctx, ticker)).unwrap() { + None => sepolia_coin_from_privkey(ctx, priv_key, ticker, conf, erc20), + Some(mm_coin) => match mm_coin { + MmCoinEnum::EthCoin(coin) => coin, + _ => panic!("Unexpected coin type found. Expected MmCoinEnum::EthCoin"), }, - }; - let tokens = match nft_type { - TestNftType::Erc1155 { token_id, amount } => vec![ - Token::Address(from_address), - Token::Address(to_address), - Token::Uint(U256::from(token_id)), - Token::Uint(U256::from(amount)), - Token::Bytes(vec![]), - ], - TestNftType::Erc721 { token_id } => vec![ - Token::Address(from_address), - Token::Address(to_address), - Token::Uint(U256::from(token_id)), - ], - }; - - let data = contract - .abi() - .function("safeTransferFrom") - .unwrap() - .encode_input(&tokens) - .unwrap(); - - let result = block_on( - global_nft - .sign_and_send_transaction(0.into(), Action::Call(token_address), data, U256::from(150_000)) - .compat(), - ) - .unwrap(); - - log!("Transaction sent: {:?}", result); - Ok(result) + } } /// Fills the private key's public address with ETH and ERC20 tokens @@ -531,6 +547,7 @@ pub fn fill_eth_erc20_with_private_key(priv_key: Secp256k1Secret) { } fn send_and_refund_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { + thread::sleep(Duration::from_secs(3)); let eth_coin = eth_coin_with_random_privkey(swap_contract()); eth_coin.set_swap_transaction_fee_policy(swap_txfee_policy); @@ -552,7 +569,7 @@ fn send_and_refund_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { watcher_reward: None, wait_for_confirmation_until: 0, }; - let eth_maker_payment = eth_coin.send_maker_payment(send_payment_args).wait().unwrap(); + let eth_maker_payment = block_on(eth_coin.send_maker_payment(send_payment_args)).unwrap(); let confirm_input = ConfirmPaymentInput { payment_tx: eth_maker_payment.tx_hex(), @@ -561,7 +578,7 @@ fn send_and_refund_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { wait_until: now_sec() + 60, check_every: 1, }; - eth_coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(eth_coin.wait_for_confirmations(confirm_input)).unwrap(); let refund_args = RefundPaymentArgs { payment_tx: ð_maker_payment.tx_hex(), @@ -584,7 +601,7 @@ fn send_and_refund_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { wait_until: now_sec() + 60, check_every: 1, }; - eth_coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(eth_coin.wait_for_confirmations(confirm_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -638,7 +655,7 @@ fn send_and_spend_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { watcher_reward: None, wait_for_confirmation_until: 0, }; - let eth_maker_payment = maker_eth_coin.send_maker_payment(send_payment_args).wait().unwrap(); + let eth_maker_payment = block_on(maker_eth_coin.send_maker_payment(send_payment_args)).unwrap(); let confirm_input = ConfirmPaymentInput { payment_tx: eth_maker_payment.tx_hex(), @@ -647,7 +664,7 @@ fn send_and_spend_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { wait_until: now_sec() + 60, check_every: 1, }; - taker_eth_coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(taker_eth_coin.wait_for_confirmations(confirm_input)).unwrap(); let spend_args = SpendPaymentArgs { other_payment_tx: ð_maker_payment.tx_hex(), @@ -669,7 +686,7 @@ fn send_and_spend_eth_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { wait_until: now_sec() + 60, check_every: 1, }; - taker_eth_coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(taker_eth_coin.wait_for_confirmations(confirm_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -698,6 +715,7 @@ fn send_and_spend_eth_maker_payment_internal_gas_policy() { fn send_and_spend_eth_maker_payment_priority_fee() { send_and_spend_eth_maker_payment_impl(SwapTxFeePolicy::Medium); } fn send_and_refund_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { + thread::sleep(Duration::from_secs(10)); let erc20_coin = erc20_coin_with_random_privkey(swap_contract()); erc20_coin.set_swap_transaction_fee_policy(swap_txfee_policy); @@ -720,7 +738,7 @@ fn send_and_refund_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) watcher_reward: None, wait_for_confirmation_until: now_sec() + 60, }; - let eth_maker_payment = erc20_coin.send_maker_payment(send_payment_args).wait().unwrap(); + let eth_maker_payment = block_on(erc20_coin.send_maker_payment(send_payment_args)).unwrap(); let confirm_input = ConfirmPaymentInput { payment_tx: eth_maker_payment.tx_hex(), @@ -729,7 +747,7 @@ fn send_and_refund_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) wait_until: now_sec() + 60, check_every: 1, }; - erc20_coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(erc20_coin.wait_for_confirmations(confirm_input)).unwrap(); let refund_args = RefundPaymentArgs { payment_tx: ð_maker_payment.tx_hex(), @@ -752,7 +770,7 @@ fn send_and_refund_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) wait_until: now_sec() + 60, check_every: 1, }; - erc20_coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(erc20_coin.wait_for_confirmations(confirm_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -783,6 +801,7 @@ fn send_and_refund_erc20_maker_payment_priority_fee() { } fn send_and_spend_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { + thread::sleep(Duration::from_secs(7)); let maker_erc20_coin = erc20_coin_with_random_privkey(swap_contract()); let taker_erc20_coin = erc20_coin_with_random_privkey(swap_contract()); @@ -808,7 +827,7 @@ fn send_and_spend_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { watcher_reward: None, wait_for_confirmation_until: now_sec() + 60, }; - let eth_maker_payment = maker_erc20_coin.send_maker_payment(send_payment_args).wait().unwrap(); + let eth_maker_payment = block_on(maker_erc20_coin.send_maker_payment(send_payment_args)).unwrap(); let confirm_input = ConfirmPaymentInput { payment_tx: eth_maker_payment.tx_hex(), @@ -817,7 +836,7 @@ fn send_and_spend_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { wait_until: now_sec() + 60, check_every: 1, }; - taker_erc20_coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(taker_erc20_coin.wait_for_confirmations(confirm_input)).unwrap(); let spend_args = SpendPaymentArgs { other_payment_tx: ð_maker_payment.tx_hex(), @@ -839,7 +858,7 @@ fn send_and_spend_erc20_maker_payment_impl(swap_txfee_policy: SwapTxFeePolicy) { wait_until: now_sec() + 60, check_every: 1, }; - taker_erc20_coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(taker_erc20_coin.wait_for_confirmations(confirm_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -869,6 +888,7 @@ fn send_and_spend_erc20_maker_payment_priority_fee() { send_and_spend_erc20_maker_payment_impl(SwapTxFeePolicy::Medium); } +#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] /// Wait for all pending transactions for the given address to be confirmed fn wait_pending_transactions(wallet_address: Address) { let _guard = SEPOLIA_NONCE_LOCK.lock().unwrap(); @@ -879,7 +899,10 @@ fn wait_pending_transactions(wallet_address: Address) { let pending_nonce = block_on(web3.eth().transaction_count(wallet_address, Some(BlockNumber::Pending))).unwrap(); if latest_nonce == pending_nonce { - log!("All pending transactions have been confirmed."); + log!( + "All pending transactions have been confirmed. Latest nonce: {}", + latest_nonce + ); break; } else { log!( @@ -892,250 +915,85 @@ fn wait_pending_transactions(wallet_address: Address) { } } -fn get_or_create_nft(ctx: &MmArc, priv_key: &'static str, nft_type: Option) -> EthCoin { - match block_on(lp_coinfind(ctx, NFT_ETH)).unwrap() { - None => global_nft_from_privkey(ctx, sepolia_etomic_maker_nft(), priv_key, nft_type), - Some(mm_coin) => match mm_coin { - MmCoinEnum::EthCoin(nft) => nft, - _ => panic!("Unexpected coin type found. Expected MmCoinEnum::EthCoin"), - }, - } -} - #[test] fn send_and_spend_erc721_maker_payment() { - // Sepolia Maker owns tokenId = 1 - - let erc721_nft = TestNftType::Erc721 { token_id: 1 }; - - let maker_global_nft = get_or_create_nft(&MM_CTX, SEPOLIA_MAKER_PRIV, Some(erc721_nft)); - let taker_global_nft = get_or_create_nft(&MM_CTX1, SEPOLIA_TAKER_PRIV, None); - - let maker_address = block_on(maker_global_nft.my_addr()); - wait_pending_transactions(maker_address); - - let time_lock = now_sec() + 1001; - let maker_pubkey = maker_global_nft.derive_htlc_pubkey(&[]); - let taker_pubkey = taker_global_nft.derive_htlc_pubkey(&[]); - - let maker_secret = &[1; 32]; - let maker_secret_hash = sha256(maker_secret).to_vec(); - - let nft_swap_info = NftSwapInfo { - token_address: &sepolia_erc721(), - token_id: &BigUint::from(1u32).to_bytes(), - contract_type: &ContractType::Erc721, - swap_contract_address: &sepolia_etomic_maker_nft(), - }; - - let send_payment_args: SendNftMakerPaymentArgs = SendNftMakerPaymentArgs { + let token_id = 1u32; + let time_lock = now_sec() + 1000; + let activation = NftActivationV2Args::init(); + let setup = setup_test( + token_id, + None, + ContractType::Erc721, + geth_erc721_contract(), time_lock, - taker_secret_hash: &[0; 32], - maker_secret_hash: &maker_secret_hash, - amount: 1.into(), - taker_pub: &taker_global_nft.parse_pubkey(&taker_pubkey).unwrap(), - swap_unique_data: &[], - nft_swap_info: &nft_swap_info, - }; - let maker_payment = block_on(maker_global_nft.send_nft_maker_payment_v2(send_payment_args)).unwrap(); + activation, + ); + + let maker_payment = send_nft_maker_payment(&setup, 1.into()); log!( "Maker sent ERC721 NFT payment, tx hash: {:02x}", maker_payment.tx_hash() ); - let confirm_input = ConfirmPaymentInput { - payment_tx: maker_payment.tx_hex(), - confirmations: 1, - requires_nota: false, - wait_until: now_sec() + 150, - check_every: 1, - }; - maker_global_nft.wait_for_confirmations(confirm_input).wait().unwrap(); - - let validate_args = ValidateNftMakerPaymentArgs { - maker_payment_tx: &maker_payment, - time_lock, - taker_secret_hash: &[0; 32], - maker_secret_hash: &maker_secret_hash, - amount: 1.into(), - taker_pub: &taker_global_nft.parse_pubkey(&taker_pubkey).unwrap(), - maker_pub: &maker_global_nft.parse_pubkey(&maker_pubkey).unwrap(), - swap_unique_data: &[], - nft_swap_info: &nft_swap_info, - }; - block_on(maker_global_nft.validate_nft_maker_payment_v2(validate_args)).unwrap(); + wait_for_confirmations(&setup.maker_global_nft, &maker_payment, 200); + validate_nft_maker_payment(&setup, &maker_payment, 1.into()); - let spend_payment_args = SpendNftMakerPaymentArgs { - maker_payment_tx: &maker_payment, - time_lock, - taker_secret_hash: &[0; 32], - maker_secret_hash: &maker_secret_hash, - maker_secret, - maker_pub: &maker_global_nft.parse_pubkey(&maker_pubkey).unwrap(), - swap_unique_data: &[], - contract_type: &ContractType::Erc721, - swap_contract_address: &sepolia_etomic_maker_nft(), - }; - let spend_tx = block_on(taker_global_nft.spend_nft_maker_payment_v2(spend_payment_args)).unwrap(); + let spend_tx = spend_nft_maker_payment(&setup, &maker_payment, &ContractType::Erc721); log!( "Taker spent ERC721 NFT Maker payment, tx hash: {:02x}", spend_tx.tx_hash() ); - let confirm_input = ConfirmPaymentInput { - payment_tx: spend_tx.tx_hex(), - confirmations: 1, - requires_nota: false, - wait_until: now_sec() + 150, - check_every: 1, - }; - taker_global_nft.wait_for_confirmations(confirm_input).wait().unwrap(); - - let new_owner = erc712_owner(U256::from(1)); - let taker_address = block_on(taker_global_nft.my_addr()); + wait_for_confirmations(&setup.taker_global_nft, &spend_tx, 200); + let new_owner = geth_erc712_owner(U256::from(token_id)); + let taker_address = block_on(setup.taker_global_nft.my_addr()); assert_eq!(new_owner, taker_address); - - // send nft back to maker - let send_back_tx = send_safe_transfer_from( - &taker_global_nft, - sepolia_erc721(), - taker_address, - maker_address, - erc721_nft, - ) - .unwrap(); - log!( - "Taker sent ERC721 NFT back to Maker, tx hash: {:02x}", - send_back_tx.tx_hash() - ); - let confirm_input = ConfirmPaymentInput { - payment_tx: send_back_tx.tx_hex(), - confirmations: 1, - requires_nota: false, - wait_until: now_sec() + 150, - check_every: 1, - }; - taker_global_nft.wait_for_confirmations(confirm_input).wait().unwrap(); - - let new_owner = erc712_owner(U256::from(1)); - assert_eq!(new_owner, maker_address); } #[test] fn send_and_spend_erc1155_maker_payment() { - // Sepolia Maker owns tokenId = 1, amount = 3 - - let erc1155_nft = TestNftType::Erc1155 { token_id: 1, amount: 3 }; - - let maker_global_nft = get_or_create_nft(&MM_CTX, SEPOLIA_MAKER_PRIV, Some(erc1155_nft)); - let taker_global_nft = get_or_create_nft(&MM_CTX1, SEPOLIA_TAKER_PRIV, None); - + let token_id = 1u32; + let amount = 3u32; let time_lock = now_sec() + 1000; - let maker_pubkey = maker_global_nft.derive_htlc_pubkey(&[]); - let taker_pubkey = taker_global_nft.derive_htlc_pubkey(&[]); - - let maker_secret = &[1; 32]; - let maker_secret_hash = sha256(maker_secret).to_vec(); + let activation = NftActivationV2Args::init(); + let setup = setup_test( + token_id, + Some(amount), + ContractType::Erc1155, + geth_erc1155_contract(), + time_lock, + activation, + ); - let nft_swap_info = NftSwapInfo { - token_address: &sepolia_erc1155(), - token_id: &BigUint::from(1u32).to_bytes(), - contract_type: &ContractType::Erc1155, - swap_contract_address: &sepolia_etomic_maker_nft(), - }; + let maker_address = block_on(setup.maker_global_nft.my_addr()); + let maker_balance = geth_erc1155_balance(maker_address, U256::from(token_id)); + assert_eq!(U256::from(amount), maker_balance); - let send_payment_args: SendNftMakerPaymentArgs = SendNftMakerPaymentArgs { - time_lock, - taker_secret_hash: &[0; 32], - maker_secret_hash: &maker_secret_hash, - amount: 3.into(), - taker_pub: &taker_global_nft.parse_pubkey(&taker_pubkey).unwrap(), - swap_unique_data: &[], - nft_swap_info: &nft_swap_info, - }; - let maker_payment = block_on(maker_global_nft.send_nft_maker_payment_v2(send_payment_args)).unwrap(); + let swap_amount = 2u32; + let maker_payment = send_nft_maker_payment(&setup, swap_amount.into()); log!( "Maker sent ERC1155 NFT payment, tx hash: {:02x}", maker_payment.tx_hash() ); - let confirm_input = ConfirmPaymentInput { - payment_tx: maker_payment.tx_hex(), - confirmations: 1, - requires_nota: false, - wait_until: now_sec() + 80, - check_every: 1, - }; - maker_global_nft.wait_for_confirmations(confirm_input).wait().unwrap(); + wait_for_confirmations(&setup.maker_global_nft, &maker_payment, 100); - let validate_args = ValidateNftMakerPaymentArgs { - maker_payment_tx: &maker_payment, - time_lock, - taker_secret_hash: &[0; 32], - maker_secret_hash: &maker_secret_hash, - amount: 3.into(), - taker_pub: &taker_global_nft.parse_pubkey(&taker_pubkey).unwrap(), - maker_pub: &maker_global_nft.parse_pubkey(&maker_pubkey).unwrap(), - swap_unique_data: &[], - nft_swap_info: &nft_swap_info, - }; - block_on(maker_global_nft.validate_nft_maker_payment_v2(validate_args)).unwrap(); + validate_nft_maker_payment(&setup, &maker_payment, swap_amount.into()); - let spend_payment_args = SpendNftMakerPaymentArgs { - maker_payment_tx: &maker_payment, - time_lock, - taker_secret_hash: &[0; 32], - maker_secret_hash: &maker_secret_hash, - maker_secret, - maker_pub: &maker_global_nft.parse_pubkey(&maker_pubkey).unwrap(), - swap_unique_data: &[], - contract_type: &ContractType::Erc1155, - swap_contract_address: &sepolia_etomic_maker_nft(), - }; - let spend_tx = block_on(taker_global_nft.spend_nft_maker_payment_v2(spend_payment_args)).unwrap(); + let spend_tx = spend_nft_maker_payment(&setup, &maker_payment, &ContractType::Erc1155); log!( "Taker spent ERC1155 NFT Maker payment, tx hash: {:02x}", spend_tx.tx_hash() ); - let confirm_input = ConfirmPaymentInput { - payment_tx: spend_tx.tx_hex(), - confirmations: 1, - requires_nota: false, - wait_until: now_sec() + 80, - check_every: 1, - }; - taker_global_nft.wait_for_confirmations(confirm_input).wait().unwrap(); - - let taker_address = block_on(taker_global_nft.my_addr()); - let balance = erc1155_balance(taker_address, U256::from(1)); - assert_eq!(balance, U256::from(3)); - - // send nft back to maker - let maker_address = block_on(maker_global_nft.my_addr()); - let send_back_tx = send_safe_transfer_from( - &taker_global_nft, - sepolia_erc1155(), - taker_address, - maker_address, - erc1155_nft, - ) - .unwrap(); - log!( - "Taker sent ERC1155 NFT back to Maker, tx hash: {:02x}", - send_back_tx.tx_hash() - ); - let confirm_input = ConfirmPaymentInput { - payment_tx: send_back_tx.tx_hex(), - confirmations: 1, - requires_nota: false, - wait_until: now_sec() + 80, - check_every: 1, - }; - taker_global_nft.wait_for_confirmations(confirm_input).wait().unwrap(); + wait_for_confirmations(&setup.taker_global_nft, &spend_tx, 100); + + let taker_address = block_on(setup.taker_global_nft.my_addr()); + let taker_balance = geth_erc1155_balance(taker_address, U256::from(token_id)); + assert_eq!(U256::from(swap_amount), taker_balance); - let balance = erc1155_balance(maker_address, U256::from(1)); - assert_eq!(balance, U256::from(3)); + let maker_new_balance = geth_erc1155_balance(maker_address, U256::from(token_id)); + assert_eq!(U256::from(1u32), maker_new_balance); } #[test] @@ -1143,12 +1001,12 @@ fn test_nonce_several_urls() { // Use one working and one failing URL. let coin = eth_coin_with_random_privkey_using_urls(swap_contract(), &[GETH_RPC_URL, "http://127.0.0.1:0"]); let my_address = block_on(coin.derivation_method().single_addr_or_err()).unwrap(); - let (old_nonce, _) = coin.clone().get_addr_nonce(my_address).wait().unwrap(); + let (old_nonce, _) = block_on_f01(coin.clone().get_addr_nonce(my_address)).unwrap(); // Send a payment to increase the nonce. - coin.send_to_address(my_address, 200000000.into()).wait().unwrap(); + block_on_f01(coin.send_to_address(my_address, 200000000.into())).unwrap(); - let (new_nonce, _) = coin.get_addr_nonce(my_address).wait().unwrap(); + let (new_nonce, _) = block_on_f01(coin.get_addr_nonce(my_address)).unwrap(); assert_eq!(old_nonce + 1, new_nonce); } @@ -1166,3 +1024,1717 @@ fn test_nonce_lock() { result.unwrap(); } } + +#[test] +fn send_and_refund_erc721_maker_payment_timelock() { + let token_id = 2u32; + let time_lock_to_refund = now_sec() - 1000; + let activation = NftActivationV2Args::init(); + let setup = setup_test( + token_id, + None, + ContractType::Erc721, + geth_erc721_contract(), + time_lock_to_refund, + activation, + ); + + let maker_payment_to_refund = send_nft_maker_payment(&setup, 1.into()); + log!( + "Maker sent ERC721 NFT payment, tx hash: {:02x}", + maker_payment_to_refund.tx_hash() + ); + + wait_for_confirmations(&setup.maker_global_nft, &maker_payment_to_refund, 150); + let current_owner = geth_erc712_owner(U256::from(token_id)); + assert_eq!(current_owner, geth_nft_maker_swap_v2()); + + let refund_timelock_tx = refund_nft_maker_payment( + &setup, + &maker_payment_to_refund, + &ContractType::Erc721, + RefundType::Timelock, + ); + log!( + "Maker refunded ERC721 NFT payment after timelock, tx hash: {:02x}", + refund_timelock_tx.tx_hash() + ); + + wait_for_confirmations(&setup.maker_global_nft, &refund_timelock_tx, 150); + let current_owner = geth_erc712_owner(U256::from(token_id)); + let maker_address = block_on(setup.maker_global_nft.my_addr()); + assert_eq!(current_owner, maker_address); +} + +#[test] +fn send_and_refund_erc1155_maker_payment_timelock() { + let token_id = 2u32; + let amount = 3u32; + let time_lock_to_refund = now_sec() - 1000; + let activation = NftActivationV2Args::init(); + let setup = setup_test( + token_id, + Some(amount), + ContractType::Erc1155, + geth_erc1155_contract(), + time_lock_to_refund, + activation, + ); + + let maker_address = block_on(setup.maker_global_nft.my_addr()); + let balance = geth_erc1155_balance(maker_address, U256::from(token_id)); + assert_eq!(U256::from(amount), balance); + + let swap_amount = 2u32; + let maker_payment_to_refund = send_nft_maker_payment(&setup, swap_amount.into()); + log!( + "Maker sent ERC1155 NFT payment, tx hash: {:02x}", + maker_payment_to_refund.tx_hash() + ); + + wait_for_confirmations(&setup.maker_global_nft, &maker_payment_to_refund, 150); + + let swap_contract_balance = geth_erc1155_balance(geth_nft_maker_swap_v2(), U256::from(token_id)); + assert_eq!(U256::from(swap_amount), swap_contract_balance); + let balance = geth_erc1155_balance(maker_address, U256::from(token_id)); + assert_eq!(U256::from(1u32), balance); + + let refund_timelock_tx = refund_nft_maker_payment( + &setup, + &maker_payment_to_refund, + &ContractType::Erc1155, + RefundType::Timelock, + ); + log!( + "Maker refunded ERC1155 NFT payment after timelock, tx hash: {:02x}", + refund_timelock_tx.tx_hash() + ); + + wait_for_confirmations(&setup.maker_global_nft, &refund_timelock_tx, 150); + + let balance = geth_erc1155_balance(maker_address, U256::from(token_id)); + assert_eq!(U256::from(amount), balance); +} + +#[test] +fn send_and_refund_erc721_maker_payment_secret() { + let token_id = 3u32; + let time_lock_to_refund = now_sec() + 1000; + let activation = NftActivationV2Args::init(); + let setup = setup_test( + token_id, + None, + ContractType::Erc721, + geth_erc721_contract(), + time_lock_to_refund, + activation, + ); + + let maker_payment_to_refund = send_nft_maker_payment(&setup, 1.into()); + log!( + "Maker sent ERC721 NFT payment, tx hash: {:02x}", + maker_payment_to_refund.tx_hash() + ); + + wait_for_confirmations(&setup.maker_global_nft, &maker_payment_to_refund, 150); + let current_owner = geth_erc712_owner(U256::from(token_id)); + assert_eq!(current_owner, geth_nft_maker_swap_v2()); + + let refund_secret_tx = refund_nft_maker_payment( + &setup, + &maker_payment_to_refund, + &ContractType::Erc721, + RefundType::Secret, + ); + log!( + "Maker refunded ERC721 NFT payment using Taker secret, tx hash: {:02x}", + refund_secret_tx.tx_hash() + ); + + wait_for_confirmations(&setup.maker_global_nft, &refund_secret_tx, 150); + let current_owner = geth_erc712_owner(U256::from(token_id)); + let maker_address = block_on(setup.maker_global_nft.my_addr()); + assert_eq!(current_owner, maker_address); +} + +#[test] +fn send_and_refund_erc1155_maker_payment_secret() { + let token_id = 3u32; + let amount = 3u32; + let time_lock_to_refund = now_sec() + 1000; + let activation = NftActivationV2Args::init(); + let setup = setup_test( + token_id, + Some(amount), + ContractType::Erc1155, + geth_erc1155_contract(), + time_lock_to_refund, + activation, + ); + + let maker_address = block_on(setup.maker_global_nft.my_addr()); + let balance = geth_erc1155_balance(maker_address, U256::from(token_id)); + assert_eq!(U256::from(amount), balance); + + let swap_amount = 2u32; + let maker_payment_to_refund = send_nft_maker_payment(&setup, swap_amount.into()); + log!( + "Maker sent ERC1155 NFT payment, tx hash: {:02x}", + maker_payment_to_refund.tx_hash() + ); + + wait_for_confirmations(&setup.maker_global_nft, &maker_payment_to_refund, 100); + + let swap_contract_balance = geth_erc1155_balance(geth_nft_maker_swap_v2(), U256::from(token_id)); + assert_eq!(U256::from(swap_amount), swap_contract_balance); + let balance = geth_erc1155_balance(maker_address, U256::from(token_id)); + assert_eq!(U256::from(1u32), balance); + + let refund_secret_tx = refund_nft_maker_payment( + &setup, + &maker_payment_to_refund, + &ContractType::Erc1155, + RefundType::Secret, + ); + log!( + "Maker refunded ERC1155 NFT payment using Taker secret, tx hash: {:02x}", + refund_secret_tx.tx_hash() + ); + + wait_for_confirmations(&setup.maker_global_nft, &refund_secret_tx, 100); + + let balance = geth_erc1155_balance(maker_address, U256::from(token_id)); + assert_eq!(U256::from(amount), balance); +} + +struct NftTestSetup { + maker_global_nft: EthCoin, + taker_global_nft: EthCoin, + nft_swap_info: TestNftSwapInfo, + maker_secret: Vec, + maker_secret_hash: Vec, + taker_secret: Vec, + taker_secret_hash: Vec, + time_lock: u64, +} + +/// Structure representing necessary NFT info for Swap +pub struct TestNftSwapInfo { + /// The address of the NFT token + pub token_address: Coin::ContractAddress, + /// The ID of the NFT token. + pub token_id: Vec, + /// The type of smart contract that governs this NFT + pub contract_type: Coin::ContractType, +} + +struct NftActivationV2Args { + swap_contract_address: Address, + fallback_swap_contract_address: Address, + swap_v2_contracts: SwapV2Contracts, + nft_ticker: String, + platform_ticker: String, +} + +impl NftActivationV2Args { + fn init() -> Self { + Self { + swap_contract_address: swap_contract(), + fallback_swap_contract_address: swap_contract(), + swap_v2_contracts: SwapV2Contracts { + maker_swap_v2_contract: maker_swap_v2(), + taker_swap_v2_contract: taker_swap_v2(), + nft_maker_swap_v2_contract: geth_nft_maker_swap_v2(), + }, + nft_ticker: NFT_ETH.to_string(), + platform_ticker: "ETH".to_string(), + } + } +} + +fn setup_test( + token_id: u32, + amount: Option, + contract_type: ContractType, + token_contract: Address, + time_lock: u64, + activation: NftActivationV2Args, +) -> NftTestSetup { + let nft_type = match contract_type { + ContractType::Erc721 => TestNftType::Erc721 { token_id }, + ContractType::Erc1155 => TestNftType::Erc1155 { + token_id, + amount: amount.unwrap(), + }, + }; + + let maker_global_nft = global_nft_with_random_privkey( + activation.swap_v2_contracts, + activation.swap_contract_address, + activation.fallback_swap_contract_address, + Some(nft_type), + activation.nft_ticker.clone(), + activation.platform_ticker.clone(), + ); + let taker_global_nft = global_nft_with_random_privkey( + activation.swap_v2_contracts, + activation.swap_contract_address, + activation.fallback_swap_contract_address, + None, + activation.nft_ticker, + activation.platform_ticker, + ); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + + let token_id = BigUint::from(token_id).to_bytes(); + + let nft_swap_info = TestNftSwapInfo { + token_address: token_contract, + token_id, + contract_type, + }; + + NftTestSetup { + maker_global_nft, + taker_global_nft, + nft_swap_info, + maker_secret, + maker_secret_hash, + taker_secret, + taker_secret_hash, + time_lock, + } +} + +fn send_nft_maker_payment(setup: &NftTestSetup, amount: BigDecimal) -> SignedEthTx { + let nft_swap_info = NftSwapInfo { + token_address: &setup.nft_swap_info.token_address, + token_id: &setup.nft_swap_info.token_id, + contract_type: &setup.nft_swap_info.contract_type, + }; + let send_payment_args = SendNftMakerPaymentArgs:: { + time_lock: setup.time_lock, + taker_secret_hash: &setup.taker_secret_hash, + maker_secret_hash: &setup.maker_secret_hash, + amount, + taker_pub: &setup.taker_global_nft.derive_htlc_pubkey_v2(&[]), + swap_unique_data: &[], + nft_swap_info: &nft_swap_info, + }; + block_on(setup.maker_global_nft.send_nft_maker_payment_v2(send_payment_args)).unwrap() +} + +fn wait_for_confirmations(coin: &EthCoin, tx: &SignedEthTx, wait_seconds: u64) { + let confirm_input = ConfirmPaymentInput { + payment_tx: tx.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: now_sec() + wait_seconds, + check_every: 1, + }; + block_on_f01(coin.wait_for_confirmations(confirm_input)).unwrap(); +} + +fn validate_nft_maker_payment(setup: &NftTestSetup, maker_payment: &SignedEthTx, amount: BigDecimal) { + let nft_swap_info = NftSwapInfo { + token_address: &setup.nft_swap_info.token_address, + token_id: &setup.nft_swap_info.token_id, + contract_type: &setup.nft_swap_info.contract_type, + }; + let validate_args = ValidateNftMakerPaymentArgs { + maker_payment_tx: maker_payment, + time_lock: setup.time_lock, + taker_secret_hash: &setup.taker_secret_hash, + maker_secret_hash: &setup.maker_secret_hash, + amount, + taker_pub: &setup.taker_global_nft.derive_htlc_pubkey_v2(&[]), + maker_pub: &setup.maker_global_nft.derive_htlc_pubkey_v2(&[]), + swap_unique_data: &[], + nft_swap_info: &nft_swap_info, + }; + block_on(setup.maker_global_nft.validate_nft_maker_payment_v2(validate_args)).unwrap() +} + +fn spend_nft_maker_payment( + setup: &NftTestSetup, + maker_payment: &SignedEthTx, + contract_type: &ContractType, +) -> SignedEthTx { + let spend_payment_args = SpendNftMakerPaymentArgs { + maker_payment_tx: maker_payment, + taker_secret_hash: &setup.taker_secret_hash, + maker_secret_hash: &setup.maker_secret_hash, + maker_secret: &setup.maker_secret, + maker_pub: &setup.maker_global_nft.derive_htlc_pubkey_v2(&[]), + swap_unique_data: &[], + contract_type, + }; + block_on(setup.taker_global_nft.spend_nft_maker_payment_v2(spend_payment_args)).unwrap() +} + +fn refund_nft_maker_payment( + setup: &NftTestSetup, + maker_payment: &SignedEthTx, + contract_type: &ContractType, + refund_type: RefundType, +) -> SignedEthTx { + let refund_args = RefundNftMakerPaymentArgs { + maker_payment_tx: maker_payment, + taker_secret_hash: &setup.taker_secret_hash, + maker_secret_hash: &setup.maker_secret_hash, + taker_secret: &setup.taker_secret, + swap_unique_data: &[], + contract_type, + }; + match refund_type { + RefundType::Timelock => { + block_on(setup.maker_global_nft.refund_nft_maker_payment_v2_timelock(refund_args)).unwrap() + }, + RefundType::Secret => block_on(setup.maker_global_nft.refund_nft_maker_payment_v2_secret(refund_args)).unwrap(), + } +} + +enum RefundType { + Timelock, + Secret, +} + +#[derive(Copy, Clone)] +struct SwapAddresses { + swap_v2_contracts: SwapV2Contracts, + swap_contract_address: Address, + fallback_swap_contract_address: Address, +} + +#[allow(dead_code)] +/// Needed for Geth taker or maker swap v2 tests +impl SwapAddresses { + fn init() -> Self { + Self { + swap_contract_address: swap_contract(), + fallback_swap_contract_address: swap_contract(), + swap_v2_contracts: SwapV2Contracts { + maker_swap_v2_contract: maker_swap_v2(), + taker_swap_v2_contract: taker_swap_v2(), + nft_maker_swap_v2_contract: geth_nft_maker_swap_v2(), + }, + } + } +} + +#[allow(dead_code)] +/// Needed for eth or erc20 v2 activation in Geth tests +fn eth_coin_v2_activation_with_random_privkey( + ticker: &str, + conf: &Json, + swap_addr: SwapAddresses, + erc20: bool, +) -> EthCoin { + let build_policy = EthPrivKeyBuildPolicy::IguanaPrivKey(random_secp256k1_secret()); + let node = EthNode { + url: GETH_RPC_URL.to_string(), + komodo_proxy: false, + }; + let platform_request = EthActivationV2Request { + nodes: vec![node], + rpc_mode: Default::default(), + swap_contract_address: swap_addr.swap_contract_address, + swap_v2_contracts: Some(swap_addr.swap_v2_contracts), + fallback_swap_contract: Some(swap_addr.fallback_swap_contract_address), + contract_supports_watchers: false, + mm2: None, + required_confirmations: None, + priv_key_policy: Default::default(), + enable_params: Default::default(), + path_to_address: Default::default(), + gap_limit: None, + }; + let coin = block_on(eth_coin_from_conf_and_request_v2( + &MM_CTX1, + ticker, + conf, + platform_request, + build_policy, + )) + .unwrap(); + let my_address = block_on(coin.my_addr()); + fill_eth(my_address, U256::from(10).pow(U256::from(20))); + fill_erc20(my_address, U256::from(10000000000u64)); + if erc20 { + let coin_type = EthCoinType::Erc20 { + platform: ETH.to_string(), + token_addr: erc20_contract(), + }; + let coin = block_on(coin.set_coin_type(coin_type)); + return coin; + } + coin +} + +#[cfg(feature = "sepolia-taker-swap-v2-tests")] +#[test] +fn send_and_refund_taker_funding_by_secret_eth() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let funding_time_lock = now_sec() + 3000; + let payment_time_lock = now_sec() + 1000; + + let taker_address = block_on(taker_coin.my_addr()); + + let dex_fee = &DexFee::Standard("0.00001".into()); + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let maker_pub = &maker_coin.derive_htlc_pubkey_v2(&[]); + let payment_args = SendTakerFundingArgs { + funding_time_lock, + payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + maker_pub: maker_pub.as_bytes(), + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount: trading_amount.clone(), + swap_unique_data: &[], + }; + + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + thread::sleep(Duration::from_secs(2)); + let funding_tx = block_on(taker_coin.send_taker_funding(payment_args)).unwrap(); + log!("Taker sent ETH funding, tx hash: {:02x}", funding_tx.tx_hash()); + + let refund_args = RefundFundingSecretArgs { + funding_tx: &funding_tx, + funding_time_lock, + payment_time_lock, + maker_pubkey: maker_pub, + taker_secret: &taker_secret, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + dex_fee, + premium_amount: Default::default(), + trading_amount, + swap_unique_data: &[], + watcher_reward: false, + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx_refund = block_on(taker_coin.refund_taker_funding_secret(refund_args)).unwrap(); + log!( + "Taker refunded ETH funding by secret, tx hash: {:02x}", + funding_tx_refund.tx_hash() + ); + + wait_for_confirmations(&taker_coin, &funding_tx_refund, 100); +} + +#[cfg(feature = "sepolia-taker-swap-v2-tests")] +#[test] +fn send_and_refund_taker_funding_by_secret_erc20() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let erc20_conf = &sepolia_erc20_dev_conf(&sepolia_erc20_contract_checksum()); + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + + let taker_address = block_on(taker_coin.my_addr()); + + let funding_time_lock = now_sec() + 3000; + let payment_time_lock = now_sec() + 1000; + + let dex_fee = &DexFee::Standard("0.00001".into()); + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let maker_pub = &maker_coin.derive_htlc_pubkey_v2(&[]); + let payment_args = SendTakerFundingArgs { + funding_time_lock, + payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + maker_pub: maker_pub.as_bytes(), + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount: trading_amount.clone(), + swap_unique_data: &[], + }; + + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx = block_on(taker_coin.send_taker_funding(payment_args)).unwrap(); + log!("Taker sent ERC20 funding, tx hash: {:02x}", funding_tx.tx_hash()); + wait_for_confirmations(&taker_coin, &funding_tx, 200); + + let refund_args = RefundFundingSecretArgs { + funding_tx: &funding_tx, + funding_time_lock, + payment_time_lock, + maker_pubkey: &taker_coin.derive_htlc_pubkey_v2(&[]), + taker_secret: &taker_secret, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + dex_fee, + premium_amount: Default::default(), + trading_amount, + swap_unique_data: &[], + watcher_reward: false, + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx_refund = block_on(taker_coin.refund_taker_funding_secret(refund_args)).unwrap(); + log!( + "Taker refunded ERC20 funding by secret, tx hash: {:02x}", + funding_tx_refund.tx_hash() + ); + wait_for_confirmations(&taker_coin, &funding_tx_refund, 200); +} + +#[cfg(feature = "sepolia-taker-swap-v2-tests")] +#[test] +fn send_and_refund_taker_funding_exceed_pre_approve_timelock_eth() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + + let taker_address = block_on(taker_coin.my_addr()); + + // if TakerPaymentState is `PaymentSent` then timestamp should exceed payment pre-approve lock time (funding_time_lock) + let funding_time_lock = now_sec() - 3000; + let payment_time_lock = now_sec() + 1000; + + let dex_fee = &DexFee::Standard("0.00001".into()); + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let maker_pub = &maker_coin.derive_htlc_pubkey_v2(&[]); + let payment_args = SendTakerFundingArgs { + funding_time_lock, + payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + maker_pub: maker_pub.as_bytes(), + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount: trading_amount.clone(), + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx = block_on(taker_coin.send_taker_funding(payment_args)).unwrap(); + log!("Taker sent ETH funding, tx hash: {:02x}", funding_tx.tx_hash()); + wait_for_confirmations(&taker_coin, &funding_tx, 100); + + let tx_type_with_secret_hash = SwapTxTypeWithSecretHash::TakerPaymentV2 { + maker_secret_hash: &maker_secret_hash, + taker_secret_hash: &taker_secret_hash, + }; + + let refund_args = RefundTakerPaymentArgs { + payment_tx: &funding_tx.to_bytes(), + time_lock: payment_time_lock, + maker_pub: maker_pub.as_bytes(), + tx_type_with_secret_hash, + swap_unique_data: &[], + watcher_reward: false, + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount, + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx_refund = block_on(taker_coin.refund_taker_funding_timelock(refund_args)).unwrap(); + log!( + "Taker refunded ETH funding after pre-approval lock time was exceeded, tx hash: {:02x}", + funding_tx_refund.tx_hash() + ); + + wait_for_confirmations(&taker_coin, &funding_tx_refund, 100); +} + +#[cfg(feature = "sepolia-taker-swap-v2-tests")] +#[test] +fn taker_send_approve_and_spend_eth() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let funding_time_lock = now_sec() + 3000; + let payment_time_lock = now_sec() + 600; + + let taker_address = block_on(taker_coin.my_addr()); + let maker_address = block_on(maker_coin.my_addr()); + + let dex_fee = &DexFee::Standard("0.00001".into()); + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let maker_pub = &maker_coin.derive_htlc_pubkey_v2(&[]); + let payment_args = SendTakerFundingArgs { + funding_time_lock, + payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + maker_pub: maker_pub.as_bytes(), + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount: trading_amount.clone(), + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx = block_on(taker_coin.send_taker_funding(payment_args)).unwrap(); + log!("Taker sent ETH funding, tx hash: {:02x}", funding_tx.tx_hash()); + wait_for_confirmations(&taker_coin, &funding_tx, 100); + + let taker_pub = &taker_coin.derive_htlc_pubkey_v2(&[]); + let validate = ValidateTakerFundingArgs { + funding_tx: &funding_tx, + funding_time_lock, + payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + taker_pub, + dex_fee, + premium_amount: Default::default(), + trading_amount: trading_amount.clone(), + swap_unique_data: &[], + }; + block_on(maker_coin.validate_taker_funding(validate)).unwrap(); + + let approve_args = GenTakerFundingSpendArgs { + funding_tx: &funding_tx, + maker_pub, + taker_pub, + funding_time_lock, + taker_secret_hash: &taker_secret_hash, + taker_payment_time_lock: funding_time_lock, + maker_secret_hash: &maker_secret_hash, + }; + let preimage = TxPreimageWithSig { + preimage: funding_tx.clone(), + signature: taker_coin.parse_signature(&[0u8; 65]).unwrap(), + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let taker_approve_tx = + block_on(taker_coin.sign_and_send_taker_funding_spend(&preimage, &approve_args, &[])).unwrap(); + log!( + "Taker approved ETH payment, tx hash: {:02x}", + taker_approve_tx.tx_hash() + ); + wait_for_confirmations(&taker_coin, &taker_approve_tx, 100); + + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let check_taker_approved_tx = block_on(maker_coin.search_for_taker_funding_spend(&taker_approve_tx, 0u64, &[])) + .unwrap() + .unwrap(); + match check_taker_approved_tx { + FundingTxSpend::TransferredToTakerPayment(tx) => { + assert_eq!(tx, taker_approve_tx); + }, + FundingTxSpend::RefundedTimelock(_) | FundingTxSpend::RefundedSecret { .. } => { + panic!("Wrong FundingTxSpend variant, expected TransferredToTakerPayment") + }, + }; + + let dex_fee_pub = sepolia_taker_swap_v2(); + let spend_args = GenTakerPaymentSpendArgs { + taker_tx: &taker_approve_tx, + time_lock: payment_time_lock, + maker_secret_hash: &maker_secret_hash, + maker_pub, + maker_address: &maker_address, + taker_pub, + dex_fee_pub: dex_fee_pub.as_bytes(), + dex_fee, + premium_amount: Default::default(), + trading_amount, + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let spend_tx = + block_on(maker_coin.sign_and_broadcast_taker_payment_spend(&preimage, &spend_args, &maker_secret, &[])) + .unwrap(); + log!("Maker spent ETH payment, tx hash: {:02x}", spend_tx.tx_hash()); + wait_for_confirmations(&maker_coin, &spend_tx, 100); + block_on(taker_coin.wait_for_taker_payment_spend(&spend_tx, 0u64, payment_time_lock)).unwrap(); +} + +#[cfg(feature = "sepolia-taker-swap-v2-tests")] +#[test] +fn taker_send_approve_and_spend_erc20() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let erc20_conf = &sepolia_erc20_dev_conf(&sepolia_erc20_contract_checksum()); + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let funding_time_lock = now_sec() + 3000; + let payment_time_lock = now_sec() + 600; + + let taker_address = block_on(taker_coin.my_addr()); + let maker_address = block_on(maker_coin.my_addr()); + + let dex_fee = &DexFee::Standard("0.00001".into()); + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let maker_pub = &maker_coin.derive_htlc_pubkey_v2(&[]); + let payment_args = SendTakerFundingArgs { + funding_time_lock, + payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + maker_pub: maker_pub.as_bytes(), + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount: trading_amount.clone(), + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx = block_on(taker_coin.send_taker_funding(payment_args)).unwrap(); + log!("Taker sent ERC20 funding, tx hash: {:02x}", funding_tx.tx_hash()); + wait_for_confirmations(&taker_coin, &funding_tx, 100); + + let taker_pub = &taker_coin.derive_htlc_pubkey_v2(&[]); + let validate = ValidateTakerFundingArgs { + funding_tx: &funding_tx, + funding_time_lock, + payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + taker_pub, + dex_fee, + premium_amount: Default::default(), + trading_amount: trading_amount.clone(), + swap_unique_data: &[], + }; + block_on(maker_coin.validate_taker_funding(validate)).unwrap(); + + let approve_args = GenTakerFundingSpendArgs { + funding_tx: &funding_tx, + maker_pub, + taker_pub, + funding_time_lock, + taker_secret_hash: &taker_secret_hash, + taker_payment_time_lock: funding_time_lock, + maker_secret_hash: &maker_secret_hash, + }; + let preimage = TxPreimageWithSig { + preimage: funding_tx.clone(), + signature: taker_coin.parse_signature(&[0u8; 65]).unwrap(), + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let taker_approve_tx = + block_on(taker_coin.sign_and_send_taker_funding_spend(&preimage, &approve_args, &[])).unwrap(); + log!( + "Taker approved ERC20 payment, tx hash: {:02x}", + taker_approve_tx.tx_hash() + ); + wait_for_confirmations(&taker_coin, &taker_approve_tx, 100); + + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let check_taker_approved_tx = block_on(maker_coin.search_for_taker_funding_spend(&taker_approve_tx, 0u64, &[])) + .unwrap() + .unwrap(); + match check_taker_approved_tx { + FundingTxSpend::TransferredToTakerPayment(tx) => { + assert_eq!(tx, taker_approve_tx); + }, + FundingTxSpend::RefundedTimelock(_) | FundingTxSpend::RefundedSecret { .. } => { + panic!("Wrong FundingTxSpend variant, expected TransferredToTakerPayment") + }, + }; + + let dex_fee_pub = sepolia_taker_swap_v2(); + let spend_args = GenTakerPaymentSpendArgs { + taker_tx: &taker_approve_tx, + time_lock: payment_time_lock, + maker_secret_hash: &maker_secret_hash, + maker_pub, + maker_address: &maker_address, + taker_pub, + dex_fee_pub: dex_fee_pub.as_bytes(), + dex_fee, + premium_amount: Default::default(), + trading_amount, + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let spend_tx = + block_on(maker_coin.sign_and_broadcast_taker_payment_spend(&preimage, &spend_args, &maker_secret, &[])) + .unwrap(); + log!("Maker spent ERC20 payment, tx hash: {:02x}", spend_tx.tx_hash()); + block_on(taker_coin.wait_for_taker_payment_spend(&spend_tx, 0u64, payment_time_lock)).unwrap(); +} + +#[cfg(feature = "sepolia-taker-swap-v2-tests")] +#[test] +fn send_and_refund_taker_funding_exceed_payment_timelock_eth() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let funding_time_lock = now_sec() + 3000; + let payment_time_lock = now_sec() - 1000; + + let taker_address = block_on(taker_coin.my_addr()); + + let dex_fee = &DexFee::Standard("0.00001".into()); + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let maker_pub = &maker_coin.derive_htlc_pubkey_v2(&[]); + let payment_args = SendTakerFundingArgs { + funding_time_lock, + payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + maker_pub: maker_pub.as_bytes(), + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount: trading_amount.clone(), + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx = block_on(taker_coin.send_taker_funding(payment_args)).unwrap(); + log!("Taker sent ETH funding, tx hash: {:02x}", funding_tx.tx_hash()); + wait_for_confirmations(&taker_coin, &funding_tx, 100); + + let taker_pub = &taker_coin.derive_htlc_pubkey_v2(&[]); + let approve_args = GenTakerFundingSpendArgs { + funding_tx: &funding_tx, + maker_pub, + taker_pub, + funding_time_lock, + taker_secret_hash: &taker_secret_hash, + taker_payment_time_lock: funding_time_lock, + maker_secret_hash: &maker_secret_hash, + }; + let preimage = TxPreimageWithSig { + preimage: funding_tx.clone(), + signature: taker_coin.parse_signature(&[0u8; 65]).unwrap(), + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let taker_approve_tx = + block_on(taker_coin.sign_and_send_taker_funding_spend(&preimage, &approve_args, &[])).unwrap(); + log!( + "Taker approved ETH payment, tx hash: {:02x}", + taker_approve_tx.tx_hash() + ); + wait_for_confirmations(&taker_coin, &taker_approve_tx, 100); + + let tx_type_with_secret_hash = SwapTxTypeWithSecretHash::TakerPaymentV2 { + maker_secret_hash: &maker_secret_hash, + taker_secret_hash: &taker_secret_hash, + }; + let refund_args = RefundTakerPaymentArgs { + payment_tx: &funding_tx.to_bytes(), + time_lock: payment_time_lock, + maker_pub: maker_pub.as_bytes(), + tx_type_with_secret_hash, + swap_unique_data: &[], + watcher_reward: false, + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount, + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx_refund = block_on(taker_coin.refund_taker_funding_timelock(refund_args)).unwrap(); + log!( + "Taker refunded ETH funding after payment lock time was exceeded, tx hash: {:02x}", + funding_tx_refund.tx_hash() + ); + wait_for_confirmations(&taker_coin, &funding_tx_refund, 100); +} + +#[cfg(feature = "sepolia-taker-swap-v2-tests")] +#[test] +fn send_and_refund_taker_funding_exceed_payment_timelock_erc20() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let erc20_conf = &sepolia_erc20_dev_conf(&sepolia_erc20_contract_checksum()); + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let funding_time_lock = now_sec() + 29; + let payment_time_lock = now_sec() + 15; + + let taker_address = block_on(taker_coin.my_addr()); + + let dex_fee = &DexFee::Standard("0.00001".into()); + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let maker_pub = &maker_coin.derive_htlc_pubkey_v2(&[]); + let payment_args = SendTakerFundingArgs { + funding_time_lock, + payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + maker_pub: maker_pub.as_bytes(), + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount: trading_amount.clone(), + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx = block_on(taker_coin.send_taker_funding(payment_args)).unwrap(); + log!("Taker sent ERC20 funding, tx hash: {:02x}", funding_tx.tx_hash()); + wait_for_confirmations(&taker_coin, &funding_tx, 100); + thread::sleep(Duration::from_secs(16)); + + let taker_pub = &taker_coin.derive_htlc_pubkey_v2(&[]); + let approve_args = GenTakerFundingSpendArgs { + funding_tx: &funding_tx, + maker_pub, + taker_pub, + funding_time_lock, + taker_secret_hash: &taker_secret_hash, + taker_payment_time_lock: funding_time_lock, + maker_secret_hash: &maker_secret_hash, + }; + let preimage = TxPreimageWithSig { + preimage: funding_tx.clone(), + signature: taker_coin.parse_signature(&[0u8; 65]).unwrap(), + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let taker_approve_tx = + block_on(taker_coin.sign_and_send_taker_funding_spend(&preimage, &approve_args, &[])).unwrap(); + log!( + "Taker approved ERC20 payment, tx hash: {:02x}", + taker_approve_tx.tx_hash() + ); + wait_for_confirmations(&taker_coin, &taker_approve_tx, 100); + + let tx_type_with_secret_hash = SwapTxTypeWithSecretHash::TakerPaymentV2 { + maker_secret_hash: &maker_secret_hash, + taker_secret_hash: &taker_secret_hash, + }; + let refund_args = RefundTakerPaymentArgs { + payment_tx: &funding_tx.to_bytes(), + time_lock: payment_time_lock, + maker_pub: maker_pub.as_bytes(), + tx_type_with_secret_hash, + swap_unique_data: &[], + watcher_reward: false, + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount, + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx_refund = block_on(taker_coin.refund_taker_funding_timelock(refund_args)).unwrap(); + log!( + "Taker refunded ERC20 funding after payment lock time was exceeded, tx hash: {:02x}", + funding_tx_refund.tx_hash() + ); + wait_for_confirmations(&taker_coin, &funding_tx_refund, 100); +} + +#[cfg(feature = "sepolia-taker-swap-v2-tests")] +#[test] +fn send_and_refund_taker_funding_exceed_pre_approve_timelock_erc20() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let erc20_conf = &sepolia_erc20_dev_conf(&sepolia_erc20_contract_checksum()); + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + + let taker_address = block_on(taker_coin.my_addr()); + + // if TakerPaymentState is `PaymentSent` then timestamp should exceed payment pre-approve lock time (funding_time_lock) + let funding_time_lock = now_sec() + 29; + let payment_time_lock = now_sec() + 1000; + + let dex_fee = &DexFee::Standard("0.00001".into()); + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let maker_pub = &maker_coin.derive_htlc_pubkey_v2(&[]); + let payment_args = SendTakerFundingArgs { + funding_time_lock, + payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + maker_pub: maker_pub.as_bytes(), + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount: trading_amount.clone(), + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx = block_on(taker_coin.send_taker_funding(payment_args)).unwrap(); + log!("Taker sent ERC20 funding, tx hash: {:02x}", funding_tx.tx_hash()); + wait_for_confirmations(&taker_coin, &funding_tx, 150); + thread::sleep(Duration::from_secs(29)); + + let tx_type_with_secret_hash = SwapTxTypeWithSecretHash::TakerPaymentV2 { + maker_secret_hash: &maker_secret_hash, + taker_secret_hash: &taker_secret_hash, + }; + + let refund_args = RefundTakerPaymentArgs { + payment_tx: &funding_tx.to_bytes(), + time_lock: payment_time_lock, + maker_pub: maker_pub.as_bytes(), + tx_type_with_secret_hash, + swap_unique_data: &[], + watcher_reward: false, + dex_fee, + premium_amount: BigDecimal::default(), + trading_amount, + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let funding_tx_refund = block_on(taker_coin.refund_taker_funding_timelock(refund_args)).unwrap(); + log!( + "Taker refunded ERC20 funding after pre-approval lock time was exceeded, tx hash: {:02x}", + funding_tx_refund.tx_hash() + ); + wait_for_confirmations(&taker_coin, &funding_tx_refund, 150); +} + +#[cfg(feature = "sepolia-maker-swap-v2-tests")] +#[test] +fn send_maker_payment_and_refund_timelock_eth() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let payment_time_lock = now_sec() - 1000; + + let maker_address = block_on(maker_coin.my_addr()); + let taker_pub = &taker_coin.derive_htlc_pubkey_v2(&[]); + + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let payment_args = SendMakerPaymentArgs { + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + amount: trading_amount.clone(), + taker_pub, + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let payment_tx = block_on(maker_coin.send_maker_payment_v2(payment_args)).unwrap(); + log!("Maker sent ETH payment, tx hash: {:02x}", payment_tx.tx_hash()); + wait_for_confirmations(&maker_coin, &payment_tx, 100); + + let tx_type_with_secret_hash = SwapTxTypeWithSecretHash::MakerPaymentV2 { + maker_secret_hash: &maker_secret_hash, + taker_secret_hash: &taker_secret_hash, + }; + let refund_args = RefundMakerPaymentTimelockArgs { + payment_tx: &payment_tx.to_bytes(), + time_lock: payment_time_lock, + taker_pub: &taker_pub.to_bytes(), + tx_type_with_secret_hash, + swap_unique_data: &[], + watcher_reward: false, + amount: trading_amount, + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let payment_tx_refund = block_on(maker_coin.refund_maker_payment_v2_timelock(refund_args)).unwrap(); + log!( + "Maker refunded ETH payment after timelock, tx hash: {:02x}", + payment_tx_refund.tx_hash() + ); + wait_for_confirmations(&maker_coin, &payment_tx_refund, 100); +} + +#[cfg(feature = "sepolia-maker-swap-v2-tests")] +#[test] +fn send_maker_payment_and_refund_timelock_erc20() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let erc20_conf = &sepolia_erc20_dev_conf(&sepolia_erc20_contract_checksum()); + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let payment_time_lock = now_sec() - 1000; + + let maker_address = block_on(maker_coin.my_addr()); + let taker_pub = &taker_coin.derive_htlc_pubkey_v2(&[]); + + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let payment_args = SendMakerPaymentArgs { + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + amount: trading_amount.clone(), + taker_pub, + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let payment_tx = block_on(maker_coin.send_maker_payment_v2(payment_args)).unwrap(); + log!("Maker sent ERC20 payment, tx hash: {:02x}", payment_tx.tx_hash()); + wait_for_confirmations(&maker_coin, &payment_tx, 100); + + let tx_type_with_secret_hash = SwapTxTypeWithSecretHash::MakerPaymentV2 { + maker_secret_hash: &maker_secret_hash, + taker_secret_hash: &taker_secret_hash, + }; + let refund_args = RefundMakerPaymentTimelockArgs { + payment_tx: &payment_tx.to_bytes(), + time_lock: payment_time_lock, + taker_pub: &taker_pub.to_bytes(), + tx_type_with_secret_hash, + swap_unique_data: &[], + watcher_reward: false, + amount: trading_amount, + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let payment_tx_refund = block_on(maker_coin.refund_maker_payment_v2_timelock(refund_args)).unwrap(); + log!( + "Maker refunded ERC20 payment after timelock, tx hash: {:02x}", + payment_tx_refund.tx_hash() + ); + wait_for_confirmations(&maker_coin, &payment_tx_refund, 100); +} + +#[cfg(feature = "sepolia-maker-swap-v2-tests")] +#[test] +fn send_maker_payment_and_refund_secret_eth() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let payment_time_lock = now_sec() + 1000; + + let maker_address = block_on(maker_coin.my_addr()); + let taker_pub = &taker_coin.derive_htlc_pubkey_v2(&[]); + + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let payment_args = SendMakerPaymentArgs { + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + amount: trading_amount.clone(), + taker_pub, + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let payment_tx = block_on(maker_coin.send_maker_payment_v2(payment_args)).unwrap(); + log!("Maker sent ETH payment, tx hash: {:02x}", payment_tx.tx_hash()); + wait_for_confirmations(&maker_coin, &payment_tx, 100); + + let refund_args = RefundMakerPaymentSecretArgs { + maker_payment_tx: &payment_tx, + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + taker_secret: &taker_secret, + taker_pub, + swap_unique_data: &[], + amount: trading_amount, + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let payment_tx_refund = block_on(maker_coin.refund_maker_payment_v2_secret(refund_args)).unwrap(); + log!( + "Maker refunded ETH payment using taker secret, tx hash: {:02x}", + payment_tx_refund.tx_hash() + ); + wait_for_confirmations(&maker_coin, &payment_tx_refund, 100); +} + +#[cfg(feature = "sepolia-maker-swap-v2-tests")] +#[test] +fn send_maker_payment_and_refund_secret_erc20() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let erc20_conf = &sepolia_erc20_dev_conf(&sepolia_erc20_contract_checksum()); + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let payment_time_lock = now_sec() + 1000; + + let maker_address = block_on(maker_coin.my_addr()); + let taker_pub = &taker_coin.derive_htlc_pubkey_v2(&[]); + + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let payment_args = SendMakerPaymentArgs { + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + amount: trading_amount.clone(), + taker_pub, + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let payment_tx = block_on(maker_coin.send_maker_payment_v2(payment_args)).unwrap(); + log!("Maker sent ERC20 payment, tx hash: {:02x}", payment_tx.tx_hash()); + wait_for_confirmations(&maker_coin, &payment_tx, 100); + + let refund_args = RefundMakerPaymentSecretArgs { + maker_payment_tx: &payment_tx, + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + taker_secret: &taker_secret, + taker_pub, + swap_unique_data: &[], + amount: trading_amount, + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let payment_tx_refund = block_on(maker_coin.refund_maker_payment_v2_secret(refund_args)).unwrap(); + log!( + "Maker refunded ERC20 payment using taker secret, tx hash: {:02x}", + payment_tx_refund.tx_hash() + ); + wait_for_confirmations(&maker_coin, &payment_tx_refund, 100); +} + +#[cfg(feature = "sepolia-maker-swap-v2-tests")] +#[test] +fn send_and_spend_maker_payment_eth() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ETH, ð_sepolia_conf(), false); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ETH, ð_sepolia_conf(), false); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let payment_time_lock = now_sec() + 1000; + + let maker_address = block_on(maker_coin.my_addr()); + let taker_address = block_on(taker_coin.my_addr()); + let maker_pub = &maker_coin.derive_htlc_pubkey_v2(&[]); + let taker_pub = &taker_coin.derive_htlc_pubkey_v2(&[]); + + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let payment_args = SendMakerPaymentArgs { + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + amount: trading_amount.clone(), + taker_pub, + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let payment_tx = block_on(maker_coin.send_maker_payment_v2(payment_args)).unwrap(); + log!("Maker sent ETH payment, tx hash: {:02x}", payment_tx.tx_hash()); + wait_for_confirmations(&maker_coin, &payment_tx, 100); + + let validation_args = ValidateMakerPaymentArgs { + maker_payment_tx: &payment_tx, + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + amount: trading_amount.clone(), + maker_pub, + swap_unique_data: &[], + }; + block_on(taker_coin.validate_maker_payment_v2(validation_args)).unwrap(); + log!("Taker validated maker ETH payment"); + + let spend_args = SpendMakerPaymentArgs { + maker_payment_tx: &payment_tx, + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + maker_secret: &maker_secret, + maker_pub, + swap_unique_data: &[], + amount: trading_amount, + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let spend_tx = block_on(taker_coin.spend_maker_payment_v2(spend_args)).unwrap(); + log!("Taker spent maker ETH payment, tx hash: {:02x}", spend_tx.tx_hash()); + wait_for_confirmations(&taker_coin, &spend_tx, 100); +} + +#[cfg(feature = "sepolia-maker-swap-v2-tests")] +#[test] +fn send_and_spend_maker_payment_erc20() { + let _guard = SEPOLIA_TESTS_LOCK.lock().unwrap(); + + let erc20_conf = &sepolia_erc20_dev_conf(&sepolia_erc20_contract_checksum()); + let taker_coin = get_or_create_sepolia_coin(&MM_CTX1, SEPOLIA_TAKER_PRIV, ERC20, erc20_conf, true); + let maker_coin = get_or_create_sepolia_coin(&MM_CTX, SEPOLIA_MAKER_PRIV, ERC20, erc20_conf, true); + + let taker_secret = vec![0; 32]; + let taker_secret_hash = sha256(&taker_secret).to_vec(); + let maker_secret = vec![1; 32]; + let maker_secret_hash = sha256(&maker_secret).to_vec(); + let payment_time_lock = now_sec() + 1000; + + let maker_address = block_on(maker_coin.my_addr()); + let taker_address = block_on(taker_coin.my_addr()); + let maker_pub = &maker_coin.derive_htlc_pubkey_v2(&[]); + let taker_pub = &taker_coin.derive_htlc_pubkey_v2(&[]); + + let trading_amount = BigDecimal::from_str("0.0001").unwrap(); + + let payment_args = SendMakerPaymentArgs { + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + amount: trading_amount.clone(), + taker_pub, + swap_unique_data: &[], + }; + wait_pending_transactions(Address::from_slice(maker_address.as_bytes())); + let payment_tx = block_on(maker_coin.send_maker_payment_v2(payment_args)).unwrap(); + log!("Maker sent ERC20 payment, tx hash: {:02x}", payment_tx.tx_hash()); + wait_for_confirmations(&maker_coin, &payment_tx, 100); + + let validation_args = ValidateMakerPaymentArgs { + maker_payment_tx: &payment_tx, + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + amount: trading_amount.clone(), + maker_pub, + swap_unique_data: &[], + }; + block_on(taker_coin.validate_maker_payment_v2(validation_args)).unwrap(); + log!("Taker validated maker ERC20 payment"); + + let spend_args = SpendMakerPaymentArgs { + maker_payment_tx: &payment_tx, + time_lock: payment_time_lock, + taker_secret_hash: &taker_secret_hash, + maker_secret_hash: &maker_secret_hash, + maker_secret: &maker_secret, + maker_pub, + swap_unique_data: &[], + amount: trading_amount, + }; + wait_pending_transactions(Address::from_slice(taker_address.as_bytes())); + let spend_tx = block_on(taker_coin.spend_maker_payment_v2(spend_args)).unwrap(); + log!("Taker spent maker ERC20 payment, tx hash: {:02x}", spend_tx.tx_hash()); + wait_for_confirmations(&taker_coin, &spend_tx, 100); +} + +#[test] +fn test_eth_erc20_hd() { + const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; + + let coins = json!([eth_dev_conf(), erc20_dev_conf(&erc20_contract_checksum())]); + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + + // Withdraw from HD account 0, change address 0, index 0 + let path_to_address = HDAccountAddressId::default(); + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + let eth_enable = block_on(enable_eth_with_tokens_v2( + &mm_hd, + "ETH", + &["ERC20DEV"], + &swap_contract, + &[GETH_RPC_URL], + 60, + Some(path_to_address), + )); + let activation_result = match eth_enable { + EthWithTokensActivationResult::HD(hd) => hd, + _ => panic!("Expected EthWithTokensActivationResult::HD"), + }; + let balance = match activation_result.wallet_balance { + EnableCoinBalanceMap::HD(hd) => hd, + _ => panic!("Expected EnableCoinBalance::HD"), + }; + let account = balance.accounts.get(0).expect("Expected account at index 0"); + assert_eq!( + account.addresses[0].address, + "0x1737F1FaB40c6Fd3dc729B51C0F97DB3297CCA93" + ); + assert_eq!(account.addresses[0].balance.len(), 2); + assert!(account.addresses[0].balance.contains_key("ETH")); + assert!(account.addresses[0].balance.contains_key("ERC20DEV")); + + block_on(mm_hd.stop()).unwrap(); + + // Enable HD account 0, change address 0, index 1 + let path_to_address = HDAccountAddressId { + account_id: 0, + chain: Bip44Chain::External, + address_id: 1, + }; + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + let eth_enable = block_on(enable_eth_with_tokens_v2( + &mm_hd, + "ETH", + &["ERC20DEV"], + &swap_contract, + &[GETH_RPC_URL], + 60, + Some(path_to_address), + )); + let activation_result = match eth_enable { + EthWithTokensActivationResult::HD(hd) => hd, + _ => panic!("Expected EthWithTokensActivationResult::HD"), + }; + let balance = match activation_result.wallet_balance { + EnableCoinBalanceMap::HD(hd) => hd, + _ => panic!("Expected EnableCoinBalance::HD"), + }; + let account = balance.accounts.get(0).expect("Expected account at index 0"); + assert_eq!( + account.addresses[1].address, + "0xDe841899aB4A22E23dB21634e54920aDec402397" + ); + assert_eq!(account.addresses[0].balance.len(), 2); + assert!(account.addresses[0].balance.contains_key("ETH")); + assert!(account.addresses[0].balance.contains_key("ERC20DEV")); + + let get_new_address = block_on(get_new_address(&mm_hd, "ETH", 0, Some(Bip44Chain::External))); + assert!(get_new_address.new_address.balance.contains_key("ETH")); + // Make sure balance is returned for any token enabled with ETH as platform coin + assert!(get_new_address.new_address.balance.contains_key("ERC20DEV")); + assert_eq!( + get_new_address.new_address.address, + "0x4249E165a68E4FF9C41B1C3C3b4245c30ecB43CC" + ); + // Make sure that the address is also added to tokens + let account_balance = block_on(account_balance(&mm_hd, "ERC20DEV", 0, Bip44Chain::External)); + assert_eq!( + account_balance.addresses[2].address, + "0x4249E165a68E4FF9C41B1C3C3b4245c30ecB43CC" + ); + + block_on(mm_hd.stop()).unwrap(); + + // Enable HD account 77, change address 0, index 7 + let path_to_address = HDAccountAddressId { + account_id: 77, + chain: Bip44Chain::External, + address_id: 7, + }; + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + let eth_enable = block_on(enable_eth_with_tokens_v2( + &mm_hd, + "ETH", + &["ERC20DEV"], + &swap_contract, + &[GETH_RPC_URL], + 60, + Some(path_to_address), + )); + let activation_result = match eth_enable { + EthWithTokensActivationResult::HD(hd) => hd, + _ => panic!("Expected EthWithTokensActivationResult::HD"), + }; + let balance = match activation_result.wallet_balance { + EnableCoinBalanceMap::HD(hd) => hd, + _ => panic!("Expected EnableCoinBalance::HD"), + }; + let account = balance.accounts.get(0).expect("Expected account at index 0"); + assert_eq!( + account.addresses[7].address, + "0xa420a4DBd8C50e6240014Db4587d2ec8D0cE0e6B" + ); + assert_eq!(account.addresses[0].balance.len(), 2); + assert!(account.addresses[0].balance.contains_key("ETH")); + assert!(account.addresses[0].balance.contains_key("ERC20DEV")); + + block_on(mm_hd.stop()).unwrap(); +} + +#[test] +fn test_enable_custom_erc20() { + const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; + + let coins = json!([eth_dev_conf()]); + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + + let path_to_address = HDAccountAddressId::default(); + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + // Enable platform coin in HD mode + block_on(enable_eth_with_tokens_v2( + &mm_hd, + "ETH", + &[], + &swap_contract, + &[GETH_RPC_URL], + 60, + Some(path_to_address.clone()), + )); + + // Test `get_token_info` rpc, we also use it to get the token symbol to use it as the ticker + let protocol = erc20_dev_conf(&erc20_contract_checksum())["protocol"].clone(); + let TokenInfo::ERC20(custom_token_info) = block_on(get_token_info(&mm_hd, protocol.clone())).info; + let ticker = custom_token_info.symbol; + assert_eq!(ticker, "QTC"); + assert_eq!(custom_token_info.decimals, 8); + + // Enable the custom token in HD mode + block_on(enable_erc20_token_v2( + &mm_hd, + &ticker, + Some(protocol.clone()), + 60, + Some(path_to_address.clone()), + )) + .unwrap(); + + // Test that the custom token is wallet only by using it in a swap + let buy = block_on(mm_hd.rpc(&json!({ + "userpass": mm_hd.userpass, + "method": "buy", + "base": "ETH", + "rel": ticker, + "price": "1", + "volume": "1", + }))) + .unwrap(); + assert!(!buy.0.is_success(), "buy success, but should fail: {}", buy.1); + assert!( + buy.1.contains(&format!("Rel coin {} is wallet only", ticker)), + "Expected error message indicating that the token is wallet only, but got: {}", + buy.1 + ); + + // Enabling the same custom token using a different ticker should fail + let err = block_on(enable_erc20_token_v2( + &mm_hd, + "ERC20DEV", + Some(protocol.clone()), + 60, + Some(path_to_address), + )) + .unwrap_err(); + let expected_error_type = "CustomTokenError"; + assert_eq!(err["error_type"], expected_error_type); + let expected_error_data = json!({ + "TokenWithSameContractAlreadyActivated": { + "ticker": ticker, + "contract_address": protocol["protocol_data"]["contract_address"] + } + }); + assert_eq!(err["error_data"], expected_error_data); + + // Disable the custom token + block_on(disable_coin(&mm_hd, &ticker, true)); +} + +#[test] +fn test_enable_custom_erc20_with_duplicate_contract_in_config() { + const PASSPHRASE: &str = "tank abandon bind salon remove wisdom net size aspect direct source fossil"; + + let erc20_dev_conf = erc20_dev_conf(&erc20_contract_checksum()); + let coins = json!([eth_dev_conf(), erc20_dev_conf]); + let swap_contract = format!("0x{}", hex::encode(swap_contract())); + + let path_to_address = HDAccountAddressId::default(); + let conf = Mm2TestConf::seednode_with_hd_account(PASSPHRASE, &coins); + let mm_hd = MarketMakerIt::start(conf.conf, conf.rpc_password, None).unwrap(); + let (_mm_dump_log, _mm_dump_dashboard) = mm_hd.mm_dump(); + log!("Alice log path: {}", mm_hd.log_path.display()); + + // Enable platform coin in HD mode + block_on(enable_eth_with_tokens_v2( + &mm_hd, + "ETH", + &[], + &swap_contract, + &[GETH_RPC_URL], + 60, + Some(path_to_address.clone()), + )); + + let protocol = erc20_dev_conf["protocol"].clone(); + // Enable the custom token in HD mode. + // Since the contract is already in the coins config, this should fail with an error + // that specifies the ticker in config so that the user can enable the right coin. + let err = block_on(enable_erc20_token_v2( + &mm_hd, + "QTC", + Some(protocol.clone()), + 60, + Some(path_to_address.clone()), + )) + .unwrap_err(); + let expected_error_type = "CustomTokenError"; + assert_eq!(err["error_type"], expected_error_type); + let expected_error_data = json!({ + "DuplicateContractInConfig": { + "ticker_in_config": "ERC20DEV" + } + }); + assert_eq!(err["error_data"], expected_error_data); + + // Another way is to use the `get_token_info` RPC and use the config ticker to enable the token. + let custom_token_info = block_on(get_token_info(&mm_hd, protocol)); + assert!(custom_token_info.config_ticker.is_some()); + let config_ticker = custom_token_info.config_ticker.unwrap(); + assert_eq!(config_ticker, "ERC20DEV"); + // Parameters passed here are for normal enabling of a coin in config and not for a custom token + block_on(enable_erc20_token_v2( + &mm_hd, + &config_ticker, + None, + 60, + Some(path_to_address), + )) + .unwrap(); + + // Disable the custom token, this to check that it was enabled correctly + block_on(disable_coin(&mm_hd, &config_ticker, true)); +} diff --git a/mm2src/mm2_main/tests/docker_tests/mod.rs b/mm2src/mm2_main/tests/docker_tests/mod.rs index 2adfe3b2c9..4b969b9fec 100644 --- a/mm2src/mm2_main/tests/docker_tests/mod.rs +++ b/mm2src/mm2_main/tests/docker_tests/mod.rs @@ -4,8 +4,8 @@ mod docker_ordermatch_tests; mod docker_tests_inner; mod eth_docker_tests; pub mod qrc20_tests; +#[cfg(feature = "enable-sia")] mod sia_docker_tests; mod slp_tests; -#[cfg(feature = "enable-solana")] mod solana_tests; mod swap_proto_v2_tests; mod swap_watcher_tests; mod swaps_confs_settings_sync_tests; diff --git a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs index fbf7df14d2..cfbd2df664 100644 --- a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs @@ -11,13 +11,12 @@ use coins::{CheckIfMyPaymentSentArgs, ConfirmPaymentInput, DexFee, FeeApproxStag SwapTxTypeWithSecretHash, TradePreimageValue, TransactionEnum, ValidateFeeArgs, ValidatePaymentInput, WaitForHTLCTxSpendArgs}; use common::log::debug; -use common::{temp_dir, DEX_FEE_ADDR_RAW_PUBKEY}; +use common::{block_on_f01, temp_dir, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::Secp256k1Secret; use ethereum_types::H160; -use futures01::Future; use http::StatusCode; use mm2_core::mm_ctx::{MmArc, MmCtxBuilder}; -use mm2_main::mm2::lp_swap::{dex_fee_amount, max_taker_vol_from_available}; +use mm2_main::lp_swap::{dex_fee_amount, max_taker_vol_from_available}; use mm2_number::BigDecimal; use mm2_rpc::data::legacy::{CoinInitResponse, OrderbookResponse}; use mm2_test_helpers::structs::{trade_preimage_error, RpcErrorResponse, RpcSuccessResponse, TransactionDetails}; @@ -78,9 +77,7 @@ impl QtumDockerOps { match self.coin.as_ref().rpc_client { UtxoRpcClientEnum::Native(ref native) => { - let result = native - .create_contract(&bytecode.into(), gas_limit, gas_price, sender) - .wait() + let result = block_on_f01(native.create_contract(&bytecode.into(), gas_limit, gas_price, sender)) .expect("!createcontract"); result.address.0.into() }, @@ -165,14 +162,8 @@ fn withdraw_and_send(mm: &MarketMakerIt, coin: &str, to: &str, amount: f64) { fn test_taker_spends_maker_payment() { let (_ctx, maker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); let (_ctx, taker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 1.into()); - let maker_old_balance = maker_coin - .my_spendable_balance() - .wait() - .expect("Error on get maker balance"); - let taker_old_balance = taker_coin - .my_spendable_balance() - .wait() - .expect("Error on get taker balance"); + let maker_old_balance = block_on_f01(maker_coin.my_spendable_balance()).expect("Error on get maker balance"); + let taker_old_balance = block_on_f01(taker_coin.my_spendable_balance()).expect("Error on get taker balance"); assert_eq!(maker_old_balance, BigDecimal::from(10)); assert_eq!(taker_old_balance, BigDecimal::from(1)); @@ -194,7 +185,7 @@ fn test_taker_spends_maker_payment() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); + let payment = block_on(maker_coin.send_maker_payment(maker_payment_args)).unwrap(); let payment_tx_hash = payment.tx_hash_as_bytes(); let payment_tx_hex = payment.tx_hex(); log!("Maker payment: {:?}", payment_tx_hash); @@ -210,7 +201,7 @@ fn test_taker_spends_maker_payment() { wait_until, check_every, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let input = ValidatePaymentInput { payment_tx: payment_tx_hex.clone(), @@ -249,16 +240,10 @@ fn test_taker_spends_maker_payment() { wait_until, check_every, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); - - let maker_balance = maker_coin - .my_spendable_balance() - .wait() - .expect("Error on get maker balance"); - let taker_balance = taker_coin - .my_spendable_balance() - .wait() - .expect("Error on get taker balance"); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); + + let maker_balance = block_on_f01(maker_coin.my_spendable_balance()).expect("Error on get maker balance"); + let taker_balance = block_on_f01(taker_coin.my_spendable_balance()).expect("Error on get taker balance"); assert_eq!(maker_old_balance - amount.clone(), maker_balance); assert_eq!(taker_old_balance + amount, taker_balance); } @@ -267,14 +252,8 @@ fn test_taker_spends_maker_payment() { fn test_maker_spends_taker_payment() { let (_ctx, maker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); let (_ctx, taker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); - let maker_old_balance = maker_coin - .my_spendable_balance() - .wait() - .expect("Error on get maker balance"); - let taker_old_balance = taker_coin - .my_spendable_balance() - .wait() - .expect("Error on get taker balance"); + let maker_old_balance = block_on_f01(maker_coin.my_spendable_balance()).expect("Error on get maker balance"); + let taker_old_balance = block_on_f01(taker_coin.my_spendable_balance()).expect("Error on get taker balance"); assert_eq!(maker_old_balance, BigDecimal::from(10)); assert_eq!(taker_old_balance, BigDecimal::from(10)); @@ -296,7 +275,7 @@ fn test_maker_spends_taker_payment() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let payment = taker_coin.send_taker_payment(taker_payment_args).wait().unwrap(); + let payment = block_on(taker_coin.send_taker_payment(taker_payment_args)).unwrap(); let payment_tx_hash = payment.tx_hash_as_bytes(); let payment_tx_hex = payment.tx_hex(); log!("Taker payment: {:?}", payment_tx_hash); @@ -312,7 +291,7 @@ fn test_maker_spends_taker_payment() { wait_until, check_every, }; - maker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(maker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let input = ValidatePaymentInput { payment_tx: payment_tx_hex.clone(), @@ -351,16 +330,10 @@ fn test_maker_spends_taker_payment() { wait_until, check_every, }; - maker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); - - let maker_balance = maker_coin - .my_spendable_balance() - .wait() - .expect("Error on get maker balance"); - let taker_balance = taker_coin - .my_spendable_balance() - .wait() - .expect("Error on get taker balance"); + block_on_f01(maker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); + + let maker_balance = block_on_f01(maker_coin.my_spendable_balance()).expect("Error on get maker balance"); + let taker_balance = block_on_f01(taker_coin.my_spendable_balance()).expect("Error on get taker balance"); assert_eq!(maker_old_balance + amount.clone(), maker_balance); assert_eq!(taker_old_balance - amount, taker_balance); } @@ -368,7 +341,7 @@ fn test_maker_spends_taker_payment() { #[test] fn test_maker_refunds_payment() { let (_ctx, coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); - let expected_balance = coin.my_spendable_balance().wait().unwrap(); + let expected_balance = block_on_f01(coin.my_spendable_balance()).unwrap(); assert_eq!(expected_balance, BigDecimal::from(10)); let timelock = now_sec() - 200; @@ -387,7 +360,7 @@ fn test_maker_refunds_payment() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let payment = coin.send_maker_payment(maker_payment).wait().unwrap(); + let payment = block_on(coin.send_maker_payment(maker_payment)).unwrap(); let payment_tx_hash = payment.tx_hash_as_bytes(); let payment_tx_hex = payment.tx_hex(); log!("Maker payment: {:?}", payment_tx_hash); @@ -403,9 +376,9 @@ fn test_maker_refunds_payment() { wait_until, check_every, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let balance_after_payment = coin.my_spendable_balance().wait().unwrap(); + let balance_after_payment = block_on_f01(coin.my_spendable_balance()).unwrap(); assert_eq!(expected_balance.clone() - amount, balance_after_payment); let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &payment_tx_hex, @@ -431,16 +404,16 @@ fn test_maker_refunds_payment() { wait_until, check_every, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let balance_after_refund = coin.my_spendable_balance().wait().unwrap(); + let balance_after_refund = block_on_f01(coin.my_spendable_balance()).unwrap(); assert_eq!(expected_balance, balance_after_refund); } #[test] fn test_taker_refunds_payment() { let (_ctx, coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); - let expected_balance = coin.my_spendable_balance().wait().unwrap(); + let expected_balance = block_on_f01(coin.my_spendable_balance()).unwrap(); assert_eq!(expected_balance, BigDecimal::from(10)); let timelock = now_sec() - 200; @@ -459,7 +432,7 @@ fn test_taker_refunds_payment() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let payment = coin.send_taker_payment(taker_payment_args).wait().unwrap(); + let payment = block_on(coin.send_taker_payment(taker_payment_args)).unwrap(); let payment_tx_hash = payment.tx_hash_as_bytes(); let payment_tx_hex = payment.tx_hex(); log!("Taker payment: {:?}", payment_tx_hash); @@ -475,9 +448,9 @@ fn test_taker_refunds_payment() { wait_until, check_every, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let balance_after_payment = coin.my_spendable_balance().wait().unwrap(); + let balance_after_payment = block_on_f01(coin.my_spendable_balance()).unwrap(); assert_eq!(expected_balance.clone() - amount, balance_after_payment); let taker_refunds_payment_args = RefundPaymentArgs { payment_tx: &payment_tx_hex, @@ -503,9 +476,9 @@ fn test_taker_refunds_payment() { wait_until, check_every, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let balance_after_refund = coin.my_spendable_balance().wait().unwrap(); + let balance_after_refund = block_on_f01(coin.my_spendable_balance()).unwrap(); assert_eq!(expected_balance, balance_after_refund); } @@ -528,7 +501,7 @@ fn test_check_if_my_payment_sent() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let payment = coin.send_maker_payment(maker_payment_args).wait().unwrap(); + let payment = block_on(coin.send_maker_payment(maker_payment_args)).unwrap(); let payment_tx_hash = payment.tx_hash_as_bytes(); let payment_tx_hex = payment.tx_hex(); log!("Maker payment: {:?}", payment_tx_hash); @@ -544,9 +517,9 @@ fn test_check_if_my_payment_sent() { wait_until, check_every, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let search_from_block = coin.current_block().wait().expect("!current_block") - 10; + let search_from_block = block_on_f01(coin.current_block()).expect("!current_block") - 10; let if_my_payment_sent_args = CheckIfMyPaymentSentArgs { time_lock: timelock, other_pub: &taker_pub, @@ -557,7 +530,7 @@ fn test_check_if_my_payment_sent() { amount: &amount, payment_instructions: &None, }; - let found = coin.check_if_my_payment_sent(if_my_payment_sent_args).wait().unwrap(); + let found = block_on(coin.check_if_my_payment_sent(if_my_payment_sent_args)).unwrap(); assert_eq!(found, Some(payment)); } @@ -565,7 +538,7 @@ fn test_check_if_my_payment_sent() { fn test_search_for_swap_tx_spend_taker_spent() { let (_ctx, maker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); let (_ctx, taker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 1.into()); - let search_from_block = maker_coin.current_block().wait().expect("!current_block"); + let search_from_block = block_on_f01(maker_coin.current_block()).expect("!current_block"); let timelock = now_sec() - 200; let maker_pub = maker_coin.my_public_key().unwrap(); @@ -585,7 +558,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); + let payment = block_on(maker_coin.send_maker_payment(maker_payment_args)).unwrap(); let payment_tx_hash = payment.tx_hash_as_bytes(); let payment_tx_hex = payment.tx_hex(); log!("Maker payment: {:?}", payment_tx_hash); @@ -601,7 +574,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { wait_until, check_every, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let taker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &payment_tx_hex, time_lock: timelock, @@ -625,7 +598,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { wait_until, check_every, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock: timelock, @@ -645,7 +618,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { #[test] fn test_search_for_swap_tx_spend_maker_refunded() { let (_ctx, maker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); - let search_from_block = maker_coin.current_block().wait().expect("!current_block"); + let search_from_block = block_on_f01(maker_coin.current_block()).expect("!current_block"); let timelock = now_sec() - 200; let taker_pub = hex::decode("022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1a").unwrap(); @@ -664,7 +637,7 @@ fn test_search_for_swap_tx_spend_maker_refunded() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); + let payment = block_on(maker_coin.send_maker_payment(maker_payment_args)).unwrap(); let payment_tx_hash = payment.tx_hash_as_bytes(); let payment_tx_hex = payment.tx_hex(); log!("Maker payment: {:?}", payment_tx_hash); @@ -680,7 +653,7 @@ fn test_search_for_swap_tx_spend_maker_refunded() { wait_until, check_every, }; - maker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(maker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &payment_tx_hex, time_lock: timelock, @@ -705,7 +678,7 @@ fn test_search_for_swap_tx_spend_maker_refunded() { wait_until, check_every, }; - maker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(maker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock: timelock, @@ -725,7 +698,7 @@ fn test_search_for_swap_tx_spend_maker_refunded() { #[test] fn test_search_for_swap_tx_spend_not_spent() { let (_ctx, maker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); - let search_from_block = maker_coin.current_block().wait().expect("!current_block"); + let search_from_block = block_on_f01(maker_coin.current_block()).expect("!current_block"); let timelock = now_sec() - 200; let taker_pub = hex::decode("022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1a").unwrap(); @@ -744,7 +717,7 @@ fn test_search_for_swap_tx_spend_not_spent() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); + let payment = block_on(maker_coin.send_maker_payment(maker_payment_args)).unwrap(); let payment_tx_hash = payment.tx_hash_as_bytes(); let payment_tx_hex = payment.tx_hex(); log!("Maker payment: {:?}", payment_tx_hash); @@ -760,7 +733,7 @@ fn test_search_for_swap_tx_spend_not_spent() { wait_until, check_every, }; - maker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(maker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock: timelock, @@ -781,7 +754,7 @@ fn test_search_for_swap_tx_spend_not_spent() { fn test_wait_for_tx_spend() { let (_ctx, maker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 10.into()); let (_ctx, taker_coin, _priv_key) = generate_qrc20_coin_with_random_privkey("QICK", 20.into(), 1.into()); - let from_block = maker_coin.current_block().wait().expect("!current_block"); + let from_block = block_on_f01(maker_coin.current_block()).expect("!current_block"); let timelock = now_sec() - 200; let maker_pub = maker_coin.my_public_key().unwrap(); @@ -801,7 +774,7 @@ fn test_wait_for_tx_spend() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); + let payment = block_on(maker_coin.send_maker_payment(maker_payment_args)).unwrap(); let payment_tx_hash = payment.tx_hash_as_bytes(); let payment_tx_hex = payment.tx_hex(); log!("Maker payment: {:?}", payment_tx_hash); @@ -817,22 +790,20 @@ fn test_wait_for_tx_spend() { wait_until, check_every, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); // first try to check if the wait_for_htlc_tx_spend() returns an error correctly let wait_until = wait_until_sec(5); - let tx_err = maker_coin - .wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { - tx_bytes: &payment_tx_hex, - secret_hash: &[], - wait_until, - from_block, - swap_contract_address: &maker_coin.swap_contract_address(), - check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, - watcher_reward: false, - }) - .wait() - .expect_err("Expected 'Waited too long' error"); + let tx_err = block_on(maker_coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { + tx_bytes: &payment_tx_hex, + secret_hash: &[], + wait_until, + from_block, + swap_contract_address: &maker_coin.swap_contract_address(), + check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, + watcher_reward: false, + })) + .expect_err("Expected 'Waited too long' error"); let err = tx_err.get_plain_text_format(); log!("error: {:?}", err); @@ -860,18 +831,16 @@ fn test_wait_for_tx_spend() { }); let wait_until = wait_until_sec(120); - let found = maker_coin - .wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { - tx_bytes: &payment_tx_hex, - secret_hash: &[], - wait_until, - from_block, - swap_contract_address: &maker_coin.swap_contract_address(), - check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, - watcher_reward: false, - }) - .wait() - .unwrap(); + let found = block_on(maker_coin.wait_for_htlc_tx_spend(WaitForHTLCTxSpendArgs { + tx_bytes: &payment_tx_hex, + secret_hash: &[], + wait_until, + from_block, + swap_contract_address: &maker_coin.swap_contract_address(), + check_every: TAKER_PAYMENT_SPEND_SEARCH_INTERVAL, + watcher_reward: false, + })) + .unwrap(); unsafe { assert_eq!(Some(found), SPEND_TX) } } @@ -1030,7 +999,7 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ log!("{:?}", block_on(enable_native(&mm, "MYCOIN", &[], None))); log!("{:?}", block_on(enable_native(&mm, "QTUM", &[], None))); - let qtum_balance = coin.my_spendable_balance().wait().expect("!my_balance"); + let qtum_balance = block_on_f01(coin.my_spendable_balance()).expect("!my_balance"); let qtum_min_tx_amount = MmNumber::from("0.000728"); // - `max_possible = balance - locked_amount`, where `locked_amount = 0` @@ -1103,10 +1072,8 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ let secret_hash = &[0; 20]; let dex_fee = dex_fee_amount("QTUM", "MYCOIN", &expected_max_taker_vol, &qtum_min_tx_amount); - let _taker_fee_tx = coin - .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, dex_fee, &[], timelock) - .wait() - .expect("!send_taker_fee"); + let _taker_fee_tx = + block_on(coin.send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, dex_fee, &[], timelock)).expect("!send_taker_fee"); let taker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, @@ -1120,12 +1087,9 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ wait_for_confirmation_until: 0, }; - let _taker_payment_tx = coin - .send_taker_payment(taker_payment_args) - .wait() - .expect("!send_taker_payment"); + let _taker_payment_tx = block_on(coin.send_taker_payment(taker_payment_args)).expect("!send_taker_payment"); - let my_balance = coin.my_spendable_balance().wait().expect("!my_balance"); + let my_balance = block_on_f01(coin.my_spendable_balance()).expect("!my_balance"); assert_eq!( my_balance, BigDecimal::from(0u32), @@ -1519,7 +1483,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_maker() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let tx = coin.send_maker_payment(maker_payment).wait().unwrap(); + let tx = block_on(coin.send_maker_payment(maker_payment)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: tx.tx_hex(), @@ -1528,7 +1492,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_maker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), time_lock, @@ -1549,7 +1513,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_maker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -1587,7 +1551,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_taker() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let tx = coin.send_taker_payment(taker_payment).wait().unwrap(); + let tx = block_on(coin.send_taker_payment(taker_payment)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: tx.tx_hex(), @@ -1596,7 +1560,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_taker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), time_lock, @@ -1617,7 +1581,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_taker() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, @@ -1741,26 +1705,23 @@ fn test_send_taker_fee_qtum() { generate_segwit_qtum_coin_with_random_privkey("QTUM", BigDecimal::try_from(0.5).unwrap(), Some(0)); let amount = BigDecimal::from_str("0.01").unwrap(); - let tx = coin - .send_taker_fee( - &DEX_FEE_ADDR_RAW_PUBKEY, - DexFee::Standard(amount.clone().into()), - &[], - 0, - ) - .wait() - .expect("!send_taker_fee"); + let tx = block_on(coin.send_taker_fee( + &DEX_FEE_ADDR_RAW_PUBKEY, + DexFee::Standard(amount.clone().into()), + &[], + 0, + )) + .expect("!send_taker_fee"); assert!(matches!(tx, TransactionEnum::UtxoTx(_)), "Expected UtxoTx"); - coin.validate_fee(ValidateFeeArgs { + block_on(coin.validate_fee(ValidateFeeArgs { fee_tx: &tx, expected_sender: coin.my_public_key().unwrap(), fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, dex_fee: &DexFee::Standard(amount.into()), min_block_number: 0, uuid: &[], - }) - .wait() + })) .expect("!validate_fee"); } @@ -1773,25 +1734,22 @@ fn test_send_taker_fee_qrc20() { ); let amount = BigDecimal::from_str("0.01").unwrap(); - let tx = coin - .send_taker_fee( - &DEX_FEE_ADDR_RAW_PUBKEY, - DexFee::Standard(amount.clone().into()), - &[], - 0, - ) - .wait() - .expect("!send_taker_fee"); + let tx = block_on(coin.send_taker_fee( + &DEX_FEE_ADDR_RAW_PUBKEY, + DexFee::Standard(amount.clone().into()), + &[], + 0, + )) + .expect("!send_taker_fee"); assert!(matches!(tx, TransactionEnum::UtxoTx(_)), "Expected UtxoTx"); - coin.validate_fee(ValidateFeeArgs { + block_on(coin.validate_fee(ValidateFeeArgs { fee_tx: &tx, expected_sender: coin.my_public_key().unwrap(), fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, dex_fee: &DexFee::Standard(amount.into()), min_block_number: 0, uuid: &[], - }) - .wait() + })) .expect("!validate_fee"); } diff --git a/mm2src/mm2_main/tests/docker_tests/sia_docker_tests.rs b/mm2src/mm2_main/tests/docker_tests/sia_docker_tests.rs new file mode 100644 index 0000000000..b8546aa218 --- /dev/null +++ b/mm2src/mm2_main/tests/docker_tests/sia_docker_tests.rs @@ -0,0 +1,118 @@ +use common::block_on; +use sia_rust::http_client::{SiaApiClient, SiaApiClientError, SiaHttpConf}; +use sia_rust::http_endpoints::{AddressBalanceRequest, AddressUtxosRequest, ConsensusTipRequest, TxpoolBroadcastRequest}; +use sia_rust::spend_policy::SpendPolicy; +use sia_rust::transaction::{SiacoinOutput, V2TransactionBuilder}; +use sia_rust::types::{Address, Currency}; +use sia_rust::{Keypair, PublicKey, SecretKey}; +use std::process::Command; +use std::str::FromStr; +use url::Url; + +#[cfg(test)] +fn mine_blocks(n: u64, addr: &Address) { + Command::new("docker") + .arg("exec") + .arg("sia-docker") + .arg("walletd") + .arg("mine") + .arg(format!("-addr={}", addr)) + .arg(format!("-n={}", n)) + .status() + .expect("Failed to execute docker command"); +} + +#[test] +fn test_sia_new_client() { + let conf = SiaHttpConf { + url: Url::parse("http://localhost:9980/").unwrap(), + password: "password".to_string(), + }; + let _api_client = block_on(SiaApiClient::new(conf)).unwrap(); +} + +#[test] +fn test_sia_client_bad_auth() { + let conf = SiaHttpConf { + url: Url::parse("http://localhost:9980/").unwrap(), + password: "foo".to_string(), + }; + let result = block_on(SiaApiClient::new(conf)); + assert!(matches!(result, Err(SiaApiClientError::UnexpectedHttpStatus(401)))); +} + +#[test] +fn test_sia_client_consensus_tip() { + let conf = SiaHttpConf { + url: Url::parse("http://localhost:9980/").unwrap(), + password: "password".to_string(), + }; + let api_client = block_on(SiaApiClient::new(conf)).unwrap(); + let _response = block_on(api_client.dispatcher(ConsensusTipRequest)).unwrap(); +} + +// This test likely needs to be removed because mine_blocks has possibility of interfering with other async tests +// related to block height +#[test] +fn test_sia_client_address_balance() { + let conf = SiaHttpConf { + url: Url::parse("http://localhost:9980/").unwrap(), + password: "password".to_string(), + }; + let api_client = block_on(SiaApiClient::new(conf)).unwrap(); + + let address = + Address::from_str("addr:591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e411f").unwrap(); + mine_blocks(10, &address); + + let request = AddressBalanceRequest { address }; + let response = block_on(api_client.dispatcher(request)).unwrap(); + + let expected = Currency::new(12919594847110692864, 54210108624275221); + assert_eq!(response.siacoins, expected); + assert_eq!(expected.to_u128(), 1000000000000000000000000000000000000); +} + +#[test] +fn test_sia_client_build_tx() { + let conf = SiaHttpConf { + url: Url::parse("http://localhost:9980/").unwrap(), + password: "password".to_string(), + }; + let api_client = block_on(SiaApiClient::new(conf)).unwrap(); + let sk: SecretKey = SecretKey::from_bytes( + &hex::decode("0100000000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let pk: PublicKey = (&sk).into(); + let keypair = Keypair { public: pk, secret: sk }; + let spend_policy = SpendPolicy::PublicKey(pk); + + let address = spend_policy.address(); + + mine_blocks(201, &address); + + let utxos = block_on(api_client.dispatcher(AddressUtxosRequest { + address: address.clone(), + })) + .unwrap(); + let spend_this = utxos[0].clone(); + let vin = spend_this.clone(); + println!("utxo[0]: {:?}", spend_this); + let vout = SiacoinOutput { + value: spend_this.siacoin_output.value, + address, + }; + let tx = V2TransactionBuilder::new(0u64.into()) + .add_siacoin_input(vin, spend_policy) + .add_siacoin_output(vout) + .sign_simple(vec![&keypair]) + .unwrap() + .build(); + + let req = TxpoolBroadcastRequest { + transactions: vec![], + v2transactions: vec![tx], + }; + let _response = block_on(api_client.dispatcher(req)).unwrap(); +} diff --git a/mm2src/mm2_main/tests/docker_tests/solana_tests.rs b/mm2src/mm2_main/tests/docker_tests/solana_tests.rs deleted file mode 100644 index d1faa5d2a7..0000000000 --- a/mm2src/mm2_main/tests/docker_tests/solana_tests.rs +++ /dev/null @@ -1,144 +0,0 @@ -use crate::docker_tests::docker_tests_common::*; -use mm2_number::bigdecimal::Zero; -use mm2_test_helpers::for_tests::{disable_coin, enable_solana_with_tokens, enable_spl, sign_message, verify_message}; -use mm2_test_helpers::structs::{EnableSolanaWithTokensResponse, EnableSplResponse, RpcV2Response, SignatureResponse, - VerificationResponse}; -use serde_json as json; - -#[test] -fn test_solana_and_spl_balance_enable_spl_v2() { - let mm = _solana_supplied_node(); - let tx_history = false; - let enable_solana_with_tokens = block_on(enable_solana_with_tokens( - &mm, - "SOL-DEVNET", - &["USDC-SOL-DEVNET"], - "https://api.devnet.solana.com", - tx_history, - )); - let enable_solana_with_tokens: RpcV2Response = - json::from_value(enable_solana_with_tokens).unwrap(); - - let (_, solana_balance) = enable_solana_with_tokens - .result - .solana_addresses_infos - .into_iter() - .next() - .unwrap(); - assert!(solana_balance.balances.unwrap().spendable > 0.into()); - - let spl_balances = enable_solana_with_tokens - .result - .spl_addresses_infos - .into_iter() - .next() - .unwrap() - .1 - .balances - .unwrap(); - let usdc_spl = spl_balances.get("USDC-SOL-DEVNET").unwrap(); - assert!(usdc_spl.spendable.is_zero()); - - let enable_spl = block_on(enable_spl(&mm, "ADEX-SOL-DEVNET")); - let enable_spl: RpcV2Response = json::from_value(enable_spl).unwrap(); - assert_eq!(1, enable_spl.result.balances.len()); - - let (_, balance) = enable_spl.result.balances.into_iter().next().unwrap(); - assert!(balance.spendable > 0.into()); -} - -#[test] -fn test_sign_verify_message_solana() { - let mm = _solana_supplied_node(); - let tx_history = false; - block_on(enable_solana_with_tokens( - &mm, - "SOL-DEVNET", - &["USDC-SOL-DEVNET"], - "https://api.devnet.solana.com", - tx_history, - )); - - let response = block_on(sign_message(&mm, "SOL-DEVNET")); - let response: RpcV2Response = json::from_value(response).unwrap(); - let response = response.result; - - assert_eq!( - response.signature, - "3AoWCXHq3ACYHYEHUsCzPmRNiXn5c6kodXn9KDd1tz52e1da3dZKYXD5nrJW31XLtN6zzJiwHWtDta52w7Cd7qyE" - ); - - let response = block_on(verify_message( - &mm, - "SOL-DEVNET", - "3AoWCXHq3ACYHYEHUsCzPmRNiXn5c6kodXn9KDd1tz52e1da3dZKYXD5nrJW31XLtN6zzJiwHWtDta52w7Cd7qyE", - "FJktmyjV9aBHEShT4hfnLpr9ELywdwVtEL1w1rSWgbVf", - )); - let response: RpcV2Response = json::from_value(response).unwrap(); - let response = response.result; - - assert!(response.is_valid); -} - -#[test] -fn test_sign_verify_message_spl() { - let mm = _solana_supplied_node(); - let tx_history = false; - block_on(enable_solana_with_tokens( - &mm, - "SOL-DEVNET", - &["USDC-SOL-DEVNET"], - "https://api.devnet.solana.com", - tx_history, - )); - - block_on(enable_spl(&mm, "ADEX-SOL-DEVNET")); - - let response = block_on(sign_message(&mm, "ADEX-SOL-DEVNET")); - let response: RpcV2Response = json::from_value(response).unwrap(); - let response = response.result; - - assert_eq!( - response.signature, - "3AoWCXHq3ACYHYEHUsCzPmRNiXn5c6kodXn9KDd1tz52e1da3dZKYXD5nrJW31XLtN6zzJiwHWtDta52w7Cd7qyE" - ); - - let response = block_on(verify_message( - &mm, - "ADEX-SOL-DEVNET", - "3AoWCXHq3ACYHYEHUsCzPmRNiXn5c6kodXn9KDd1tz52e1da3dZKYXD5nrJW31XLtN6zzJiwHWtDta52w7Cd7qyE", - "FJktmyjV9aBHEShT4hfnLpr9ELywdwVtEL1w1rSWgbVf", - )); - let response: RpcV2Response = json::from_value(response).unwrap(); - let response = response.result; - - assert!(response.is_valid); -} - -#[test] -fn test_disable_solana_platform_coin_with_tokens() { - let mm = _solana_supplied_node(); - block_on(enable_solana_with_tokens( - &mm, - "SOL-DEVNET", - &["USDC-SOL-DEVNET"], - "https://api.devnet.solana.com", - false, - )); - block_on(enable_spl(&mm, "ADEX-SOL-DEVNET")); - - // Try to passive platform coin, SOL-DEVNET. - let res = block_on(disable_coin(&mm, "SOL-DEVNET", false)); - assert!(res.passivized); - - // Try to disable ADEX-SOL-DEVNET and USDC-SOL-DEVNET - // This should work, because platform coin is still in the memory. - let res = block_on(disable_coin(&mm, "ADEX-SOL-DEVNET", false)); - assert!(!res.passivized); - let res = block_on(disable_coin(&mm, "USDC-SOL-DEVNET", false)); - assert!(!res.passivized); - - // Then try to force disable SOL-DEVNET platform coin. - let res = block_on(disable_coin(&mm, "SOL-DEVNET", true)); - assert!(!res.passivized); -} diff --git a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs index a090f64a50..304f6f4819 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs @@ -2,11 +2,11 @@ use crate::{generate_utxo_coin_with_random_privkey, MYCOIN, MYCOIN1}; use bitcrypto::dhash160; use coins::utxo::UtxoCommonOps; use coins::{ConfirmPaymentInput, DexFee, FundingTxSpend, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, - MakerCoinSwapOpsV2, MarketCoinOps, ParseCoinAssocTypes, RefundFundingSecretArgs, RefundMakerPaymentArgs, - RefundPaymentArgs, SendMakerPaymentArgs, SendTakerFundingArgs, SwapTxTypeWithSecretHash, - TakerCoinSwapOpsV2, Transaction, ValidateMakerPaymentArgs, ValidateTakerFundingArgs}; -use common::{block_on, now_sec, DEX_FEE_ADDR_RAW_PUBKEY}; -use futures01::Future; + MakerCoinSwapOpsV2, MarketCoinOps, ParseCoinAssocTypes, RefundFundingSecretArgs, + RefundMakerPaymentSecretArgs, RefundMakerPaymentTimelockArgs, RefundTakerPaymentArgs, + SendMakerPaymentArgs, SendTakerFundingArgs, SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, Transaction, + ValidateMakerPaymentArgs, ValidateTakerFundingArgs}; +use common::{block_on, block_on_f01, now_sec, DEX_FEE_ADDR_RAW_PUBKEY}; use mm2_number::MmNumber; use mm2_test_helpers::for_tests::{active_swaps, check_recent_swaps, coins_needed_for_kickstart, disable_coin, disable_coin_err, enable_native, get_locked_amount, mm_dump, my_swap_status, @@ -22,14 +22,16 @@ use uuid::Uuid; fn send_and_refund_taker_funding_timelock() { let (_mm_arc, coin, _privkey) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); - let time_lock = now_sec() - 1000; + let funding_time_lock = now_sec() - 1000; let taker_secret_hash = &[0; 20]; let maker_pub = coin.my_public_key().unwrap(); let dex_fee = &DexFee::Standard("0.01".into()); let send_args = SendTakerFundingArgs { - time_lock, + funding_time_lock, + payment_time_lock: 0, taker_secret_hash, + maker_secret_hash: &[0; 20], maker_pub, dex_fee, premium_amount: "0.1".parse().unwrap(), @@ -53,9 +55,11 @@ fn send_and_refund_taker_funding_timelock() { let validate_args = ValidateTakerFundingArgs { funding_tx: &taker_funding_utxo_tx, - time_lock, + payment_time_lock: 0, + funding_time_lock, taker_secret_hash, - other_pub: maker_pub, + maker_secret_hash: &[], + taker_pub: maker_pub, dex_fee, premium_amount: "0.1".parse().unwrap(), trading_amount: 1.into(), @@ -63,16 +67,18 @@ fn send_and_refund_taker_funding_timelock() { }; block_on(coin.validate_taker_funding(validate_args)).unwrap(); - let refund_args = RefundPaymentArgs { + let refund_args = RefundTakerPaymentArgs { payment_tx: &serialize(&taker_funding_utxo_tx).take(), - time_lock, - other_pubkey: coin.my_public_key().unwrap(), + time_lock: funding_time_lock, + maker_pub: coin.my_public_key().unwrap(), tx_type_with_secret_hash: SwapTxTypeWithSecretHash::TakerFunding { taker_secret_hash: &[0; 20], }, swap_unique_data: &[], - swap_contract_address: &None, watcher_reward: false, + dex_fee, + premium_amount: Default::default(), + trading_amount: Default::default(), }; let refund_tx = block_on(coin.refund_taker_funding_timelock(refund_args)).unwrap(); @@ -86,7 +92,7 @@ fn send_and_refund_taker_funding_timelock() { wait_until: now_sec() + 20, check_every: 1, }; - coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_input)).unwrap(); let found_refund_tx = block_on(coin.search_for_taker_funding_spend(&taker_funding_utxo_tx, 1, taker_secret_hash)).unwrap(); @@ -100,7 +106,7 @@ fn send_and_refund_taker_funding_timelock() { fn send_and_refund_taker_funding_secret() { let (_mm_arc, coin, _privkey) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); - let time_lock = now_sec() - 1000; + let funding_time_lock = now_sec() - 1000; let taker_secret = [0; 32]; let taker_secret_hash_owned = dhash160(&taker_secret); let taker_secret_hash = taker_secret_hash_owned.as_slice(); @@ -108,8 +114,10 @@ fn send_and_refund_taker_funding_secret() { let dex_fee = &DexFee::Standard("0.01".into()); let send_args = SendTakerFundingArgs { - time_lock, + funding_time_lock, + payment_time_lock: 0, taker_secret_hash, + maker_secret_hash: &[0; 20], maker_pub, dex_fee, premium_amount: "0.1".parse().unwrap(), @@ -133,9 +141,11 @@ fn send_and_refund_taker_funding_secret() { let validate_args = ValidateTakerFundingArgs { funding_tx: &taker_funding_utxo_tx, - time_lock, + funding_time_lock, + payment_time_lock: 0, taker_secret_hash, - other_pub: maker_pub, + maker_secret_hash: &[], + taker_pub: maker_pub, dex_fee, premium_amount: "0.1".parse().unwrap(), trading_amount: 1.into(), @@ -145,12 +155,16 @@ fn send_and_refund_taker_funding_secret() { let refund_args = RefundFundingSecretArgs { funding_tx: &taker_funding_utxo_tx, - time_lock, + funding_time_lock, + payment_time_lock: 0, maker_pubkey: maker_pub, taker_secret: &taker_secret, taker_secret_hash, + maker_secret_hash: &[], + dex_fee, + premium_amount: "0.1".parse().unwrap(), + trading_amount: 1.into(), swap_unique_data: &[], - swap_contract_address: &None, watcher_reward: false, }; @@ -165,7 +179,7 @@ fn send_and_refund_taker_funding_secret() { wait_until: now_sec() + 20, check_every: 1, }; - coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_input)).unwrap(); let found_refund_tx = block_on(coin.search_for_taker_funding_spend(&taker_funding_utxo_tx, 1, taker_secret_hash)).unwrap(); @@ -192,8 +206,10 @@ fn send_and_spend_taker_funding() { let dex_fee = &DexFee::Standard("0.01".into()); let send_args = SendTakerFundingArgs { - time_lock: funding_time_lock, + funding_time_lock, + payment_time_lock: 0, taker_secret_hash, + maker_secret_hash: &[0; 20], maker_pub, dex_fee, premium_amount: "0.1".parse().unwrap(), @@ -217,9 +233,11 @@ fn send_and_spend_taker_funding() { let validate_args = ValidateTakerFundingArgs { funding_tx: &taker_funding_utxo_tx, - time_lock: funding_time_lock, + payment_time_lock: 0, + funding_time_lock, taker_secret_hash, - other_pub: taker_pub, + maker_secret_hash: &[], + taker_pub, dex_fee, premium_amount: "0.1".parse().unwrap(), trading_amount: 1.into(), @@ -249,7 +267,7 @@ fn send_and_spend_taker_funding() { wait_until: now_sec() + 20, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_input)).unwrap(); let found_spend_tx = block_on(taker_coin.search_for_taker_funding_spend(&taker_funding_utxo_tx, 1, taker_secret_hash)).unwrap(); @@ -279,8 +297,10 @@ fn send_and_spend_taker_payment_dex_fee_burn() { let dex_fee = &DexFee::with_burn("0.75".into(), "0.25".into()); let send_args = SendTakerFundingArgs { - time_lock: funding_time_lock, + funding_time_lock, + payment_time_lock: 0, taker_secret_hash, + maker_secret_hash, maker_pub, dex_fee, premium_amount: 0.into(), @@ -304,9 +324,11 @@ fn send_and_spend_taker_payment_dex_fee_burn() { let validate_args = ValidateTakerFundingArgs { funding_tx: &taker_funding_utxo_tx, - time_lock: funding_time_lock, + funding_time_lock, + payment_time_lock: 0, taker_secret_hash, - other_pub: taker_pub, + maker_secret_hash, + taker_pub, dex_fee, premium_amount: 0.into(), trading_amount: 777.into(), @@ -382,8 +404,10 @@ fn send_and_spend_taker_payment_standard_dex_fee() { let dex_fee = &DexFee::Standard(1.into()); let send_args = SendTakerFundingArgs { - time_lock: funding_time_lock, + funding_time_lock, + payment_time_lock: 0, taker_secret_hash, + maker_secret_hash, maker_pub, dex_fee, premium_amount: 0.into(), @@ -407,9 +431,11 @@ fn send_and_spend_taker_payment_standard_dex_fee() { let validate_args = ValidateTakerFundingArgs { funding_tx: &taker_funding_utxo_tx, - time_lock: funding_time_lock, + funding_time_lock, + payment_time_lock: 0, taker_secret_hash, - other_pub: taker_pub, + maker_secret_hash, + taker_pub, dex_fee, premium_amount: 0.into(), trading_amount: 777.into(), @@ -513,17 +539,17 @@ fn send_and_refund_maker_payment_timelock() { }; block_on(coin.validate_maker_payment_v2(validate_args)).unwrap(); - let refund_args = RefundPaymentArgs { + let refund_args = RefundMakerPaymentTimelockArgs { payment_tx: &serialize(&maker_payment).take(), time_lock, - other_pubkey: coin.my_public_key().unwrap(), + taker_pub: coin.my_public_key().unwrap(), tx_type_with_secret_hash: SwapTxTypeWithSecretHash::MakerPaymentV2 { taker_secret_hash, maker_secret_hash, }, swap_unique_data: &[], - swap_contract_address: &None, watcher_reward: false, + amount: Default::default(), }; let refund_tx = block_on(coin.refund_maker_payment_v2_timelock(refund_args)).unwrap(); @@ -577,7 +603,7 @@ fn send_and_refund_maker_payment_taker_secret() { }; block_on(coin.validate_maker_payment_v2(validate_args)).unwrap(); - let refund_args = RefundMakerPaymentArgs { + let refund_args = RefundMakerPaymentSecretArgs { maker_payment_tx: &maker_payment, time_lock, taker_secret_hash, @@ -585,6 +611,7 @@ fn send_and_refund_maker_payment_taker_secret() { swap_unique_data: &[], taker_secret, taker_pub, + amount: Default::default(), }; let refund_tx = block_on(coin.refund_maker_payment_v2_secret(refund_args)).unwrap(); diff --git a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs index 98973aaf96..49abc5c77f 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -13,12 +13,11 @@ use coins::{ConfirmPaymentInput, FoundSwapTxSpend, MarketCoinOps, MmCoin, MmCoin INVALID_CONTRACT_ADDRESS_ERR_LOG, INVALID_PAYMENT_STATE_ERR_LOG, INVALID_RECEIVER_ERR_LOG, INVALID_REFUND_TX_ERR_LOG, INVALID_SCRIPT_ERR_LOG, INVALID_SENDER_ERR_LOG, INVALID_SWAP_ID_ERR_LOG, OLD_TRANSACTION_ERR_LOG}; -use common::{block_on, now_sec, wait_until_sec, DEX_FEE_ADDR_RAW_PUBKEY}; +use common::{block_on, block_on_f01, now_sec, wait_until_sec, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::privkey::{key_pair_from_secret, key_pair_from_seed}; -use futures01::Future; -use mm2_main::mm2::lp_swap::{dex_fee_amount, dex_fee_amount_from_taker_coin, generate_secret, get_payment_locktime, - MAKER_PAYMENT_SENT_LOG, MAKER_PAYMENT_SPEND_FOUND_LOG, MAKER_PAYMENT_SPEND_SENT_LOG, - REFUND_TEST_FAILURE_LOG, TAKER_PAYMENT_REFUND_SENT_LOG, WATCHER_MESSAGE_SENT_LOG}; +use mm2_main::lp_swap::{dex_fee_amount, dex_fee_amount_from_taker_coin, generate_secret, get_payment_locktime, + MAKER_PAYMENT_SENT_LOG, MAKER_PAYMENT_SPEND_FOUND_LOG, MAKER_PAYMENT_SPEND_SENT_LOG, + REFUND_TEST_FAILURE_LOG, TAKER_PAYMENT_REFUND_SENT_LOG, WATCHER_MESSAGE_SENT_LOG}; use mm2_number::BigDecimal; use mm2_number::MmNumber; use mm2_test_helpers::for_tests::{enable_eth_coin, erc20_dev_conf, eth_dev_conf, eth_jst_testnet_conf, mm_dump, @@ -410,6 +409,7 @@ fn test_taker_saves_the_swap_as_successful_after_restart_panic_at_wait_for_taker "WatcherMessageSent", "TakerPaymentSpent", "MakerPaymentSpentByWatcher", + "MakerPaymentSpendConfirmed", "Finished", ]; check_actual_events(&mm_alice, &uuids[0], &expected_events); @@ -468,6 +468,7 @@ fn test_taker_saves_the_swap_as_successful_after_restart_panic_at_maker_payment_ "WatcherMessageSent", "TakerPaymentSpent", "MakerPaymentSpentByWatcher", + "MakerPaymentSpendConfirmed", "Finished", ]; check_actual_events(&mm_alice, &uuids[0], &expected_events); @@ -685,11 +686,8 @@ fn test_taker_completes_swap_after_taker_payment_spent_while_offline() { // stop taker after taker payment sent let taker_payment_msg = "Taker payment tx hash "; block_on(mm_alice.wait_for_log(120., |log| log.contains(taker_payment_msg))).unwrap(); - let alice_log = mm_alice.log_as_utf8().unwrap(); - let tx_hash_start = alice_log.find(taker_payment_msg).unwrap() + taker_payment_msg.len(); - let payment_tx_hash = alice_log[tx_hash_start..tx_hash_start + 64].to_string(); // ensure p2p message is sent to the maker, this happens before this message: - block_on(mm_alice.wait_for_log(120., |log| log.contains(&format!("Waiting for tx {}", payment_tx_hash)))).unwrap(); + block_on(mm_alice.wait_for_log(120., |log| log.contains("Waiting for maker to spend taker payment!"))).unwrap(); alice_conf.conf["dbdir"] = mm_alice.folder.join("DB").to_str().unwrap().into(); block_on(mm_alice.stop()).unwrap(); @@ -1208,15 +1206,13 @@ fn test_watcher_validate_taker_fee_utxo() { let taker_amount = MmNumber::from((10, 1)); let fee_amount = dex_fee_amount_from_taker_coin(&taker_coin, maker_coin.ticker(), &taker_amount); - let taker_fee = taker_coin - .send_taker_fee( - &DEX_FEE_ADDR_RAW_PUBKEY, - fee_amount, - Uuid::new_v4().as_bytes(), - lock_duration, - ) - .wait() - .unwrap(); + let taker_fee = block_on(taker_coin.send_taker_fee( + &DEX_FEE_ADDR_RAW_PUBKEY, + fee_amount, + Uuid::new_v4().as_bytes(), + lock_duration, + )) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_fee.tx_hex(), @@ -1226,30 +1222,26 @@ fn test_watcher_validate_taker_fee_utxo() { check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let validate_taker_fee_res = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: taker_pubkey.to_vec(), - min_block_number: 0, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), - lock_duration, - }) - .wait(); + let validate_taker_fee_res = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: taker_pubkey.to_vec(), + min_block_number: 0, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), + lock_duration, + })); assert!(validate_taker_fee_res.is_ok()); - let error = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: maker_coin.my_public_key().unwrap().to_vec(), - min_block_number: 0, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), - lock_duration, - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: maker_coin.my_public_key().unwrap().to_vec(), + min_block_number: 0, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), + lock_duration, + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { @@ -1259,17 +1251,15 @@ fn test_watcher_validate_taker_fee_utxo() { _ => panic!("Expected `WrongPaymentTx` invalid public key, found {:?}", error), } - let error = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: taker_pubkey.to_vec(), - min_block_number: std::u64::MAX, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), - lock_duration, - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: taker_pubkey.to_vec(), + min_block_number: std::u64::MAX, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), + lock_duration, + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -1281,17 +1271,15 @@ fn test_watcher_validate_taker_fee_utxo() { ), } - let error = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: taker_pubkey.to_vec(), - min_block_number: 0, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), - lock_duration: 0, - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: taker_pubkey.to_vec(), + min_block_number: 0, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), + lock_duration: 0, + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -1300,17 +1288,15 @@ fn test_watcher_validate_taker_fee_utxo() { _ => panic!("Expected `WrongPaymentTx` transaction too old, found {:?}", error), } - let error = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: taker_pubkey.to_vec(), - min_block_number: 0, - fee_addr: taker_pubkey.to_vec(), - lock_duration, - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: taker_pubkey.to_vec(), + min_block_number: 0, + fee_addr: taker_pubkey.to_vec(), + lock_duration, + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -1334,15 +1320,13 @@ fn test_watcher_validate_taker_fee_eth() { let taker_amount = MmNumber::from((1, 1)); let fee_amount = dex_fee_amount_from_taker_coin(&taker_coin, "ETH", &taker_amount); - let taker_fee = taker_coin - .send_taker_fee( - &DEX_FEE_ADDR_RAW_PUBKEY, - fee_amount, - Uuid::new_v4().as_bytes(), - lock_duration, - ) - .wait() - .unwrap(); + let taker_fee = block_on(taker_coin.send_taker_fee( + &DEX_FEE_ADDR_RAW_PUBKEY, + fee_amount, + Uuid::new_v4().as_bytes(), + lock_duration, + )) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_fee.tx_hex(), @@ -1351,31 +1335,27 @@ fn test_watcher_validate_taker_fee_eth() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); - - let validate_taker_fee_res = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: taker_pubkey.to_vec(), - min_block_number: 0, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), - lock_duration, - }) - .wait(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); + + let validate_taker_fee_res = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: taker_pubkey.to_vec(), + min_block_number: 0, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), + lock_duration, + })); assert!(validate_taker_fee_res.is_ok()); let wrong_keypair = key_pair_from_secret(random_secp256k1_secret().as_slice()).unwrap(); - let error = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: wrong_keypair.public().to_vec(), - min_block_number: 0, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), - lock_duration, - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: wrong_keypair.public().to_vec(), + min_block_number: 0, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), + lock_duration, + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { @@ -1385,17 +1365,15 @@ fn test_watcher_validate_taker_fee_eth() { _ => panic!("Expected `WrongPaymentTx` invalid public key, found {:?}", error), } - let error = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: taker_pubkey.to_vec(), - min_block_number: std::u64::MAX, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), - lock_duration, - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: taker_pubkey.to_vec(), + min_block_number: std::u64::MAX, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), + lock_duration, + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -1407,17 +1385,15 @@ fn test_watcher_validate_taker_fee_eth() { ), } - let error = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: taker_pubkey.to_vec(), - min_block_number: 0, - fee_addr: taker_pubkey.to_vec(), - lock_duration, - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: taker_pubkey.to_vec(), + min_block_number: 0, + fee_addr: taker_pubkey.to_vec(), + lock_duration, + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -1441,15 +1417,13 @@ fn test_watcher_validate_taker_fee_erc20() { let taker_amount = MmNumber::from((1, 1)); let fee_amount = dex_fee_amount_from_taker_coin(&taker_coin, "ETH", &taker_amount); - let taker_fee = taker_coin - .send_taker_fee( - &DEX_FEE_ADDR_RAW_PUBKEY, - fee_amount, - Uuid::new_v4().as_bytes(), - lock_duration, - ) - .wait() - .unwrap(); + let taker_fee = block_on(taker_coin.send_taker_fee( + &DEX_FEE_ADDR_RAW_PUBKEY, + fee_amount, + Uuid::new_v4().as_bytes(), + lock_duration, + )) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_fee.tx_hex(), @@ -1458,31 +1432,27 @@ fn test_watcher_validate_taker_fee_erc20() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); - - let validate_taker_fee_res = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: taker_pubkey.to_vec(), - min_block_number: 0, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), - lock_duration, - }) - .wait(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); + + let validate_taker_fee_res = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: taker_pubkey.to_vec(), + min_block_number: 0, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), + lock_duration, + })); assert!(validate_taker_fee_res.is_ok()); let wrong_keypair = key_pair_from_secret(random_secp256k1_secret().as_slice()).unwrap(); - let error = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: wrong_keypair.public().to_vec(), - min_block_number: 0, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), - lock_duration, - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: wrong_keypair.public().to_vec(), + min_block_number: 0, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), + lock_duration, + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { @@ -1492,17 +1462,15 @@ fn test_watcher_validate_taker_fee_erc20() { _ => panic!("Expected `WrongPaymentTx` invalid public key, found {:?}", error), } - let error = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: taker_pubkey.to_vec(), - min_block_number: std::u64::MAX, - fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), - lock_duration, - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: taker_pubkey.to_vec(), + min_block_number: std::u64::MAX, + fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), + lock_duration, + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -1514,17 +1482,15 @@ fn test_watcher_validate_taker_fee_erc20() { ), } - let error = taker_coin - .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { - taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), - sender_pubkey: taker_pubkey.to_vec(), - min_block_number: 0, - fee_addr: taker_pubkey.to_vec(), - lock_duration, - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_fee(WatcherValidateTakerFeeInput { + taker_fee_hash: taker_fee.tx_hash_as_bytes().into_vec(), + sender_pubkey: taker_pubkey.to_vec(), + min_block_number: 0, + fee_addr: taker_pubkey.to_vec(), + lock_duration, + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -1552,21 +1518,19 @@ fn test_watcher_validate_taker_payment_utxo() { let secret_hash = dhash160(&generate_secret().unwrap()); - let taker_payment = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pubkey, - secret_hash: secret_hash.as_slice(), - amount: BigDecimal::from(10), - swap_contract_address: &None, - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: None, - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pubkey, + secret_hash: secret_hash.as_slice(), + amount: BigDecimal::from(10), + swap_contract_address: &None, + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: None, + wait_for_confirmation_until, + })) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_payment.tx_hex(), @@ -1575,21 +1539,19 @@ fn test_watcher_validate_taker_payment_utxo() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let taker_payment_refund_preimage = taker_coin - .create_taker_payment_refund_preimage( - &taker_payment.tx_hex(), - time_lock, - maker_pubkey, - secret_hash.as_slice(), - &None, - &[], - ) - .wait() - .unwrap(); - let validate_taker_payment_res = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { + let taker_payment_refund_preimage = block_on_f01(taker_coin.create_taker_payment_refund_preimage( + &taker_payment.tx_hex(), + time_lock, + maker_pubkey, + secret_hash.as_slice(), + &None, + &[], + )) + .unwrap(); + let validate_taker_payment_res = + block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { payment_tx: taker_payment.tx_hex(), taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), time_lock, @@ -1599,25 +1561,22 @@ fn test_watcher_validate_taker_payment_utxo() { wait_until: timeout, confirmations: 1, maker_coin: MmCoinEnum::UtxoCoin(maker_coin.clone()), - }) - .wait(); + })); assert!(validate_taker_payment_res.is_ok()); - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment.tx_hex(), - taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), - time_lock, - taker_pub: maker_pubkey.to_vec(), - maker_pub: maker_pubkey.to_vec(), - secret_hash: secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::UtxoCoin(maker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), + time_lock, + taker_pub: maker_pubkey.to_vec(), + maker_pub: maker_pubkey.to_vec(), + secret_hash: secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::UtxoCoin(maker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { @@ -1629,21 +1588,19 @@ fn test_watcher_validate_taker_payment_utxo() { // Used to get wrong swap id let wrong_secret_hash = dhash160(&generate_secret().unwrap()); - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment.tx_hex(), - taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), - time_lock, - taker_pub: taker_pubkey.to_vec(), - maker_pub: maker_pubkey.to_vec(), - secret_hash: wrong_secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::UtxoCoin(maker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), + time_lock, + taker_pub: taker_pubkey.to_vec(), + maker_pub: maker_pubkey.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::UtxoCoin(maker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { @@ -1656,21 +1613,19 @@ fn test_watcher_validate_taker_payment_utxo() { ), } - let taker_payment_wrong_secret = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pubkey, - secret_hash: wrong_secret_hash.as_slice(), - amount: BigDecimal::from(10), - swap_contract_address: &taker_coin.swap_contract_address(), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: None, - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment_wrong_secret = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pubkey, + secret_hash: wrong_secret_hash.as_slice(), + amount: BigDecimal::from(10), + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: None, + wait_for_confirmation_until, + })) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_payment_wrong_secret.tx_hex(), @@ -1679,23 +1634,21 @@ fn test_watcher_validate_taker_payment_utxo() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment.tx_hex(), - taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), - time_lock: 500, - taker_pub: taker_pubkey.to_vec(), - maker_pub: maker_pubkey.to_vec(), - secret_hash: wrong_secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::UtxoCoin(maker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), + time_lock: 500, + taker_pub: taker_pubkey.to_vec(), + maker_pub: maker_pubkey.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::UtxoCoin(maker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { @@ -1708,33 +1661,29 @@ fn test_watcher_validate_taker_payment_utxo() { ), } - let wrong_taker_payment_refund_preimage = taker_coin - .create_taker_payment_refund_preimage( - &taker_payment.tx_hex(), - time_lock, - maker_pubkey, - wrong_secret_hash.as_slice(), - &None, - &[], - ) - .wait() - .unwrap(); + let wrong_taker_payment_refund_preimage = block_on_f01(taker_coin.create_taker_payment_refund_preimage( + &taker_payment.tx_hex(), + time_lock, + maker_pubkey, + wrong_secret_hash.as_slice(), + &None, + &[], + )) + .unwrap(); - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment.tx_hex(), - taker_payment_refund_preimage: wrong_taker_payment_refund_preimage.tx_hex(), - time_lock, - taker_pub: taker_pubkey.to_vec(), - maker_pub: maker_pubkey.to_vec(), - secret_hash: secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::UtxoCoin(maker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: wrong_taker_payment_refund_preimage.tx_hex(), + time_lock, + taker_pub: taker_pubkey.to_vec(), + maker_pub: maker_pubkey.to_vec(), + secret_hash: secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::UtxoCoin(maker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { @@ -1777,21 +1726,19 @@ fn test_watcher_validate_taker_payment_eth() { .unwrap(), ); - let taker_payment = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pub, - secret_hash: secret_hash.as_slice(), - amount: taker_amount.clone(), - swap_contract_address: &taker_coin.swap_contract_address(), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: watcher_reward.clone(), - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: secret_hash.as_slice(), + amount: taker_amount.clone(), + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: watcher_reward.clone(), + wait_for_confirmation_until, + })) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_payment.tx_hex(), @@ -1800,10 +1747,10 @@ fn test_watcher_validate_taker_payment_eth() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let validate_taker_payment_res = taker_coin - .watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { + let validate_taker_payment_res = block_on_f01(taker_coin.watcher_validate_taker_payment( + coins::WatcherValidatePaymentInput { payment_tx: taker_payment.tx_hex(), taker_payment_refund_preimage: Vec::new(), time_lock, @@ -1813,12 +1760,12 @@ fn test_watcher_validate_taker_payment_eth() { wait_until: timeout, confirmations: 1, maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait(); + }, + )); assert!(validate_taker_payment_res.is_ok()); - let error = taker_coin - .watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { + let error = block_on_f01( + taker_coin.watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { payment_tx: taker_payment.tx_hex(), taker_payment_refund_preimage: Vec::new(), time_lock, @@ -1828,10 +1775,10 @@ fn test_watcher_validate_taker_payment_eth() { wait_until: timeout, confirmations: 1, maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + }), + ) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -1843,24 +1790,22 @@ fn test_watcher_validate_taker_payment_eth() { ), } - let taker_payment_wrong_contract = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pub, - secret_hash: secret_hash.as_slice(), - amount: taker_amount.clone(), - swap_contract_address: &Some("9130b257d37a52e52f21054c4da3450c72f595ce".into()), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: watcher_reward.clone(), - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment_wrong_contract = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: secret_hash.as_slice(), + amount: taker_amount.clone(), + swap_contract_address: &Some("9130b257d37a52e52f21054c4da3450c72f595ce".into()), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: watcher_reward.clone(), + wait_for_confirmation_until, + })) + .unwrap(); - let error = taker_coin - .watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { + let error = block_on_f01( + taker_coin.watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { payment_tx: taker_payment_wrong_contract.tx_hex(), taker_payment_refund_preimage: Vec::new(), time_lock, @@ -1870,10 +1815,10 @@ fn test_watcher_validate_taker_payment_eth() { wait_until: timeout, confirmations: 1, maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + }), + ) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -1887,8 +1832,8 @@ fn test_watcher_validate_taker_payment_eth() { // Used to get wrong swap id let wrong_secret_hash = dhash160(&generate_secret().unwrap()); - let error = taker_coin - .watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { + let error = block_on_f01( + taker_coin.watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { payment_tx: taker_payment.tx_hex(), taker_payment_refund_preimage: Vec::new(), time_lock, @@ -1898,10 +1843,10 @@ fn test_watcher_validate_taker_payment_eth() { wait_until: timeout, confirmations: 1, maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + }), + ) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::UnexpectedPaymentState(err) => { @@ -1913,21 +1858,19 @@ fn test_watcher_validate_taker_payment_eth() { ), } - let taker_payment_wrong_secret = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pub, - secret_hash: wrong_secret_hash.as_slice(), - amount: taker_amount, - swap_contract_address: &taker_coin.swap_contract_address(), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward, - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment_wrong_secret = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: wrong_secret_hash.as_slice(), + amount: taker_amount, + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward, + wait_for_confirmation_until, + })) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_payment_wrong_secret.tx_hex(), @@ -1936,23 +1879,21 @@ fn test_watcher_validate_taker_payment_eth() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment.tx_hex(), - taker_payment_refund_preimage: Vec::new(), - time_lock, - taker_pub: taker_pub.to_vec(), - maker_pub: maker_pub.to_vec(), - secret_hash: wrong_secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -1964,21 +1905,19 @@ fn test_watcher_validate_taker_payment_eth() { ), } - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment.tx_hex(), - taker_payment_refund_preimage: Vec::new(), - time_lock, - taker_pub: taker_pub.to_vec(), - maker_pub: taker_pub.to_vec(), - secret_hash: secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: taker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -2023,21 +1962,19 @@ fn test_watcher_validate_taker_payment_erc20() { .unwrap(), ); - let taker_payment = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pub, - secret_hash: secret_hash.as_slice(), - amount: taker_amount.clone(), - swap_contract_address: &taker_coin.swap_contract_address(), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: watcher_reward.clone(), - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: secret_hash.as_slice(), + amount: taker_amount.clone(), + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: watcher_reward.clone(), + wait_for_confirmation_until, + })) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_payment.tx_hex(), @@ -2046,10 +1983,10 @@ fn test_watcher_validate_taker_payment_erc20() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let validate_taker_payment_res = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { + let validate_taker_payment_res = + block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { payment_tx: taker_payment.tx_hex(), taker_payment_refund_preimage: Vec::new(), time_lock, @@ -2059,25 +1996,22 @@ fn test_watcher_validate_taker_payment_erc20() { wait_until: timeout, confirmations: 1, maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait(); + })); assert!(validate_taker_payment_res.is_ok()); - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment.tx_hex(), - taker_payment_refund_preimage: Vec::new(), - time_lock, - taker_pub: maker_pub.to_vec(), - maker_pub: maker_pub.to_vec(), - secret_hash: secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: maker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -2089,37 +2023,33 @@ fn test_watcher_validate_taker_payment_erc20() { ), } - let taker_payment_wrong_contract = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pub, - secret_hash: secret_hash.as_slice(), - amount: taker_amount.clone(), - swap_contract_address: &Some("9130b257d37a52e52f21054c4da3450c72f595ce".into()), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: watcher_reward.clone(), - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment_wrong_contract = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: secret_hash.as_slice(), + amount: taker_amount.clone(), + swap_contract_address: &Some("9130b257d37a52e52f21054c4da3450c72f595ce".into()), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: watcher_reward.clone(), + wait_for_confirmation_until, + })) + .unwrap(); - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment_wrong_contract.tx_hex(), - taker_payment_refund_preimage: Vec::new(), - time_lock, - taker_pub: taker_pub.to_vec(), - maker_pub: maker_pub.to_vec(), - secret_hash: secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment_wrong_contract.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -2133,21 +2063,19 @@ fn test_watcher_validate_taker_payment_erc20() { // Used to get wrong swap id let wrong_secret_hash = dhash160(&generate_secret().unwrap()); - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment.tx_hex(), - taker_payment_refund_preimage: Vec::new(), - time_lock, - taker_pub: taker_pub.to_vec(), - maker_pub: maker_pub.to_vec(), - secret_hash: wrong_secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::UnexpectedPaymentState(err) => { @@ -2159,21 +2087,19 @@ fn test_watcher_validate_taker_payment_erc20() { ), } - let taker_payment_wrong_secret = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pub, - secret_hash: wrong_secret_hash.as_slice(), - amount: taker_amount, - swap_contract_address: &taker_coin.swap_contract_address(), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward, - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment_wrong_secret = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: wrong_secret_hash.as_slice(), + amount: taker_amount, + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward, + wait_for_confirmation_until, + })) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_payment_wrong_secret.tx_hex(), @@ -2182,23 +2108,21 @@ fn test_watcher_validate_taker_payment_erc20() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment.tx_hex(), - taker_payment_refund_preimage: Vec::new(), - time_lock, - taker_pub: taker_pub.to_vec(), - maker_pub: maker_pub.to_vec(), - secret_hash: wrong_secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -2210,21 +2134,19 @@ fn test_watcher_validate_taker_payment_erc20() { ), } - let error = taker_coin - .watcher_validate_taker_payment(WatcherValidatePaymentInput { - payment_tx: taker_payment.tx_hex(), - taker_payment_refund_preimage: Vec::new(), - time_lock, - taker_pub: taker_pub.to_vec(), - maker_pub: taker_pub.to_vec(), - secret_hash: secret_hash.to_vec(), - wait_until: timeout, - confirmations: 1, - maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), - }) - .wait() - .unwrap_err() - .into_inner(); + let error = block_on_f01(taker_coin.watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: taker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + wait_until: timeout, + confirmations: 1, + maker_coin: MmCoinEnum::EthCoin(taker_coin.clone()), + })) + .unwrap_err() + .into_inner(); log!("error: {:?}", error); match error { ValidatePaymentError::WrongPaymentTx(err) => { @@ -2250,21 +2172,19 @@ fn test_taker_validates_taker_payment_refund_utxo() { let secret_hash = dhash160(&generate_secret().unwrap()); - let taker_payment = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pubkey, - secret_hash: secret_hash.as_slice(), - amount: BigDecimal::from(10), - swap_contract_address: &None, - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: None, - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pubkey, + secret_hash: secret_hash.as_slice(), + amount: BigDecimal::from(10), + swap_contract_address: &None, + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: None, + wait_for_confirmation_until, + })) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_payment.tx_hex(), @@ -2273,34 +2193,30 @@ fn test_taker_validates_taker_payment_refund_utxo() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let taker_payment_refund_preimage = taker_coin - .create_taker_payment_refund_preimage( - &taker_payment.tx_hex(), - time_lock, - maker_pubkey, - secret_hash.as_slice(), - &None, - &[], - ) - .wait() - .unwrap(); + let taker_payment_refund_preimage = block_on_f01(taker_coin.create_taker_payment_refund_preimage( + &taker_payment.tx_hex(), + time_lock, + maker_pubkey, + secret_hash.as_slice(), + &None, + &[], + )) + .unwrap(); - let taker_payment_refund = taker_coin - .send_taker_payment_refund_preimage(RefundPaymentArgs { - payment_tx: &taker_payment_refund_preimage.tx_hex(), - other_pubkey: maker_pubkey, - tx_type_with_secret_hash: SwapTxTypeWithSecretHash::TakerOrMakerPayment { - maker_secret_hash: secret_hash.as_slice(), - }, - time_lock, - swap_contract_address: &None, - swap_unique_data: &[], - watcher_reward: false, - }) - .wait() - .unwrap(); + let taker_payment_refund = block_on_f01(taker_coin.send_taker_payment_refund_preimage(RefundPaymentArgs { + payment_tx: &taker_payment_refund_preimage.tx_hex(), + other_pubkey: maker_pubkey, + tx_type_with_secret_hash: SwapTxTypeWithSecretHash::TakerOrMakerPayment { + maker_secret_hash: secret_hash.as_slice(), + }, + time_lock, + swap_contract_address: &None, + swap_unique_data: &[], + watcher_reward: false, + })) + .unwrap(); let validate_input = ValidateWatcherSpendInput { payment_tx: taker_payment_refund.tx_hex(), @@ -2313,9 +2229,7 @@ fn test_taker_validates_taker_payment_refund_utxo() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let validate_watcher_refund = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait(); + let validate_watcher_refund = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)); assert!(validate_watcher_refund.is_ok()); } @@ -2347,21 +2261,19 @@ fn test_taker_validates_taker_payment_refund_eth() { )) .unwrap(); - let taker_payment = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pub, - secret_hash: secret_hash.as_slice(), - amount: taker_amount.clone(), - swap_contract_address: &taker_coin.swap_contract_address(), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: Some(watcher_reward.clone()), - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: secret_hash.as_slice(), + amount: taker_amount.clone(), + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: Some(watcher_reward.clone()), + wait_for_confirmation_until, + })) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_payment.tx_hex(), @@ -2370,19 +2282,17 @@ fn test_taker_validates_taker_payment_refund_eth() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let taker_payment_refund_preimage = taker_coin - .create_taker_payment_refund_preimage( - &taker_payment.tx_hex(), - time_lock, - taker_pub, - secret_hash.as_slice(), - &taker_coin.swap_contract_address(), - &[], - ) - .wait() - .unwrap(); + let taker_payment_refund_preimage = block_on_f01(taker_coin.create_taker_payment_refund_preimage( + &taker_payment.tx_hex(), + time_lock, + taker_pub, + secret_hash.as_slice(), + &taker_coin.swap_contract_address(), + &[], + )) + .unwrap(); let validate_input = ValidateWatcherSpendInput { payment_tx: taker_payment_refund_preimage.tx_hex(), @@ -2395,9 +2305,7 @@ fn test_taker_validates_taker_payment_refund_eth() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -2411,20 +2319,18 @@ fn test_taker_validates_taker_payment_refund_eth() { ), } - let taker_payment_refund = taker_coin - .send_taker_payment_refund_preimage(RefundPaymentArgs { - payment_tx: &taker_payment_refund_preimage.tx_hex(), - other_pubkey: taker_pub, - tx_type_with_secret_hash: SwapTxTypeWithSecretHash::TakerOrMakerPayment { - maker_secret_hash: secret_hash.as_slice(), - }, - time_lock, - swap_contract_address: &taker_coin.swap_contract_address(), - swap_unique_data: &[], - watcher_reward: true, - }) - .wait() - .unwrap(); + let taker_payment_refund = block_on_f01(taker_coin.send_taker_payment_refund_preimage(RefundPaymentArgs { + payment_tx: &taker_payment_refund_preimage.tx_hex(), + other_pubkey: taker_pub, + tx_type_with_secret_hash: SwapTxTypeWithSecretHash::TakerOrMakerPayment { + maker_secret_hash: secret_hash.as_slice(), + }, + time_lock, + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + watcher_reward: true, + })) + .unwrap(); let validate_input = ValidateWatcherSpendInput { payment_tx: taker_payment_refund.tx_hex(), @@ -2437,9 +2343,7 @@ fn test_taker_validates_taker_payment_refund_eth() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let validate_watcher_refund = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait(); + let validate_watcher_refund = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)); assert!(validate_watcher_refund.is_ok()); let validate_input = ValidateWatcherSpendInput { @@ -2452,9 +2356,7 @@ fn test_taker_validates_taker_payment_refund_eth() { watcher_reward: Some(watcher_reward.clone()), spend_type: WatcherSpendType::TakerPaymentRefund, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -2479,9 +2381,7 @@ fn test_taker_validates_taker_payment_refund_eth() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let error = maker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(maker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -2506,9 +2406,7 @@ fn test_taker_validates_taker_payment_refund_eth() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -2536,9 +2434,7 @@ fn test_taker_validates_taker_payment_refund_eth() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -2566,9 +2462,7 @@ fn test_taker_validates_taker_payment_refund_eth() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -2596,9 +2490,7 @@ fn test_taker_validates_taker_payment_refund_eth() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -2623,9 +2515,7 @@ fn test_taker_validates_taker_payment_refund_eth() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -2672,21 +2562,19 @@ fn test_taker_validates_taker_payment_refund_erc20() { .unwrap(), ); - let taker_payment = taker_coin - .send_taker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: maker_pub, - secret_hash: secret_hash.as_slice(), - amount: taker_amount.clone(), - swap_contract_address: &taker_coin.swap_contract_address(), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: watcher_reward.clone(), - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let taker_payment = block_on(taker_coin.send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: secret_hash.as_slice(), + amount: taker_amount.clone(), + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: watcher_reward.clone(), + wait_for_confirmation_until, + })) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: taker_payment.tx_hex(), @@ -2695,34 +2583,30 @@ fn test_taker_validates_taker_payment_refund_erc20() { wait_until: timeout, check_every: 1, }; - taker_coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(taker_coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let taker_payment_refund_preimage = taker_coin - .create_taker_payment_refund_preimage( - &taker_payment.tx_hex(), - time_lock, - taker_pub, - secret_hash.as_slice(), - &taker_coin.swap_contract_address(), - &[], - ) - .wait() - .unwrap(); + let taker_payment_refund_preimage = block_on_f01(taker_coin.create_taker_payment_refund_preimage( + &taker_payment.tx_hex(), + time_lock, + taker_pub, + secret_hash.as_slice(), + &taker_coin.swap_contract_address(), + &[], + )) + .unwrap(); - let taker_payment_refund = taker_coin - .send_taker_payment_refund_preimage(RefundPaymentArgs { - payment_tx: &taker_payment_refund_preimage.tx_hex(), - other_pubkey: taker_pub, - tx_type_with_secret_hash: SwapTxTypeWithSecretHash::TakerOrMakerPayment { - maker_secret_hash: secret_hash.as_slice(), - }, - time_lock, - swap_contract_address: &taker_coin.swap_contract_address(), - swap_unique_data: &[], - watcher_reward: true, - }) - .wait() - .unwrap(); + let taker_payment_refund = block_on_f01(taker_coin.send_taker_payment_refund_preimage(RefundPaymentArgs { + payment_tx: &taker_payment_refund_preimage.tx_hex(), + other_pubkey: taker_pub, + tx_type_with_secret_hash: SwapTxTypeWithSecretHash::TakerOrMakerPayment { + maker_secret_hash: secret_hash.as_slice(), + }, + time_lock, + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + watcher_reward: true, + })) + .unwrap(); let validate_input = ValidateWatcherSpendInput { payment_tx: taker_payment_refund.tx_hex(), @@ -2735,9 +2619,7 @@ fn test_taker_validates_taker_payment_refund_erc20() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let validate_watcher_refund = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait(); + let validate_watcher_refund = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)); assert!(validate_watcher_refund.is_ok()); let validate_input = ValidateWatcherSpendInput { @@ -2751,9 +2633,7 @@ fn test_taker_validates_taker_payment_refund_erc20() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -2783,54 +2663,48 @@ fn test_taker_validates_maker_payment_spend_utxo() { let secret = generate_secret().unwrap(); let secret_hash = dhash160(&secret); - let maker_payment = maker_coin - .send_maker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: taker_pubkey, - secret_hash: secret_hash.as_slice(), - amount: BigDecimal::from(10), - swap_contract_address: &None, - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: None, - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let maker_payment = block_on(maker_coin.send_maker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: taker_pubkey, + secret_hash: secret_hash.as_slice(), + amount: BigDecimal::from(10), + swap_contract_address: &None, + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: None, + wait_for_confirmation_until, + })) + .unwrap(); - maker_coin - .wait_for_confirmations(ConfirmPaymentInput { - payment_tx: maker_payment.tx_hex(), - confirmations: 1, - requires_nota: false, - wait_until: timeout, - check_every: 1, - }) - .wait() - .unwrap(); + block_on_f01(maker_coin.wait_for_confirmations(ConfirmPaymentInput { + payment_tx: maker_payment.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + })) + .unwrap(); - let maker_payment_spend_preimage = taker_coin - .create_maker_payment_spend_preimage( - &maker_payment.tx_hex(), - time_lock, - maker_pubkey, - secret_hash.as_slice(), - &[], - ) - .wait() - .unwrap(); + let maker_payment_spend_preimage = block_on_f01(taker_coin.create_maker_payment_spend_preimage( + &maker_payment.tx_hex(), + time_lock, + maker_pubkey, + secret_hash.as_slice(), + &[], + )) + .unwrap(); - let maker_payment_spend = taker_coin - .send_maker_payment_spend_preimage(SendMakerPaymentSpendPreimageInput { + let maker_payment_spend = block_on_f01(taker_coin.send_maker_payment_spend_preimage( + SendMakerPaymentSpendPreimageInput { preimage: &maker_payment_spend_preimage.tx_hex(), secret_hash: secret_hash.as_slice(), secret: secret.as_slice(), taker_pub: taker_pubkey, watcher_reward: false, - }) - .wait() - .unwrap(); + }, + )) + .unwrap(); let validate_input = ValidateWatcherSpendInput { payment_tx: maker_payment_spend.tx_hex(), @@ -2843,9 +2717,7 @@ fn test_taker_validates_maker_payment_spend_utxo() { spend_type: WatcherSpendType::TakerPaymentRefund, }; - let validate_watcher_spend = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait(); + let validate_watcher_spend = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)); assert!(validate_watcher_spend.is_ok()); } @@ -2877,43 +2749,37 @@ fn test_taker_validates_maker_payment_spend_eth() { .unwrap() .unwrap(); - let maker_payment = maker_coin - .send_maker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: taker_pub, - secret_hash: secret_hash.as_slice(), - amount: maker_amount.clone(), - swap_contract_address: &maker_coin.swap_contract_address(), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: Some(watcher_reward.clone()), - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let maker_payment = block_on(maker_coin.send_maker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: taker_pub, + secret_hash: secret_hash.as_slice(), + amount: maker_amount.clone(), + swap_contract_address: &maker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: Some(watcher_reward.clone()), + wait_for_confirmation_until, + })) + .unwrap(); - maker_coin - .wait_for_confirmations(ConfirmPaymentInput { - payment_tx: maker_payment.tx_hex(), - confirmations: 1, - requires_nota: false, - wait_until: timeout, - check_every: 1, - }) - .wait() - .unwrap(); + block_on_f01(maker_coin.wait_for_confirmations(ConfirmPaymentInput { + payment_tx: maker_payment.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + })) + .unwrap(); - let maker_payment_spend_preimage = taker_coin - .create_maker_payment_spend_preimage( - &maker_payment.tx_hex(), - time_lock, - maker_pub, - secret_hash.as_slice(), - &[], - ) - .wait() - .unwrap(); + let maker_payment_spend_preimage = block_on_f01(taker_coin.create_maker_payment_spend_preimage( + &maker_payment.tx_hex(), + time_lock, + maker_pub, + secret_hash.as_slice(), + &[], + )) + .unwrap(); let validate_input = ValidateWatcherSpendInput { payment_tx: maker_payment_spend_preimage.tx_hex(), @@ -2926,9 +2792,7 @@ fn test_taker_validates_maker_payment_spend_eth() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -2942,27 +2806,25 @@ fn test_taker_validates_maker_payment_spend_eth() { ), } - let maker_payment_spend = taker_coin - .send_maker_payment_spend_preimage(SendMakerPaymentSpendPreimageInput { + let maker_payment_spend = block_on_f01(taker_coin.send_maker_payment_spend_preimage( + SendMakerPaymentSpendPreimageInput { preimage: &maker_payment_spend_preimage.tx_hex(), secret_hash: secret_hash.as_slice(), secret: secret.as_slice(), taker_pub, watcher_reward: true, - }) - .wait() - .unwrap(); + }, + )) + .unwrap(); - maker_coin - .wait_for_confirmations(ConfirmPaymentInput { - payment_tx: maker_payment_spend.tx_hex(), - confirmations: 1, - requires_nota: false, - wait_until: timeout, - check_every: 1, - }) - .wait() - .unwrap(); + block_on_f01(maker_coin.wait_for_confirmations(ConfirmPaymentInput { + payment_tx: maker_payment_spend.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + })) + .unwrap(); let validate_input = ValidateWatcherSpendInput { payment_tx: maker_payment_spend.tx_hex(), @@ -2975,10 +2837,7 @@ fn test_taker_validates_maker_payment_spend_eth() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() - .unwrap(); + block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)).unwrap(); let validate_input = ValidateWatcherSpendInput { payment_tx: maker_payment_spend.tx_hex(), @@ -2991,9 +2850,7 @@ fn test_taker_validates_maker_payment_spend_eth() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -3018,9 +2875,7 @@ fn test_taker_validates_maker_payment_spend_eth() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -3045,9 +2900,7 @@ fn test_taker_validates_maker_payment_spend_eth() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - let error = maker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(maker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -3075,9 +2928,7 @@ fn test_taker_validates_maker_payment_spend_eth() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -3105,9 +2956,7 @@ fn test_taker_validates_maker_payment_spend_eth() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -3135,9 +2984,7 @@ fn test_taker_validates_maker_payment_spend_eth() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -3162,9 +3009,7 @@ fn test_taker_validates_maker_payment_spend_eth() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -3206,65 +3051,57 @@ fn test_taker_validates_maker_payment_spend_erc20() { )) .unwrap(); - let maker_payment = maker_coin - .send_maker_payment(SendPaymentArgs { - time_lock_duration, - time_lock, - other_pubkey: taker_pub, - secret_hash: secret_hash.as_slice(), - amount: maker_amount.clone(), - swap_contract_address: &maker_coin.swap_contract_address(), - swap_unique_data: &[], - payment_instructions: &None, - watcher_reward: watcher_reward.clone(), - wait_for_confirmation_until, - }) - .wait() - .unwrap(); + let maker_payment = block_on(maker_coin.send_maker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: taker_pub, + secret_hash: secret_hash.as_slice(), + amount: maker_amount.clone(), + swap_contract_address: &maker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: watcher_reward.clone(), + wait_for_confirmation_until, + })) + .unwrap(); - maker_coin - .wait_for_confirmations(ConfirmPaymentInput { - payment_tx: maker_payment.tx_hex(), - confirmations: 1, - requires_nota: false, - wait_until: timeout, - check_every: 1, - }) - .wait() - .unwrap(); + block_on_f01(maker_coin.wait_for_confirmations(ConfirmPaymentInput { + payment_tx: maker_payment.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + })) + .unwrap(); - let maker_payment_spend_preimage = taker_coin - .create_maker_payment_spend_preimage( - &maker_payment.tx_hex(), - time_lock, - maker_pub, - secret_hash.as_slice(), - &[], - ) - .wait() - .unwrap(); + let maker_payment_spend_preimage = block_on_f01(taker_coin.create_maker_payment_spend_preimage( + &maker_payment.tx_hex(), + time_lock, + maker_pub, + secret_hash.as_slice(), + &[], + )) + .unwrap(); - let maker_payment_spend = taker_coin - .send_maker_payment_spend_preimage(SendMakerPaymentSpendPreimageInput { + let maker_payment_spend = block_on_f01(taker_coin.send_maker_payment_spend_preimage( + SendMakerPaymentSpendPreimageInput { preimage: &maker_payment_spend_preimage.tx_hex(), secret_hash: secret_hash.as_slice(), secret: secret.as_slice(), taker_pub, watcher_reward: true, - }) - .wait() - .unwrap(); + }, + )) + .unwrap(); - maker_coin - .wait_for_confirmations(ConfirmPaymentInput { - payment_tx: maker_payment_spend.tx_hex(), - confirmations: 1, - requires_nota: false, - wait_until: timeout, - check_every: 1, - }) - .wait() - .unwrap(); + block_on_f01(maker_coin.wait_for_confirmations(ConfirmPaymentInput { + payment_tx: maker_payment_spend.tx_hex(), + confirmations: 1, + requires_nota: false, + wait_until: timeout, + check_every: 1, + })) + .unwrap(); let validate_input = ValidateWatcherSpendInput { payment_tx: maker_payment_spend.tx_hex(), @@ -3277,10 +3114,7 @@ fn test_taker_validates_maker_payment_spend_erc20() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() - .unwrap(); + block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)).unwrap(); let validate_input = ValidateWatcherSpendInput { payment_tx: maker_payment_spend.tx_hex(), @@ -3293,9 +3127,7 @@ fn test_taker_validates_maker_payment_spend_erc20() { spend_type: WatcherSpendType::MakerPaymentSpend, }; - let error = taker_coin - .taker_validates_payment_spend_or_refund(validate_input) - .wait() + let error = block_on_f01(taker_coin.taker_validates_payment_spend_or_refund(validate_input)) .unwrap_err() .into_inner(); log!("error: {:?}", error); @@ -3329,7 +3161,7 @@ fn test_send_taker_payment_refund_preimage_utxo() { watcher_reward: None, wait_for_confirmation_until: 0, }; - let tx = coin.send_taker_payment(taker_payment_args).wait().unwrap(); + let tx = block_on(coin.send_taker_payment(taker_payment_args)).unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: tx.tx_hex(), @@ -3338,27 +3170,30 @@ fn test_send_taker_payment_refund_preimage_utxo() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); - let refund_tx = coin - .create_taker_payment_refund_preimage(&tx.tx_hex(), time_lock, my_public_key, &[0; 20], &None, &[]) - .wait() - .unwrap(); + let refund_tx = block_on_f01(coin.create_taker_payment_refund_preimage( + &tx.tx_hex(), + time_lock, + my_public_key, + &[0; 20], + &None, + &[], + )) + .unwrap(); - let refund_tx = coin - .send_taker_payment_refund_preimage(RefundPaymentArgs { - payment_tx: &refund_tx.tx_hex(), - swap_contract_address: &None, - tx_type_with_secret_hash: SwapTxTypeWithSecretHash::TakerOrMakerPayment { - maker_secret_hash: &[0; 20], - }, - other_pubkey: my_public_key, - time_lock, - swap_unique_data: &[], - watcher_reward: false, - }) - .wait() - .unwrap(); + let refund_tx = block_on_f01(coin.send_taker_payment_refund_preimage(RefundPaymentArgs { + payment_tx: &refund_tx.tx_hex(), + swap_contract_address: &None, + tx_type_with_secret_hash: SwapTxTypeWithSecretHash::TakerOrMakerPayment { + maker_secret_hash: &[0; 20], + }, + other_pubkey: my_public_key, + time_lock, + swap_unique_data: &[], + watcher_reward: false, + })) + .unwrap(); let confirm_payment_input = ConfirmPaymentInput { payment_tx: refund_tx.tx_hex(), @@ -3367,7 +3202,7 @@ fn test_send_taker_payment_refund_preimage_utxo() { wait_until: timeout, check_every: 1, }; - coin.wait_for_confirmations(confirm_payment_input).wait().unwrap(); + block_on_f01(coin.wait_for_confirmations(confirm_payment_input)).unwrap(); let search_input = SearchForSwapTxSpendInput { time_lock, diff --git a/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs b/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs index 13c35cb0be..31c6c264e3 100644 --- a/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swaps_confs_settings_sync_tests.rs @@ -1,7 +1,7 @@ use crate::generate_utxo_coin_with_random_privkey; use crate::integration_tests_common::enable_native; use common::block_on; -use mm2_main::mm2::lp_swap::get_payment_locktime; +use mm2_main::lp_swap::get_payment_locktime; use mm2_rpc::data::legacy::OrderConfirmationsSettings; use mm2_test_helpers::for_tests::{mm_dump, MarketMakerIt}; use serde_json::Value as Json; diff --git a/mm2src/mm2_main/tests/docker_tests/swaps_file_lock_tests.rs b/mm2src/mm2_main/tests/docker_tests/swaps_file_lock_tests.rs index 8ca965e784..785eb0a849 100644 --- a/mm2src/mm2_main/tests/docker_tests/swaps_file_lock_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swaps_file_lock_tests.rs @@ -13,8 +13,8 @@ use std::time::Duration; 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"]}"#; +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","MakerPaymentSpendConfirmed","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":"MakerPaymentSpendConfirmed"}},{"timestamp":1588244078872,"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","MakerPaymentSpendConfirmed","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 (_ctx, _, bob_priv_key) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000.into()); diff --git a/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs b/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs index 80e4470d70..9fe3858736 100644 --- a/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/tendermint_tests.rs @@ -314,7 +314,7 @@ fn test_custom_gas_limit_on_tendermint_withdraw() { #[test] fn test_tendermint_ibc_withdraw() { // visit `{swagger_address}/ibc/core/channel/v1/channels?pagination.limit=10000` to see the full list of ibc channels - const IBC_SOURCE_CHANNEL: &str = "channel-1"; + const IBC_SOURCE_CHANNEL: &str = "channel-3"; const IBC_TARGET_ADDRESS: &str = "cosmos1r5v5srda7xfth3hn2s26txvrcrntldjumt8mhl"; const MY_ADDRESS: &str = "nuc150evuj4j7k9kgu38e453jdv9m3u0ft2n4fgzfr"; @@ -360,7 +360,7 @@ fn test_tendermint_ibc_withdraw() { #[test] fn test_tendermint_ibc_withdraw_hd() { // visit `{swagger_address}/ibc/core/channel/v1/channels?pagination.limit=10000` to see the full list of ibc channels - const IBC_SOURCE_CHANNEL: &str = "channel-1"; + const IBC_SOURCE_CHANNEL: &str = "channel-3"; const IBC_TARGET_ADDRESS: &str = "nuc150evuj4j7k9kgu38e453jdv9m3u0ft2n4fgzfr"; const MY_ADDRESS: &str = "cosmos134h9tv7866jcuw708w5w76lcfx7s3x2ysyalxy"; @@ -489,7 +489,7 @@ fn test_tendermint_tx_history() { const TEST_SEED: &str = "Vdo8Xt8pTAetRlMq3kV0LzE393eVYbPSn5Mhtw4p"; const TX_FINISHED_LOG: &str = "Tx history fetching finished for NUCLEUS-TEST."; const TX_HISTORY_PAGE_LIMIT: usize = 50; - const NUCLEUS_EXPECTED_TX_COUNT: u64 = 7; + const NUCLEUS_EXPECTED_TX_COUNT: u64 = 9; const IRIS_IBC_EXPECTED_TX_COUNT: u64 = 1; let nucleus_constant_history_txs = include_str!("../../../mm2_test_helpers/dummy_files/nucleus-history.json"); diff --git a/mm2src/mm2_main/tests/docker_tests_main.rs b/mm2src/mm2_main/tests/docker_tests_main.rs index d9c64039a0..707f558631 100644 --- a/mm2src/mm2_main/tests/docker_tests_main.rs +++ b/mm2src/mm2_main/tests/docker_tests_main.rs @@ -56,8 +56,8 @@ pub fn docker_tests_runner(tests: &[&TestDescAndFn]) { QTUM_REGTEST_DOCKER_IMAGE_WITH_TAG, GETH_DOCKER_IMAGE_WITH_TAG, NUCLEUS_IMAGE, - ATOM_IMAGE, - IBC_RELAYER_IMAGE, + ATOM_IMAGE_WITH_TAG, + IBC_RELAYER_IMAGE_WITH_TAG, ]; for image in IMAGES { @@ -90,6 +90,9 @@ pub fn docker_tests_runner(tests: &[&TestDescAndFn]) { wait_for_geth_node_ready(); init_geth_node(); + prepare_ibc_channels(ibc_relayer_node.container.id()); + + thread::sleep(Duration::from_secs(10)); wait_until_relayer_container_is_ready(ibc_relayer_node.container.id()); containers.push(utxo_node); @@ -172,6 +175,7 @@ fn remove_docker_containers(name: &str) { .expect("Failed to execute docker command"); } } + fn prepare_runtime_dir() -> std::io::Result { let project_root = { let mut current_dir = std::env::current_dir().unwrap(); diff --git a/mm2src/mm2_main/tests/docker_tests_sia_unique.rs b/mm2src/mm2_main/tests/docker_tests_sia_unique.rs new file mode 100644 index 0000000000..521da60e01 --- /dev/null +++ b/mm2src/mm2_main/tests/docker_tests_sia_unique.rs @@ -0,0 +1,106 @@ +#![allow(unused_imports, dead_code)] +#![cfg(feature = "enable-sia")] +#![feature(async_closure)] +#![feature(custom_test_frameworks)] +#![feature(test)] +#![test_runner(docker_tests_runner)] +#![feature(drain_filter)] +#![feature(hash_raw_entry)] +#![cfg(not(target_arch = "wasm32"))] + +#[cfg(test)] +#[macro_use] +extern crate common; +#[cfg(test)] +#[macro_use] +extern crate gstuff; +#[cfg(test)] +#[macro_use] +extern crate lazy_static; +#[cfg(test)] +#[macro_use] +extern crate serde_json; +#[cfg(test)] extern crate ser_error_derive; +#[cfg(test)] extern crate test; + +use std::env; +use std::io::{BufRead, BufReader}; +use std::path::PathBuf; +use std::process::Command; +use test::{test_main, StaticBenchFn, StaticTestFn, TestDescAndFn}; +use testcontainers::clients::Cli; + +mod docker_tests; +use docker_tests::docker_tests_common::*; + +#[allow(dead_code)] mod integration_tests_common; + +/// Custom test runner intended to initialize the SIA coin daemon in a Docker container. +pub fn docker_tests_runner(tests: &[&TestDescAndFn]) { + let docker = Cli::default(); + let mut containers = vec![]; + + let skip_docker_tests_runner = std::env::var("SKIP_DOCKER_TESTS_RUNNER") + .map(|v| v == "1") + .unwrap_or(false); + + if !skip_docker_tests_runner { + const IMAGES: &[&str] = &[SIA_DOCKER_IMAGE_WITH_TAG]; + + for image in IMAGES { + pull_docker_image(image); + remove_docker_containers(image); + } + + let sia_node = sia_docker_node(&docker, "SIA", 9980); + println!("ran container?"); + containers.push(sia_node); + } + // detect if docker is installed + // skip the tests that use docker if not installed + let owned_tests: Vec<_> = tests + .iter() + .map(|t| match t.testfn { + StaticTestFn(f) => TestDescAndFn { + testfn: StaticTestFn(f), + desc: t.desc.clone(), + }, + StaticBenchFn(f) => TestDescAndFn { + testfn: StaticBenchFn(f), + desc: t.desc.clone(), + }, + _ => panic!("non-static tests passed to lp_coins test runner"), + }) + .collect(); + let args: Vec = env::args().collect(); + test_main(&args, owned_tests, None); +} + +fn pull_docker_image(name: &str) { + Command::new("docker") + .arg("pull") + .arg(name) + .status() + .expect("Failed to execute docker command"); +} + +fn remove_docker_containers(name: &str) { + let stdout = Command::new("docker") + .arg("ps") + .arg("-f") + .arg(format!("ancestor={}", name)) + .arg("-q") + .output() + .expect("Failed to execute docker command"); + + let reader = BufReader::new(stdout.stdout.as_slice()); + let ids: Vec<_> = reader.lines().map(|line| line.unwrap()).collect(); + if !ids.is_empty() { + Command::new("docker") + .arg("rm") + .arg("-f") + .args(ids) + .status() + .expect("Failed to execute docker command"); + } +} diff --git a/mm2src/mm2_main/tests/integration_tests_common/mod.rs b/mm2src/mm2_main/tests/integration_tests_common/mod.rs index 13393bd8d1..0c22f3c8bd 100644 --- a/mm2src/mm2_main/tests/integration_tests_common/mod.rs +++ b/mm2src/mm2_main/tests/integration_tests_common/mod.rs @@ -2,12 +2,12 @@ use common::executor::Timer; use common::log::LogLevel; use common::{block_on, log, now_ms, wait_until_ms}; use crypto::privkey::key_pair_from_seed; -use mm2_main::mm2::{lp_main, LpMainParams}; +use mm2_main::{lp_main, LpMainParams}; use mm2_rpc::data::legacy::CoinInitResponse; use mm2_test_helpers::electrums::{doc_electrums, marty_electrums}; use mm2_test_helpers::for_tests::{create_new_account_status, enable_native as enable_native_impl, init_create_new_account, MarketMakerIt}; -use mm2_test_helpers::structs::{CreateNewAccountStatus, HDAccountAddressId, HDAccountBalance, InitTaskResult, +use mm2_test_helpers::structs::{CreateNewAccountStatus, HDAccountAddressId, HDAccountBalanceMap, InitTaskResult, RpcV2Response}; use serde_json::{self as json, Value as Json}; use std::collections::HashMap; @@ -99,7 +99,7 @@ pub async fn create_new_account( coin: &str, account_id: Option, timeout: u64, -) -> HDAccountBalance { +) -> HDAccountBalanceMap { let init = init_create_new_account(mm, coin, account_id).await; let init: RpcV2Response = json::from_value(init).unwrap(); let timeout = wait_until_ms(timeout * 1000); diff --git a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs index 06abd46a92..c8d3252ad4 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -5,7 +5,7 @@ use common::executor::Timer; use common::{cfg_native, cfg_wasm32, log, new_uuid}; use crypto::privkey::key_pair_from_seed; use http::{HeaderMap, StatusCode}; -use mm2_main::mm2::lp_ordermatch::MIN_ORDER_KEEP_ALIVE_INTERVAL; +use mm2_main::lp_ordermatch::MIN_ORDER_KEEP_ALIVE_INTERVAL; use mm2_metrics::{MetricType, MetricsJson}; use mm2_number::{BigDecimal, BigRational, Fraction, MmNumber}; use mm2_rpc::data::legacy::{CoinInitResponse, MmVersionResponse, OrderbookResponse}; @@ -14,15 +14,15 @@ use mm2_test_helpers::electrums::*; use mm2_test_helpers::for_tests::wait_check_stats_swap_status; use mm2_test_helpers::for_tests::{account_balance, btc_segwit_conf, btc_with_spv_conf, btc_with_sync_starting_header, check_recent_swaps, enable_qrc20, enable_utxo_v2_electrum, eth_dev_conf, - find_metrics_in_json, from_env_file, get_new_address, get_shared_db_id, mm_spat, - morty_conf, my_balance, rick_conf, sign_message, start_swaps, tbtc_conf, - tbtc_segwit_conf, tbtc_with_spv_conf, test_qrc20_history_impl, tqrc20_conf, - verify_message, wait_for_swaps_finish_and_check_status, - wait_till_history_has_records, MarketMakerIt, Mm2InitPrivKeyPolicy, Mm2TestConf, - Mm2TestConfForSwap, RaiiDump, DOC_ELECTRUM_ADDRS, ETH_MAINNET_NODE, - ETH_MAINNET_SWAP_CONTRACT, ETH_SEPOLIA_NODES, ETH_SEPOLIA_SWAP_CONTRACT, - MARTY_ELECTRUM_ADDRS, MORTY, QRC20_ELECTRUMS, RICK, RICK_ELECTRUM_ADDRS, - TBTC_ELECTRUMS, T_BCH_ELECTRUMS}; + find_metrics_in_json, from_env_file, get_new_address, get_shared_db_id, + get_wallet_names, mm_spat, morty_conf, my_balance, rick_conf, sign_message, + start_swaps, tbtc_conf, tbtc_segwit_conf, tbtc_with_spv_conf, + test_qrc20_history_impl, tqrc20_conf, verify_message, + wait_for_swaps_finish_and_check_status, wait_till_history_has_records, + MarketMakerIt, Mm2InitPrivKeyPolicy, Mm2TestConf, Mm2TestConfForSwap, RaiiDump, + DOC_ELECTRUM_ADDRS, ETH_MAINNET_NODE, ETH_MAINNET_SWAP_CONTRACT, ETH_SEPOLIA_NODES, + ETH_SEPOLIA_SWAP_CONTRACT, MARTY_ELECTRUM_ADDRS, MORTY, QRC20_ELECTRUMS, RICK, + RICK_ELECTRUM_ADDRS, TBTC_ELECTRUMS, T_BCH_ELECTRUMS}; use mm2_test_helpers::get_passphrase; use mm2_test_helpers::structs::*; use serde_json::{self as json, json, Value as Json}; @@ -35,7 +35,7 @@ use uuid::Uuid; cfg_native! { use common::block_on; - use mm2_test_helpers::for_tests::{get_passphrase, new_mm2_temp_folder_path}; + use mm2_test_helpers::for_tests::{get_passphrase, new_mm2_temp_folder_path, peer_connection_healthcheck}; use mm2_io::fs::slurp; use hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN; } @@ -2351,7 +2351,7 @@ fn test_electrum_tx_history() { {"coin":"RICK","asset":"RICK","rpcport":8923,"txversion":4,"overwintered":1,"protocol":{"type":"UTXO"}}, ]); - let mut mm = MarketMakerIt::start( + let mm_bob = MarketMakerIt::start( json! ({ "gui": "nogui", "netid": 9998, @@ -2367,14 +2367,26 @@ fn test_electrum_tx_history() { None, ) .unwrap(); + let (_dump_log, _dump_dashboard) = mm_bob.mm_dump(); + log!("log path: {}", mm_bob.log_path.display()); + + let bob_electrum = block_on(enable_electrum(&mm_bob, "RICK", false, DOC_ELECTRUM_ADDRS)); + let mut enable_res_bob = HashMap::new(); + enable_res_bob.insert("RICK", bob_electrum); + log!("enable_coins_bob: {:?}", enable_res_bob); + + let mmconf = Mm2TestConf::seednode_with_wallet_name(&coins, "wallet", "pass"); + let mut mm = MarketMakerIt::start(mmconf.conf, mmconf.rpc_password, None).unwrap(); let (_dump_log, _dump_dashboard) = mm.mm_dump(); log!("log path: {}", mm.log_path.display()); // Enable RICK electrum client with tx_history loop. let electrum = block_on(enable_electrum(&mm, "RICK", true, DOC_ELECTRUM_ADDRS)); + log!("enable_coins: {:?}", electrum); + let receiving_address = electrum.address; // Wait till tx_history will not be loaded - block_on(mm.wait_for_log(500., |log| log.contains("history has been loaded successfully"))).unwrap(); + block_on(mm.wait_for_log(5., |log| log.contains("history has been loaded successfully"))).unwrap(); // tx_history is requested every 30 seconds, wait another iteration thread::sleep(Duration::from_secs(31)); @@ -2384,16 +2396,13 @@ fn test_electrum_tx_history() { assert_eq!(get_tx_history_request_count(&mm), 1); // make a transaction to change balance - let mut enable_res = HashMap::new(); - enable_res.insert("RICK", electrum); - log!("enable_coins: {:?}", enable_res); withdraw_and_send( - &mm, + &mm_bob, "RICK", None, - "RRYmiZSDo3UdHHqj1rLKf8cbJroyv9NxXw", - &enable_res, - "-0.00001", + &receiving_address, + &enable_res_bob, + "-0.00101", 0.001, ); @@ -5662,7 +5671,7 @@ fn test_enable_utxo_with_enable_hd() { None, )); let balance = match rick.wallet_balance { - EnableCoinBalance::HD(hd) => hd, + EnableCoinBalanceMap::HD(hd) => hd, _ => panic!("Expected EnableCoinBalance::HD"), }; let account = balance.accounts.get(0).expect("Expected account at index 0"); @@ -5680,7 +5689,7 @@ fn test_enable_utxo_with_enable_hd() { None, )); let balance = match btc_segwit.wallet_balance { - EnableCoinBalance::HD(hd) => hd, + EnableCoinBalanceMap::HD(hd) => hd, _ => panic!("Expected EnableCoinBalance::HD"), }; let account = balance.accounts.get(0).expect("Expected account at index 0"); @@ -5809,6 +5818,41 @@ fn test_get_shared_db_id() { ); } +#[test] +#[cfg(not(target_arch = "wasm32"))] +fn test_get_wallet_names() { + let coins = json!([]); + + // Initialize the first wallet with a specific name + let wallet_1 = Mm2TestConf::seednode_with_wallet_name(&coins, "wallet_1", "pass"); + let mm_wallet_1 = MarketMakerIt::start(wallet_1.conf, wallet_1.rpc_password, None).unwrap(); + + // Retrieve and verify the wallet names for the first wallet + let get_wallet_names_1 = block_on(get_wallet_names(&mm_wallet_1)); + assert_eq!(get_wallet_names_1.wallet_names, vec!["wallet_1"]); + assert_eq!(get_wallet_names_1.activated_wallet.unwrap(), "wallet_1"); + + // Initialize the second wallet with a different name + let mut wallet_2 = Mm2TestConf::seednode_with_wallet_name(&coins, "wallet_2", "pass"); + + // Set the database directory for the second wallet to the same as the first wallet + wallet_2.conf["dbdir"] = mm_wallet_1.folder.join("DB").to_str().unwrap().into(); + + // Stop the first wallet before starting the second one + block_on(mm_wallet_1.stop()).unwrap(); + + // Start the second wallet + let mm_wallet_2 = MarketMakerIt::start(wallet_2.conf, wallet_2.rpc_password, None).unwrap(); + + // Retrieve and verify the wallet names for the second wallet + let get_wallet_names_2 = block_on(get_wallet_names(&mm_wallet_2)); + assert_eq!(get_wallet_names_2.wallet_names, vec!["wallet_1", "wallet_2"]); + assert_eq!(get_wallet_names_2.activated_wallet.unwrap(), "wallet_2"); + + // Stop the second wallet + block_on(mm_wallet_2.stop()).unwrap(); +} + #[test] #[cfg(not(target_arch = "wasm32"))] fn test_sign_raw_transaction_rick() { @@ -5919,6 +5963,46 @@ fn test_sign_raw_transaction_p2wpkh() { assert!(response["error"].as_str().unwrap().contains("Signing error")); } +#[test] +#[cfg(not(target_arch = "wasm32"))] +fn test_connection_healthcheck_rpc() { + const BOB_ADDRESS: &str = "12D3KooWEtuv7kmgGCC7oAQ31hB7AR5KkhT3eEWB2bP2roo3M7rY"; + const BOB_SEED: &str = "dummy-value-bob"; + + const ALICE_ADDRESS: &str = "12D3KooWHnoKd2Lr7BoxHCCeBhcnfAZsdiCdojbEMLE7DDSbMo1g"; + const ALICE_SEED: &str = "dummy-value-alice"; + + let bob_conf = Mm2TestConf::seednode(BOB_SEED, &json!([])); + let bob_mm = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); + + thread::sleep(Duration::from_secs(2)); + + let mut alice_conf = Mm2TestConf::seednode(ALICE_SEED, &json!([])); + alice_conf.conf["seednodes"] = json!([bob_mm.my_seed_addr()]); + alice_conf.conf["skip_startup_checks"] = json!(true); + let alice_mm = MarketMakerIt::start(alice_conf.conf, alice_conf.rpc_password, None).unwrap(); + + thread::sleep(Duration::from_secs(2)); + + // Self-address check for Bob + let response = block_on(peer_connection_healthcheck(&bob_mm, BOB_ADDRESS)); + assert_eq!(response["result"], json!(true)); + + // Check address of Alice + let response = block_on(peer_connection_healthcheck(&bob_mm, ALICE_ADDRESS)); + assert_eq!(response["result"], json!(true)); + + thread::sleep(Duration::from_secs(1)); + + // Self-address check for Alice + let response = block_on(peer_connection_healthcheck(&alice_mm, ALICE_ADDRESS)); + assert_eq!(response["result"], json!(true)); + + // Check address of Bob + let response = block_on(peer_connection_healthcheck(&alice_mm, BOB_ADDRESS)); + assert_eq!(response["result"], json!(true)); +} + #[cfg(all(feature = "run-device-tests", not(target_arch = "wasm32")))] mod trezor_tests { use coins::eth::{eth_coin_from_conf_and_request, gas_limit, EthCoin}; @@ -5937,8 +6021,8 @@ mod trezor_tests { use crypto::hw_rpc_task::HwRpcTaskAwaitingStatus; use crypto::CryptoCtx; use mm2_core::mm_ctx::MmArc; - use mm2_main::mm2::init_hw::init_trezor_user_action; - use mm2_main::mm2::init_hw::{init_trezor, init_trezor_status, InitHwRequest, InitHwResponse}; + use mm2_main::init_hw::init_trezor_user_action; + use mm2_main::init_hw::{init_trezor, init_trezor_status, InitHwRequest, InitHwResponse}; use mm2_test_helpers::electrums::tbtc_electrums; use mm2_test_helpers::for_tests::{enable_utxo_v2_electrum, eth_sepolia_trezor_firmware_compat_conf, eth_testnet_conf_trezor, init_trezor_rpc, init_trezor_status_rpc, diff --git a/mm2src/mm2_main/tests/mm2_tests/orderbook_sync_tests.rs b/mm2src/mm2_main/tests/mm2_tests/orderbook_sync_tests.rs index bc3ec5e93d..187404a580 100644 --- a/mm2src/mm2_main/tests/mm2_tests/orderbook_sync_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/orderbook_sync_tests.rs @@ -1,11 +1,11 @@ use crate::integration_tests_common::{enable_coins_rick_morty_electrum, enable_electrum, enable_electrum_json}; use common::{block_on, log}; use http::StatusCode; -use mm2_main::mm2::lp_ordermatch::MIN_ORDER_KEEP_ALIVE_INTERVAL; +use mm2_main::lp_ordermatch::MIN_ORDER_KEEP_ALIVE_INTERVAL; use mm2_number::{BigDecimal, BigRational, MmNumber}; use mm2_rpc::data::legacy::{AggregatedOrderbookEntry, CoinInitResponse, OrderbookResponse}; use mm2_test_helpers::electrums::doc_electrums; -use mm2_test_helpers::for_tests::{enable_z_coin_light, get_passphrase, morty_conf, orderbook_v2, rick_conf, +use mm2_test_helpers::for_tests::{enable_z_coin_light, get_passphrase, morty_conf, orderbook_v2, rick_conf, set_price, zombie_conf, MarketMakerIt, Mm2TestConf, DOC_ELECTRUM_ADDRS, MARTY_ELECTRUM_ADDRS, RICK, ZOMBIE_ELECTRUMS, ZOMBIE_LIGHTWALLETD_URLS, ZOMBIE_TICKER}; use mm2_test_helpers::get_passphrase; @@ -1308,6 +1308,79 @@ fn setprice_min_volume_should_be_displayed_in_orderbook() { assert_eq!(min_volume, "1", "Alice MORTY/RICK ask must display correct min_volume"); } +#[test] +fn test_order_cancellation_received_before_creation() { + let coins = json!([rick_conf(), morty_conf()]); + + let bob_passphrase = "bob passphrase"; + let mm_bob_conf = Mm2TestConf::seednode(bob_passphrase, &coins); + let mm_bob = MarketMakerIt::start(mm_bob_conf.conf, mm_bob_conf.rpc_password, None).unwrap(); + let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); + log!("Bob log path: {}", mm_bob.log_path.display()); + log!( + "enable_coins (bob): {:?}", + block_on(enable_coins_rick_morty_electrum(&mm_bob)) + ); + + // Bob places maker order before Alice connects to the network so that Alice receives the order creation through IHAVE/IWANT messages. + let set_price = block_on(set_price(&mm_bob, "RICK", "MORTY", "0.9", "0.9", false)); + + let mm_alice_conf = Mm2TestConf::light_node("alice passphrase", &coins, &[&mm_bob.ip.to_string()]); + let mut mm_alice = MarketMakerIt::start(mm_alice_conf.conf, mm_alice_conf.rpc_password, None).unwrap(); + let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); + log!("Alice log path: {}", mm_alice.log_path.display()); + log!( + "enable_coins (alice): {:?}", + block_on(enable_coins_rick_morty_electrum(&mm_alice)) + ); + + // Alice requests the orderbook to subscribe to the topic + let orderbook = block_on(orderbook_v2(&mm_alice, "RICK", "MORTY")); + let asks = orderbook["result"]["asks"].as_array().unwrap(); + // Alice should see the order in the orderbook as she got it through `GetOrderbook` p2p request. + assert_eq!(asks.len(), 1, "Alice RICK/MORTY orderbook must have exactly 1 ask"); + + // Bob cancels the order straight after Alice subscribes to the orderbook topic + // so that Alice receives the cancellation message faster than the creation message + // that she will receive later through IHAVE/IWANT messages. + let cancel_orders = block_on(mm_bob.rpc(&json! ({ + "userpass": mm_bob.userpass, + "method": "cancel_all_orders", + "cancel_by": { + "type": "All", + } + }))) + .unwrap(); + assert!(cancel_orders.0.is_success(), "!cancel_all_orders: {}", cancel_orders.1); + + // Make sure Alice receives the order cancellation message. + block_on(mm_alice.wait_for_log(10., |log| { + log.contains("received ordermatch message MakerOrderCancelled") + })) + .unwrap(); + + // Make sure Alice receives the order creation message. + block_on(mm_alice.wait_for_log(10., |log| log.contains("received ordermatch message MakerOrderCreated"))).unwrap(); + + // Make sure Alice ignores inserting of the order into the orderbook. + block_on(mm_alice.wait_for_log(10., |log| { + log.contains(&format!( + "Maker order {} was recently cancelled, ignoring", + set_price.result.uuid + )) + })) + .unwrap(); + + // Check Alice's orderbook again to make sure the order wasn't re-inserted after the cancellation. + let orderbook = block_on(orderbook_v2(&mm_alice, "RICK", "MORTY")); + let asks = orderbook["result"]["asks"].as_array().unwrap(); + // Alice shouldn't find the order in the orderbook. + assert_eq!(asks.len(), 0, "Alice RICK/MORTY orderbook must have exactly 0 ask"); + + block_on(mm_bob.stop()).unwrap(); + block_on(mm_alice.stop()).unwrap(); +} + // ignored because it requires a long-running ZOMBIE initialization process #[test] #[ignore] diff --git a/mm2src/mm2_net/Cargo.toml b/mm2src/mm2_net/Cargo.toml index 844f07e1f3..a720327d96 100644 --- a/mm2src/mm2_net/Cargo.toml +++ b/mm2src/mm2_net/Cargo.toml @@ -7,11 +7,9 @@ edition = "2018" doctest = false [features] -event-stream = ["mm2_event_stream", "async-stream" , "p2p"] -p2p = ["mm2-libp2p", "parking_lot"] [dependencies] -async-stream = { version = "0.3", optional = true } +async-stream = { version = "0.3" } async-trait = "0.1" bytes = "1.1" cfg-if = "1.0" @@ -19,14 +17,13 @@ common = { path = "../common" } derive_more = "0.99" ethkey = { git = "https://github.com/KomodoPlatform/mm2-parity-ethereum.git", rev = "mm2-v2.1.1" } futures = { version = "0.3", package = "futures", features = ["compat", "async-await", "thread-pool"] } +gstuff = "0.7" http = "0.2" lazy_static = "1.4" mm2_core = { path = "../mm2_core" } mm2_err_handle = { path = "../mm2_err_handle" } -mm2_event_stream = { path = "../mm2_event_stream", optional = true } -mm2-libp2p = { path = "../mm2_p2p", package = "mm2_p2p", optional = true } -parking_lot = { version = "0.12.0", features = ["nightly"], optional = true } -prost = "0.11" +mm2_number = { path = "../mm2_number" } +prost = "0.12" rand = { version = "0.7", features = ["std", "small_rng", "wasm-bindgen"] } serde = "1" serde_json = { version = "1", features = ["preserve_order", "raw_value"] } @@ -35,13 +32,12 @@ thiserror = "1.0.30" [target.'cfg(target_arch = "wasm32")'.dependencies] base64 = "0.21.7" futures-util = "0.3" -gstuff = { version = "0.7", features = ["nightly"] } mm2_state_machine = { path = "../mm2_state_machine"} http-body = "0.4" httparse = "1.8.0" js-sys = "0.3.27" pin-project = "1.1.2" -tonic = { version = "0.9", default-features = false, features = ["prost", "codegen"] } +tonic = { version = "0.10", default-features = false, features = ["prost", "codegen"] } tower-service = "0.3" wasm-bindgen = "0.2.86" wasm-bindgen-test = { version = "0.3.2" } @@ -55,7 +51,6 @@ web-sys = { version = "0.3.55", features = ["console", "CloseEvent", "DomExcepti [target.'cfg(not(target_arch = "wasm32"))'.dependencies] futures-util = { version = "0.3" } hyper = { version = "0.14.26", features = ["client", "http2", "server", "tcp", "stream"] } -gstuff = { version = "0.7", features = ["nightly"] } rustls = { version = "0.21", default-features = false } tokio = { version = "1.20" } tokio-rustls = { version = "0.24", default-features = false } diff --git a/mm2src/mm2_net/src/lib.rs b/mm2src/mm2_net/src/lib.rs index 954e25c5a0..4ae26ca182 100644 --- a/mm2src/mm2_net/src/lib.rs +++ b/mm2src/mm2_net/src/lib.rs @@ -1,13 +1,9 @@ pub mod grpc_web; -#[cfg(feature = "event-stream")] pub mod network_event; -#[cfg(feature = "p2p")] pub mod p2p; pub mod transport; #[cfg(not(target_arch = "wasm32"))] pub mod ip_addr; #[cfg(not(target_arch = "wasm32"))] pub mod native_http; #[cfg(not(target_arch = "wasm32"))] pub mod native_tls; -#[cfg(all(feature = "event-stream", not(target_arch = "wasm32")))] -pub mod sse_handler; +#[cfg(not(target_arch = "wasm32"))] pub mod sse_handler; #[cfg(target_arch = "wasm32")] pub mod wasm; -#[cfg(all(feature = "event-stream", target_arch = "wasm32"))] -pub mod wasm_event_stream; +#[cfg(target_arch = "wasm32")] pub mod wasm_event_stream; diff --git a/mm2src/mm2_net/src/sse_handler.rs b/mm2src/mm2_net/src/sse_handler.rs index 3b3afeee58..568bfc98c0 100644 --- a/mm2src/mm2_net/src/sse_handler.rs +++ b/mm2src/mm2_net/src/sse_handler.rs @@ -1,12 +1,11 @@ use hyper::{body::Bytes, Body, Request, Response}; use mm2_core::mm_ctx::MmArc; use serde_json::json; -use std::convert::Infallible; pub const SSE_ENDPOINT: &str = "/event-stream"; /// Handles broadcasted messages from `mm2_event_stream` continuously. -pub async fn handle_sse(request: Request, ctx_h: u32) -> Result, Infallible> { +pub async fn handle_sse(request: Request, ctx_h: u32) -> Response { // This is only called once for per client on the initialization, // meaning this is not a resource intensive computation. let ctx = match MmArc::from_ffi_handle(ctx_h) { @@ -62,17 +61,15 @@ pub async fn handle_sse(request: Request, ctx_h: u32) -> Result Ok(res), + Ok(res) => res, Err(err) => handle_internal_error(err.to_string()).await, } } /// Fallback function for handling errors in SSE connections -async fn handle_internal_error(message: String) -> Result, Infallible> { - let response = Response::builder() +async fn handle_internal_error(message: String) -> Response { + Response::builder() .status(500) .body(Body::from(message)) - .expect("Returning 500 should never fail."); - - Ok(response) + .expect("Returning 500 should never fail.") } diff --git a/mm2src/mm2_net/src/transport.rs b/mm2src/mm2_net/src/transport.rs index dd966feb84..750d2561c2 100644 --- a/mm2src/mm2_net/src/transport.rs +++ b/mm2src/mm2_net/src/transport.rs @@ -1,6 +1,5 @@ use common::jsonrpc_client::JsonRpcErrorType; use derive_more::Display; -use ethkey::Secret; use http::{HeaderMap, StatusCode}; use mm2_err_handle::prelude::*; use serde::{Deserialize, Serialize}; @@ -69,23 +68,6 @@ where }) } -#[derive(Clone, Debug)] -pub struct ProxyAuthValidationGenerator { - pub coin_ticker: String, - pub secret: Secret, - pub address: String, -} - -/// Proxy-auth specific data-type that needed in order to perform proxy-auth calls. -/// Represents a signed message used for authenticating and validating requests processed by the proxy. -#[derive(Clone, Serialize)] -pub struct KomodefiProxyAuthValidation { - pub coin_ticker: String, - pub address: String, - pub timestamp_message: i64, - pub signature: String, -} - /// Errors encountered when making HTTP requests to fetch information from a URI. #[derive(Clone, Debug, Deserialize, Display, PartialEq, Serialize)] pub enum GetInfoFromUriError { diff --git a/mm2src/mm2_net/src/wasm/wasm_ws.rs b/mm2src/mm2_net/src/wasm/wasm_ws.rs index 7464dcf142..1d19d43a60 100644 --- a/mm2src/mm2_net/src/wasm/wasm_ws.rs +++ b/mm2src/mm2_net/src/wasm/wasm_ws.rs @@ -22,7 +22,7 @@ const NORMAL_CLOSURE_CODE: u16 = 1000; pub type ConnIdx = usize; -pub type WsOutgoingReceiver = mpsc::Receiver; +pub type WsOutgoingReceiver = mpsc::Receiver>; pub type WsIncomingSender = mpsc::Sender<(ConnIdx, WebSocketEvent)>; type WsTransportReceiver = mpsc::Receiver; @@ -69,14 +69,14 @@ impl InitWsError { } } -/// The `WsEventReceiver` wrapper that filters and maps the incoming `WebSocketEvent` events into `Result`. +/// The `WsEventReceiver` wrapper that filters and maps the incoming `WebSocketEvent` events into `Result, WebSocketError>`. pub struct WsIncomingReceiver { inner: WsEventReceiver, closed: bool, } impl Stream for WsIncomingReceiver { - type Item = Result; + type Item = Result, WebSocketError>; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { if self.closed { @@ -122,7 +122,7 @@ impl Stream for WsEventReceiver { #[derive(Debug, Clone)] pub struct WsOutgoingSender { - inner: mpsc::Sender, + inner: mpsc::Sender>, /// Is used to determine when all senders are dropped. #[allow(dead_code)] shutdown_tx: OutgoingShutdownTx, @@ -132,9 +132,9 @@ pub struct WsOutgoingSender { /// Please note `WsOutgoingSender` must not provide a way to close the [`WsOutgoingSender::inner`] channel, /// because the shutdown_tx wouldn't be closed properly. impl WsOutgoingSender { - pub async fn send(&mut self, msg: Json) -> Result<(), SendError> { self.inner.send(msg).await } + pub async fn send(&mut self, msg: Vec) -> Result<(), SendError> { self.inner.send(msg).await } - pub fn try_send(&mut self, msg: Json) -> Result<(), TrySendError> { self.inner.try_send(msg) } + pub fn try_send(&mut self, msg: Vec) -> Result<(), TrySendError>> { self.inner.try_send(msg) } } #[derive(Debug)] @@ -147,12 +147,12 @@ pub enum WebSocketEvent { /// Please note some of the errors lead to the connection close. Error(WebSocketError), /// A message has been received through a WebSocket connection. - Incoming(Json), + Incoming(Vec), } #[derive(Debug)] pub enum WebSocketError { - OutgoingError { reason: OutgoingError, outgoing: Json }, + OutgoingError { reason: OutgoingError, outgoing: Vec }, InvalidIncoming { description: String }, } @@ -212,6 +212,7 @@ fn spawn_ws_transport( ) -> InitWsResult<(WsOutgoingSender, WsEventReceiver)> { let (ws, ws_transport_rx) = WebSocketImpl::init(url)?; let (incoming_tx, incoming_rx, incoming_shutdown) = incoming_channel(1024); + let (outgoing_tx, outgoing_rx, outgoing_shutdown) = outgoing_channel(1024); let user_shutdown = into_one_shutdown(incoming_shutdown, outgoing_shutdown); @@ -353,17 +354,11 @@ impl WebSocketImpl { Ok((WebSocketImpl { ws, closures }, rx)) } - fn send_to_ws(&self, outgoing: Json) -> Result<(), WebSocketError> { - match json::to_string(&outgoing) { - Ok(req) => self.ws.send_with_str(&req).map_err(|error| { - let reason = OutgoingError::UnderlyingError(stringify_js_error(&error)); - WebSocketError::OutgoingError { reason, outgoing } - }), - Err(e) => { - let reason = OutgoingError::SerializingError(e.to_string()); - Err(WebSocketError::OutgoingError { reason, outgoing }) - }, - } + fn send_to_ws(&self, outgoing: Vec) -> Result<(), WebSocketError> { + self.ws.send_with_u8_array(&outgoing).map_err(|error| { + let reason = OutgoingError::UnderlyingError(stringify_js_error(&error)); + WebSocketError::OutgoingError { reason, outgoing } + }) } fn validate_websocket_url(url: &str) -> Result<(), MmError> { @@ -423,7 +418,7 @@ impl WsStateMachine { } } - fn send_unexpected_outgoing_back(&mut self, outgoing: Json, current_state: &str) { + fn send_unexpected_outgoing_back(&mut self, outgoing: Vec, current_state: &str) { error!( "Unexpected outgoing message while the socket idx={} state is {}", self.idx, current_state @@ -478,7 +473,7 @@ enum StateEvent { /// All instances of `WsOutgoingSender` and `WsIncomingReceiver` were dropped. UserSideClosed, /// Received an outgoing message. It should be forwarded to `WebSocket`. - OutgoingMessage(Json), + OutgoingMessage(Vec), /// Received a `WsTransportEvent` event. It might be an incoming message from `WebSocket` or something else. WsTransportEvent(WsTransportEvent), } @@ -491,7 +486,7 @@ enum WsTransportEvent { code: u16, }, Error(WsTransportError), - Incoming(Json), + Incoming(Vec), } #[derive(Debug)] @@ -565,8 +560,8 @@ impl State for ConnectingState { } }, StateEvent::WsTransportEvent(WsTransportEvent::Incoming(incoming)) => error!( - "Unexpected incoming message {} while the socket idx={} state is ConnectingState", - ctx.idx, incoming + "Unexpected incoming message {:?} while the socket idx={} state is ConnectingState", + incoming, ctx.idx ), } } @@ -647,11 +642,11 @@ impl ClosedState { } } -fn decode_incoming(incoming: MessageEvent) -> Result { +fn decode_incoming(incoming: MessageEvent) -> Result, String> { match incoming.data().dyn_into::() { Ok(txt) => { let txt = String::from(txt); - json::from_str(&txt).map_err(|e| format!("Error deserializing an incoming payload: {}", e)) + Ok(txt.into_bytes()) }, Err(e) => Err(format!("Unknown MessageEvent {:?}", e)), } @@ -724,10 +719,12 @@ mod tests { "method": "server.version", "params": ["1.2", "1.4"], }); + let get_version = json::to_vec(&get_version).expect("Vec serialization won't fail"); outgoing_tx.send(get_version).await.expect("!outgoing_tx.send"); match incoming_rx.next().timeout_secs(5.).await.unwrap_w() { Some((_conn_idx, WebSocketEvent::Incoming(response))) => { + let response: Json = json::from_slice(&response).expect("Failed to parse incoming message"); debug!("Response: {:?}", response); assert!(response.get("result").is_some()); }, diff --git a/mm2src/mm2_number/src/lib.rs b/mm2src/mm2_number/src/lib.rs index 6d1d20f1ca..ba8b520ec0 100644 --- a/mm2src/mm2_number/src/lib.rs +++ b/mm2src/mm2_number/src/lib.rs @@ -13,7 +13,7 @@ pub use num_bigint; pub use num_rational; pub use bigdecimal::BigDecimal; -pub use num_bigint::{BigInt, BigUint}; +pub use num_bigint::{BigInt, BigUint, ParseBigIntError}; pub use num_rational::BigRational; pub use paste::paste; diff --git a/mm2src/mm2_p2p/Cargo.toml b/mm2src/mm2_p2p/Cargo.toml index 7001ebeabf..6b7f43e7f4 100644 --- a/mm2src/mm2_p2p/Cargo.toml +++ b/mm2src/mm2_p2p/Cargo.toml @@ -3,6 +3,10 @@ name = "mm2_p2p" version = "0.1.0" edition = "2021" +[features] +default = [] +application = ["dep:mm2_number"] + [lib] doctest = false @@ -15,12 +19,17 @@ futures-ticker = "0.0.3" hex = "0.4.2" lazy_static = "1.4" log = "0.4" +mm2_core = { path = "../mm2_core" } +mm2_event_stream = { path = "../mm2_event_stream" } +mm2_number = { path = "../mm2_number", optional = true } +parking_lot = { version = "0.12.0", features = ["nightly"] } rand = { version = "0.7", default-features = false, features = ["wasm-bindgen"] } regex = "1" rmp-serde = "0.14.3" secp256k1 = { version = "0.20", features = ["rand"] } serde = { version = "1.0", default-features = false } serde_bytes = "0.11.5" +serde_json = { version = "1", features = ["preserve_order", "raw_value"] } sha2 = "0.10" smallvec = "1.6.1" syn = "2.0.18" diff --git a/mm2src/mm2_p2p/src/application/mod.rs b/mm2src/mm2_p2p/src/application/mod.rs new file mode 100644 index 0000000000..bccb70ac5c --- /dev/null +++ b/mm2src/mm2_p2p/src/application/mod.rs @@ -0,0 +1,5 @@ +//! This module contains KDF application logic related to P2P network gated +//! by the "application" feature. + +pub mod network_event; +pub mod request_response; diff --git a/mm2src/mm2_net/src/network_event.rs b/mm2src/mm2_p2p/src/application/network_event.rs similarity index 78% rename from mm2src/mm2_net/src/network_event.rs rename to mm2src/mm2_p2p/src/application/network_event.rs index b88655f383..c3c0a0eb5c 100644 --- a/mm2src/mm2_net/src/network_event.rs +++ b/mm2src/mm2_p2p/src/application/network_event.rs @@ -1,12 +1,11 @@ -use crate::p2p::P2PContext; use async_trait::async_trait; use common::{executor::{SpawnFuture, Timer}, log::info}; use futures::channel::oneshot::{self, Receiver, Sender}; + use mm2_core::mm_ctx::MmArc; pub use mm2_event_stream::behaviour::EventBehaviour; use mm2_event_stream::{behaviour::EventInitStatus, Event, EventName, EventStreamConfiguration}; -use mm2_libp2p::behaviours::atomicdex; use serde_json::json; pub struct NetworkEvent { @@ -22,7 +21,7 @@ impl EventBehaviour for NetworkEvent { fn event_name() -> EventName { EventName::NETWORK } async fn handle(self, interval: f64, tx: oneshot::Sender) { - let p2p_ctx = P2PContext::fetch_from_mm_arc(&self.ctx); + let p2p_ctx = crate::p2p_ctx::P2PContext::fetch_from_mm_arc(&self.ctx); let mut previously_sent = json!({}); tx.send(EventInitStatus::Success).unwrap(); @@ -30,14 +29,14 @@ impl EventBehaviour for NetworkEvent { loop { let p2p_cmd_tx = p2p_ctx.cmd_tx.lock().clone(); - let peers_info = atomicdex::get_peers_info(p2p_cmd_tx.clone()).await; - let gossip_mesh = atomicdex::get_gossip_mesh(p2p_cmd_tx.clone()).await; - let gossip_peer_topics = atomicdex::get_gossip_peer_topics(p2p_cmd_tx.clone()).await; - let gossip_topic_peers = atomicdex::get_gossip_topic_peers(p2p_cmd_tx.clone()).await; - let relay_mesh = atomicdex::get_relay_mesh(p2p_cmd_tx).await; + let directly_connected_peers = crate::get_directly_connected_peers(p2p_cmd_tx.clone()).await; + let gossip_mesh = crate::get_gossip_mesh(p2p_cmd_tx.clone()).await; + let gossip_peer_topics = crate::get_gossip_peer_topics(p2p_cmd_tx.clone()).await; + let gossip_topic_peers = crate::get_gossip_topic_peers(p2p_cmd_tx.clone()).await; + let relay_mesh = crate::get_relay_mesh(p2p_cmd_tx).await; let event_data = json!({ - "peers_info": peers_info, + "directly_connected_peers": directly_connected_peers, "gossip_mesh": gossip_mesh, "gossip_peer_topics": gossip_peer_topics, "gossip_topic_peers": gossip_topic_peers, diff --git a/mm2src/mm2_p2p/src/application/request_response/mod.rs b/mm2src/mm2_p2p/src/application/request_response/mod.rs new file mode 100644 index 0000000000..28da482bdc --- /dev/null +++ b/mm2src/mm2_p2p/src/application/request_response/mod.rs @@ -0,0 +1,21 @@ +//! This module defines types exclusively for the request-response P2P protocol +//! which are separate from other request types such as RPC requests or Gossipsub +//! messages. + +pub mod network_info; +pub mod ordermatch; + +use serde::{Deserialize, Serialize}; + +/// Wrapper type for handling request-response P2P requests. +#[derive(Eq, Debug, Deserialize, PartialEq, Serialize)] +pub enum P2PRequest { + /// Request for order matching. + Ordermatch(ordermatch::OrdermatchRequest), + /// Request for network information from the target peer. + /// + /// TODO: This should be called `PeerInfoRequest` instead. However, renaming it + /// will introduce a breaking change in the network and is not worth it. Do this + /// renaming when there is already a breaking change in the release. + NetworkInfo(network_info::NetworkInfoRequest), +} diff --git a/mm2src/mm2_p2p/src/application/request_response/network_info.rs b/mm2src/mm2_p2p/src/application/request_response/network_info.rs new file mode 100644 index 0000000000..c8dece2ef5 --- /dev/null +++ b/mm2src/mm2_p2p/src/application/request_response/network_info.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +/// Wraps the different types of network information requests for the P2P request-response +/// protocol. +#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum NetworkInfoRequest { + /// Get MM2 version of nodes added to stats collection + GetMm2Version, +} diff --git a/mm2src/mm2_p2p/src/application/request_response/ordermatch.rs b/mm2src/mm2_p2p/src/application/request_response/ordermatch.rs new file mode 100644 index 0000000000..250758f594 --- /dev/null +++ b/mm2src/mm2_p2p/src/application/request_response/ordermatch.rs @@ -0,0 +1,46 @@ +use mm2_number::BigRational; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +type AlbOrderedOrderbookPair = String; +type H64 = [u8; 8]; + +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum BestOrdersAction { + Buy, + Sell, +} + +/// Wraps the different types of order matching requests for the P2P request-response protocol. +/// +/// TODO: We should use fixed sizes for dynamic fields (such as strings and maps) +/// and prefer stricter types instead of accepting `String` for nearly everything. +/// See https://github.com/KomodoPlatform/komodo-defi-framework/issues/2236 for reference. +#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)] +pub enum OrdermatchRequest { + /// Get an orderbook for the given pair. + GetOrderbook { base: String, rel: String }, + /// Sync specific pubkey orderbook state if our known Patricia trie state doesn't match the latest keep alive message + SyncPubkeyOrderbookState { + pubkey: String, + /// Request using this condition + trie_roots: HashMap, + }, + /// Request best orders for a specific coin and action. + BestOrders { + coin: String, + action: BestOrdersAction, + volume: BigRational, + }, + /// Get orderbook depth for the specified pairs + OrderbookDepth { pairs: Vec<(String, String)> }, + /// Request best orders for a specific coin and action limited by the number of results. + /// + /// Q: Shouldn't we support pagination here? + BestOrdersByNumber { + coin: String, + action: BestOrdersAction, + number: usize, + }, +} diff --git a/mm2src/mm2_p2p/src/behaviours/atomicdex.rs b/mm2src/mm2_p2p/src/behaviours/atomicdex.rs index c1bcea5476..9d58da4e1e 100644 --- a/mm2src/mm2_p2p/src/behaviours/atomicdex.rs +++ b/mm2src/mm2_p2p/src/behaviours/atomicdex.rs @@ -18,7 +18,6 @@ use libp2p::{identity, noise, PeerId, Swarm}; use libp2p::{Multiaddr, Transport}; use log::{debug, error, info}; use rand::seq::SliceRandom; -use rand::Rng; use std::collections::hash_map::DefaultHasher; use std::collections::HashMap; use std::hash::{Hash, Hasher}; @@ -163,8 +162,8 @@ pub enum AdexBehaviourCmd { }, } -/// Returns info about connected peers -pub async fn get_peers_info(mut cmd_tx: AdexCmdTx) -> HashMap> { +/// Returns info about directly connected peers. +pub async fn get_directly_connected_peers(mut cmd_tx: AdexCmdTx) -> HashMap> { let (result_tx, rx) = oneshot::channel(); let cmd = AdexBehaviourCmd::GetPeersInfo { result_tx }; cmd_tx.send(cmd).await.expect("Rx should be present"); @@ -563,18 +562,10 @@ impl From for AdexBehaviourError { fn from(e: PublishError) -> Self { AdexBehaviourError::PublishError(e) } } -fn generate_ed25519_keypair(rng: &mut R, force_key: Option<[u8; 32]>) -> identity::Keypair { - let mut raw_key = match force_key { - Some(key) => key, - None => { - let mut key = [0; 32]; - rng.fill_bytes(&mut key); - key - }, - }; - let secret = identity::ed25519::SecretKey::try_from_bytes(&mut raw_key).expect("Secret length is 32 bytes"); +pub fn generate_ed25519_keypair(mut p2p_key: [u8; 32]) -> identity::Keypair { + let secret = identity::ed25519::SecretKey::try_from_bytes(&mut p2p_key).expect("Secret length is 32 bytes"); let keypair = identity::ed25519::Keypair::from(secret); - keypair.into() + identity::Keypair::from(keypair) } /// Custom types mapping the complex associated types of AtomicDexBehaviour to the ExpandedSwarm @@ -596,7 +587,7 @@ fn start_gossipsub( ) -> Result<(Sender, AdexEventRx, PeerId), AdexBehaviourError> { let i_am_relay = config.node_type.is_relay(); let mut rng = rand::thread_rng(); - let local_key = generate_ed25519_keypair(&mut rng, config.force_key); + let local_key = generate_ed25519_keypair(config.p2p_key); let local_peer_id = PeerId::from(local_key.public()); info!("Local peer id: {:?}", local_peer_id); @@ -1081,7 +1072,7 @@ impl NetworkBehaviour for AtomicDexBehaviour { pub struct GossipsubConfig { netid: u16, - force_key: Option<[u8; 32]>, + p2p_key: [u8; 32], runtime: SwarmRuntime, to_dial: Vec, node_type: NodeType, @@ -1091,9 +1082,12 @@ pub struct GossipsubConfig { impl GossipsubConfig { #[cfg(test)] pub(crate) fn new_for_tests(runtime: SwarmRuntime, to_dial: Vec, node_type: NodeType) -> Self { + let mut p2p_key = [0u8; 32]; + common::os_rng(&mut p2p_key).unwrap(); + GossipsubConfig { netid: 333, - force_key: None, + p2p_key, runtime, to_dial, node_type, @@ -1101,10 +1095,10 @@ impl GossipsubConfig { } } - pub fn new(netid: u16, runtime: SwarmRuntime, node_type: NodeType) -> Self { + pub fn new(netid: u16, runtime: SwarmRuntime, node_type: NodeType, p2p_key: [u8; 32]) -> Self { GossipsubConfig { netid, - force_key: None, + p2p_key, runtime, to_dial: vec![], node_type, @@ -1117,11 +1111,6 @@ impl GossipsubConfig { self } - pub fn force_key(&mut self, force_key: Option<[u8; 32]>) -> &mut Self { - self.force_key = force_key; - self - } - pub fn max_num_streams(&mut self, max_num_streams: usize) -> &mut Self { self.max_num_streams = max_num_streams; self diff --git a/mm2src/mm2_p2p/src/lib.rs b/mm2src/mm2_p2p/src/lib.rs index 03d6a301db..b1d0283be0 100644 --- a/mm2src/mm2_p2p/src/lib.rs +++ b/mm2src/mm2_p2p/src/lib.rs @@ -6,20 +6,26 @@ mod network; mod relay_address; mod swarm_runtime; +#[cfg(feature = "application")] pub mod application; +pub mod p2p_ctx; + +use derive_more::Display; use lazy_static::lazy_static; use secp256k1::{Message as SecpMessage, PublicKey as Secp256k1Pubkey, Secp256k1, SecretKey, SignOnly, Signature, VerifyOnly}; use serde::{de, Deserialize, Serialize, Serializer}; use sha2::digest::Update; use sha2::{Digest, Sha256}; +use std::str::FromStr; pub use crate::swarm_runtime::SwarmRuntime; // atomicdex related re-exports -pub use behaviours::atomicdex::{get_gossip_mesh, get_gossip_peer_topics, get_gossip_topic_peers, get_peers_info, - get_relay_mesh, spawn_gossipsub, AdexBehaviourCmd, AdexBehaviourError, - AdexBehaviourEvent, AdexCmdTx, AdexEventRx, AdexResponse, AdexResponseChannel, - GossipsubEvent, GossipsubMessage, MessageId, NodeType, TopicHash, WssCerts}; +pub use behaviours::atomicdex::{get_directly_connected_peers, get_gossip_mesh, get_gossip_peer_topics, + get_gossip_topic_peers, get_relay_mesh, spawn_gossipsub, AdexBehaviourCmd, + AdexBehaviourError, AdexBehaviourEvent, AdexCmdTx, AdexEventRx, AdexResponse, + AdexResponseChannel, GossipsubEvent, GossipsubMessage, MessageId, NodeType, TopicHash, + WssCerts}; // peers-exchange re-exports pub use behaviours::peers_exchange::PeerAddresses; @@ -29,7 +35,7 @@ pub use behaviours::request_response::RequestResponseBehaviourEvent; // libp2p related re-exports pub use libp2p::identity::DecodingError; -pub use libp2p::identity::{secp256k1::PublicKey as Libp2pSecpPublic, PublicKey as Libp2pPublic}; +pub use libp2p::identity::{secp256k1::PublicKey as Libp2pSecpPublic, Keypair, PublicKey as Libp2pPublic, SigningError}; pub use libp2p::{Multiaddr, PeerId}; // relay-address related re-exports @@ -42,6 +48,69 @@ lazy_static! { static ref SECP_SIGN: Secp256k1 = Secp256k1::signing_only(); } +/// Wrapper of `libp2p::PeerId` with trait additional implementations. +/// +/// TODO: This should be used as a replacement of `libp2p::PeerId` in the entire project. +#[derive(Clone, Copy, Debug, Display, Eq, Hash, PartialEq)] +pub struct PeerAddress(PeerId); + +impl From for PeerAddress { + fn from(value: PeerId) -> Self { Self(value) } +} + +impl From for PeerId { + fn from(value: PeerAddress) -> Self { value.0 } +} + +impl Serialize for PeerAddress { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.0.to_string()) + } +} + +impl<'de> Deserialize<'de> for PeerAddress { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct PeerAddressVisitor; + + impl<'de> serde::de::Visitor<'de> for PeerAddressVisitor { + type Value = PeerAddress; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a string representation of peer id.") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + if value.len() > 100 { + return Err(serde::de::Error::invalid_length( + value.len(), + &"peer id cannot exceed 100 characters.", + )); + } + + Ok(PeerId::from_str(value).map_err(de::Error::custom)?.into()) + } + + fn visit_string(self, value: String) -> Result + where + E: de::Error, + { + self.visit_str(&value) + } + } + + deserializer.deserialize_str(PeerAddressVisitor) + } +} + #[derive(Clone, Copy, Debug)] pub enum NetworkInfo { /// The in-memory network. diff --git a/mm2src/mm2_net/src/p2p.rs b/mm2src/mm2_p2p/src/p2p_ctx.rs similarity index 62% rename from mm2src/mm2_net/src/p2p.rs rename to mm2src/mm2_p2p/src/p2p_ctx.rs index d76cd550bd..2d9d991298 100644 --- a/mm2src/mm2_net/src/p2p.rs +++ b/mm2src/mm2_p2p/src/p2p_ctx.rs @@ -1,20 +1,31 @@ +use libp2p::{identity::Keypair, PeerId}; use mm2_core::mm_ctx::MmArc; -use mm2_libp2p::behaviours::atomicdex::AdexCmdTx; use parking_lot::Mutex; use std::sync::Arc; +use crate::AdexCmdTx; + pub struct P2PContext { /// Using Mutex helps to prevent cloning which can actually result to channel being unbounded in case of using 1 tx clone per 1 message. pub cmd_tx: Mutex, + /// Host's keypair used for address derivation of peer and message signing. + keypair: Keypair, } impl P2PContext { - pub fn new(cmd_tx: AdexCmdTx) -> Self { + pub fn new(cmd_tx: AdexCmdTx, keypair: Keypair) -> Self { P2PContext { cmd_tx: Mutex::new(cmd_tx), + keypair, } } + #[inline(always)] + pub fn keypair(&self) -> &Keypair { &self.keypair } + + #[inline(always)] + pub fn peer_id(&self) -> PeerId { self.keypair.public().to_peer_id() } + pub fn store_to_mm_arc(self, ctx: &MmArc) { *ctx.p2p_ctx.lock().unwrap() = Some(Arc::new(self)) } pub fn fetch_from_mm_arc(ctx: &MmArc) -> Arc { diff --git a/mm2src/mm2_test_helpers/contract_bytes/erc1155_test_token_bytes b/mm2src/mm2_test_helpers/contract_bytes/erc1155_test_token_bytes new file mode 100644 index 0000000000..d5610ec91b --- /dev/null +++ b/mm2src/mm2_test_helpers/contract_bytes/erc1155_test_token_bytes @@ -0,0 +1 @@ +608060405234801562000010575f80fd5b50604051620024eb380380620024eb8339818101604052810190620000369190620001ea565b8062000048816200005060201b60201c565b505062000554565b806002908162000061919062000470565b5050565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b620000c6826200007e565b810181811067ffffffffffffffff82111715620000e857620000e76200008e565b5b80604052505050565b5f620000fc62000065565b90506200010a8282620000bb565b919050565b5f67ffffffffffffffff8211156200012c576200012b6200008e565b5b62000137826200007e565b9050602081019050919050565b5f5b838110156200016357808201518184015260208101905062000146565b5f8484015250505050565b5f620001846200017e846200010f565b620000f1565b905082815260208101848484011115620001a357620001a26200007a565b5b620001b084828562000144565b509392505050565b5f82601f830112620001cf57620001ce62000076565b5b8151620001e18482602086016200016e565b91505092915050565b5f602082840312156200020257620002016200006e565b5b5f82015167ffffffffffffffff81111562000222576200022162000072565b5b6200023084828501620001b8565b91505092915050565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200028857607f821691505b6020821081036200029e576200029d62000243565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302620003027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82620002c5565b6200030e8683620002c5565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f62000358620003526200034c8462000326565b6200032f565b62000326565b9050919050565b5f819050919050565b620003738362000338565b6200038b62000382826200035f565b848454620002d1565b825550505050565b5f90565b620003a162000393565b620003ae81848462000368565b505050565b5b81811015620003d557620003c95f8262000397565b600181019050620003b4565b5050565b601f8211156200042457620003ee81620002a4565b620003f984620002b6565b8101602085101562000409578190505b620004216200041885620002b6565b830182620003b3565b50505b505050565b5f82821c905092915050565b5f620004465f198460080262000429565b1980831691505092915050565b5f62000460838362000435565b9150826002028217905092915050565b6200047b8262000239565b67ffffffffffffffff8111156200049757620004966200008e565b5b620004a3825462000270565b620004b0828285620003d9565b5f60209050601f831160018114620004e6575f8415620004d1578287015190505b620004dd858262000453565b8655506200054c565b601f198416620004f686620002a4565b5f5b828110156200051f57848901518255600182019150602085019450602081019050620004f8565b868310156200053f57848901516200053b601f89168262000435565b8355505b6001600288020188555050505b505050505050565b611f8980620005625f395ff3fe608060405234801561000f575f80fd5b5060043610610090575f3560e01c80634e1273f4116100645780634e1273f414610140578063731133e914610170578063a22cb4651461018c578063e985e9c5146101a8578063f242432a146101d857610090565b8062fdd58e1461009457806301ffc9a7146100c45780630e89341c146100f45780632eb2c2d614610124575b5f80fd5b6100ae60048036038101906100a991906113bd565b6101f4565b6040516100bb919061140a565b60405180910390f35b6100de60048036038101906100d99190611478565b610249565b6040516100eb91906114bd565b60405180910390f35b61010e600480360381019061010991906114d6565b61032a565b60405161011b919061158b565b60405180910390f35b61013e6004803603810190610139919061179b565b6103bc565b005b61015a60048036038101906101559190611926565b610463565b6040516101679190611a53565b60405180910390f35b61018a60048036038101906101859190611a73565b61056a565b005b6101a660048036038101906101a19190611b1d565b61057c565b005b6101c260048036038101906101bd9190611b5b565b610592565b6040516101cf91906114bd565b60405180910390f35b6101f260048036038101906101ed9190611b99565b610620565b005b5f805f8381526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f7fd9b67a26000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061031357507f0e89341c000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b806103235750610322826106c7565b5b9050919050565b60606002805461033990611c59565b80601f016020809104026020016040519081016040528092919081815260200182805461036590611c59565b80156103b05780601f10610387576101008083540402835291602001916103b0565b820191905f5260205f20905b81548152906001019060200180831161039357829003601f168201915b50505050509050919050565b5f6103c5610730565b90508073ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff161415801561040a57506104088682610592565b155b1561044e5780866040517fe237d922000000000000000000000000000000000000000000000000000000008152600401610445929190611c98565b60405180910390fd5b61045b8686868686610737565b505050505050565b606081518351146104af57815183516040517f5b0599910000000000000000000000000000000000000000000000000000000081526004016104a6929190611cbf565b60405180910390fd5b5f835167ffffffffffffffff8111156104cb576104ca6115af565b5b6040519080825280602002602001820160405280156104f95781602001602082028036833780820191505090505b5090505f5b845181101561055f5761053561051d828761082b90919063ffffffff16565b610530838761083e90919063ffffffff16565b6101f4565b82828151811061054857610547611ce6565b5b6020026020010181815250508060010190506104fe565b508091505092915050565b61057684848484610851565b50505050565b61058e610587610730565b83836108e6565b5050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16905092915050565b5f610629610730565b90508073ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff161415801561066e575061066c8682610592565b155b156106b25780866040517fe237d9220000000000000000000000000000000000000000000000000000000081526004016106a9929190611c98565b60405180910390fd5b6106bf8686868686610a4f565b505050505050565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b5f33905090565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036107a7575f6040517f57f447ce00000000000000000000000000000000000000000000000000000000815260040161079e9190611d13565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1603610817575f6040517f01a8351400000000000000000000000000000000000000000000000000000000815260040161080e9190611d13565b60405180910390fd5b6108248585858585610b55565b5050505050565b5f60208202602084010151905092915050565b5f60208202602084010151905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036108c1575f6040517f57f447ce0000000000000000000000000000000000000000000000000000000081526004016108b89190611d13565b60405180910390fd5b5f806108cd8585610c01565b915091506108de5f87848487610b55565b505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610956575f6040517fced3e10000000000000000000000000000000000000000000000000000000000815260040161094d9190611d13565b60405180910390fd5b8060015f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3183604051610a4291906114bd565b60405180910390a3505050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610abf575f6040517f57f447ce000000000000000000000000000000000000000000000000000000008152600401610ab69190611d13565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1603610b2f575f6040517f01a83514000000000000000000000000000000000000000000000000000000008152600401610b269190611d13565b60405180910390fd5b5f80610b3b8585610c01565b91509150610b4c8787848487610b55565b50505050505050565b610b6185858585610c31565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1614610bfa575f610b9d610730565b90506001845103610be9575f610bbc5f8661083e90919063ffffffff16565b90505f610bd25f8661083e90919063ffffffff16565b9050610be2838989858589610fc1565b5050610bf8565b610bf7818787878787611170565b5b505b5050505050565b60608060405191506001825283602083015260408201905060018152826020820152604081016040529250929050565b8051825114610c7b57815181516040517f5b059991000000000000000000000000000000000000000000000000000000008152600401610c72929190611cbf565b60405180910390fd5b5f610c84610730565b90505f5b8351811015610e80575f610ca5828661083e90919063ffffffff16565b90505f610cbb838661083e90919063ffffffff16565b90505f73ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614610dde575f805f8481526020019081526020015f205f8a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905081811015610d8a57888183856040517f03dee4c5000000000000000000000000000000000000000000000000000000008152600401610d819493929190611d2c565b60405180910390fd5b8181035f808581526020019081526020015f205f8b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff1614610e7357805f808481526020019081526020015f205f8973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f828254610e6b9190611d9c565b925050819055505b5050806001019050610c88565b506001835103610f3b575f610e9e5f8561083e90919063ffffffff16565b90505f610eb45f8561083e90919063ffffffff16565b90508573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f628585604051610f2c929190611cbf565b60405180910390a45050610fba565b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb8686604051610fb1929190611dcf565b60405180910390a45b5050505050565b5f8473ffffffffffffffffffffffffffffffffffffffff163b1115611168578373ffffffffffffffffffffffffffffffffffffffff1663f23a6e6187878686866040518663ffffffff1660e01b8152600401611021959493929190611e56565b6020604051808303815f875af192505050801561105c57506040513d601f19601f820116820180604052508101906110599190611ec2565b60015b6110dd573d805f811461108a576040519150601f19603f3d011682016040523d82523d5f602084013e61108f565b606091505b505f8151036110d557846040517f57f447ce0000000000000000000000000000000000000000000000000000000081526004016110cc9190611d13565b60405180910390fd5b805181602001fd5b63f23a6e6160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461116657846040517f57f447ce00000000000000000000000000000000000000000000000000000000815260040161115d9190611d13565b60405180910390fd5b505b505050505050565b5f8473ffffffffffffffffffffffffffffffffffffffff163b1115611317578373ffffffffffffffffffffffffffffffffffffffff1663bc197c8187878686866040518663ffffffff1660e01b81526004016111d0959493929190611eed565b6020604051808303815f875af192505050801561120b57506040513d601f19601f820116820180604052508101906112089190611ec2565b60015b61128c573d805f8114611239576040519150601f19603f3d011682016040523d82523d5f602084013e61123e565b606091505b505f81510361128457846040517f57f447ce00000000000000000000000000000000000000000000000000000000815260040161127b9190611d13565b60405180910390fd5b805181602001fd5b63bc197c8160e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161461131557846040517f57f447ce00000000000000000000000000000000000000000000000000000000815260040161130c9190611d13565b60405180910390fd5b505b505050505050565b5f604051905090565b5f80fd5b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61135982611330565b9050919050565b6113698161134f565b8114611373575f80fd5b50565b5f8135905061138481611360565b92915050565b5f819050919050565b61139c8161138a565b81146113a6575f80fd5b50565b5f813590506113b781611393565b92915050565b5f80604083850312156113d3576113d2611328565b5b5f6113e085828601611376565b92505060206113f1858286016113a9565b9150509250929050565b6114048161138a565b82525050565b5f60208201905061141d5f8301846113fb565b92915050565b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b61145781611423565b8114611461575f80fd5b50565b5f813590506114728161144e565b92915050565b5f6020828403121561148d5761148c611328565b5b5f61149a84828501611464565b91505092915050565b5f8115159050919050565b6114b7816114a3565b82525050565b5f6020820190506114d05f8301846114ae565b92915050565b5f602082840312156114eb576114ea611328565b5b5f6114f8848285016113a9565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f5b8381101561153857808201518184015260208101905061151d565b5f8484015250505050565b5f601f19601f8301169050919050565b5f61155d82611501565b611567818561150b565b935061157781856020860161151b565b61158081611543565b840191505092915050565b5f6020820190508181035f8301526115a38184611553565b905092915050565b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6115e582611543565b810181811067ffffffffffffffff82111715611604576116036115af565b5b80604052505050565b5f61161661131f565b905061162282826115dc565b919050565b5f67ffffffffffffffff821115611641576116406115af565b5b602082029050602081019050919050565b5f80fd5b5f61166861166384611627565b61160d565b9050808382526020820190506020840283018581111561168b5761168a611652565b5b835b818110156116b457806116a088826113a9565b84526020840193505060208101905061168d565b5050509392505050565b5f82601f8301126116d2576116d16115ab565b5b81356116e2848260208601611656565b91505092915050565b5f80fd5b5f67ffffffffffffffff821115611709576117086115af565b5b61171282611543565b9050602081019050919050565b828183375f83830152505050565b5f61173f61173a846116ef565b61160d565b90508281526020810184848401111561175b5761175a6116eb565b5b61176684828561171f565b509392505050565b5f82601f830112611782576117816115ab565b5b813561179284826020860161172d565b91505092915050565b5f805f805f60a086880312156117b4576117b3611328565b5b5f6117c188828901611376565b95505060206117d288828901611376565b945050604086013567ffffffffffffffff8111156117f3576117f261132c565b5b6117ff888289016116be565b935050606086013567ffffffffffffffff8111156118205761181f61132c565b5b61182c888289016116be565b925050608086013567ffffffffffffffff81111561184d5761184c61132c565b5b6118598882890161176e565b9150509295509295909350565b5f67ffffffffffffffff8211156118805761187f6115af565b5b602082029050602081019050919050565b5f6118a361189e84611866565b61160d565b905080838252602082019050602084028301858111156118c6576118c5611652565b5b835b818110156118ef57806118db8882611376565b8452602084019350506020810190506118c8565b5050509392505050565b5f82601f83011261190d5761190c6115ab565b5b813561191d848260208601611891565b91505092915050565b5f806040838503121561193c5761193b611328565b5b5f83013567ffffffffffffffff8111156119595761195861132c565b5b611965858286016118f9565b925050602083013567ffffffffffffffff8111156119865761198561132c565b5b611992858286016116be565b9150509250929050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b6119ce8161138a565b82525050565b5f6119df83836119c5565b60208301905092915050565b5f602082019050919050565b5f611a018261199c565b611a0b81856119a6565b9350611a16836119b6565b805f5b83811015611a46578151611a2d88826119d4565b9750611a38836119eb565b925050600181019050611a19565b5085935050505092915050565b5f6020820190508181035f830152611a6b81846119f7565b905092915050565b5f805f8060808587031215611a8b57611a8a611328565b5b5f611a9887828801611376565b9450506020611aa9878288016113a9565b9350506040611aba878288016113a9565b925050606085013567ffffffffffffffff811115611adb57611ada61132c565b5b611ae78782880161176e565b91505092959194509250565b611afc816114a3565b8114611b06575f80fd5b50565b5f81359050611b1781611af3565b92915050565b5f8060408385031215611b3357611b32611328565b5b5f611b4085828601611376565b9250506020611b5185828601611b09565b9150509250929050565b5f8060408385031215611b7157611b70611328565b5b5f611b7e85828601611376565b9250506020611b8f85828601611376565b9150509250929050565b5f805f805f60a08688031215611bb257611bb1611328565b5b5f611bbf88828901611376565b9550506020611bd088828901611376565b9450506040611be1888289016113a9565b9350506060611bf2888289016113a9565b925050608086013567ffffffffffffffff811115611c1357611c1261132c565b5b611c1f8882890161176e565b9150509295509295909350565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680611c7057607f821691505b602082108103611c8357611c82611c2c565b5b50919050565b611c928161134f565b82525050565b5f604082019050611cab5f830185611c89565b611cb86020830184611c89565b9392505050565b5f604082019050611cd25f8301856113fb565b611cdf60208301846113fb565b9392505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f602082019050611d265f830184611c89565b92915050565b5f608082019050611d3f5f830187611c89565b611d4c60208301866113fb565b611d5960408301856113fb565b611d6660608301846113fb565b95945050505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f611da68261138a565b9150611db18361138a565b9250828201905080821115611dc957611dc8611d6f565b5b92915050565b5f6040820190508181035f830152611de781856119f7565b90508181036020830152611dfb81846119f7565b90509392505050565b5f81519050919050565b5f82825260208201905092915050565b5f611e2882611e04565b611e328185611e0e565b9350611e4281856020860161151b565b611e4b81611543565b840191505092915050565b5f60a082019050611e695f830188611c89565b611e766020830187611c89565b611e8360408301866113fb565b611e9060608301856113fb565b8181036080830152611ea28184611e1e565b90509695505050505050565b5f81519050611ebc8161144e565b92915050565b5f60208284031215611ed757611ed6611328565b5b5f611ee484828501611eae565b91505092915050565b5f60a082019050611f005f830188611c89565b611f0d6020830187611c89565b8181036040830152611f1f81866119f7565b90508181036060830152611f3381856119f7565b90508181036080830152611f478184611e1e565b9050969550505050505056fea26469706673582212203835581c6344b12728c44fa4d9e912cd60e64012c1b772bb703d1c36825c16fd64736f6c63430008180033 \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/contract_bytes/erc20_token_bytes b/mm2src/mm2_test_helpers/contract_bytes/erc20_token_bytes new file mode 100644 index 0000000000..21d608c62d --- /dev/null +++ b/mm2src/mm2_test_helpers/contract_bytes/erc20_token_bytes @@ -0,0 +1 @@ +6080604052600860ff16600a0a633b9aca000260005534801561002157600080fd5b50600054600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610c69806100776000396000f3006080604052600436106100a4576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100a9578063095ea7b31461013957806318160ddd1461019e57806323b872dd146101c9578063313ce5671461024e5780635a3b7e421461027f57806370a082311461030f57806395d89b4114610366578063a9059cbb146103f6578063dd62ed3e1461045b575b600080fd5b3480156100b557600080fd5b506100be6104d2565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100fe5780820151818401526020810190506100e3565b50505050905090810190601f16801561012b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561014557600080fd5b50610184600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061050b565b604051808215151515815260200191505060405180910390f35b3480156101aa57600080fd5b506101b36106bb565b6040518082815260200191505060405180910390f35b3480156101d557600080fd5b50610234600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506106c1565b604051808215151515815260200191505060405180910390f35b34801561025a57600080fd5b506102636109a1565b604051808260ff1660ff16815260200191505060405180910390f35b34801561028b57600080fd5b506102946109a6565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156102d45780820151818401526020810190506102b9565b50505050905090810190601f1680156103015780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561031b57600080fd5b50610350600480360381019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506109df565b6040518082815260200191505060405180910390f35b34801561037257600080fd5b5061037b6109f7565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103bb5780820151818401526020810190506103a0565b50505050905090810190601f1680156103e85780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561040257600080fd5b50610441600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610a30565b604051808215151515815260200191505060405180910390f35b34801561046757600080fd5b506104bc600480360381019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610be1565b6040518082815260200191505060405180910390f35b6040805190810160405280600881526020017f515243205445535400000000000000000000000000000000000000000000000081525081565b60008260008173ffffffffffffffffffffffffffffffffffffffff161415151561053457600080fd5b60008314806105bf57506000600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054145b15156105ca57600080fd5b82600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508373ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925856040518082815260200191505060405180910390a3600191505092915050565b60005481565b60008360008173ffffffffffffffffffffffffffffffffffffffff16141515156106ea57600080fd5b8360008173ffffffffffffffffffffffffffffffffffffffff161415151561071157600080fd5b610797600260008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205485610c06565b600260008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610860600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205485610c06565b600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506108ec600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205485610c1f565b600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508473ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef866040518082815260200191505060405180910390a36001925050509392505050565b600881565b6040805190810160405280600981526020017f546f6b656e20302e31000000000000000000000000000000000000000000000081525081565b60016020528060005260406000206000915090505481565b6040805190810160405280600381526020017f515443000000000000000000000000000000000000000000000000000000000081525081565b60008260008173ffffffffffffffffffffffffffffffffffffffff1614151515610a5957600080fd5b610aa2600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205484610c06565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610b2e600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205484610c1f565b600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508373ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef856040518082815260200191505060405180910390a3600191505092915050565b6002602052816000526040600020602052806000526040600020600091509150505481565b6000818310151515610c1457fe5b818303905092915050565b6000808284019050838110151515610c3357fe5b80915050929150505600a165627a7a723058207f2e5248b61b80365ea08a0f6d11ac0b47374c4dfd538de76bc2f19591bbbba40029 \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/contract_bytes/erc721_test_token_bytes b/mm2src/mm2_test_helpers/contract_bytes/erc721_test_token_bytes new file mode 100644 index 0000000000..62f7b588b2 --- /dev/null +++ b/mm2src/mm2_test_helpers/contract_bytes/erc721_test_token_bytes @@ -0,0 +1 @@ +608060405234801562000010575f80fd5b50604051620022ac380380620022ac8339818101604052810190620000369190620001ea565b8181815f9081620000489190620004a4565b5080600190816200005a9190620004a4565b505050505062000588565b5f604051905090565b5f80fd5b5f80fd5b5f80fd5b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b620000c6826200007e565b810181811067ffffffffffffffff82111715620000e857620000e76200008e565b5b80604052505050565b5f620000fc62000065565b90506200010a8282620000bb565b919050565b5f67ffffffffffffffff8211156200012c576200012b6200008e565b5b62000137826200007e565b9050602081019050919050565b5f5b838110156200016357808201518184015260208101905062000146565b5f8484015250505050565b5f620001846200017e846200010f565b620000f1565b905082815260208101848484011115620001a357620001a26200007a565b5b620001b084828562000144565b509392505050565b5f82601f830112620001cf57620001ce62000076565b5b8151620001e18482602086016200016e565b91505092915050565b5f80604083850312156200020357620002026200006e565b5b5f83015167ffffffffffffffff81111562000223576200022262000072565b5b6200023185828601620001b8565b925050602083015167ffffffffffffffff81111562000255576200025462000072565b5b6200026385828601620001b8565b9150509250929050565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680620002bc57607f821691505b602082108103620002d257620002d162000277565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f60088302620003367fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82620002f9565b620003428683620002f9565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f6200038c6200038662000380846200035a565b62000363565b6200035a565b9050919050565b5f819050919050565b620003a7836200036c565b620003bf620003b68262000393565b84845462000305565b825550505050565b5f90565b620003d5620003c7565b620003e28184846200039c565b505050565b5b818110156200040957620003fd5f82620003cb565b600181019050620003e8565b5050565b601f82111562000458576200042281620002d8565b6200042d84620002ea565b810160208510156200043d578190505b620004556200044c85620002ea565b830182620003e7565b50505b505050565b5f82821c905092915050565b5f6200047a5f19846008026200045d565b1980831691505092915050565b5f62000494838362000469565b9150826002028217905092915050565b620004af826200026d565b67ffffffffffffffff811115620004cb57620004ca6200008e565b5b620004d78254620002a4565b620004e48282856200040d565b5f60209050601f8311600181146200051a575f841562000505578287015190505b62000511858262000487565b86555062000580565b601f1984166200052a86620002d8565b5f5b8281101562000553578489015182556001820191506020850194506020810190506200052c565b868310156200057357848901516200056f601f89168262000469565b8355505b6001600288020188555050505b505050505050565b611d1680620005965f395ff3fe608060405234801561000f575f80fd5b50600436106100e8575f3560e01c80636352211e1161008a578063a22cb46511610064578063a22cb46514610258578063b88d4fde14610274578063c87b56dd14610290578063e985e9c5146102c0576100e8565b80636352211e146101da57806370a082311461020a57806395d89b411461023a576100e8565b8063095ea7b3116100c6578063095ea7b31461016a57806323b872dd1461018657806340c10f19146101a257806342842e0e146101be576100e8565b806301ffc9a7146100ec57806306fdde031461011c578063081812fc1461013a575b5f80fd5b610106600480360381019061010191906115a7565b6102f0565b60405161011391906115ec565b60405180910390f35b6101246103d1565b604051610131919061168f565b60405180910390f35b610154600480360381019061014f91906116e2565b610460565b604051610161919061174c565b60405180910390f35b610184600480360381019061017f919061178f565b61047b565b005b6101a0600480360381019061019b91906117cd565b610491565b005b6101bc60048036038101906101b7919061178f565b610590565b005b6101d860048036038101906101d391906117cd565b61059e565b005b6101f460048036038101906101ef91906116e2565b6105bd565b604051610201919061174c565b60405180910390f35b610224600480360381019061021f919061181d565b6105ce565b6040516102319190611857565b60405180910390f35b610242610684565b60405161024f919061168f565b60405180910390f35b610272600480360381019061026d919061189a565b610714565b005b61028e60048036038101906102899190611a04565b61072a565b005b6102aa60048036038101906102a591906116e2565b610747565b6040516102b7919061168f565b60405180910390f35b6102da60048036038101906102d59190611a84565b6107ad565b6040516102e791906115ec565b60405180910390f35b5f7f80ac58cd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806103ba57507f5b5e139f000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b806103ca57506103c98261083b565b5b9050919050565b60605f80546103df90611aef565b80601f016020809104026020016040519081016040528092919081815260200182805461040b90611aef565b80156104565780601f1061042d57610100808354040283529160200191610456565b820191905f5260205f20905b81548152906001019060200180831161043957829003601f168201915b5050505050905090565b5f61046a826108a4565b506104748261092a565b9050919050565b61048d8282610488610963565b61096a565b5050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610501575f6040517f64a0ae920000000000000000000000000000000000000000000000000000000081526004016104f8919061174c565b60405180910390fd5b5f610514838361050f610963565b61097c565b90508373ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161461058a578382826040517f64283d7b00000000000000000000000000000000000000000000000000000000815260040161058193929190611b1f565b60405180910390fd5b50505050565b61059a8282610b87565b5050565b6105b883838360405180602001604052805f81525061072a565b505050565b5f6105c7826108a4565b9050919050565b5f8073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361063f575f6040517f89c62b64000000000000000000000000000000000000000000000000000000008152600401610636919061174c565b60405180910390fd5b60035f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b60606001805461069390611aef565b80601f01602080910402602001604051908101604052809291908181526020018280546106bf90611aef565b801561070a5780601f106106e15761010080835404028352916020019161070a565b820191905f5260205f20905b8154815290600101906020018083116106ed57829003601f168201915b5050505050905090565b61072661071f610963565b8383610c7a565b5050565b610735848484610491565b61074184848484610de3565b50505050565b6060610752826108a4565b505f61075c610f95565b90505f81511161077a5760405180602001604052805f8152506107a5565b8061078484610fab565b604051602001610795929190611b8e565b6040516020818303038152906040525b915050919050565b5f60055f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16905092915050565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b5f806108af83611075565b90505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361092157826040517f7e2732890000000000000000000000000000000000000000000000000000000081526004016109189190611857565b60405180910390fd5b80915050919050565b5f60045f8381526020019081526020015f205f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b5f33905090565b61097783838360016110ae565b505050565b5f8061098784611075565b90505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16146109c8576109c781848661126d565b5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614610a5357610a075f855f806110ae565b600160035f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825403925050819055505b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614610ad257600160035f8773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8460025f8681526020019081526020015f205f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550838573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef60405160405180910390a4809150509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610bf7575f6040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401610bee919061174c565b60405180910390fd5b5f610c0383835f61097c565b90505f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614610c75575f6040517f73c6ac6e000000000000000000000000000000000000000000000000000000008152600401610c6c919061174c565b60405180910390fd5b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610cea57816040517f5b08ba18000000000000000000000000000000000000000000000000000000008152600401610ce1919061174c565b60405180910390fd5b8060055f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3183604051610dd691906115ec565b60405180910390a3505050565b5f8373ffffffffffffffffffffffffffffffffffffffff163b1115610f8f578273ffffffffffffffffffffffffffffffffffffffff1663150b7a02610e26610963565b8685856040518563ffffffff1660e01b8152600401610e489493929190611c03565b6020604051808303815f875af1925050508015610e8357506040513d601f19601f82011682018060405250810190610e809190611c61565b60015b610f04573d805f8114610eb1576040519150601f19603f3d011682016040523d82523d5f602084013e610eb6565b606091505b505f815103610efc57836040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401610ef3919061174c565b60405180910390fd5b805181602001fd5b63150b7a0260e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614610f8d57836040517f64a0ae92000000000000000000000000000000000000000000000000000000008152600401610f84919061174c565b60405180910390fd5b505b50505050565b606060405180602001604052805f815250905090565b60605f6001610fb984611330565b0190505f8167ffffffffffffffff811115610fd757610fd66118e0565b5b6040519080825280601f01601f1916602001820160405280156110095781602001600182028036833780820191505090505b5090505f82602001820190505b60011561106a578080600190039150507f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a858161105f5761105e611c8c565b5b0494505f8503611016575b819350505050919050565b5f60025f8381526020019081526020015f205f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b80806110e657505f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b15611218575f6110f5846108a4565b90505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415801561115f57508273ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b8015611172575061117081846107ad565b155b156111b457826040517fa9fbf51f0000000000000000000000000000000000000000000000000000000081526004016111ab919061174c565b60405180910390fd5b811561121657838573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45b505b8360045f8581526020019081526020015f205f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505050565b611278838383611481565b61132b575f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036112ec57806040517f7e2732890000000000000000000000000000000000000000000000000000000081526004016112e39190611857565b60405180910390fd5b81816040517f177e802f000000000000000000000000000000000000000000000000000000008152600401611322929190611cb9565b60405180910390fd5b505050565b5f805f90507a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000831061138c577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000838161138257611381611c8c565b5b0492506040810190505b6d04ee2d6d415b85acef810000000083106113c9576d04ee2d6d415b85acef810000000083816113bf576113be611c8c565b5b0492506020810190505b662386f26fc1000083106113f857662386f26fc1000083816113ee576113ed611c8c565b5b0492506010810190505b6305f5e1008310611421576305f5e100838161141757611416611c8c565b5b0492506008810190505b612710831061144657612710838161143c5761143b611c8c565b5b0492506004810190505b60648310611469576064838161145f5761145e611c8c565b5b0492506002810190505b600a8310611478576001810190505b80915050919050565b5f8073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415801561153857508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1614806114f957506114f884846107ad565b5b8061153757508273ffffffffffffffffffffffffffffffffffffffff1661151f8361092a565b73ffffffffffffffffffffffffffffffffffffffff16145b5b90509392505050565b5f604051905090565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b61158681611552565b8114611590575f80fd5b50565b5f813590506115a18161157d565b92915050565b5f602082840312156115bc576115bb61154a565b5b5f6115c984828501611593565b91505092915050565b5f8115159050919050565b6115e6816115d2565b82525050565b5f6020820190506115ff5f8301846115dd565b92915050565b5f81519050919050565b5f82825260208201905092915050565b5f5b8381101561163c578082015181840152602081019050611621565b5f8484015250505050565b5f601f19601f8301169050919050565b5f61166182611605565b61166b818561160f565b935061167b81856020860161161f565b61168481611647565b840191505092915050565b5f6020820190508181035f8301526116a78184611657565b905092915050565b5f819050919050565b6116c1816116af565b81146116cb575f80fd5b50565b5f813590506116dc816116b8565b92915050565b5f602082840312156116f7576116f661154a565b5b5f611704848285016116ce565b91505092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6117368261170d565b9050919050565b6117468161172c565b82525050565b5f60208201905061175f5f83018461173d565b92915050565b61176e8161172c565b8114611778575f80fd5b50565b5f8135905061178981611765565b92915050565b5f80604083850312156117a5576117a461154a565b5b5f6117b28582860161177b565b92505060206117c3858286016116ce565b9150509250929050565b5f805f606084860312156117e4576117e361154a565b5b5f6117f18682870161177b565b93505060206118028682870161177b565b9250506040611813868287016116ce565b9150509250925092565b5f602082840312156118325761183161154a565b5b5f61183f8482850161177b565b91505092915050565b611851816116af565b82525050565b5f60208201905061186a5f830184611848565b92915050565b611879816115d2565b8114611883575f80fd5b50565b5f8135905061189481611870565b92915050565b5f80604083850312156118b0576118af61154a565b5b5f6118bd8582860161177b565b92505060206118ce85828601611886565b9150509250929050565b5f80fd5b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b61191682611647565b810181811067ffffffffffffffff82111715611935576119346118e0565b5b80604052505050565b5f611947611541565b9050611953828261190d565b919050565b5f67ffffffffffffffff821115611972576119716118e0565b5b61197b82611647565b9050602081019050919050565b828183375f83830152505050565b5f6119a86119a384611958565b61193e565b9050828152602081018484840111156119c4576119c36118dc565b5b6119cf848285611988565b509392505050565b5f82601f8301126119eb576119ea6118d8565b5b81356119fb848260208601611996565b91505092915050565b5f805f8060808587031215611a1c57611a1b61154a565b5b5f611a298782880161177b565b9450506020611a3a8782880161177b565b9350506040611a4b878288016116ce565b925050606085013567ffffffffffffffff811115611a6c57611a6b61154e565b5b611a78878288016119d7565b91505092959194509250565b5f8060408385031215611a9a57611a9961154a565b5b5f611aa78582860161177b565b9250506020611ab88582860161177b565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680611b0657607f821691505b602082108103611b1957611b18611ac2565b5b50919050565b5f606082019050611b325f83018661173d565b611b3f6020830185611848565b611b4c604083018461173d565b949350505050565b5f81905092915050565b5f611b6882611605565b611b728185611b54565b9350611b8281856020860161161f565b80840191505092915050565b5f611b998285611b5e565b9150611ba58284611b5e565b91508190509392505050565b5f81519050919050565b5f82825260208201905092915050565b5f611bd582611bb1565b611bdf8185611bbb565b9350611bef81856020860161161f565b611bf881611647565b840191505092915050565b5f608082019050611c165f83018761173d565b611c23602083018661173d565b611c306040830185611848565b8181036060830152611c428184611bcb565b905095945050505050565b5f81519050611c5b8161157d565b92915050565b5f60208284031215611c7657611c7561154a565b5b5f611c8384828501611c4d565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f604082019050611ccc5f83018561173d565b611cd96020830184611848565b939250505056fea26469706673582212207439b47c2a9a1624955997732075917bbf1da26949d000c778f561eb5687576164736f6c63430008180033 \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/contract_bytes/maker_swap_v2_bytes b/mm2src/mm2_test_helpers/contract_bytes/maker_swap_v2_bytes new file mode 100644 index 0000000000..5ac667f370 --- /dev/null +++ b/mm2src/mm2_test_helpers/contract_bytes/maker_swap_v2_bytes @@ -0,0 +1 @@ +6080604052348015600e575f80fd5b50611d9f8061001c5f395ff3fe608060405260043610610054575f3560e01c80631299a27a146100585780637466be601461008057806374a4788a1461009c5780639b949dee146100c4578063a53bc126146100ec578063efccb9eb14610114575b5f80fd5b348015610063575f80fd5b5061007e60048036038101906100799190611427565b610152565b005b61009a600480360381019061009591906114e9565b61044b565b005b3480156100a7575f80fd5b506100c260048036038101906100bd9190611427565b6106fc565b005b3480156100cf575f80fd5b506100ea60048036038101906100e59190611427565b6109f5565b005b3480156100f7575f80fd5b50610112600480360381019061010d9190611560565b610ced565b005b34801561011f575f80fd5b5061013a600480360381019061013591906115fd565b610fd2565b604051610149939291906116e4565b60405180910390f35b6001600381111561016657610165611671565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff16600381111561019857610197611671565b5b146101d8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101cf90611799565b60405180910390fd5b5f6003863387876002886040516020016101f291906117d7565b60405160208183030381529060405260405161020e9190611843565b602060405180830381855afa158015610229573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061024c919061186d565b87604051602001610262969594939291906118fd565b60405160208183030381529060405260405161027e9190611843565b602060405180830381855afa158015610299573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610323576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161031a906119b6565b60405180910390fd5b60025f808981526020019081526020015f205f0160186101000a81548160ff0219169083600381111561035957610358611671565b5b02179055507fad62ed075fe8969df63026f45152d6e996a0697a736a8de92ee85ae9c9958cf08760405161038d91906119e3565b60405180910390a15f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610411573373ffffffffffffffffffffffffffffffffffffffff166108fc8790811502906040515f60405180830381858888f1935050505015801561040b573d5f803e3d5ffd5b50610442565b5f82905061044033888373ffffffffffffffffffffffffffffffffffffffff1661101e9092919063ffffffff16565b505b50505050505050565b5f600381111561045e5761045d611671565b5b5f808781526020019081526020015f205f0160189054906101000a900460ff1660038111156104905761048f611671565b5b146104d0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104c790611a6c565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff160361053e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161053590611ad4565b60405180910390fd5b5f3411610580576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161057790611b62565b60405180910390fd5b5f600334863387875f60405160200161059e969594939291906118fd565b6040516020818303038152906040526040516105ba9190611843565b602060405180830381855afa1580156105d5573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018363ffffffff1681526020016001600381111561062157610620611671565b5b8152505f808881526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548160ff021916908360038111156106b5576106b4611671565b5b02179055509050507ff1dc11bbb6d7542c4267ecf1d370ff4c7092518633ecae9939e8488f4e53d2ad866040516106ec91906119e3565b60405180910390a1505050505050565b600160038111156107105761070f611671565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff16600381111561074257610741611671565b5b14610782576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161077990611799565b60405180910390fd5b5f600386863360028860405160200161079b91906117d7565b6040516020818303038152906040526040516107b79190611843565b602060405180830381855afa1580156107d2573d5f803e3d5ffd5b5050506040513d601f19601f820116820180604052508101906107f5919061186d565b878760405160200161080c969594939291906118fd565b6040516020818303038152906040526040516108289190611843565b602060405180830381855afa158015610843573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916146108cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108c4906119b6565b60405180910390fd5b60035f808981526020019081526020015f205f0160186101000a81548160ff0219169083600381111561090357610902611671565b5b02179055507fac509cdcc7ddb189f81fff6f4824f5c95076e64c3bdce542c50feaa6779afd738760405161093791906119e3565b60405180910390a15f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036109bb573373ffffffffffffffffffffffffffffffffffffffff166108fc8790811502906040515f60405180830381858888f193505050501580156109b5573d5f803e3d5ffd5b506109ec565b5f8290506109ea33888373ffffffffffffffffffffffffffffffffffffffff1661101e9092919063ffffffff16565b505b50505050505050565b60016003811115610a0957610a08611671565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff166003811115610a3b57610a3a611671565b5b14610a7b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a7290611799565b60405180910390fd5b5f6003868633878787604051602001610a99969594939291906118fd565b604051602081830303815290604052604051610ab59190611843565b602060405180830381855afa158015610ad0573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610b5a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b51906119b6565b60405180910390fd5b5f808881526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff16421015610bc5576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610bbc90611bf0565b60405180910390fd5b60035f808981526020019081526020015f205f0160186101000a81548160ff02191690836003811115610bfb57610bfa611671565b5b02179055507f5dedc4f52b757d9112d09ca0b2f022927104d54e3f54da091587e8ad1921907287604051610c2f91906119e3565b60405180910390a15f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610cb3573373ffffffffffffffffffffffffffffffffffffffff166108fc8790811502906040515f60405180830381858888f19350505050158015610cad573d5f803e3d5ffd5b50610ce4565b5f829050610ce233888373ffffffffffffffffffffffffffffffffffffffff1661101e9092919063ffffffff16565b505b50505050505050565b5f6003811115610d0057610cff611671565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff166003811115610d3257610d31611671565b5b14610d72576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d6990611a6c565b60405180910390fd5b5f8611610db4576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610dab90611c58565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610e22576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e1990611ad4565b60405180910390fd5b5f600387863387878b604051602001610e40969594939291906118fd565b604051602081830303815290604052604051610e5c9190611843565b602060405180830381855afa158015610e77573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018363ffffffff16815260200160016003811115610ec357610ec2611671565b5b8152505f808a81526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548160ff02191690836003811115610f5757610f56611671565b5b02179055509050507ff1dc11bbb6d7542c4267ecf1d370ff4c7092518633ecae9939e8488f4e53d2ad88604051610f8e91906119e3565b60405180910390a15f869050610fc733308a8473ffffffffffffffffffffffffffffffffffffffff1661109d909392919063ffffffff16565b505050505050505050565b5f602052805f5260405f205f91509050805f015f9054906101000a900460601b90805f0160149054906101000a900463ffffffff1690805f0160189054906101000a900460ff16905083565b611098838473ffffffffffffffffffffffffffffffffffffffff1663a9059cbb8585604051602401611051929190611c94565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505061111f565b505050565b611119848573ffffffffffffffffffffffffffffffffffffffff166323b872dd8686866040516024016110d293929190611cbb565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505061111f565b50505050565b5f611149828473ffffffffffffffffffffffffffffffffffffffff166111b490919063ffffffff16565b90505f81511415801561116d57508080602001905181019061116b9190611d25565b155b156111af57826040517f5274afe70000000000000000000000000000000000000000000000000000000081526004016111a69190611d50565b60405180910390fd5b505050565b60606111c183835f6111c9565b905092915050565b60608147101561121057306040517fcd7860590000000000000000000000000000000000000000000000000000000081526004016112079190611d50565b60405180910390fd5b5f808573ffffffffffffffffffffffffffffffffffffffff1684866040516112389190611843565b5f6040518083038185875af1925050503d805f8114611272576040519150601f19603f3d011682016040523d82523d5f602084013e611277565b606091505b5091509150611287868383611292565b925050509392505050565b6060826112a7576112a28261131f565b611317565b5f82511480156112cd57505f8473ffffffffffffffffffffffffffffffffffffffff163b145b1561130f57836040517f9996b3150000000000000000000000000000000000000000000000000000000081526004016113069190611d50565b60405180910390fd5b819050611318565b5b9392505050565b5f815111156113315780518082602001fd5b6040517f1425ea4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f80fd5b5f819050919050565b61137981611367565b8114611383575f80fd5b50565b5f8135905061139481611370565b92915050565b5f819050919050565b6113ac8161139a565b81146113b6575f80fd5b50565b5f813590506113c7816113a3565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6113f6826113cd565b9050919050565b611406816113ec565b8114611410575f80fd5b50565b5f81359050611421816113fd565b92915050565b5f805f805f8060c0878903121561144157611440611363565b5b5f61144e89828a01611386565b965050602061145f89828a016113b9565b955050604061147089828a01611413565b945050606061148189828a01611386565b935050608061149289828a01611386565b92505060a06114a389828a01611413565b9150509295509295509295565b5f63ffffffff82169050919050565b6114c8816114b0565b81146114d2575f80fd5b50565b5f813590506114e3816114bf565b92915050565b5f805f805f60a0868803121561150257611501611363565b5b5f61150f88828901611386565b955050602061152088828901611413565b945050604061153188828901611386565b935050606061154288828901611386565b9250506080611553888289016114d5565b9150509295509295909350565b5f805f805f805f60e0888a03121561157b5761157a611363565b5b5f6115888a828b01611386565b97505060206115998a828b016113b9565b96505060406115aa8a828b01611413565b95505060606115bb8a828b01611413565b94505060806115cc8a828b01611386565b93505060a06115dd8a828b01611386565b92505060c06115ee8a828b016114d5565b91505092959891949750929550565b5f6020828403121561161257611611611363565b5b5f61161f84828501611386565b91505092915050565b5f7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000082169050919050565b61165c81611628565b82525050565b61166b816114b0565b82525050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b600481106116af576116ae611671565b5b50565b5f8190506116bf8261169e565b919050565b5f6116ce826116b2565b9050919050565b6116de816116c4565b82525050565b5f6060820190506116f75f830186611653565b6117046020830185611662565b61171160408301846116d5565b949350505050565b5f82825260208201905092915050565b7f496e76616c6964207061796d656e742073746174652e204d75737420626520505f8201527f61796d656e7453656e7400000000000000000000000000000000000000000000602082015250565b5f611783602a83611719565b915061178e82611729565b604082019050919050565b5f6020820190508181035f8301526117b081611777565b9050919050565b5f819050919050565b6117d16117cc82611367565b6117b7565b82525050565b5f6117e282846117c0565b60208201915081905092915050565b5f81519050919050565b5f81905092915050565b8281835e5f83830152505050565b5f61181d826117f1565b61182781856117fb565b9350611837818560208601611805565b80840191505092915050565b5f61184e8284611813565b915081905092915050565b5f8151905061186781611370565b92915050565b5f6020828403121561188257611881611363565b5b5f61188f84828501611859565b91505092915050565b5f819050919050565b6118b26118ad8261139a565b611898565b82525050565b5f8160601b9050919050565b5f6118ce826118b8565b9050919050565b5f6118df826118c4565b9050919050565b6118f76118f2826113ec565b6118d5565b82525050565b5f61190882896118a1565b60208201915061191882886118e6565b60148201915061192882876118e6565b60148201915061193882866117c0565b60208201915061194882856117c0565b60208201915061195882846118e6565b601482019150819050979650505050505050565b7f496e76616c6964207061796d656e7448617368000000000000000000000000005f82015250565b5f6119a0601383611719565b91506119ab8261196c565b602082019050919050565b5f6020820190508181035f8301526119cd81611994565b9050919050565b6119dd81611367565b82525050565b5f6020820190506119f65f8301846119d4565b92915050565b7f4d616b6572207061796d656e7420697320616c726561647920696e697469616c5f8201527f697a656400000000000000000000000000000000000000000000000000000000602082015250565b5f611a56602483611719565b9150611a61826119fc565b604082019050919050565b5f6020820190508181035f830152611a8381611a4a565b9050919050565b7f54616b6572206d757374206e6f74206265207a65726f206164647265737300005f82015250565b5f611abe601e83611719565b9150611ac982611a8a565b602082019050919050565b5f6020820190508181035f830152611aeb81611ab2565b9050919050565b7f4554482076616c7565206d7573742062652067726561746572207468616e207a5f8201527f65726f0000000000000000000000000000000000000000000000000000000000602082015250565b5f611b4c602383611719565b9150611b5782611af2565b604082019050919050565b5f6020820190508181035f830152611b7981611b40565b9050919050565b7f43757272656e742074696d657374616d70206469646e277420657863656564205f8201527f7061796d656e7420726566756e64206c6f636b2074696d650000000000000000602082015250565b5f611bda603883611719565b9150611be582611b80565b604082019050919050565b5f6020820190508181035f830152611c0781611bce565b9050919050565b7f416d6f756e74206d757374206e6f74206265207a65726f0000000000000000005f82015250565b5f611c42601783611719565b9150611c4d82611c0e565b602082019050919050565b5f6020820190508181035f830152611c6f81611c36565b9050919050565b611c7f816113ec565b82525050565b611c8e8161139a565b82525050565b5f604082019050611ca75f830185611c76565b611cb46020830184611c85565b9392505050565b5f606082019050611cce5f830186611c76565b611cdb6020830185611c76565b611ce86040830184611c85565b949350505050565b5f8115159050919050565b611d0481611cf0565b8114611d0e575f80fd5b50565b5f81519050611d1f81611cfb565b92915050565b5f60208284031215611d3a57611d39611363565b5b5f611d4784828501611d11565b91505092915050565b5f602082019050611d635f830184611c76565b9291505056fea26469706673582212200d7b67fe7b5e6829da1f56f14431b4ee61dc3a66fb17a79f0587fda0329d09ba64736f6c634300081a0033 \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/contract_bytes/nft_maker_swap_v2_bytes b/mm2src/mm2_test_helpers/contract_bytes/nft_maker_swap_v2_bytes new file mode 100644 index 0000000000..ea4cd8c817 --- /dev/null +++ b/mm2src/mm2_test_helpers/contract_bytes/nft_maker_swap_v2_bytes @@ -0,0 +1 @@ +6080604052348015600e575f80fd5b50612ffd8061001c5f395ff3fe608060405234801561000f575f80fd5b50600436106100a7575f3560e01c8063b27e46fb1161006f578063b27e46fb1461015f578063bc197c811461017b578063c8d9009b146101ab578063c92cd12d146101c7578063efccb9eb146101e3578063f23a6e6114610215576100a7565b806301ffc9a7146100ab57806305ec158d146100db5780630f235fce146100f7578063150b7a02146101135780636e6bf6d214610143575b5f80fd5b6100c560048036038101906100c09190611ebc565b610245565b6040516100d29190611f01565b60405180910390f35b6100f560048036038101906100f09190611fda565b610326565b005b610111600480360381019061010c9190612077565b6105e6565b005b61012d60048036038101906101289190612161565b6108a0565b60405161013a91906121f4565b60405180910390f35b61015d60048036038101906101589190612077565b610cef565b005b61017960048036038101906101749190611fda565b610faa565b005b61019560048036038101906101909190612262565b611269565b6040516101a291906121f4565b60405180910390f35b6101c560048036038101906101c09190612077565b6112a5565b005b6101e160048036038101906101dc9190611fda565b6115ce565b005b6101fd60048036038101906101f89190612339565b6118fc565b60405161020c9392919061242f565b60405180910390f35b61022f600480360381019061022a9190612464565b611948565b60405161023c91906121f4565b60405180910390f35b5f7f4e2312e0000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061030f57507f150b7a02000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b8061031f575061031e82611ddc565b5b9050919050565b6001600381111561033a576103396123bc565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff16600381111561036c5761036b6123bc565b5b146103ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103a39061257a565b60405180910390fd5b5f600387336002896040516020016103c491906125b8565b6040516020818303038152906040526040516103e09190612624565b602060405180830381855afa1580156103fb573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061041e919061264e565b8888888860405160200161043897969594939291906126de565b6040516020818303038152906040526040516104549190612624565b602060405180830381855afa15801561046f573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916146104f9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104f0906127a8565b60405180910390fd5b60035f808a81526020019081526020015f205f0160186101000a81548160ff0219169083600381111561052f5761052e6123bc565b5b02179055507fac509cdcc7ddb189f81fff6f4824f5c95076e64c3bdce542c50feaa6779afd738860405161056391906127d5565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b81526004016105ae949392919061283f565b5f604051808303815f87803b1580156105c5575f80fd5b505af11580156105d7573d5f803e3d5ffd5b50505050505050505050505050565b600160038111156105fa576105f96123bc565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff16600381111561062c5761062b6123bc565b5b1461066c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106639061257a565b60405180910390fd5b5f600386338787878760405160200161068a96959493929190612895565b6040516020818303038152906040526040516106a69190612624565b602060405180830381855afa1580156106c1573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461074b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610742906127a8565b60405180910390fd5b5f808881526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff164210156107b6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107ad90612974565b60405180910390fd5b60035f808981526020019081526020015f205f0160186101000a81548160ff021916908360038111156107ec576107eb6123bc565b5b02179055507f5dedc4f52b757d9112d09ca0b2f022927104d54e3f54da091587e8ad192190728760405161082091906127d5565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b815260040161086993929190612992565b5f604051808303815f87803b158015610880575f80fd5b505af1158015610892573d5f803e3d5ffd5b505050505050505050505050565b5f8083838101906108b19190612b1a565b90505f60038111156108c6576108c56123bc565b5b5f80835f015181526020019081526020015f205f0160189054906101000a900460ff1660038111156108fb576108fa6123bc565b5b1461093b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161093290612bb5565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816020015173ffffffffffffffffffffffffffffffffffffffff16036109ad576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109a490612c1d565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816040015173ffffffffffffffffffffffffffffffffffffffff1603610a1f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a1690612c85565b60405180910390fd5b806040015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610a91576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a8890612d13565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff1614610aff576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610af690612d7b565b60405180910390fd5b610b0c8160200151611e45565b15610b4c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b4390612de3565b60405180910390fd5b5f60038260200151888460600151856080015186604001518b604051602001610b7a96959493929190612895565b604051602081830303815290604052604051610b969190612624565b602060405180830381855afa158015610bb1573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018360a0015163ffffffff16815260200160016003811115610c0157610c006123bc565b5b8152505f80845f015181526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548160ff02191690836003811115610c9857610c976123bc565b5b02179055509050507ff1dc11bbb6d7542c4267ecf1d370ff4c7092518633ecae9939e8488f4e53d2ad825f0151604051610cd291906127d5565b60405180910390a163150b7a0260e01b9250505095945050505050565b60016003811115610d0357610d026123bc565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff166003811115610d3557610d346123bc565b5b14610d75576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d6c9061257a565b60405180910390fd5b5f60038633600288604051602001610d8d91906125b8565b604051602081830303815290604052604051610da99190612624565b602060405180830381855afa158015610dc4573d5f803e3d5ffd5b5050506040513d601f19601f82011682018060405250810190610de7919061264e565b878787604051602001610dff96959493929190612895565b604051602081830303815290604052604051610e1b9190612624565b602060405180830381855afa158015610e36573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610ec0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610eb7906127a8565b60405180910390fd5b60035f808981526020019081526020015f205f0160186101000a81548160ff02191690836003811115610ef657610ef56123bc565b5b02179055507fac509cdcc7ddb189f81fff6f4824f5c95076e64c3bdce542c50feaa6779afd7387604051610f2a91906127d5565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b8152600401610f7393929190612992565b5f604051808303815f87803b158015610f8a575f80fd5b505af1158015610f9c573d5f803e3d5ffd5b505050505050505050505050565b60016003811115610fbe57610fbd6123bc565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff166003811115610ff057610fef6123bc565b5b14611030576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110279061257a565b60405180910390fd5b5f60038733888888888860405160200161105097969594939291906126de565b60405160208183030381529060405260405161106c9190612624565b602060405180830381855afa158015611087573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614611111576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611108906127a8565b60405180910390fd5b5f808981526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff1642101561117c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161117390612974565b60405180910390fd5b60035f808a81526020019081526020015f205f0160186101000a81548160ff021916908360038111156111b2576111b16123bc565b5b02179055507f5dedc4f52b757d9112d09ca0b2f022927104d54e3f54da091587e8ad19219072886040516111e691906127d5565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b8152600401611231949392919061283f565b5f604051808303815f87803b158015611248575f80fd5b505af115801561125a573d5f803e3d5ffd5b50505050505050505050505050565b5f6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161129c90612e4b565b60405180910390fd5b600160038111156112b9576112b86123bc565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff1660038111156112eb576112ea6123bc565b5b1461132b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016113229061257a565b60405180910390fd5b3273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611399576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161139090612eb3565b60405180910390fd5b5f60033387876002886040516020016113b291906125b8565b6040516020818303038152906040526040516113ce9190612624565b602060405180830381855afa1580156113e9573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061140c919061264e565b878760405160200161142396959493929190612895565b60405160208183030381529060405260405161143f9190612624565b602060405180830381855afa15801561145a573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916146114e4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114db906127a8565b60405180910390fd5b60025f808981526020019081526020015f205f0160186101000a81548160ff0219169083600381111561151a576115196123bc565b5b02179055507fad62ed075fe8969df63026f45152d6e996a0697a736a8de92ee85ae9c9958cf08760405161154e91906127d5565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b815260040161159793929190612992565b5f604051808303815f87803b1580156115ae575f80fd5b505af11580156115c0573d5f803e3d5ffd5b505050505050505050505050565b600160038111156115e2576115e16123bc565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff166003811115611614576116136123bc565b5b14611654576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161164b9061257a565b60405180910390fd5b3273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146116c2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016116b990612eb3565b60405180910390fd5b5f60033388886002896040516020016116db91906125b8565b6040516020818303038152906040526040516116f79190612624565b602060405180830381855afa158015611712573d5f803e3d5ffd5b5050506040513d601f19601f82011682018060405250810190611735919061264e565b88888860405160200161174e97969594939291906126de565b60405160208183030381529060405260405161176a9190612624565b602060405180830381855afa158015611785573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461180f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611806906127a8565b60405180910390fd5b60025f808a81526020019081526020015f205f0160186101000a81548160ff02191690836003811115611845576118446123bc565b5b02179055507fad62ed075fe8969df63026f45152d6e996a0697a736a8de92ee85ae9c9958cf08860405161187991906127d5565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b81526004016118c4949392919061283f565b5f604051808303815f87803b1580156118db575f80fd5b505af11580156118ed573d5f803e3d5ffd5b50505050505050505050505050565b5f602052805f5260405f205f91509050805f015f9054906101000a900460601b90805f0160149054906101000a900463ffffffff1690805f0160189054906101000a900460ff16905083565b5f8083838101906119599190612b1a565b90505f600381111561196e5761196d6123bc565b5b5f80835f015181526020019081526020015f205f0160189054906101000a900460ff1660038111156119a3576119a26123bc565b5b146119e3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016119da90612f41565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816020015173ffffffffffffffffffffffffffffffffffffffff1603611a55576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611a4c90612c1d565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816040015173ffffffffffffffffffffffffffffffffffffffff1603611ac7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611abe90612c85565b60405180910390fd5b806040015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611b39576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b3090612d13565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614611ba7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b9e90612d7b565b60405180910390fd5b5f8511611be9576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611be090612fa9565b60405180910390fd5b611bf68160200151611e45565b15611c36576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c2d90612de3565b60405180910390fd5b5f60038260200151898460600151856080015186604001518c8c604051602001611c6697969594939291906126de565b604051602081830303815290604052604051611c829190612624565b602060405180830381855afa158015611c9d573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018360a0015163ffffffff16815260200160016003811115611ced57611cec6123bc565b5b8152505f80845f015181526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548160ff02191690836003811115611d8457611d836123bc565b5b02179055509050507ff1dc11bbb6d7542c4267ecf1d370ff4c7092518633ecae9939e8488f4e53d2ad825f0151604051611dbe91906127d5565b60405180910390a163f23a6e6160e01b925050509695505050505050565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b5f80823b90505f8111915050919050565b5f604051905090565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b611e9b81611e67565b8114611ea5575f80fd5b50565b5f81359050611eb681611e92565b92915050565b5f60208284031215611ed157611ed0611e5f565b5b5f611ede84828501611ea8565b91505092915050565b5f8115159050919050565b611efb81611ee7565b82525050565b5f602082019050611f145f830184611ef2565b92915050565b5f819050919050565b611f2c81611f1a565b8114611f36575f80fd5b50565b5f81359050611f4781611f23565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f611f7682611f4d565b9050919050565b611f8681611f6c565b8114611f90575f80fd5b50565b5f81359050611fa181611f7d565b92915050565b5f819050919050565b611fb981611fa7565b8114611fc3575f80fd5b50565b5f81359050611fd481611fb0565b92915050565b5f805f805f805f60e0888a031215611ff557611ff4611e5f565b5b5f6120028a828b01611f39565b97505060206120138a828b01611f93565b96505060406120248a828b01611f39565b95505060606120358a828b01611f39565b94505060806120468a828b01611f93565b93505060a06120578a828b01611fc6565b92505060c06120688a828b01611fc6565b91505092959891949750929550565b5f805f805f8060c0878903121561209157612090611e5f565b5b5f61209e89828a01611f39565b96505060206120af89828a01611f93565b95505060406120c089828a01611f39565b94505060606120d189828a01611f39565b93505060806120e289828a01611f93565b92505060a06120f389828a01611fc6565b9150509295509295509295565b5f80fd5b5f80fd5b5f80fd5b5f8083601f84011261212157612120612100565b5b8235905067ffffffffffffffff81111561213e5761213d612104565b5b60208301915083600182028301111561215a57612159612108565b5b9250929050565b5f805f805f6080868803121561217a57612179611e5f565b5b5f61218788828901611f93565b955050602061219888828901611f93565b94505060406121a988828901611fc6565b935050606086013567ffffffffffffffff8111156121ca576121c9611e63565b5b6121d68882890161210c565b92509250509295509295909350565b6121ee81611e67565b82525050565b5f6020820190506122075f8301846121e5565b92915050565b5f8083601f84011261222257612221612100565b5b8235905067ffffffffffffffff81111561223f5761223e612104565b5b60208301915083602082028301111561225b5761225a612108565b5b9250929050565b5f805f805f805f8060a0898b03121561227e5761227d611e5f565b5b5f61228b8b828c01611f93565b985050602061229c8b828c01611f93565b975050604089013567ffffffffffffffff8111156122bd576122bc611e63565b5b6122c98b828c0161220d565b9650965050606089013567ffffffffffffffff8111156122ec576122eb611e63565b5b6122f88b828c0161220d565b9450945050608089013567ffffffffffffffff81111561231b5761231a611e63565b5b6123278b828c0161210c565b92509250509295985092959890939650565b5f6020828403121561234e5761234d611e5f565b5b5f61235b84828501611f39565b91505092915050565b5f7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000082169050919050565b61239881612364565b82525050565b5f63ffffffff82169050919050565b6123b68161239e565b82525050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b600481106123fa576123f96123bc565b5b50565b5f81905061240a826123e9565b919050565b5f612419826123fd565b9050919050565b6124298161240f565b82525050565b5f6060820190506124425f83018661238f565b61244f60208301856123ad565b61245c6040830184612420565b949350505050565b5f805f805f8060a0878903121561247e5761247d611e5f565b5b5f61248b89828a01611f93565b965050602061249c89828a01611f93565b95505060406124ad89828a01611fc6565b94505060606124be89828a01611fc6565b935050608087013567ffffffffffffffff8111156124df576124de611e63565b5b6124eb89828a0161210c565b92509250509295509295509295565b5f82825260208201905092915050565b7f496e76616c6964207061796d656e742073746174652e204d75737420626520505f8201527f61796d656e7453656e7400000000000000000000000000000000000000000000602082015250565b5f612564602a836124fa565b915061256f8261250a565b604082019050919050565b5f6020820190508181035f83015261259181612558565b9050919050565b5f819050919050565b6125b26125ad82611f1a565b612598565b82525050565b5f6125c382846125a1565b60208201915081905092915050565b5f81519050919050565b5f81905092915050565b8281835e5f83830152505050565b5f6125fe826125d2565b61260881856125dc565b93506126188185602086016125e6565b80840191505092915050565b5f61262f82846125f4565b915081905092915050565b5f8151905061264881611f23565b92915050565b5f6020828403121561266357612662611e5f565b5b5f6126708482850161263a565b91505092915050565b5f8160601b9050919050565b5f61268f82612679565b9050919050565b5f6126a082612685565b9050919050565b6126b86126b382611f6c565b612696565b82525050565b5f819050919050565b6126d86126d382611fa7565b6126be565b82525050565b5f6126e9828a6126a7565b6014820191506126f982896126a7565b60148201915061270982886125a1565b60208201915061271982876125a1565b60208201915061272982866126a7565b60148201915061273982856126c7565b60208201915061274982846126c7565b60208201915081905098975050505050505050565b7f496e76616c6964207061796d656e7448617368000000000000000000000000005f82015250565b5f6127926013836124fa565b915061279d8261275e565b602082019050919050565b5f6020820190508181035f8301526127bf81612786565b9050919050565b6127cf81611f1a565b82525050565b5f6020820190506127e85f8301846127c6565b92915050565b6127f781611f6c565b82525050565b61280681611fa7565b82525050565b5f82825260208201905092915050565b50565b5f61282a5f8361280c565b91506128358261281c565b5f82019050919050565b5f60a0820190506128525f8301876127ee565b61285f60208301866127ee565b61286c60408301856127fd565b61287960608301846127fd565b818103608083015261288a8161281f565b905095945050505050565b5f6128a082896126a7565b6014820191506128b082886126a7565b6014820191506128c082876125a1565b6020820191506128d082866125a1565b6020820191506128e082856126a7565b6014820191506128f082846126c7565b602082019150819050979650505050505050565b7f43757272656e742074696d657374616d70206469646e277420657863656564205f8201527f7061796d656e7420726566756e64206c6f636b2074696d650000000000000000602082015250565b5f61295e6038836124fa565b915061296982612904565b604082019050919050565b5f6020820190508181035f83015261298b81612952565b9050919050565b5f6060820190506129a55f8301866127ee565b6129b260208301856127ee565b6129bf60408301846127fd565b949350505050565b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b612a11826129cb565b810181811067ffffffffffffffff82111715612a3057612a2f6129db565b5b80604052505050565b5f612a42611e56565b9050612a4e8282612a08565b919050565b612a5c8161239e565b8114612a66575f80fd5b50565b5f81359050612a7781612a53565b92915050565b5f60c08284031215612a9257612a916129c7565b5b612a9c60c0612a39565b90505f612aab84828501611f39565b5f830152506020612abe84828501611f93565b6020830152506040612ad284828501611f93565b6040830152506060612ae684828501611f39565b6060830152506080612afa84828501611f39565b60808301525060a0612b0e84828501612a69565b60a08301525092915050565b5f60c08284031215612b2f57612b2e611e5f565b5b5f612b3c84828501612a7d565b91505092915050565b7f4d616b657220455243373231207061796d656e74206d75737420626520556e695f8201527f6e697469616c697a656400000000000000000000000000000000000000000000602082015250565b5f612b9f602a836124fa565b9150612baa82612b45565b604082019050919050565b5f6020820190508181035f830152612bcc81612b93565b9050919050565b7f54616b6572206d757374206e6f74206265207a65726f206164647265737300005f82015250565b5f612c07601e836124fa565b9150612c1282612bd3565b602082019050919050565b5f6020820190508181035f830152612c3481612bfb565b9050919050565b7f546f6b656e206d757374206e6f74206265207a65726f206164647265737300005f82015250565b5f612c6f601e836124fa565b9150612c7a82612c3b565b602082019050919050565b5f6020820190508181035f830152612c9c81612c63565b9050919050565b7f546f6b656e206164647265737320646f6573206e6f74206d617463682073656e5f8201527f6465720000000000000000000000000000000000000000000000000000000000602082015250565b5f612cfd6023836124fa565b9150612d0882612ca3565b604082019050919050565b5f6020820190508181035f830152612d2a81612cf1565b9050919050565b7f4f70657261746f72206d757374206265207468652073656e64657200000000005f82015250565b5f612d65601b836124fa565b9150612d7082612d31565b602082019050919050565b5f6020820190508181035f830152612d9281612d59565b9050919050565b7f54616b65722063616e6e6f74206265206120636f6e74726163740000000000005f82015250565b5f612dcd601a836124fa565b9150612dd882612d99565b602082019050919050565b5f6020820190508181035f830152612dfa81612dc1565b9050919050565b7f4261746368207472616e7366657273206e6f7420737570706f727465640000005f82015250565b5f612e35601d836124fa565b9150612e4082612e01565b602082019050919050565b5f6020820190508181035f830152612e6281612e29565b9050919050565b7f43616c6c6572206d75737420626520616e20454f4100000000000000000000005f82015250565b5f612e9d6015836124fa565b9150612ea882612e69565b602082019050919050565b5f6020820190508181035f830152612eca81612e91565b9050919050565b7f4d616b65722045524331313535207061796d656e74206d75737420626520556e5f8201527f696e697469616c697a6564000000000000000000000000000000000000000000602082015250565b5f612f2b602b836124fa565b9150612f3682612ed1565b604082019050919050565b5f6020820190508181035f830152612f5881612f1f565b9050919050565b7f56616c7565206d7573742062652067726561746572207468616e2030000000005f82015250565b5f612f93601c836124fa565b9150612f9e82612f5f565b602082019050919050565b5f6020820190508181035f830152612fc081612f87565b905091905056fea26469706673582212208adfd9bc3010e8e9bf1d503f3e439ea5632ee575d696af59d80e0a2268d48d7e64736f6c634300081a0033 \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/contract_bytes/swap_contract_bytes b/mm2src/mm2_test_helpers/contract_bytes/swap_contract_bytes new file mode 100644 index 0000000000..fea8557914 --- /dev/null +++ b/mm2src/mm2_test_helpers/contract_bytes/swap_contract_bytes @@ -0,0 +1 @@ +608060405234801561001057600080fd5b50611437806100206000396000f3fe60806040526004361061004a5760003560e01c806302ed292b1461004f5780630716326d146100de578063152cf3af1461017b57806346fc0294146101f65780639b415b2a14610294575b600080fd5b34801561005b57600080fd5b506100dc600480360360a081101561007257600080fd5b81019080803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610339565b005b3480156100ea57600080fd5b506101176004803603602081101561010157600080fd5b8101908080359060200190929190505050610867565b60405180846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526020018367ffffffffffffffff1667ffffffffffffffff16815260200182600381111561016557fe5b60ff168152602001935050505060405180910390f35b6101f46004803603608081101561019157600080fd5b8101908080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080356bffffffffffffffffffffffff19169060200190929190803567ffffffffffffffff1690602001909291905050506108bf565b005b34801561020257600080fd5b50610292600480360360a081101561021957600080fd5b81019080803590602001909291908035906020019092919080356bffffffffffffffffffffffff19169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610bd9565b005b610337600480360360c08110156102aa57600080fd5b810190808035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080356bffffffffffffffffffffffff19169060200190929190803567ffffffffffffffff169060200190929190505050610fe2565b005b6001600381111561034657fe5b600080878152602001908152602001600020600001601c9054906101000a900460ff16600381111561037457fe5b1461037e57600080fd5b6000600333836003600288604051602001808281526020019150506040516020818303038152906040526040518082805190602001908083835b602083106103db57805182526020820191506020810190506020830392506103b8565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa15801561041d573d6000803e3d6000fd5b5050506040513d602081101561043257600080fd5b8101908080519060200190929190505050604051602001808281526020019150506040516020818303038152906040526040518082805190602001908083835b602083106104955780518252602082019150602081019050602083039250610472565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa1580156104d7573d6000803e3d6000fd5b5050506040515160601b8689604051602001808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526014018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001955050505050506040516020818303038152906040526040518082805190602001908083835b602083106105fc57805182526020820191506020810190506020830392506105d9565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa15801561063e573d6000803e3d6000fd5b5050506040515160601b905060008087815260200190815260200160002060000160009054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461069657600080fd5b6002600080888152602001908152602001600020600001601c6101000a81548160ff021916908360038111156106c857fe5b0217905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561074e573373ffffffffffffffffffffffffffffffffffffffff166108fc869081150290604051600060405180830381858888f19350505050158015610748573d6000803e3d6000fd5b50610820565b60008390508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33886040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b1580156107da57600080fd5b505af11580156107ee573d6000803e3d6000fd5b505050506040513d602081101561080457600080fd5b810190808051906020019092919050505061081e57600080fd5b505b7f36c177bcb01c6d568244f05261e2946c8c977fa50822f3fa098c470770ee1f3e8685604051808381526020018281526020019250505060405180910390a1505050505050565b60006020528060005260406000206000915090508060000160009054906101000a900460601b908060000160149054906101000a900467ffffffffffffffff169080600001601c9054906101000a900460ff16905083565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141580156108fc5750600034115b801561094057506000600381111561091057fe5b600080868152602001908152602001600020600001601c9054906101000a900460ff16600381111561093e57fe5b145b61094957600080fd5b60006003843385600034604051602001808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526014018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001955050505050506040516020818303038152906040526040518082805190602001908083835b60208310610a6c5780518252602082019150602081019050602083039250610a49565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa158015610aae573d6000803e3d6000fd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018367ffffffffffffffff16815260200160016003811115610af757fe5b81525060008087815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c021790555060208201518160000160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550604082015181600001601c6101000a81548160ff02191690836003811115610b9357fe5b02179055509050507fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad57856040518082815260200191505060405180910390a15050505050565b60016003811115610be657fe5b600080878152602001908152602001600020600001601c9054906101000a900460ff166003811115610c1457fe5b14610c1e57600080fd5b600060038233868689604051602001808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526014018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001955050505050506040516020818303038152906040526040518082805190602001908083835b60208310610d405780518252602082019150602081019050602083039250610d1d565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa158015610d82573d6000803e3d6000fd5b5050506040515160601b905060008087815260200190815260200160002060000160009054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916148015610e10575060008087815260200190815260200160002060000160149054906101000a900467ffffffffffffffff1667ffffffffffffffff164210155b610e1957600080fd5b6003600080888152602001908152602001600020600001601c6101000a81548160ff02191690836003811115610e4b57fe5b0217905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610ed1573373ffffffffffffffffffffffffffffffffffffffff166108fc869081150290604051600060405180830381858888f19350505050158015610ecb573d6000803e3d6000fd5b50610fa3565b60008390508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33886040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b158015610f5d57600080fd5b505af1158015610f71573d6000803e3d6000fd5b505050506040513d6020811015610f8757600080fd5b8101908080519060200190929190505050610fa157600080fd5b505b7f1797d500133f8e427eb9da9523aa4a25cb40f50ebc7dbda3c7c81778973f35ba866040518082815260200191505060405180910390a1505050505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415801561101f5750600085115b801561106357506000600381111561103357fe5b600080888152602001908152602001600020600001601c9054906101000a900460ff16600381111561106157fe5b145b61106c57600080fd5b60006003843385888a604051602001808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401846bffffffffffffffffffffffff19166bffffffffffffffffffffffff191681526014018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001955050505050506040516020818303038152906040526040518082805190602001908083835b6020831061118e578051825260208201915060208101905060208303925061116b565b6001836020036101000a038019825116818451168082178552505050505050905001915050602060405180830381855afa1580156111d0573d6000803e3d6000fd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018367ffffffffffffffff1681526020016001600381111561121957fe5b81525060008089815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c021790555060208201518160000160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550604082015181600001601c6101000a81548160ff021916908360038111156112b557fe5b021790555090505060008590508073ffffffffffffffffffffffffffffffffffffffff166323b872dd33308a6040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019350505050602060405180830381600087803b15801561137d57600080fd5b505af1158015611391573d6000803e3d6000fd5b505050506040513d60208110156113a757600080fd5b81019080805190602001909291905050506113c157600080fd5b7fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad57886040518082815260200191505060405180910390a1505050505050505056fea265627a7a723158208c83db436905afce0b7be1012be64818c49323c12d451fe2ab6bce76ff6421c964736f6c63430005110032 \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/contract_bytes/taker_swap_v2_bytes b/mm2src/mm2_test_helpers/contract_bytes/taker_swap_v2_bytes new file mode 100644 index 0000000000..ea4cd8c817 --- /dev/null +++ b/mm2src/mm2_test_helpers/contract_bytes/taker_swap_v2_bytes @@ -0,0 +1 @@ +6080604052348015600e575f80fd5b50612ffd8061001c5f395ff3fe608060405234801561000f575f80fd5b50600436106100a7575f3560e01c8063b27e46fb1161006f578063b27e46fb1461015f578063bc197c811461017b578063c8d9009b146101ab578063c92cd12d146101c7578063efccb9eb146101e3578063f23a6e6114610215576100a7565b806301ffc9a7146100ab57806305ec158d146100db5780630f235fce146100f7578063150b7a02146101135780636e6bf6d214610143575b5f80fd5b6100c560048036038101906100c09190611ebc565b610245565b6040516100d29190611f01565b60405180910390f35b6100f560048036038101906100f09190611fda565b610326565b005b610111600480360381019061010c9190612077565b6105e6565b005b61012d60048036038101906101289190612161565b6108a0565b60405161013a91906121f4565b60405180910390f35b61015d60048036038101906101589190612077565b610cef565b005b61017960048036038101906101749190611fda565b610faa565b005b61019560048036038101906101909190612262565b611269565b6040516101a291906121f4565b60405180910390f35b6101c560048036038101906101c09190612077565b6112a5565b005b6101e160048036038101906101dc9190611fda565b6115ce565b005b6101fd60048036038101906101f89190612339565b6118fc565b60405161020c9392919061242f565b60405180910390f35b61022f600480360381019061022a9190612464565b611948565b60405161023c91906121f4565b60405180910390f35b5f7f4e2312e0000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916148061030f57507f150b7a02000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b8061031f575061031e82611ddc565b5b9050919050565b6001600381111561033a576103396123bc565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff16600381111561036c5761036b6123bc565b5b146103ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103a39061257a565b60405180910390fd5b5f600387336002896040516020016103c491906125b8565b6040516020818303038152906040526040516103e09190612624565b602060405180830381855afa1580156103fb573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061041e919061264e565b8888888860405160200161043897969594939291906126de565b6040516020818303038152906040526040516104549190612624565b602060405180830381855afa15801561046f573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916146104f9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104f0906127a8565b60405180910390fd5b60035f808a81526020019081526020015f205f0160186101000a81548160ff0219169083600381111561052f5761052e6123bc565b5b02179055507fac509cdcc7ddb189f81fff6f4824f5c95076e64c3bdce542c50feaa6779afd738860405161056391906127d5565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b81526004016105ae949392919061283f565b5f604051808303815f87803b1580156105c5575f80fd5b505af11580156105d7573d5f803e3d5ffd5b50505050505050505050505050565b600160038111156105fa576105f96123bc565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff16600381111561062c5761062b6123bc565b5b1461066c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106639061257a565b60405180910390fd5b5f600386338787878760405160200161068a96959493929190612895565b6040516020818303038152906040526040516106a69190612624565b602060405180830381855afa1580156106c1573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461074b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610742906127a8565b60405180910390fd5b5f808881526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff164210156107b6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107ad90612974565b60405180910390fd5b60035f808981526020019081526020015f205f0160186101000a81548160ff021916908360038111156107ec576107eb6123bc565b5b02179055507f5dedc4f52b757d9112d09ca0b2f022927104d54e3f54da091587e8ad192190728760405161082091906127d5565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b815260040161086993929190612992565b5f604051808303815f87803b158015610880575f80fd5b505af1158015610892573d5f803e3d5ffd5b505050505050505050505050565b5f8083838101906108b19190612b1a565b90505f60038111156108c6576108c56123bc565b5b5f80835f015181526020019081526020015f205f0160189054906101000a900460ff1660038111156108fb576108fa6123bc565b5b1461093b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161093290612bb5565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816020015173ffffffffffffffffffffffffffffffffffffffff16036109ad576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016109a490612c1d565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816040015173ffffffffffffffffffffffffffffffffffffffff1603610a1f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a1690612c85565b60405180910390fd5b806040015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610a91576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a8890612d13565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff1614610aff576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610af690612d7b565b60405180910390fd5b610b0c8160200151611e45565b15610b4c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b4390612de3565b60405180910390fd5b5f60038260200151888460600151856080015186604001518b604051602001610b7a96959493929190612895565b604051602081830303815290604052604051610b969190612624565b602060405180830381855afa158015610bb1573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018360a0015163ffffffff16815260200160016003811115610c0157610c006123bc565b5b8152505f80845f015181526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548160ff02191690836003811115610c9857610c976123bc565b5b02179055509050507ff1dc11bbb6d7542c4267ecf1d370ff4c7092518633ecae9939e8488f4e53d2ad825f0151604051610cd291906127d5565b60405180910390a163150b7a0260e01b9250505095945050505050565b60016003811115610d0357610d026123bc565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff166003811115610d3557610d346123bc565b5b14610d75576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d6c9061257a565b60405180910390fd5b5f60038633600288604051602001610d8d91906125b8565b604051602081830303815290604052604051610da99190612624565b602060405180830381855afa158015610dc4573d5f803e3d5ffd5b5050506040513d601f19601f82011682018060405250810190610de7919061264e565b878787604051602001610dff96959493929190612895565b604051602081830303815290604052604051610e1b9190612624565b602060405180830381855afa158015610e36573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610ec0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610eb7906127a8565b60405180910390fd5b60035f808981526020019081526020015f205f0160186101000a81548160ff02191690836003811115610ef657610ef56123bc565b5b02179055507fac509cdcc7ddb189f81fff6f4824f5c95076e64c3bdce542c50feaa6779afd7387604051610f2a91906127d5565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b8152600401610f7393929190612992565b5f604051808303815f87803b158015610f8a575f80fd5b505af1158015610f9c573d5f803e3d5ffd5b505050505050505050505050565b60016003811115610fbe57610fbd6123bc565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff166003811115610ff057610fef6123bc565b5b14611030576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110279061257a565b60405180910390fd5b5f60038733888888888860405160200161105097969594939291906126de565b60405160208183030381529060405260405161106c9190612624565b602060405180830381855afa158015611087573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614611111576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611108906127a8565b60405180910390fd5b5f808981526020019081526020015f205f0160149054906101000a900463ffffffff1663ffffffff1642101561117c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161117390612974565b60405180910390fd5b60035f808a81526020019081526020015f205f0160186101000a81548160ff021916908360038111156111b2576111b16123bc565b5b02179055507f5dedc4f52b757d9112d09ca0b2f022927104d54e3f54da091587e8ad19219072886040516111e691906127d5565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b8152600401611231949392919061283f565b5f604051808303815f87803b158015611248575f80fd5b505af115801561125a573d5f803e3d5ffd5b50505050505050505050505050565b5f6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161129c90612e4b565b60405180910390fd5b600160038111156112b9576112b86123bc565b5b5f808881526020019081526020015f205f0160189054906101000a900460ff1660038111156112eb576112ea6123bc565b5b1461132b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016113229061257a565b60405180910390fd5b3273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611399576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161139090612eb3565b60405180910390fd5b5f60033387876002886040516020016113b291906125b8565b6040516020818303038152906040526040516113ce9190612624565b602060405180830381855afa1580156113e9573d5f803e3d5ffd5b5050506040513d601f19601f8201168201806040525081019061140c919061264e565b878760405160200161142396959493929190612895565b60405160208183030381529060405260405161143f9190612624565b602060405180830381855afa15801561145a573d5f803e3d5ffd5b5050506040515160601b90505f808881526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916146114e4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114db906127a8565b60405180910390fd5b60025f808981526020019081526020015f205f0160186101000a81548160ff0219169083600381111561151a576115196123bc565b5b02179055507fad62ed075fe8969df63026f45152d6e996a0697a736a8de92ee85ae9c9958cf08760405161154e91906127d5565b60405180910390a15f8390508073ffffffffffffffffffffffffffffffffffffffff166342842e0e3033866040518463ffffffff1660e01b815260040161159793929190612992565b5f604051808303815f87803b1580156115ae575f80fd5b505af11580156115c0573d5f803e3d5ffd5b505050505050505050505050565b600160038111156115e2576115e16123bc565b5b5f808981526020019081526020015f205f0160189054906101000a900460ff166003811115611614576116136123bc565b5b14611654576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161164b9061257a565b60405180910390fd5b3273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146116c2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016116b990612eb3565b60405180910390fd5b5f60033388886002896040516020016116db91906125b8565b6040516020818303038152906040526040516116f79190612624565b602060405180830381855afa158015611712573d5f803e3d5ffd5b5050506040513d601f19601f82011682018060405250810190611735919061264e565b88888860405160200161174e97969594939291906126de565b60405160208183030381529060405260405161176a9190612624565b602060405180830381855afa158015611785573d5f803e3d5ffd5b5050506040515160601b90505f808981526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461180f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611806906127a8565b60405180910390fd5b60025f808a81526020019081526020015f205f0160186101000a81548160ff02191690836003811115611845576118446123bc565b5b02179055507fad62ed075fe8969df63026f45152d6e996a0697a736a8de92ee85ae9c9958cf08860405161187991906127d5565b60405180910390a15f8490508073ffffffffffffffffffffffffffffffffffffffff1663f242432a303387876040518563ffffffff1660e01b81526004016118c4949392919061283f565b5f604051808303815f87803b1580156118db575f80fd5b505af11580156118ed573d5f803e3d5ffd5b50505050505050505050505050565b5f602052805f5260405f205f91509050805f015f9054906101000a900460601b90805f0160149054906101000a900463ffffffff1690805f0160189054906101000a900460ff16905083565b5f8083838101906119599190612b1a565b90505f600381111561196e5761196d6123bc565b5b5f80835f015181526020019081526020015f205f0160189054906101000a900460ff1660038111156119a3576119a26123bc565b5b146119e3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016119da90612f41565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816020015173ffffffffffffffffffffffffffffffffffffffff1603611a55576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611a4c90612c1d565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff16816040015173ffffffffffffffffffffffffffffffffffffffff1603611ac7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611abe90612c85565b60405180910390fd5b806040015173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611b39576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b3090612d13565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff1614611ba7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b9e90612d7b565b60405180910390fd5b5f8511611be9576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611be090612fa9565b60405180910390fd5b611bf68160200151611e45565b15611c36576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c2d90612de3565b60405180910390fd5b5f60038260200151898460600151856080015186604001518c8c604051602001611c6697969594939291906126de565b604051602081830303815290604052604051611c829190612624565b602060405180830381855afa158015611c9d573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018360a0015163ffffffff16815260200160016003811115611ced57611cec6123bc565b5b8152505f80845f015181526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548163ffffffff021916908363ffffffff1602179055506040820151815f0160186101000a81548160ff02191690836003811115611d8457611d836123bc565b5b02179055509050507ff1dc11bbb6d7542c4267ecf1d370ff4c7092518633ecae9939e8488f4e53d2ad825f0151604051611dbe91906127d5565b60405180910390a163f23a6e6160e01b925050509695505050505050565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b5f80823b90505f8111915050919050565b5f604051905090565b5f80fd5b5f80fd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b611e9b81611e67565b8114611ea5575f80fd5b50565b5f81359050611eb681611e92565b92915050565b5f60208284031215611ed157611ed0611e5f565b5b5f611ede84828501611ea8565b91505092915050565b5f8115159050919050565b611efb81611ee7565b82525050565b5f602082019050611f145f830184611ef2565b92915050565b5f819050919050565b611f2c81611f1a565b8114611f36575f80fd5b50565b5f81359050611f4781611f23565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f611f7682611f4d565b9050919050565b611f8681611f6c565b8114611f90575f80fd5b50565b5f81359050611fa181611f7d565b92915050565b5f819050919050565b611fb981611fa7565b8114611fc3575f80fd5b50565b5f81359050611fd481611fb0565b92915050565b5f805f805f805f60e0888a031215611ff557611ff4611e5f565b5b5f6120028a828b01611f39565b97505060206120138a828b01611f93565b96505060406120248a828b01611f39565b95505060606120358a828b01611f39565b94505060806120468a828b01611f93565b93505060a06120578a828b01611fc6565b92505060c06120688a828b01611fc6565b91505092959891949750929550565b5f805f805f8060c0878903121561209157612090611e5f565b5b5f61209e89828a01611f39565b96505060206120af89828a01611f93565b95505060406120c089828a01611f39565b94505060606120d189828a01611f39565b93505060806120e289828a01611f93565b92505060a06120f389828a01611fc6565b9150509295509295509295565b5f80fd5b5f80fd5b5f80fd5b5f8083601f84011261212157612120612100565b5b8235905067ffffffffffffffff81111561213e5761213d612104565b5b60208301915083600182028301111561215a57612159612108565b5b9250929050565b5f805f805f6080868803121561217a57612179611e5f565b5b5f61218788828901611f93565b955050602061219888828901611f93565b94505060406121a988828901611fc6565b935050606086013567ffffffffffffffff8111156121ca576121c9611e63565b5b6121d68882890161210c565b92509250509295509295909350565b6121ee81611e67565b82525050565b5f6020820190506122075f8301846121e5565b92915050565b5f8083601f84011261222257612221612100565b5b8235905067ffffffffffffffff81111561223f5761223e612104565b5b60208301915083602082028301111561225b5761225a612108565b5b9250929050565b5f805f805f805f8060a0898b03121561227e5761227d611e5f565b5b5f61228b8b828c01611f93565b985050602061229c8b828c01611f93565b975050604089013567ffffffffffffffff8111156122bd576122bc611e63565b5b6122c98b828c0161220d565b9650965050606089013567ffffffffffffffff8111156122ec576122eb611e63565b5b6122f88b828c0161220d565b9450945050608089013567ffffffffffffffff81111561231b5761231a611e63565b5b6123278b828c0161210c565b92509250509295985092959890939650565b5f6020828403121561234e5761234d611e5f565b5b5f61235b84828501611f39565b91505092915050565b5f7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000082169050919050565b61239881612364565b82525050565b5f63ffffffff82169050919050565b6123b68161239e565b82525050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b600481106123fa576123f96123bc565b5b50565b5f81905061240a826123e9565b919050565b5f612419826123fd565b9050919050565b6124298161240f565b82525050565b5f6060820190506124425f83018661238f565b61244f60208301856123ad565b61245c6040830184612420565b949350505050565b5f805f805f8060a0878903121561247e5761247d611e5f565b5b5f61248b89828a01611f93565b965050602061249c89828a01611f93565b95505060406124ad89828a01611fc6565b94505060606124be89828a01611fc6565b935050608087013567ffffffffffffffff8111156124df576124de611e63565b5b6124eb89828a0161210c565b92509250509295509295509295565b5f82825260208201905092915050565b7f496e76616c6964207061796d656e742073746174652e204d75737420626520505f8201527f61796d656e7453656e7400000000000000000000000000000000000000000000602082015250565b5f612564602a836124fa565b915061256f8261250a565b604082019050919050565b5f6020820190508181035f83015261259181612558565b9050919050565b5f819050919050565b6125b26125ad82611f1a565b612598565b82525050565b5f6125c382846125a1565b60208201915081905092915050565b5f81519050919050565b5f81905092915050565b8281835e5f83830152505050565b5f6125fe826125d2565b61260881856125dc565b93506126188185602086016125e6565b80840191505092915050565b5f61262f82846125f4565b915081905092915050565b5f8151905061264881611f23565b92915050565b5f6020828403121561266357612662611e5f565b5b5f6126708482850161263a565b91505092915050565b5f8160601b9050919050565b5f61268f82612679565b9050919050565b5f6126a082612685565b9050919050565b6126b86126b382611f6c565b612696565b82525050565b5f819050919050565b6126d86126d382611fa7565b6126be565b82525050565b5f6126e9828a6126a7565b6014820191506126f982896126a7565b60148201915061270982886125a1565b60208201915061271982876125a1565b60208201915061272982866126a7565b60148201915061273982856126c7565b60208201915061274982846126c7565b60208201915081905098975050505050505050565b7f496e76616c6964207061796d656e7448617368000000000000000000000000005f82015250565b5f6127926013836124fa565b915061279d8261275e565b602082019050919050565b5f6020820190508181035f8301526127bf81612786565b9050919050565b6127cf81611f1a565b82525050565b5f6020820190506127e85f8301846127c6565b92915050565b6127f781611f6c565b82525050565b61280681611fa7565b82525050565b5f82825260208201905092915050565b50565b5f61282a5f8361280c565b91506128358261281c565b5f82019050919050565b5f60a0820190506128525f8301876127ee565b61285f60208301866127ee565b61286c60408301856127fd565b61287960608301846127fd565b818103608083015261288a8161281f565b905095945050505050565b5f6128a082896126a7565b6014820191506128b082886126a7565b6014820191506128c082876125a1565b6020820191506128d082866125a1565b6020820191506128e082856126a7565b6014820191506128f082846126c7565b602082019150819050979650505050505050565b7f43757272656e742074696d657374616d70206469646e277420657863656564205f8201527f7061796d656e7420726566756e64206c6f636b2074696d650000000000000000602082015250565b5f61295e6038836124fa565b915061296982612904565b604082019050919050565b5f6020820190508181035f83015261298b81612952565b9050919050565b5f6060820190506129a55f8301866127ee565b6129b260208301856127ee565b6129bf60408301846127fd565b949350505050565b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b612a11826129cb565b810181811067ffffffffffffffff82111715612a3057612a2f6129db565b5b80604052505050565b5f612a42611e56565b9050612a4e8282612a08565b919050565b612a5c8161239e565b8114612a66575f80fd5b50565b5f81359050612a7781612a53565b92915050565b5f60c08284031215612a9257612a916129c7565b5b612a9c60c0612a39565b90505f612aab84828501611f39565b5f830152506020612abe84828501611f93565b6020830152506040612ad284828501611f93565b6040830152506060612ae684828501611f39565b6060830152506080612afa84828501611f39565b60808301525060a0612b0e84828501612a69565b60a08301525092915050565b5f60c08284031215612b2f57612b2e611e5f565b5b5f612b3c84828501612a7d565b91505092915050565b7f4d616b657220455243373231207061796d656e74206d75737420626520556e695f8201527f6e697469616c697a656400000000000000000000000000000000000000000000602082015250565b5f612b9f602a836124fa565b9150612baa82612b45565b604082019050919050565b5f6020820190508181035f830152612bcc81612b93565b9050919050565b7f54616b6572206d757374206e6f74206265207a65726f206164647265737300005f82015250565b5f612c07601e836124fa565b9150612c1282612bd3565b602082019050919050565b5f6020820190508181035f830152612c3481612bfb565b9050919050565b7f546f6b656e206d757374206e6f74206265207a65726f206164647265737300005f82015250565b5f612c6f601e836124fa565b9150612c7a82612c3b565b602082019050919050565b5f6020820190508181035f830152612c9c81612c63565b9050919050565b7f546f6b656e206164647265737320646f6573206e6f74206d617463682073656e5f8201527f6465720000000000000000000000000000000000000000000000000000000000602082015250565b5f612cfd6023836124fa565b9150612d0882612ca3565b604082019050919050565b5f6020820190508181035f830152612d2a81612cf1565b9050919050565b7f4f70657261746f72206d757374206265207468652073656e64657200000000005f82015250565b5f612d65601b836124fa565b9150612d7082612d31565b602082019050919050565b5f6020820190508181035f830152612d9281612d59565b9050919050565b7f54616b65722063616e6e6f74206265206120636f6e74726163740000000000005f82015250565b5f612dcd601a836124fa565b9150612dd882612d99565b602082019050919050565b5f6020820190508181035f830152612dfa81612dc1565b9050919050565b7f4261746368207472616e7366657273206e6f7420737570706f727465640000005f82015250565b5f612e35601d836124fa565b9150612e4082612e01565b602082019050919050565b5f6020820190508181035f830152612e6281612e29565b9050919050565b7f43616c6c6572206d75737420626520616e20454f4100000000000000000000005f82015250565b5f612e9d6015836124fa565b9150612ea882612e69565b602082019050919050565b5f6020820190508181035f830152612eca81612e91565b9050919050565b7f4d616b65722045524331313535207061796d656e74206d75737420626520556e5f8201527f696e697469616c697a6564000000000000000000000000000000000000000000602082015250565b5f612f2b602b836124fa565b9150612f3682612ed1565b604082019050919050565b5f6020820190508181035f830152612f5881612f1f565b9050919050565b7f56616c7565206d7573742062652067726561746572207468616e2030000000005f82015250565b5f612f93601c836124fa565b9150612f9e82612f5f565b602082019050919050565b5f6020820190508181035f830152612fc081612f87565b905091905056fea26469706673582212208adfd9bc3010e8e9bf1d503f3e439ea5632ee575d696af59d80e0a2268d48d7e64736f6c634300081a0033 \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/contract_bytes/watchers_swap_contract_bytes b/mm2src/mm2_test_helpers/contract_bytes/watchers_swap_contract_bytes new file mode 100644 index 0000000000..1b00a95ba1 --- /dev/null +++ b/mm2src/mm2_test_helpers/contract_bytes/watchers_swap_contract_bytes @@ -0,0 +1 @@ +608060405234801561000f575f80fd5b50612aa48061001d5f395ff3fe608060405260043610610085575f3560e01c806346fc02941161005857806346fc0294146101275780636a3227861461014f5780639b415b2a1461016b578063b5985c4d14610193578063cd1dde34146101bb57610085565b806302ed292b146100895780630716326d146100b15780630971fd54146100ef578063152cf3af1461010b575b5f80fd5b348015610094575f80fd5b506100af60048036038101906100aa9190611e1d565b6101e3565b005b3480156100bc575f80fd5b506100d760048036038101906100d29190611e94565b610518565b6040516100e693929190611f8e565b60405180910390f35b6101096004803603810190610104919061206f565b610568565b005b6101256004803603810190610120919061210c565b610787565b005b348015610132575f80fd5b5061014d60048036038101906101489190612170565b61099d565b005b610169600480360381019061016491906121e7565b610c4d565b005b348015610176575f80fd5b50610191600480360381019061018c91906122ab565b610f61565b005b34801561019e575f80fd5b506101b960048036038101906101b49190612334565b611203565b005b3480156101c6575f80fd5b506101e160048036038101906101dc91906123f8565b611887565b005b600160038111156101f7576101f6611f1b565b5b5f808781526020019081526020015f205f01601c9054906101000a900460ff16600381111561022957610228611f1b565b5b14610232575f80fd5b5f60033383600360028860405160200161024c91906124dc565b6040516020818303038152906040526040516102689190612562565b602060405180830381855afa158015610283573d5f803e3d5ffd5b5050506040513d601f19601f820116820180604052508101906102a6919061258c565b6040516020016102b691906124dc565b6040516020818303038152906040526040516102d29190612562565b602060405180830381855afa1580156102ed573d5f803e3d5ffd5b5050506040515160601b868960405160200161030d95949392919061263c565b6040516020818303038152906040526040516103299190612562565b602060405180830381855afa158015610344573d5f803e3d5ffd5b5050506040515160601b90505f808781526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff191614610397575f80fd5b60025f808881526020019081526020015f205f01601c6101000a81548160ff021916908360038111156103cd576103cc611f1b565b5b02179055505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361044e573373ffffffffffffffffffffffffffffffffffffffff166108fc8690811502906040515f60405180830381858888f19350505050158015610448573d5f803e3d5ffd5b506104d7565b5f8390508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33886040518363ffffffff1660e01b815260040161048d9291906126b8565b6020604051808303815f875af11580156104a9573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104cd91906126f3565b6104d5575f80fd5b505b7f36c177bcb01c6d568244f05261e2946c8c977fa50822f3fa098c470770ee1f3e868560405161050892919061272d565b60405180910390a1505050505050565b5f602052805f5260405f205f91509050805f015f9054906101000a900460601b90805f0160149054906101000a900467ffffffffffffffff1690805f01601c9054906101000a900460ff16905083565b5f73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff16141580156105a357505f34115b80156105f157505f60038111156105bd576105bc611f1b565b5b5f808981526020019081526020015f205f01601c9054906101000a900460ff1660038111156105ef576105ee611f1b565b5b145b6105f9575f80fd5b5f60038733885f3489898960405160200161061b9897969594939291906127e7565b6040516020818303038152906040526040516106379190612562565b602060405180830381855afa158015610652573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018667ffffffffffffffff168152602001600160038111156106a2576106a1611f1b565b5b8152505f808a81526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506040820151815f01601c6101000a81548160ff0219169083600381111561073e5761073d611f1b565b5b02179055509050507fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad57886040516107759190612878565b60405180910390a15050505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141580156107c257505f34115b801561081057505f60038111156107dc576107db611f1b565b5b5f808681526020019081526020015f205f01601c9054906101000a900460ff16600381111561080e5761080d611f1b565b5b145b610818575f80fd5b5f60038433855f3460405160200161083495949392919061263c565b6040516020818303038152906040526040516108509190612562565b602060405180830381855afa15801561086b573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018367ffffffffffffffff168152602001600160038111156108bb576108ba611f1b565b5b8152505f808781526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506040820151815f01601c6101000a81548160ff0219169083600381111561095757610956611f1b565b5b02179055509050507fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad578560405161098e9190612878565b60405180910390a15050505050565b600160038111156109b1576109b0611f1b565b5b5f808781526020019081526020015f205f01601c9054906101000a900460ff1660038111156109e3576109e2611f1b565b5b146109ec575f80fd5b5f60038233868689604051602001610a0895949392919061263c565b604051602081830303815290604052604051610a249190612562565b602060405180830381855afa158015610a3f573d5f803e3d5ffd5b5050506040515160601b90505f808781526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff1916148015610ac657505f808781526020019081526020015f205f0160149054906101000a900467ffffffffffffffff1667ffffffffffffffff164210155b610ace575f80fd5b60035f808881526020019081526020015f205f01601c6101000a81548160ff02191690836003811115610b0457610b03611f1b565b5b02179055505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610b85573373ffffffffffffffffffffffffffffffffffffffff166108fc8690811502906040515f60405180830381858888f19350505050158015610b7f573d5f803e3d5ffd5b50610c0e565b5f8390508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33886040518363ffffffff1660e01b8152600401610bc49291906126b8565b6020604051808303815f875af1158015610be0573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c0491906126f3565b610c0c575f80fd5b505b7f1797d500133f8e427eb9da9523aa4a25cb40f50ebc7dbda3c7c81778973f35ba86604051610c3d9190612878565b60405180910390a1505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff1614158015610c8857505f88115b8015610cd657505f6003811115610ca257610ca1611f1b565b5b5f808b81526020019081526020015f205f01601c9054906101000a900460ff166003811115610cd457610cd3611f1b565b5b145b610cde575f80fd5b5f6003811115610cf157610cf0611f1b565b5b836003811115610d0457610d03611f1b565b5b14158015610d365750600380811115610d2057610d1f611f1b565b5b836003811115610d3357610d32611f1b565b5b14155b15610d4757803414610d46575f80fd5b5b5f60038733888b8d898989604051602001610d699897969594939291906127e7565b604051602081830303815290604052604051610d859190612562565b602060405180830381855afa158015610da0573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018667ffffffffffffffff16815260200160016003811115610df057610def611f1b565b5b8152505f808c81526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506040820151815f01601c6101000a81548160ff02191690836003811115610e8c57610e8b611f1b565b5b02179055509050505f8890508073ffffffffffffffffffffffffffffffffffffffff166323b872dd33308d6040518463ffffffff1660e01b8152600401610ed593929190612891565b6020604051808303815f875af1158015610ef1573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610f1591906126f3565b610f1d575f80fd5b7fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad578b604051610f4c9190612878565b60405180910390a15050505050505050505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614158015610f9c57505f85115b8015610fea57505f6003811115610fb657610fb5611f1b565b5b5f808881526020019081526020015f205f01601c9054906101000a900460ff166003811115610fe857610fe7611f1b565b5b145b610ff2575f80fd5b5f6003843385888a60405160200161100e95949392919061263c565b60405160208183030381529060405260405161102a9190612562565b602060405180830381855afa158015611045573d5f803e3d5ffd5b5050506040515160601b90506040518060600160405280826bffffffffffffffffffffffff191681526020018367ffffffffffffffff1681526020016001600381111561109557611094611f1b565b5b8152505f808981526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908360601c02179055506020820151815f0160146101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506040820151815f01601c6101000a81548160ff0219169083600381111561113157611130611f1b565b5b02179055509050505f8590508073ffffffffffffffffffffffffffffffffffffffff166323b872dd33308a6040518463ffffffff1660e01b815260040161117a93929190612891565b6020604051808303815f875af1158015611196573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906111ba91906126f3565b6111c2575f80fd5b7fccc9c05183599bd3135da606eaaf535daffe256e9de33c048014cffcccd4ad57886040516111f19190612878565b60405180910390a15050505050505050565b6001600381111561121757611216611f1b565b5b5f808b81526020019081526020015f205f01601c9054906101000a900460ff16600381111561124957611248611f1b565b5b14611289576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161128090612920565b60405180910390fd5b5f60038587600360028c6040516020016112a391906124dc565b6040516020818303038152906040526040516112bf9190612562565b602060405180830381855afa1580156112da573d5f803e3d5ffd5b5050506040513d601f19601f820116820180604052508101906112fd919061258c565b60405160200161130d91906124dc565b6040516020818303038152906040526040516113299190612562565b602060405180830381855afa158015611344573d5f803e3d5ffd5b5050506040515160601b8a8d89898960405160200161136a9897969594939291906127e7565b6040516020818303038152906040526040516113869190612562565b602060405180830381855afa1580156113a1573d5f803e3d5ffd5b5050506040515160601b90505f808b81526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161461142b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161142290612988565b60405180910390fd5b60025f808c81526020019081526020015f205f01601c6101000a81548160ff0219169083600381111561146157611460611f1b565b5b02179055505f73ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff160361159e575f8060038111156114ad576114ac611f1b565b5b8560038111156114c0576114bf611f1b565b5b1480156114cb575083155b6114e057828a6114db91906129d3565b6114e2565b895b90508573ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f19350505050158015611527573d5f803e3d5ffd5b5060038081111561153b5761153a611f1b565b5b85600381111561154e5761154d611f1b565b5b03611598573373ffffffffffffffffffffffffffffffffffffffff166108fc8490811502906040515f60405180830381858888f19350505050158015611596573d5f803e3d5ffd5b505b50611786565b5f6003808111156115b2576115b1611f1b565b5b8560038111156115c5576115c4611f1b565b5b146115d057896115dd565b828a6115dc91906129d3565b5b90505f8890508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb88846040518363ffffffff1660e01b815260040161161e9291906126b8565b6020604051808303815f875af115801561163a573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061165e91906126f3565b61169d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161169490612a50565b60405180910390fd5b6003808111156116b0576116af611f1b565b5b8660038111156116c3576116c2611f1b565b5b03611783578073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33866040518363ffffffff1660e01b81526004016117039291906126b8565b6020604051808303815f875af115801561171f573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061174391906126f3565b611782576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161177990612a50565b60405180910390fd5b5b50505b6002600381111561179a57611799611f1b565b5b8460038111156117ad576117ac611f1b565b5b036117f7578573ffffffffffffffffffffffffffffffffffffffff166108fc8390811502906040515f60405180830381858888f193505050501580156117f5573d5f803e3d5ffd5b505b8215611842573373ffffffffffffffffffffffffffffffffffffffff166108fc8390811502906040515f60405180830381858888f19350505050158015611840573d5f803e3d5ffd5b505b7f36c177bcb01c6d568244f05261e2946c8c977fa50822f3fa098c470770ee1f3e8a8960405161187392919061272d565b60405180910390a150505050505050505050565b6001600381111561189b5761189a611f1b565b5b5f808b81526020019081526020015f205f01601c9054906101000a900460ff1660038111156118cd576118cc611f1b565b5b146118d6575f80fd5b5f600385878a8a8d8989896040516020016118f89897969594939291906127e7565b6040516020818303038152906040526040516119149190612562565b602060405180830381855afa15801561192f573d5f803e3d5ffd5b5050506040515160601b90505f808b81526020019081526020015f205f015f9054906101000a900460601b6bffffffffffffffffffffffff1916816bffffffffffffffffffffffff19161480156119b657505f808b81526020019081526020015f205f0160149054906101000a900467ffffffffffffffff1667ffffffffffffffff164210155b6119be575f80fd5b60035f808c81526020019081526020015f205f01601c6101000a81548160ff021916908360038111156119f4576119f3611f1b565b5b02179055505f73ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff1603611b27575f806003811115611a4057611a3f611f1b565b5b856003811115611a5357611a52611f1b565b5b14611a6957828a611a6491906129d3565b611a6b565b895b90508673ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f19350505050158015611ab0573d5f803e3d5ffd5b505f6003811115611ac457611ac3611f1b565b5b856003811115611ad757611ad6611f1b565b5b14611b21573373ffffffffffffffffffffffffffffffffffffffff166108fc8490811502906040515f60405180830381858888f19350505050158015611b1f573d5f803e3d5ffd5b505b50611d16565b5f600380811115611b3b57611b3a611f1b565b5b856003811115611b4e57611b4d611f1b565b5b14611b595789611b66565b828a611b6591906129d3565b5b90505f8890508073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb89846040518363ffffffff1660e01b8152600401611ba79291906126b8565b6020604051808303815f875af1158015611bc3573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611be791906126f3565b611bef575f80fd5b600380811115611c0257611c01611f1b565b5b866003811115611c1557611c14611f1b565b5b03611ca2578073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb33866040518363ffffffff1660e01b8152600401611c559291906126b8565b6020604051808303815f875af1158015611c71573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611c9591906126f3565b611c9d575f80fd5b611d13565b5f6003811115611cb557611cb4611f1b565b5b866003811115611cc857611cc7611f1b565b5b14611d12573373ffffffffffffffffffffffffffffffffffffffff166108fc8590811502906040515f60405180830381858888f19350505050158015611d10573d5f803e3d5ffd5b505b5b50505b7f1797d500133f8e427eb9da9523aa4a25cb40f50ebc7dbda3c7c81778973f35ba8a604051611d459190612878565b60405180910390a150505050505050505050565b5f80fd5b5f819050919050565b611d6f81611d5d565b8114611d79575f80fd5b50565b5f81359050611d8a81611d66565b92915050565b5f819050919050565b611da281611d90565b8114611dac575f80fd5b50565b5f81359050611dbd81611d99565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f611dec82611dc3565b9050919050565b611dfc81611de2565b8114611e06575f80fd5b50565b5f81359050611e1781611df3565b92915050565b5f805f805f60a08688031215611e3657611e35611d59565b5b5f611e4388828901611d7c565b9550506020611e5488828901611daf565b9450506040611e6588828901611d7c565b9350506060611e7688828901611e09565b9250506080611e8788828901611e09565b9150509295509295909350565b5f60208284031215611ea957611ea8611d59565b5b5f611eb684828501611d7c565b91505092915050565b5f7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000082169050919050565b611ef381611ebf565b82525050565b5f67ffffffffffffffff82169050919050565b611f1581611ef9565b82525050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602160045260245ffd5b60048110611f5957611f58611f1b565b5b50565b5f819050611f6982611f48565b919050565b5f611f7882611f5c565b9050919050565b611f8881611f6e565b82525050565b5f606082019050611fa15f830186611eea565b611fae6020830185611f0c565b611fbb6040830184611f7f565b949350505050565b611fcc81611ebf565b8114611fd6575f80fd5b50565b5f81359050611fe781611fc3565b92915050565b611ff681611ef9565b8114612000575f80fd5b50565b5f8135905061201181611fed565b92915050565b60048110612023575f80fd5b50565b5f8135905061203481612017565b92915050565b5f8115159050919050565b61204e8161203a565b8114612058575f80fd5b50565b5f8135905061206981612045565b92915050565b5f805f805f805f60e0888a03121561208a57612089611d59565b5b5f6120978a828b01611d7c565b97505060206120a88a828b01611e09565b96505060406120b98a828b01611fd9565b95505060606120ca8a828b01612003565b94505060806120db8a828b01612026565b93505060a06120ec8a828b0161205b565b92505060c06120fd8a828b01611daf565b91505092959891949750929550565b5f805f806080858703121561212457612123611d59565b5b5f61213187828801611d7c565b945050602061214287828801611e09565b935050604061215387828801611fd9565b925050606061216487828801612003565b91505092959194509250565b5f805f805f60a0868803121561218957612188611d59565b5b5f61219688828901611d7c565b95505060206121a788828901611daf565b94505060406121b888828901611fd9565b93505060606121c988828901611e09565b92505060806121da88828901611e09565b9150509295509295909350565b5f805f805f805f805f6101208a8c03121561220557612204611d59565b5b5f6122128c828d01611d7c565b99505060206122238c828d01611daf565b98505060406122348c828d01611e09565b97505060606122458c828d01611e09565b96505060806122568c828d01611fd9565b95505060a06122678c828d01612003565b94505060c06122788c828d01612026565b93505060e06122898c828d0161205b565b92505061010061229b8c828d01611daf565b9150509295985092959850929598565b5f805f805f8060c087890312156122c5576122c4611d59565b5b5f6122d289828a01611d7c565b96505060206122e389828a01611daf565b95505060406122f489828a01611e09565b945050606061230589828a01611e09565b935050608061231689828a01611fd9565b92505060a061232789828a01612003565b9150509295509295509295565b5f805f805f805f805f6101208a8c03121561235257612351611d59565b5b5f61235f8c828d01611d7c565b99505060206123708c828d01611daf565b98505060406123818c828d01611d7c565b97505060606123928c828d01611e09565b96505060806123a38c828d01611e09565b95505060a06123b48c828d01611e09565b94505060c06123c58c828d01612026565b93505060e06123d68c828d0161205b565b9250506101006123e88c828d01611daf565b9150509295985092959850929598565b5f805f805f805f805f6101208a8c03121561241657612415611d59565b5b5f6124238c828d01611d7c565b99505060206124348c828d01611daf565b98505060406124458c828d01611fd9565b97505060606124568c828d01611e09565b96505060806124678c828d01611e09565b95505060a06124788c828d01611e09565b94505060c06124898c828d01612026565b93505060e061249a8c828d0161205b565b9250506101006124ac8c828d01611daf565b9150509295985092959850929598565b5f819050919050565b6124d66124d182611d5d565b6124bc565b82525050565b5f6124e782846124c5565b60208201915081905092915050565b5f81519050919050565b5f81905092915050565b5f5b8381101561252757808201518184015260208101905061250c565b5f8484015250505050565b5f61253c826124f6565b6125468185612500565b935061255681856020860161250a565b80840191505092915050565b5f61256d8284612532565b915081905092915050565b5f8151905061258681611d66565b92915050565b5f602082840312156125a1576125a0611d59565b5b5f6125ae84828501612578565b91505092915050565b5f8160601b9050919050565b5f6125cd826125b7565b9050919050565b5f6125de826125c3565b9050919050565b6125f66125f182611de2565b6125d4565b82525050565b5f819050919050565b61261661261182611ebf565b6125fc565b82525050565b5f819050919050565b61263661263182611d90565b61261c565b82525050565b5f61264782886125e5565b60148201915061265782876125e5565b6014820191506126678286612605565b60148201915061267782856125e5565b6014820191506126878284612625565b6020820191508190509695505050505050565b6126a381611de2565b82525050565b6126b281611d90565b82525050565b5f6040820190506126cb5f83018561269a565b6126d860208301846126a9565b9392505050565b5f815190506126ed81612045565b92915050565b5f6020828403121561270857612707611d59565b5b5f612715848285016126df565b91505092915050565b61272781611d5d565b82525050565b5f6040820190506127405f83018561271e565b61274d602083018461271e565b9392505050565b6004811061276557612764611f1b565b5b50565b5f81905061277582612754565b919050565b5f61278482612768565b9050919050565b5f8160f81b9050919050565b5f6127a18261278b565b9050919050565b6127b96127b48261277a565b612797565b82525050565b5f6127c982612797565b9050919050565b6127e16127dc8261203a565b6127bf565b82525050565b5f6127f2828b6125e5565b601482019150612802828a6125e5565b6014820191506128128289612605565b60148201915061282282886125e5565b6014820191506128328287612625565b60208201915061284282866127a8565b60018201915061285282856127d0565b6001820191506128628284612625565b6020820191508190509998505050505050505050565b5f60208201905061288b5f83018461271e565b92915050565b5f6060820190506128a45f83018661269a565b6128b1602083018561269a565b6128be60408301846126a9565b949350505050565b5f82825260208201905092915050565b7f5061796d656e7420776173206e6f742073656e740000000000000000000000005f82015250565b5f61290a6014836128c6565b9150612915826128d6565b602082019050919050565b5f6020820190508181035f830152612937816128fe565b9050919050565b7f496e76616c6964207061796d656e7420686173680000000000000000000000005f82015250565b5f6129726014836128c6565b915061297d8261293e565b602082019050919050565b5f6020820190508181035f83015261299f81612966565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6129dd82611d90565b91506129e883611d90565b9250828203905081811115612a00576129ff6129a6565b5b92915050565b7f546f6b656e207472616e73666572206661696c656400000000000000000000005f82015250565b5f612a3a6015836128c6565b9150612a4582612a06565b602082019050919050565b5f6020820190508181035f830152612a6781612a2e565b905091905056fea26469706673582212203106867e1b147b377237cde0aba42d82faf0282b83d7b6d62cca039d0b7f840564736f6c63430008160033 \ No newline at end of file diff --git a/mm2src/mm2_test_helpers/dummy_files/nucleus-history.json b/mm2src/mm2_test_helpers/dummy_files/nucleus-history.json index ee56de9d79..4400c318e0 100644 --- a/mm2src/mm2_test_helpers/dummy_files/nucleus-history.json +++ b/mm2src/mm2_test_helpers/dummy_files/nucleus-history.json @@ -1,4 +1,56 @@ [ + { + "tx_hex": "0a0f30372d74656e6465726d696e742d321288070a262f6962632e6c69676874636c69656e74732e74656e6465726d696e742e76312e48656164657212dd060ad5040a98030a02080b1211636f736d6f736875622d746573746e657418df06220c08efe4c2b80610b7c1ffe8022a480a201b8fdfca293c20e10c86c89ce5a66cedd40cbf83f3833e504844fb89f6606d0b122408011220e58f2ab7b93c559d54eb0f62111fd5c065121ef09e93be370a523af1620f2c3732201692b08be7826c81b282e7f1ef95b75a3e59e824827fab5fe3419b40ba4b7e2f3a20cf0ff3ec944444eef5c483642c33019c7f1c6dd168bcb0cf70dc4a7606edb94b42209293a1e6e3030b68610f5d4eaff415483ba6d7d493b6d86ea22cb25bea4258bb4a209293a1e6e3030b68610f5d4eaff415483ba6d7d493b6d86ea22cb25bea4258bb5220048091bc7ddc283f77bfbf91d73c44da58c3df8a9cbc867405d8b7f3daada22f5a205dc49011b034d2315b08ec147193cf45dadd9133de1f4aaeea2098068b9c9d256220e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8556a20e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b85572143e49b197ac395db2bd999b4c3f35f1b6b36508d012b70108df061a480a207348ea274d50794629bb1e196d0280dfe01588687ae576f320f2e0e4c4be897c122408011220a01fbeb8a9a603cc741f7a44f050e0c9694adba7772738e6f7599019f6f7f1072268080212143e49b197ac395db2bd999b4c3f35f1b6b36508d01a0c08f4e4c2b8061094d8feeb022240bf027c9e216ed59b699a3e09ba5e09397ed401e025690463b3fce0421c917b144c335b544e994482a2160a1fb10fd7dd10b8dff5bb24e625652f3187c0feb90d127e0a3d0a143e49b197ac395db2bd999b4c3f35f1b6b36508d012220a208c9bbd034ed5ccfeb619b9588266c11de77c0e59853558368c468cd29e852d0518c801123d0a143e49b197ac395db2bd999b4c3f35f1b6b36508d012220a208c9bbd034ed5ccfeb619b9588266c11de77c0e59853558368c468cd29e852d0518c8011a03109c06227e0a3d0a143e49b197ac395db2bd999b4c3f35f1b6b36508d012220a208c9bbd034ed5ccfeb619b9588266c11de77c0e59853558368c468cd29e852d0518c801123d0a143e49b197ac395db2bd999b4c3f35f1b6b36508d012220a208c9bbd034ed5ccfeb619b9588266c11de77c0e59853558368c468cd29e852d0518c8011a2a6e7563313232726b6e3578306b677a72787a7964797966766c6e6e653433716a387578393866677a7470", + "tx_hash": "BF87EAB193BC8E1C8975105C165497DDC4AE4D5378F50AECBA38D43B715D96F3", + "from": [ + "nuc12k2pyuylm9t7ugdvz67h9pg4gmmvhn5v77y0sd" + ], + "to": [ + "nuc1z609g9z8ef4jlkcfgg36c2ljgcmna50l77n078" + ], + "total_amount": "0.615", + "spent_by_me": "0", + "received_by_me": "0.615", + "my_balance_change": "0.615", + "block_height": 1183, + "timestamp": 1729147508, + "fee_details": { + "type": "Tendermint", + "coin": "NUCLEUS-TEST", + "amount": "0.12201", + "gas_limit": 125000 + }, + "coin": "NUCLEUS-TEST", + "internal_id": "4335303135373938433145384342333931424145373846420000000000000000", + "transaction_type": "StandardTransfer", + "memo": "rly(2.5.2-28-gdf42391)" + }, + { + "tx_hex": "0a087472616e7366657212096368616e6e656c2d321a0f0a05756e75636c1206363135303030222a6e7563317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c37376e3037382a2d636f736d6f7331333468397476373836366a63757737303877357737366c6366783773337832797379616c787938f3edc3aadad1c9ff17", + "tx_hash": "95BA748DC10BE865C1EBEA10A433E8E5F626E14474676037374539C6FBC74655", + "from": [ + "nuc1z609g9z8ef4jlkcfgg36c2ljgcmna50l77n078" + ], + "to": [ + "cosmos134h9tv7866jcuw708w5w76lcfx7s3x2ysyalxy" + ], + "total_amount": "0.651435", + "spent_by_me": "0.651435", + "received_by_me": "0", + "my_balance_change": "-0.651435", + "block_height": 1128, + "timestamp": 1729142296, + "fee_details": { + "type": "Tendermint", + "coin": "NUCLEUS-TEST", + "amount": "0.036435", + "gas_limit": 125000 + }, + "coin": "NUCLEUS-TEST", + "internal_id": "3031414542453143353638454230314344383437414235390000000000000000", + "transaction_type": "StandardTransfer", + "memo": "" + }, { "tx_hex": "0a2a6e7563317a36303967397a386566346a6c6b63666767333663326c6a67636d6e6135306c37376e3037381240303932443741333142373239304433434438384231414338344336433338313137344134444638393046323831343232343335373334413145323534344641461a4039393836333361343532623636373561663632626261666364646630376461336264343633343262623935373935643637346335373162613363666661386433", "tx_hash": "17C70B8D8B4DA83F5C625A32DAD2BD2572F0770D6F7AD52284781E71C20F89E4", diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index b2f0b537c7..115f120574 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -81,7 +81,7 @@ pub const MAKER_ERROR_EVENTS: [&str; 15] = [ "MakerPaymentRefundFinished", ]; -pub const TAKER_SUCCESS_EVENTS: [&str; 11] = [ +pub const TAKER_SUCCESS_EVENTS: [&str; 12] = [ "Started", "Negotiated", "TakerFeeSent", @@ -92,10 +92,11 @@ pub const TAKER_SUCCESS_EVENTS: [&str; 11] = [ "TakerPaymentSent", "TakerPaymentSpent", "MakerPaymentSpent", + "MakerPaymentSpendConfirmed", "Finished", ]; -pub const TAKER_USING_WATCHERS_SUCCESS_EVENTS: [&str; 13] = [ +pub const TAKER_USING_WATCHERS_SUCCESS_EVENTS: [&str; 14] = [ "Started", "Negotiated", "TakerFeeSent", @@ -108,11 +109,12 @@ pub const TAKER_USING_WATCHERS_SUCCESS_EVENTS: [&str; 13] = [ "TakerPaymentSpent", "MakerPaymentSpent", "MakerPaymentSpentByWatcher", + "MakerPaymentSpendConfirmed", "Finished", ]; // Taker using watchers and watcher spends maker payment -pub const TAKER_ACTUAL_EVENTS_WATCHER_SPENDS_MAKER_PAYMENT: [&str; 12] = [ +pub const TAKER_ACTUAL_EVENTS_WATCHER_SPENDS_MAKER_PAYMENT: [&str; 13] = [ "Started", "Negotiated", "TakerFeeSent", @@ -124,11 +126,12 @@ pub const TAKER_ACTUAL_EVENTS_WATCHER_SPENDS_MAKER_PAYMENT: [&str; 12] = [ "WatcherMessageSent", "TakerPaymentSpent", "MakerPaymentSpentByWatcher", + "MakerPaymentSpendConfirmed", "Finished", ]; // Taker using watchers and spends maker payment instead of watcher -pub const TAKER_ACTUAL_EVENTS_TAKER_SPENDS_MAKER_PAYMENT: [&str; 12] = [ +pub const TAKER_ACTUAL_EVENTS_TAKER_SPENDS_MAKER_PAYMENT: [&str; 13] = [ "Started", "Negotiated", "TakerFeeSent", @@ -140,10 +143,11 @@ pub const TAKER_ACTUAL_EVENTS_TAKER_SPENDS_MAKER_PAYMENT: [&str; 12] = [ "WatcherMessageSent", "TakerPaymentSpent", "MakerPaymentSpent", + "MakerPaymentSpendConfirmed", "Finished", ]; -pub const TAKER_ERROR_EVENTS: [&str; 16] = [ +pub const TAKER_ERROR_EVENTS: [&str; 17] = [ "StartFailed", "NegotiateFailed", "TakerFeeSendFailed", @@ -154,6 +158,7 @@ pub const TAKER_ERROR_EVENTS: [&str; 16] = [ "TakerPaymentDataSendFailed", "TakerPaymentWaitForSpendFailed", "MakerPaymentSpendFailed", + "MakerPaymentSpendConfirmFailed", "TakerPaymentWaitRefundStarted", "TakerPaymentRefundStarted", "TakerPaymentRefunded", @@ -236,7 +241,7 @@ pub const ETH_MAINNET_NODE: &str = "https://mainnet.infura.io/v3/c01c1b4cf666425 pub const ETH_MAINNET_CHAIN_ID: u64 = 1; pub const ETH_MAINNET_SWAP_CONTRACT: &str = "0x24abe4c71fc658c91313b6552cd40cd808b3ea80"; -pub const ETH_SEPOLIA_NODES: &[&str] = &["https://rpc2.sepolia.org"]; +pub const ETH_SEPOLIA_NODES: &[&str] = &["https://ethereum-sepolia-rpc.publicnode.com","https://rpc2.sepolia.org","https://1rpc.io/sepolia"]; pub const ETH_SEPOLIA_CHAIN_ID: u64 = 11155111; pub const ETH_SEPOLIA_SWAP_CONTRACT: &str = "0xeA6D65434A15377081495a9E7C5893543E7c32cB"; pub const ETH_SEPOLIA_TOKEN_CONTRACT: &str = "0x09d0d71FBC00D7CCF9CFf132f5E6825C88293F19"; @@ -310,6 +315,21 @@ impl Mm2TestConf { } } + pub fn seednode_with_wallet_name(coins: &Json, wallet_name: &str, wallet_password: &str) -> Self { + Mm2TestConf { + conf: json!({ + "gui": "nogui", + "netid": 9998, + "coins": coins, + "rpc_password": DEFAULT_RPC_PASSWORD, + "i_am_seed": true, + "wallet_name": wallet_name, + "wallet_password": wallet_password, + }), + rpc_password: DEFAULT_RPC_PASSWORD.into(), + } + } + pub fn light_node(passphrase: &str, coins: &Json, seednodes: &[&str]) -> Self { Mm2TestConf { conf: json!({ @@ -828,6 +848,13 @@ pub fn erc20_dev_conf(contract_address: &str) -> Json { }) } +/// ERC20 token configuration used for dockerized tests on Sepolia +pub fn sepolia_erc20_dev_conf(contract_address: &str) -> Json { + let mut conf = erc20_dev_conf(contract_address); + set_chain_id(&mut conf, ETH_SEPOLIA_CHAIN_ID); + conf +} + /// global NFT configuration used for dockerized Geth dev node pub fn nft_dev_conf() -> Json { json!({ @@ -846,29 +873,14 @@ pub fn nft_dev_conf() -> Json { }) } -/// global NFT configuration used for Sepolia testnet -pub fn nft_sepolia_conf() -> Json { - json!({ - "coin": "NFT_ETH", - "name": "nftdev", - "chain_id": 11155111, - "mm2": 1, - "derivation_path": "m/44'/60'", - "protocol": { - "type": "NFT", - "protocol_data": { - "platform": "ETH" - } - } - }) -} +fn set_chain_id(conf: &mut Json, chain_id: u64) { conf["chain_id"] = json!(chain_id); } pub fn eth_sepolia_conf() -> Json { json!({ "coin": "ETH", "name": "ethereum", "derivation_path": "m/44'/60'", - "chain_id": 11155111, + "chain_id": ETH_SEPOLIA_CHAIN_ID, "protocol": { "type": "ETH" }, @@ -882,7 +894,7 @@ pub fn eth_sepolia_trezor_firmware_compat_conf() -> Json { "coin": "tETH", "name": "ethereum", "derivation_path": "m/44'/1'", // Note: trezor uses coin type 1' for eth for testnet (SLIP44_TESTNET) - "chain_id": 11155111, + "chain_id": ETH_SEPOLIA_CHAIN_ID, "protocol": { "type": "ETH" }, @@ -912,12 +924,12 @@ pub fn jst_sepolia_conf() -> Json { json!({ "coin": "JST", "name": "jst", - "chain_id": 11155111, + "chain_id": ETH_SEPOLIA_CHAIN_ID, "protocol": { "type": "ERC20", "protocol_data": { "platform": "ETH", - "chain_id": 11155111, + "chain_id": ETH_SEPOLIA_CHAIN_ID, "contract_address": ETH_SEPOLIA_TOKEN_CONTRACT } }, @@ -929,14 +941,14 @@ pub fn jst_sepolia_trezor_conf() -> Json { json!({ "coin": "tJST", "name": "tjst", - "chain_id": 11155111, + "chain_id": ETH_SEPOLIA_CHAIN_ID, "derivation_path": "m/44'/1'", // Note: Trezor uses 1' coin type for all testnets "trezor_coin": "tETH", "protocol": { "type": "ERC20", "protocol_data": { "platform": "ETH", - "chain_id": 11155111, + "chain_id": ETH_SEPOLIA_CHAIN_ID, "contract_address": ETH_SEPOLIA_TOKEN_CONTRACT } } @@ -1357,17 +1369,49 @@ impl MarketMakerIt { /// Start a new MarketMaker locally. /// /// * `conf` - The command-line configuration passed to the MarketMaker. + /// * `userpass` - RPC API key. + /// * `local` - Function to start the MarketMaker locally. + /// * `envs` - The environment variables passed to the process. + /// The argument is ignored for nodes running in a browser. + #[cfg(target_arch = "wasm32")] + pub async fn start_with_envs( + conf: Json, + userpass: String, + local: Option, + _envs: &[(&str, &str)], + ) -> Result { + MarketMakerIt::start_market_maker(conf, userpass, local, None).await + } + + /// Start a new MarketMaker locally with a specific database namespace. + /// + /// * `conf` - The command-line configuration passed to the MarketMaker. + /// * `userpass` - RPC API key. + /// * `local` - Function to start the MarketMaker locally. + /// * `db_namespace_id` - The test database namespace identifier. + #[cfg(target_arch = "wasm32")] + pub async fn start_with_db( + conf: Json, + userpass: String, + local: Option, + db_namespace_id: u64, + ) -> Result { + MarketMakerIt::start_market_maker(conf, userpass, local, Some(db_namespace_id)).await + } + + /// Common helper function to start the MarketMaker. + /// + /// * `conf` - The command-line configuration passed to the MarketMaker. /// Unique P2P in-memory port is injected as `p2p_in_memory_port` 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 locally. Required for nodes running in a browser. - /// * `envs` - The enviroment variables passed to the process. - /// The argument is ignore for nodes running in a browser. + /// * `db_namespace_id` - Optional test database namespace identifier. #[cfg(target_arch = "wasm32")] - pub async fn start_with_envs( + async fn start_market_maker( mut conf: Json, userpass: String, local: Option, - _envs: &[(&str, &str)], + db_namespace_id: Option, ) -> Result { if conf["p2p_in_memory"].is_null() { conf["p2p_in_memory"] = Json::Bool(true); @@ -1383,10 +1427,19 @@ impl MarketMakerIt { conf["p2p_in_memory_port"] = Json::Number(new_p2p_port.into()); } - let ctx = mm2_core::mm_ctx::MmCtxBuilder::new() - .with_conf(conf.clone()) - .with_test_db_namespace() - .into_mm_arc(); + let ctx = { + let builder = MmCtxBuilder::new() + .with_conf(conf.clone()); + + let builder = if let Some(ns) = db_namespace_id { + builder.with_test_db_namespace_with_id(ns) + } else { + builder.with_test_db_namespace() + }; + + builder.into_mm_arc() + }; + let local = try_s!(local.ok_or("!local")); local(ctx.clone()); @@ -1846,6 +1899,30 @@ pub async fn enable_qrc20( json::from_str(&electrum.1).unwrap() } +pub async fn peer_connection_healthcheck(mm: &MarketMakerIt, peer_address: &str) -> Json { + let response = mm + .rpc(&json!({ + "userpass": mm.userpass, + "method": "peer_connection_healthcheck", + "mmrpc": "2.0", + "params": { + "peer_address": peer_address + } + })) + .await + .unwrap(); + + assert_eq!( + response.0, + StatusCode::OK, + "RPC «peer_connection_healthcheck» failed with {} {}", + response.0, + response.1 + ); + + json::from_str(&response.1).unwrap() +} + /// Reads passphrase and userpass from .env file pub fn from_env_file(env: Vec) -> (Option, Option) { use regex::bytes::Regex; @@ -1952,21 +2029,6 @@ pub async fn enable_eth_coin( json::from_str(&enable.1).unwrap() } -pub async fn enable_spl(mm: &MarketMakerIt, coin: &str) -> Json { - let req = json!({ - "userpass": mm.userpass, - "method": "enable_spl", - "mmrpc": "2.0", - "params": { - "ticker": coin, - "activation_params": {} - } - }); - let enable = mm.rpc(&req).await.unwrap(); - assert_eq!(enable.0, StatusCode::OK, "'enable_spl' failed: {}", enable.1); - json::from_str(&enable.1).unwrap() -} - pub async fn enable_slp(mm: &MarketMakerIt, coin: &str) -> Json { let enable = mm .rpc(&json!({ @@ -2064,38 +2126,6 @@ pub async fn enable_bch_with_tokens( json::from_str(&enable.1).unwrap() } -pub async fn enable_solana_with_tokens( - mm: &MarketMakerIt, - platform_coin: &str, - tokens: &[&str], - solana_client_url: &str, - tx_history: bool, -) -> Json { - let spl_requests: Vec<_> = tokens.iter().map(|ticker| json!({ "ticker": ticker })).collect(); - let req = json!({ - "userpass": mm.userpass, - "method": "enable_solana_with_tokens", - "mmrpc": "2.0", - "params": { - "ticker": platform_coin, - "confirmation_commitment": "finalized", - "allow_slp_unsafe_conf": true, - "client_url": solana_client_url, - "tx_history": tx_history, - "spl_tokens_requests": spl_requests, - } - }); - - let enable = mm.rpc(&req).await.unwrap(); - assert_eq!( - enable.0, - StatusCode::OK, - "'enable_bch_with_tokens' failed: {}", - enable.1 - ); - json::from_str(&enable.1).unwrap() -} - pub async fn my_tx_history_v2( mm: &MarketMakerIt, coin: &str, @@ -2844,6 +2874,20 @@ pub async fn get_shared_db_id(mm: &MarketMakerIt) -> GetSharedDbIdResult { res.result } +pub async fn get_wallet_names(mm: &MarketMakerIt) -> GetWalletNamesResult { + let request = mm + .rpc(&json!({ + "userpass": mm.userpass, + "method": "get_wallet_names", + "mmrpc": "2.0", + })) + .await + .unwrap(); + assert_eq!(request.0, StatusCode::OK, "'get_wallet_names' failed: {}", request.1); + let res: RpcSuccessResponse<_> = json::from_str(&request.1).unwrap(); + res.result +} + pub async fn max_maker_vol(mm: &MarketMakerIt, coin: &str) -> RpcResponse { let rc = mm .rpc(&json!({ @@ -2909,6 +2953,10 @@ pub async fn enable_tendermint( tx_history: bool, ) -> Json { let ibc_requests: Vec<_> = ibc_assets.iter().map(|ticker| json!({ "ticker": ticker })).collect(); + let nodes: Vec = rpc_urls + .iter() + .map(|u| json!({"url": u, "komodo_proxy": false })) + .collect(); let request = json!({ "userpass": mm.userpass, @@ -2917,7 +2965,7 @@ pub async fn enable_tendermint( "params": { "ticker": coin, "tokens_params": ibc_requests, - "rpc_urls": rpc_urls, + "nodes": nodes, "tx_history": tx_history } }); @@ -2945,6 +2993,10 @@ pub async fn enable_tendermint_without_balance( tx_history: bool, ) -> Json { let ibc_requests: Vec<_> = ibc_assets.iter().map(|ticker| json!({ "ticker": ticker })).collect(); + let nodes: Vec = rpc_urls + .iter() + .map(|u| json!({"url": u, "komodo_proxy": false })) + .collect(); let request = json!({ "userpass": mm.userpass, @@ -2953,7 +3005,7 @@ pub async fn enable_tendermint_without_balance( "params": { "ticker": coin, "tokens_params": ibc_requests, - "rpc_urls": rpc_urls, + "nodes": nodes, "tx_history": tx_history, "get_balances": false } @@ -3201,6 +3253,103 @@ pub async fn enable_eth_with_tokens_v2( } } +async fn init_erc20_token( + mm: &MarketMakerIt, + ticker: &str, + protocol: Option, + path_to_address: Option, +) -> Result<(StatusCode, Json), Json> { + let (status, response, _) = mm.rpc(&json!({ + "userpass": mm.userpass, + "method": "task::enable_erc20::init", + "mmrpc": "2.0", + "params": { + "ticker": ticker, + "protocol": protocol, + "activation_params": { + "path_to_address": path_to_address.unwrap_or_default(), + } + } + })) + .await + .unwrap(); + + if status.is_success() { + Ok((status, json::from_str(&response).unwrap())) + } else { + Err(json::from_str(&response).unwrap()) + } +} + +async fn init_erc20_token_status(mm: &MarketMakerIt, task_id: u64) -> Json { + let request = mm + .rpc(&json!({ + "userpass": mm.userpass, + "method": "task::enable_erc20::status", + "mmrpc": "2.0", + "params": { + "task_id": task_id, + } + })) + .await + .unwrap(); + assert_eq!( + request.0, + StatusCode::OK, + "'task::enable_erc20::status' failed: {}", + request.1 + ); + json::from_str(&request.1).unwrap() +} + +pub async fn enable_erc20_token_v2( + mm: &MarketMakerIt, + ticker: &str, + protocol: Option, + timeout: u64, + path_to_address: Option, +) -> Result { + let init = init_erc20_token(mm, ticker, protocol, path_to_address).await?.1; + let init: RpcV2Response = json::from_value(init).unwrap(); + let timeout = wait_until_ms(timeout * 1000); + + loop { + if now_ms() > timeout { + panic!("{} initialization timed out", ticker); + } + + let status = init_erc20_token_status(mm, init.result.task_id).await; + let status: RpcV2Response = json::from_value(status).unwrap(); + match status.result { + InitErc20TokenStatus::Ok(result) => break Ok(result), + InitErc20TokenStatus::Error(e) => break Err(e), + _ => Timer::sleep(1.).await, + } + } +} + +pub async fn get_token_info(mm: &MarketMakerIt, protocol: Json) -> TokenInfoResponse { + let response = mm + .rpc(&json!({ + "userpass": mm.userpass, + "method": "get_token_info", + "mmrpc": "2.0", + "params": { + "protocol": protocol, + } + })) + .await + .unwrap(); + assert_eq!( + response.0, + StatusCode::OK, + "'get_token_info' failed: {}", + response.1 + ); + let response_json: Json = json::from_str(&response.1).unwrap(); + json::from_value(response_json["result"].clone()).unwrap() +} + /// Note that mm2 ignores `volume` if `max` is true. pub async fn set_price( mm: &MarketMakerIt, diff --git a/mm2src/mm2_test_helpers/src/structs.rs b/mm2src/mm2_test_helpers/src/structs.rs index aa2246186c..baba173461 100644 --- a/mm2src/mm2_test_helpers/src/structs.rs +++ b/mm2src/mm2_test_helpers/src/structs.rs @@ -621,7 +621,7 @@ pub struct ZCoinActivationResult { #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] pub struct GetNewAddressResponse { - pub new_address: HDAddressBalance, + pub new_address: HDAddressBalanceMap, } #[derive(Debug, Deserialize)] @@ -629,8 +629,8 @@ pub struct GetNewAddressResponse { pub struct HDAccountBalanceResponse { pub account_index: u32, pub derivation_path: String, - pub addresses: Vec, - pub page_balance: CoinBalance, + pub addresses: Vec, + pub page_balance: HashMap, pub limit: usize, pub skipped: u32, pub total: u32, @@ -651,7 +651,7 @@ pub struct CoinActivationResult { pub struct UtxoStandardActivationResult { pub ticker: String, pub current_block: u64, - pub wallet_balance: EnableCoinBalance, + pub wallet_balance: EnableCoinBalanceMap, } #[derive(Debug, Deserialize)] @@ -714,6 +714,15 @@ pub enum InitEthWithTokensStatus { UserActionRequired(Json), } +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields, tag = "status", content = "details")] +pub enum InitErc20TokenStatus { + Ok(InitTokenActivationResult), + Error(Json), + InProgress(Json), + UserActionRequired(Json), +} + #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields, tag = "status", content = "details")] pub enum InitLightningStatus { @@ -726,7 +735,7 @@ pub enum InitLightningStatus { #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields, tag = "status", content = "details")] pub enum CreateNewAccountStatus { - Ok(HDAccountBalance), + Ok(HDAccountBalanceMap), Error(Json), InProgress(Json), UserActionRequired(Json), @@ -827,6 +836,13 @@ pub struct GetSharedDbIdResult { pub shared_db_id: String, } +#[derive(Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct GetWalletNamesResult { + pub wallet_names: Vec, + pub activated_wallet: Option, +} + #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] pub struct RpcV2Response { @@ -906,18 +922,21 @@ pub enum EthWithTokensActivationResult { #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] -pub struct EnableBchWithTokensResponse { +pub struct InitTokenActivationResult { + pub ticker: String, + pub platform_coin: String, + pub token_contract_address: String, pub current_block: u64, - pub bch_addresses_infos: HashMap>, - pub slp_addresses_infos: HashMap>, + pub required_confirmations: u64, + pub wallet_balance: EnableCoinBalanceMap, } #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] -pub struct EnableSolanaWithTokensResponse { +pub struct EnableBchWithTokensResponse { pub current_block: u64, - pub solana_addresses_infos: HashMap>, - pub spl_addresses_infos: HashMap>, + pub bch_addresses_infos: HashMap>, + pub slp_addresses_infos: HashMap>, } #[derive(Debug, Deserialize)] @@ -1187,3 +1206,25 @@ pub struct ActiveSwapsResponse { pub uuids: Vec, pub statuses: Option>, } + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Erc20TokenInfo { + pub symbol: String, + pub decimals: u8, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(tag = "type", content = "info")] +pub enum TokenInfo { + ERC20(Erc20TokenInfo), +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct TokenInfoResponse { + pub config_ticker: Option, + #[serde(flatten)] + pub info: TokenInfo, +} diff --git a/mm2src/proxy_signature/Cargo.toml b/mm2src/proxy_signature/Cargo.toml new file mode 100644 index 0000000000..5392b9862a --- /dev/null +++ b/mm2src/proxy_signature/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "proxy_signature" +version = "0.1.0" +edition = "2018" + +[dependencies] +chrono = "0.4" +http = "0.2" +libp2p = { git = "https://github.com/KomodoPlatform/rust-libp2p.git", tag = "k-0.52.4", default-features = false, features = ["identify"] } +serde = "1" +serde_json = { version = "1", features = ["preserve_order", "raw_value"] } + +[dev-dependencies] +rand = { version = "0.7", features = ["std", "small_rng"] } diff --git a/mm2src/proxy_signature/src/lib.rs b/mm2src/proxy_signature/src/lib.rs new file mode 100644 index 0000000000..15cf596c89 --- /dev/null +++ b/mm2src/proxy_signature/src/lib.rs @@ -0,0 +1,165 @@ +use chrono::Utc; +use http::Uri; +use libp2p::identity::{Keypair, PublicKey, SigningError}; +use serde::{Deserialize, Serialize}; +use std::convert::TryFrom; + +/// Represents a message and its corresponding signature. +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct ProxySign { + /// Signature of the raw message. + pub signature_bytes: Vec, + /// Unique address of the sign's owner. + pub address: String, + /// The raw message that has been signed. + pub raw_message: RawMessage, +} + +/// Essential type that contains information required for generating signed messages (see `ProxySign`). +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct RawMessage { + /// This field is used to verify the proxy sign on the Komodo DeFi proxy side. + pub uri: String, + /// This field is used to check the payload size on the komodo-defi-proxy side. + /// Along with the `uri` field it helps confirm that the proxy sign matches the request. + pub body_size: usize, + pub public_key_encoded: Vec, + pub expires_at: i64, +} + +impl RawMessage { + fn new(uri: &Uri, body_size: usize, public_key_encoded: Vec, expires_in_seconds: i64) -> Self { + RawMessage { + uri: uri.to_string(), + body_size, + public_key_encoded, + expires_at: Utc::now().timestamp() + expires_in_seconds, + } + } + + /// Generates a byte vector representation of `self`. + fn encode(&self) -> Vec { + const PREFIX: &str = "Encoded Message for KDP\n"; + let mut bytes = PREFIX.as_bytes().to_owned(); + bytes.extend(self.public_key_encoded.clone()); + bytes.extend(self.uri.to_string().as_bytes().to_owned()); + bytes.extend(self.body_size.to_ne_bytes().to_owned()); + bytes.extend(self.expires_at.to_ne_bytes().to_owned()); + bytes + } + + /// Generates `ProxySign` using the provided keypair and coin ticker. + pub fn sign( + keypair: &Keypair, + uri: &Uri, + body_size: usize, + expires_in_seconds: i64, + ) -> Result { + let public_key_encoded = keypair.public().encode_protobuf(); + let address = keypair.public().to_peer_id().to_string(); + let raw_message = RawMessage::new(uri, body_size, public_key_encoded, expires_in_seconds); + let signature_bytes = keypair.sign(&raw_message.encode())?; + + Ok(ProxySign { + raw_message, + address, + signature_bytes, + }) + } +} + +impl ProxySign { + /// Validates if the message is still valid based on its expiration time and signature verification. + pub fn is_valid_message(&self, max_message_exp_secs: u64) -> bool { + let now = Utc::now().timestamp(); + let remaining_expiration_seconds = u64::try_from(self.raw_message.expires_at - now).unwrap_or(0); + + if remaining_expiration_seconds == 0 || remaining_expiration_seconds > max_message_exp_secs { + return false; + } + + let Ok(public_key) = PublicKey::try_decode_protobuf(&self.raw_message.public_key_encoded) else { + return false; + }; + + if self.address != public_key.to_peer_id().to_string() { + return false; + } + + public_key.verify(&self.raw_message.encode(), &self.signature_bytes) + } +} + +#[cfg(test)] +pub mod proxy_signature_tests { + use libp2p::identity; + use rand::RngCore; + + use super::*; + + fn generate_ed25519_keypair(mut p2p_key: [u8; 32]) -> identity::Keypair { + let secret = identity::ed25519::SecretKey::try_from_bytes(&mut p2p_key).expect("Secret length is 32 bytes"); + let keypair = identity::ed25519::Keypair::from(secret); + identity::Keypair::from(keypair) + } + + fn os_rng(dest: &mut [u8]) -> Result<(), rand::Error> { rand::rngs::OsRng.try_fill_bytes(dest) } + + fn random_keypair() -> Keypair { + let mut p2p_key = [0u8; 32]; + os_rng(&mut p2p_key).unwrap(); + generate_ed25519_keypair(p2p_key) + } + + #[test] + fn sign_and_verify() { + let keypair = random_keypair(); + let signed_proxy_message = RawMessage::sign(&keypair, &Uri::from_static("http://example.com"), 0, 5).unwrap(); + assert!(signed_proxy_message.is_valid_message(10)); + } + + #[test] + fn expired_signature() { + let keypair = random_keypair(); + let signed_proxy_message = RawMessage::sign(&keypair, &Uri::from_static("http://example.com"), 0, -1).unwrap(); + assert!(!signed_proxy_message.is_valid_message(10)); + } + + #[test] + fn dirty_raw_message() { + let keypair = random_keypair(); + let mut signed_proxy_message = + RawMessage::sign(&keypair, &Uri::from_static("http://example.com"), 0, 5).unwrap(); + signed_proxy_message.raw_message.uri = "http://demo.com".to_string(); + assert!(!signed_proxy_message.is_valid_message(10)); + + let mut signed_proxy_message = + RawMessage::sign(&keypair, &Uri::from_static("http://example.com"), 0, 5).unwrap(); + signed_proxy_message.raw_message.body_size += 1; + assert!(!signed_proxy_message.is_valid_message(10)); + + let mut signed_proxy_message = + RawMessage::sign(&keypair, &Uri::from_static("http://example.com"), 0, 5).unwrap(); + signed_proxy_message.raw_message.expires_at += 1; + assert!(!signed_proxy_message.is_valid_message(10)); + } + + #[test] + fn message_lifetime_overflow() { + let keypair = random_keypair(); + let signed_proxy_message = RawMessage::sign(&keypair, &Uri::from_static("http://example.com"), 0, 5).unwrap(); + assert!(!signed_proxy_message.is_valid_message(4)); + } + + #[test] + fn verify_peer_id() { + let expected_address = "12D3KooWJPtxrHVDPoETNfPJY4WWVzX7Ti4WPemtXDgb5qmFrDiv"; + + let p2p_key = [123u8; 32]; + let keypair = generate_ed25519_keypair(p2p_key); + assert_eq!(keypair.public().to_peer_id().to_string(), expected_address); + + let signed_proxy_message = RawMessage::sign(&keypair, &Uri::from_static("http://example.com"), 0, 5).unwrap(); + assert_eq!(signed_proxy_message.address, expected_address); + } +} diff --git a/mm2src/trezor/Cargo.toml b/mm2src/trezor/Cargo.toml index 5f0542d35a..36e5abb0ec 100644 --- a/mm2src/trezor/Cargo.toml +++ b/mm2src/trezor/Cargo.toml @@ -14,7 +14,7 @@ derive_more = "0.99" futures = { version = "0.3", package = "futures", features = ["compat", "async-await"] } hw_common = { path = "../hw_common" } mm2_err_handle = { path = "../mm2_err_handle" } -prost = "0.11" +prost = "0.12" rand = { version = "0.7", features = ["std", "wasm-bindgen"] } rpc_task = { path = "../rpc_task" } serde = "1.0" diff --git a/mm2src/trezor/src/error.rs b/mm2src/trezor/src/error.rs index 798185695c..6a67c592f2 100644 --- a/mm2src/trezor/src/error.rs +++ b/mm2src/trezor/src/error.rs @@ -1,3 +1,5 @@ +use std::convert::TryFrom; + use crate::proto::messages::MessageType; use crate::proto::messages_common::{failure::FailureType, Failure}; use crate::user_interaction::TrezorUserInteraction; @@ -57,7 +59,7 @@ pub enum OperationFailure { impl From for OperationFailure { fn from(failure: Failure) -> Self { - match failure.code.and_then(FailureType::from_i32) { + match failure.code.and_then(|t| FailureType::try_from(t).ok()) { Some(FailureType::FailurePinInvalid) | Some(FailureType::FailurePinMismatch) => { OperationFailure::InvalidPin }, diff --git a/mm2src/trezor/src/response.rs b/mm2src/trezor/src/response.rs index 869b910997..259db41840 100644 --- a/mm2src/trezor/src/response.rs +++ b/mm2src/trezor/src/response.rs @@ -6,6 +6,7 @@ use crate::{TrezorError, TrezorResult}; use async_trait::async_trait; use mm2_err_handle::prelude::*; use rpc_task::RpcTaskError; +use std::convert::TryFrom; use std::fmt; use std::sync::Arc; @@ -150,7 +151,10 @@ impl<'a, 'b, T> fmt::Debug for ButtonRequest<'a, 'b, T> { impl<'a, 'b, T: 'static> ButtonRequest<'a, 'b, T> { /// The type of button request. - pub fn request_type(&self) -> Option { self.message.code.and_then(ButtonRequestType::from_i32) } + #[inline(always)] + pub fn request_type(&self) -> Option { + self.message.code.and_then(|t| ButtonRequestType::try_from(t).ok()) + } /// Ack the request and get the next message from the device. pub async fn ack(self) -> TrezorResult> { @@ -174,8 +178,9 @@ impl<'a, 'b, T> fmt::Debug for PinMatrixRequest<'a, 'b, T> { impl<'a, 'b, T: 'static> PinMatrixRequest<'a, 'b, T> { /// The type of PIN matrix request. + #[inline(always)] pub fn request_type(&self) -> Option { - self.message.r#type.and_then(PinMatrixRequestType::from_i32) + self.message.r#type.and_then(|t| PinMatrixRequestType::try_from(t).ok()) } /// Ack the request with a PIN and get the next message from the device. diff --git a/mm2src/trezor/src/transport/protocol.rs b/mm2src/trezor/src/transport/protocol.rs index 6c553e5d76..e6c822eb3c 100644 --- a/mm2src/trezor/src/transport/protocol.rs +++ b/mm2src/trezor/src/transport/protocol.rs @@ -1,5 +1,7 @@ //! This file is inspired by https://github.com/tezedge/tezedge-client/blob/master/trezor_api/src/transport/protocol.rs +use std::convert::TryFrom; + use crate::proto::messages::MessageType; use crate::proto::ProtoMessage; use crate::{TrezorError, TrezorResult}; @@ -85,9 +87,10 @@ impl Protocol for ProtocolV1 { ); return MmError::err(TrezorError::ProtocolError(error)); } - let message_type_id = BigEndian::read_u16(&chunk[3..5]) as u32; - let message_type = MessageType::from_i32(message_type_id as i32) - .or_mm_err(|| TrezorError::ProtocolError(format!("Invalid message type: {}", message_type_id)))?; + let message_type_id = BigEndian::read_u16(&chunk[3..5]) as i32; + let message_type = MessageType::try_from(message_type_id).map_err(|e| { + TrezorError::ProtocolError(format!("Invalid message type: {}, Error: {}", message_type_id, e)) + })?; let data_length = BigEndian::read_u32(&chunk[5..9]) as usize; let mut data: Vec = chunk[9..].into(); diff --git a/mm2src/trezor/src/utxo/sign_utxo.rs b/mm2src/trezor/src/utxo/sign_utxo.rs index 8b56f4fbd4..7f53182639 100644 --- a/mm2src/trezor/src/utxo/sign_utxo.rs +++ b/mm2src/trezor/src/utxo/sign_utxo.rs @@ -1,3 +1,5 @@ +use std::convert::TryFrom; + use crate::proto::messages_bitcoin as proto_bitcoin; use crate::result_handler::ResultHandler; use crate::utxo::unsigned_tx::UnsignedUtxoTx; @@ -61,7 +63,9 @@ impl<'a> TrezorSession<'a> { loop { extract_serialized_data(&tx_request, &mut result)?; - let request_type = tx_request.request_type.and_then(ProtoTxRequestType::from_i32); + let request_type = tx_request + .request_type + .and_then(|t| ProtoTxRequestType::try_from(t).ok()); let request_type = match request_type { Some(ProtoTxRequestType::Txfinished) => return Ok(result), Some(req_type) => req_type,