From ff10edd8a480ef9c8db87e82c37f279258050212 Mon Sep 17 00:00:00 2001 From: Daniel Karzel Date: Mon, 28 Jun 2021 16:45:40 +1000 Subject: [PATCH] Add the `list-sellers` command to the CLI This command uses a rendezvous node to find sellers (i.e. ASBs) and query them for quotes. Sellers, that can be dialed and queried for a quote will be listed. --- Cargo.lock | 381 ++++++++++++++++++++++----------- swap/Cargo.toml | 3 +- swap/src/asb/event_loop.rs | 2 +- swap/src/bin/swap.rs | 54 ++++- swap/src/cli.rs | 2 + swap/src/cli/command.rs | 39 ++++ swap/src/cli/event_loop.rs | 2 +- swap/src/cli/list_sellers.rs | 273 +++++++++++++++++++++++ swap/src/cli/tracing.rs | 18 +- swap/src/network.rs | 1 + swap/src/network/quote.rs | 4 +- swap/src/network/rendezvous.rs | 32 +++ swap/src/network/test.rs | 236 +++++++++++--------- 13 files changed, 800 insertions(+), 247 deletions(-) create mode 100644 swap/src/cli/list_sellers.rs create mode 100644 swap/src/network/rendezvous.rs diff --git a/Cargo.lock b/Cargo.lock index 2661fef3..3f2e74f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,9 +119,9 @@ version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -261,9 +261,9 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b45570b78250774145859a8f85bfdb6e310663fc82640d7e159a44b1386074a2" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -294,6 +294,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d73a8ae8ce52d09395e4cafc83b5b81c3deb70a97740e907669c8683c4dd50a" +[[package]] +name = "bimap" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50ae17cabbc8a38a1e3e4c1a6a664e9a09672dc14d0896fa8d865d3a5a446b07" + [[package]] name = "bincode" version = "1.3.1" @@ -588,7 +594,7 @@ dependencies = [ "ansi_term 0.11.0", "atty", "bitflags", - "strsim", + "strsim 0.8.0", "textwrap", "unicode-width", "vec_map", @@ -823,6 +829,41 @@ dependencies = [ "zeroize", ] +[[package]] +name = "darling" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757c0ded2af11d8e739c4daea1ac623dd1624b06c844cf3f5a39f1bdbd99bb12" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c34d8efb62d0c2d7f60ece80f75e5c63c1588ba68032740494b0b9a996466e3" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2 1.0.27", + "quote 1.0.9", + "strsim 0.10.0", + "syn 1.0.73", +] + +[[package]] +name = "darling_macro" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade7bff147130fe5e6d39f089c6bd49ec0250f35d70b2eebf72afdfc919f15cc" +dependencies = [ + "darling_core", + "quote 1.0.9", + "syn 1.0.73", +] + [[package]] name = "data-encoding" version = "2.3.2" @@ -835,9 +876,9 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -1006,9 +1047,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" dependencies = [ "heck", - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -1149,9 +1190,9 @@ checksum = "a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121" dependencies = [ "autocfg 1.0.1", "proc-macro-hack", - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -1272,8 +1313,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" dependencies = [ "cfg-if 1.0.0", + "js-sys", "libc", "wasi 0.10.2+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1388,13 +1431,13 @@ dependencies = [ [[package]] name = "hmac-drbg" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ - "digest 0.8.1", - "generic-array 0.12.4", - "hmac 0.7.1", + "digest 0.9.0", + "generic-array 0.14.4", + "hmac 0.8.1", ] [[package]] @@ -1499,6 +1542,12 @@ dependencies = [ "webpki", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.1.5" @@ -1682,7 +1731,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f3d1e50fefe4252d2e44c805663e73a8c0b2002b73f834ea055c8ed7fc46a8" dependencies = [ "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -1692,7 +1741,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97c11e429f0eaa41fe659013680b459d2368d8f0a3e69dccfb7a35800b0dc27b" dependencies = [ "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -1744,9 +1793,8 @@ checksum = "ba4aede83fc3617411dc6993bc8c70919750c1c257c6ca6a502aed6e0e2394ae" [[package]] name = "libp2p" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebbb17eece4aec5bb970880c73825c16ca59ca05a4e41803751e68c7e5f0c618" +version = "0.39.0" +source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758" dependencies = [ "atomic", "bytes 1.0.1", @@ -1757,13 +1805,14 @@ dependencies = [ "libp2p-mplex", "libp2p-noise", "libp2p-ping", + "libp2p-rendezvous", "libp2p-request-response", "libp2p-swarm", "libp2p-swarm-derive", "libp2p-tcp", "libp2p-websocket", "libp2p-yamux", - "parity-multiaddr", + "multiaddr", "parking_lot 0.11.1", "pin-project 1.0.5", "smallvec", @@ -1772,9 +1821,8 @@ dependencies = [ [[package]] name = "libp2p-core" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "554d3e7e9e65f939d66b75fd6a4c67f258fe250da61b91f46c545fc4a89b51d9" +version = "0.29.0" +source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758" dependencies = [ "asn1_der", "bs58", @@ -1786,9 +1834,9 @@ dependencies = [ "lazy_static", "libsecp256k1", "log 0.4.14", + "multiaddr", "multihash", "multistream-select", - "parity-multiaddr", "parking_lot 0.11.1", "pin-project 1.0.5", "prost", @@ -1806,9 +1854,8 @@ dependencies = [ [[package]] name = "libp2p-dns" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e63dab8b5ff35e0c101a3e51e843ba782c07bbb1682f5fd827622e0d02b98b" +version = "0.29.0" +source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758" dependencies = [ "futures", "libp2p-core", @@ -1819,9 +1866,8 @@ dependencies = [ [[package]] name = "libp2p-mplex" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85e9b544335d1ed30af71daa96edbefadef6f19c7a55f078b9fc92c87163105d" +version = "0.29.0" +source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758" dependencies = [ "asynchronous-codec", "bytes 1.0.1", @@ -1837,9 +1883,8 @@ dependencies = [ [[package]] name = "libp2p-noise" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a2aa6fc4e6855eaf9ea1941a14f7ec4df35636fb6b85951e17481df8dcecf6" +version = "0.32.0" +source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758" dependencies = [ "bytes 1.0.1", "curve25519-dalek", @@ -1859,9 +1904,8 @@ dependencies = [ [[package]] name = "libp2p-ping" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4bfaffac63bf3c7ec11ed9d8879d455966ddea7e78ee14737f0b6dce0d1cd1" +version = "0.30.0" +source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758" dependencies = [ "futures", "libp2p-core", @@ -1872,11 +1916,32 @@ dependencies = [ "wasm-timer", ] +[[package]] +name = "libp2p-rendezvous" +version = "0.1.0" +source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758" +dependencies = [ + "asynchronous-codec", + "bimap", + "futures", + "libp2p-core", + "libp2p-swarm", + "log 0.4.14", + "prost", + "prost-build", + "rand 0.8.3", + "sha2 0.9.5", + "thiserror", + "unsigned-varint 0.7.0", + "uuid", + "void", + "wasm-timer", +] + [[package]] name = "libp2p-request-response" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cdbe172f08e6d0f95fa8634e273d4c4268c4063de2e33e7435194b0130c62e3" +version = "0.12.0" +source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758" dependencies = [ "async-trait", "bytes 1.0.1", @@ -1894,9 +1959,8 @@ dependencies = [ [[package]] name = "libp2p-swarm" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e04d8e1eef675029ec728ba14e8d0da7975d84b6679b699b4ae91a1de9c3a92" +version = "0.30.0" +source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758" dependencies = [ "either", "futures", @@ -1911,18 +1975,16 @@ dependencies = [ [[package]] name = "libp2p-swarm-derive" version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "365b0a699fea5168676840567582a012ea297b1ca02eee467e58301b9c9c5eed" +source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758" dependencies = [ "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] name = "libp2p-tcp" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b1a27d21c477951799e99d5c105d78868258502ce092988040a808d5a19bbd9" +version = "0.29.0" +source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758" dependencies = [ "futures", "futures-timer", @@ -1937,9 +1999,8 @@ dependencies = [ [[package]] name = "libp2p-websocket" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cace60995ef6f637e4752cccbb2590f6bc358e8741a0d066307636c69a4b3a74" +version = "0.30.0" +source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758" dependencies = [ "either", "futures", @@ -1955,9 +2016,8 @@ dependencies = [ [[package]] name = "libp2p-yamux" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f35da42cfc6d5cb0dcf3ad6881bc68d146cdf38f98655e09e33fbba4d13eabc4" +version = "0.33.0" +source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758" dependencies = [ "futures", "libp2p-core", @@ -1968,20 +2028,52 @@ dependencies = [ [[package]] name = "libsecp256k1" -version = "0.3.5" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962" +checksum = "bd1137239ab33b41aa9637a88a28249e5e70c40a42ccc92db7f12cc356c1fcd7" dependencies = [ "arrayref", - "crunchy", - "digest 0.8.1", + "base64 0.12.3", + "digest 0.9.0", "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", "rand 0.7.3", - "sha2 0.8.2", - "subtle 2.4.0", + "serde", + "sha2 0.9.5", "typenum", ] +[[package]] +name = "libsecp256k1-core" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee11012b293ea30093c129173cac4335513064094619f4639a25b310fd33c11" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle 2.4.0", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32239626ffbb6a095b83b37a02ceb3672b2443a87a000a884fc3c4d16925c9c0" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76acb433e21d10f5f9892b1962c2856c58c7f39a9e4bd68ac82b9436a0ffd5b9" +dependencies = [ + "libsecp256k1-core", +] + [[package]] name = "libz-sys" version = "1.1.2" @@ -2134,9 +2226,9 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ce18b5423c573a13e80cb3046ea0af6379ef725dc3af4886bdb8f4e5093068" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -2256,6 +2348,24 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "multiaddr" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7139982f583d7e53879d9f611fe48ced18e77d684309484f2252c76bcd39f549" +dependencies = [ + "arrayref", + "bs58", + "byteorder", + "data-encoding", + "multihash", + "percent-encoding 2.1.0", + "serde", + "static_assertions", + "unsigned-varint 0.7.0", + "url 2.2.2", +] + [[package]] name = "multihash" version = "0.13.2" @@ -2277,9 +2387,9 @@ checksum = "85ee3c48cb9d9b275ad967a0e96715badc13c6029adb92f34fa17b9ff28fd81f" dependencies = [ "proc-macro-crate", "proc-macro-error", - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", "synstructure", ] @@ -2291,9 +2401,8 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "multistream-select" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d91ec0a2440aaff5f78ec35631a7027d50386c6163aa975f7caa0d5da4b6ff8" +version = "0.10.3" +source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758" dependencies = [ "bytes 1.0.1", "futures", @@ -2446,24 +2555,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "parity-multiaddr" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58341485071825827b7f03cf7efd1cb21e6a709bea778fb50227fd45d2f361b4" -dependencies = [ - "arrayref", - "bs58", - "byteorder", - "data-encoding", - "multihash", - "percent-encoding 2.1.0", - "serde", - "static_assertions", - "unsigned-varint 0.7.0", - "url 2.2.2", -] - [[package]] name = "parking_lot" version = "0.10.2" @@ -2579,9 +2670,9 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -2590,9 +2681,9 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "758669ae3558c6f74bd2a18b41f7ac0b5a195aea6639d6a9b5e5d1ad5ba24c0b" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -2679,9 +2770,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", "version_check 0.9.3", ] @@ -2691,7 +2782,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", "version_check 0.9.3", ] @@ -2719,9 +2810,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.24" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" dependencies = [ "unicode-xid 0.2.1", ] @@ -2782,9 +2873,9 @@ checksum = "169a15f3008ecb5160cba7d37bcd690a7601b6d30cfb87a117d45e59d52af5d4" dependencies = [ "anyhow", "itertools 0.9.0", - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -2845,7 +2936,7 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", ] [[package]] @@ -3439,9 +3530,9 @@ version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -3467,6 +3558,29 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad9fdbb69badc8916db738c25efd04f0a65297d26c2f8de4b62e57b8c12bc72" +dependencies = [ + "rustversion", + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1569374bd54623ec8bd592cf22ba6e03c0f177ff55fbc8c29a49e296e7adecf" +dependencies = [ + "darling", + "proc-macro2 1.0.27", + "quote 1.0.9", + "syn 1.0.73", +] + [[package]] name = "serial_test" version = "0.4.0" @@ -3484,9 +3598,9 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d08338d8024b227c62bd68a12c7c9883f5c66780abaef15c550dc56f46ee6515" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -3749,11 +3863,11 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", "serde", "serde_derive", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -3763,13 +3877,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" dependencies = [ "base-x", - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", "serde", "serde_derive", "serde_json", "sha1", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -3784,6 +3898,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "structopt" version = "0.3.22" @@ -3803,9 +3923,9 @@ checksum = "7813934aecf5f51a54775e00068c237de98489463968231a51746bbbc03f9c10" dependencies = [ "heck", "proc-macro-error", - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -3824,9 +3944,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb076d8b589fde927ec90d05920d610554ca3a4d9dddb177481cadd071a19c2e" dependencies = [ "heck", - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -3893,6 +4013,7 @@ dependencies = [ "serde", "serde_cbor", "serde_json", + "serde_with", "sha2 0.9.5", "sigma_fun", "sled", @@ -3933,11 +4054,11 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.64" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f" +checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", "unicode-xid 0.2.1", ] @@ -3948,9 +4069,9 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", "unicode-xid 0.2.1", ] @@ -4033,9 +4154,9 @@ version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -4089,10 +4210,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" dependencies = [ "proc-macro-hack", - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", "standback", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -4145,9 +4266,9 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -4292,9 +4413,9 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", ] [[package]] @@ -4677,9 +4798,9 @@ dependencies = [ "bumpalo", "lazy_static", "log 0.4.14", - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", "wasm-bindgen-shared", ] @@ -4711,9 +4832,9 @@ version = "0.2.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc053ec74d454df287b9374ee8abb36ffd5acb95ba87da3ba5b7d3fe20eb401e" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4904,9 +5025,9 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f369ddb18862aba61aa49bf31e74d29f0f162dec753063200e1dc084345d16" dependencies = [ - "proc-macro2 1.0.24", + "proc-macro2 1.0.27", "quote 1.0.9", - "syn 1.0.64", + "syn 1.0.73", "synstructure", ] diff --git a/swap/Cargo.toml b/swap/Cargo.toml index 08bd201e..6eda996f 100644 --- a/swap/Cargo.toml +++ b/swap/Cargo.toml @@ -30,7 +30,7 @@ ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = ed25519-dalek = "1" futures = { version = "0.3", default-features = false } itertools = "0.10" -libp2p = { version = "0.38", default-features = false, features = [ "tcp-tokio", "yamux", "mplex", "dns-tokio", "noise", "request-response", "websocket", "ping" ] } +libp2p = { git = "https://github.com/comit-network/rust-libp2p", branch = "rendezvous", default-features = false, features = [ "tcp-tokio", "yamux", "mplex", "dns-tokio", "noise", "request-response", "websocket", "ping", "rendezvous" ] } miniscript = { version = "5", features = [ "serde" ] } monero = { version = "0.12", features = [ "serde_support" ] } monero-rpc = { path = "../monero-rpc" } @@ -45,6 +45,7 @@ rust_decimal_macros = "1" serde = { version = "1", features = [ "derive" ] } serde_cbor = "0.11" serde_json = "1" +serde_with = { version = "1.9.4", features = [ "macros" ] } sha2 = "0.9" sigma_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "ed25519", "serde" ] } sled = "0.34" diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index 327a6081..9aaa1a14 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -148,7 +148,7 @@ where loop { tokio::select! { - swarm_event = self.swarm.next_event() => { + swarm_event = self.swarm.select_next_some() => { match swarm_event { SwarmEvent::Behaviour(OutEvent::SwapSetupInitiated { mut send_wallet_snapshot }) => { diff --git a/swap/src/bin/swap.rs b/swap/src/bin/swap.rs index 15c24a43..ba3e3ed2 100644 --- a/swap/src/bin/swap.rs +++ b/swap/src/bin/swap.rs @@ -24,7 +24,7 @@ use std::sync::Arc; use std::time::Duration; use swap::bitcoin::TxLock; use swap::cli::command::{parse_args_and_apply_defaults, Arguments, Command, ParseResult}; -use swap::cli::EventLoop; +use swap::cli::{list_sellers, EventLoop}; use swap::database::Database; use swap::env::Config; use swap::libp2p_ext::MultiAddrExt; @@ -65,7 +65,7 @@ async fn main() -> Result<()> { } => { let swap_id = Uuid::new_v4(); - cli::tracing::init(debug, json, data_dir.join("logs"), swap_id)?; + cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?; let db = Database::open(data_dir.join("database").as_path()) .context("Failed to open database")?; let seed = Seed::from_file_or_generate(data_dir.as_path()) @@ -157,7 +157,7 @@ async fn main() -> Result<()> { monero_daemon_address, tor_socks5_port, } => { - cli::tracing::init(debug, json, data_dir.join("logs"), swap_id)?; + cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?; let db = Database::open(data_dir.join("database").as_path()) .context("Failed to open database")?; let seed = Seed::from_file_or_generate(data_dir.as_path()) @@ -223,7 +223,7 @@ async fn main() -> Result<()> { bitcoin_electrum_rpc_url, bitcoin_target_block, } => { - cli::tracing::init(debug, json, data_dir.join("logs"), swap_id)?; + cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?; let db = Database::open(data_dir.join("database").as_path()) .context("Failed to open database")?; let seed = Seed::from_file_or_generate(data_dir.as_path()) @@ -255,7 +255,7 @@ async fn main() -> Result<()> { bitcoin_electrum_rpc_url, bitcoin_target_block, } => { - cli::tracing::init(debug, json, data_dir.join("logs"), swap_id)?; + cli::tracing::init(debug, json, data_dir.join("logs"), Some(swap_id))?; let db = Database::open(data_dir.join("database").as_path()) .context("Failed to open database")?; let seed = Seed::from_file_or_generate(data_dir.as_path()) @@ -272,6 +272,50 @@ async fn main() -> Result<()> { cli::refund(swap_id, Arc::new(bitcoin_wallet), db, force).await??; } + Command::ListSellers { + rendezvous_node_addr, + namespace, + tor_socks5_port, + } => { + let rendezvous_node_peer_id = rendezvous_node_addr + .extract_peer_id() + .context("Rendezvous node address must contain peer ID")?; + + cli::tracing::init(debug, json, data_dir.join("logs"), None)?; + let seed = Seed::from_file_or_generate(data_dir.as_path()) + .context("Failed to read in seed file")?; + let identity = seed.derive_libp2p_identity(); + + let sellers = list_sellers( + rendezvous_node_peer_id, + rendezvous_node_addr, + namespace, + tor_socks5_port, + identity, + ) + .await?; + + if json { + for seller in sellers { + println!("{}", serde_json::to_string(&seller)?); + } + } else { + let mut table = Table::new(); + + table.set_header(vec!["PRICE", "MIN_QUANTITY", "MAX_QUANTITY", "ADDRESS"]); + + for seller in sellers { + table.add_row(vec![ + seller.quote.price.to_string(), + seller.quote.min_quantity.to_string(), + seller.quote.max_quantity.to_string(), + seller.multiaddr.to_string(), + ]); + } + + println!("{}", table); + } + } }; Ok(()) } diff --git a/swap/src/cli.rs b/swap/src/cli.rs index 30a7f7d6..5c88f133 100644 --- a/swap/src/cli.rs +++ b/swap/src/cli.rs @@ -2,6 +2,7 @@ mod behaviour; pub mod cancel; pub mod command; mod event_loop; +mod list_sellers; pub mod refund; pub mod tracing; pub mod transport; @@ -9,4 +10,5 @@ pub mod transport; pub use behaviour::{Behaviour, OutEvent}; pub use cancel::cancel; pub use event_loop::{EventLoop, EventLoopHandle}; +pub use list_sellers::list_sellers; pub use refund::refund; diff --git a/swap/src/cli/command.rs b/swap/src/cli/command.rs index d1f82398..08896045 100644 --- a/swap/src/cli/command.rs +++ b/swap/src/cli/command.rs @@ -1,5 +1,6 @@ use crate::env::GetConfig; use crate::fs::system_data_dir; +use crate::network::rendezvous::{XmrBtcNamespace, DEFAULT_RENDEZVOUS_ADDRESS}; use crate::{env, monero}; use anyhow::{Context, Result}; use libp2p::core::Multiaddr; @@ -188,6 +189,20 @@ where bitcoin_target_block: bitcoin_target_block_from(bitcoin_target_block, is_testnet), }, }, + RawCommand::ListSellers { + rendezvous_node_addr, + tor: Tor { tor_socks5_port }, + } => Arguments { + env_config: env_config_from(is_testnet), + debug, + json, + data_dir: data::data_dir_from(data, is_testnet)?, + cmd: Command::ListSellers { + rendezvous_node_addr, + namespace: rendezvous_namespace_from(is_testnet), + tor_socks5_port, + }, + }, }; Ok(ParseResult::Arguments(arguments)) @@ -224,6 +239,11 @@ pub enum Command { bitcoin_electrum_rpc_url: Url, bitcoin_target_block: usize, }, + ListSellers { + rendezvous_node_addr: Multiaddr, + namespace: XmrBtcNamespace, + tor_socks5_port: u16, + }, } #[derive(structopt::StructOpt, Debug)] @@ -311,6 +331,17 @@ pub enum RawCommand { #[structopt(flatten)] bitcoin: Bitcoin, }, + ListSellers { + #[structopt( + long, + help = "The multiaddr (including peer-id) of a rendezvous node that sellers register with", + default_value = DEFAULT_RENDEZVOUS_ADDRESS + )] + rendezvous_node_addr: Multiaddr, + + #[structopt(flatten)] + tor: Tor, + }, } #[derive(structopt::StructOpt, Debug)] @@ -406,6 +437,14 @@ fn bitcoin_electrum_rpc_url_from(url: Option, testnet: bool) -> Result } } +fn rendezvous_namespace_from(is_testnet: bool) -> XmrBtcNamespace { + if is_testnet { + XmrBtcNamespace::Testnet + } else { + XmrBtcNamespace::Mainnet + } +} + fn bitcoin_target_block_from(target_block: Option, testnet: bool) -> usize { if let Some(target_block) = target_block { target_block diff --git a/swap/src/cli/event_loop.rs b/swap/src/cli/event_loop.rs index 9d55a81a..2ed37b26 100644 --- a/swap/src/cli/event_loop.rs +++ b/swap/src/cli/event_loop.rs @@ -94,7 +94,7 @@ impl EventLoop { loop { // Note: We are making very elaborate use of `select!` macro's feature here. Make sure to read the documentation thoroughly: https://docs.rs/tokio/1.4.0/tokio/macro.select.html tokio::select! { - swarm_event = self.swarm.next_event().fuse() => { + swarm_event = self.swarm.select_next_some() => { match swarm_event { SwarmEvent::Behaviour(OutEvent::QuoteReceived { id, response }) => { if let Some(responder) = self.inflight_quote_requests.remove(&id) { diff --git a/swap/src/cli/list_sellers.rs b/swap/src/cli/list_sellers.rs new file mode 100644 index 00000000..b0df7932 --- /dev/null +++ b/swap/src/cli/list_sellers.rs @@ -0,0 +1,273 @@ +use crate::network::quote::BidQuote; +use crate::network::rendezvous::XmrBtcNamespace; +use crate::network::{quote, swarm}; +use anyhow::Result; +use futures::StreamExt; +use libp2p::multiaddr::Protocol; +use libp2p::ping::{Ping, PingConfig, PingEvent}; +use libp2p::rendezvous::{Namespace, Rendezvous}; +use libp2p::request_response::{RequestResponseEvent, RequestResponseMessage}; +use libp2p::swarm::SwarmEvent; +use libp2p::{identity, rendezvous, Multiaddr, PeerId, Swarm}; +use serde::Serialize; +use serde_with::{serde_as, DisplayFromStr}; +use std::collections::HashMap; +use std::time::Duration; + +pub async fn list_sellers( + rendezvous_node_peer_id: PeerId, + rendezvous_node_addr: Multiaddr, + namespace: XmrBtcNamespace, + tor_socks5_port: u16, + identity: identity::Keypair, +) -> Result> { + let behaviour = Behaviour { + rendezvous: Rendezvous::new(identity.clone(), rendezvous::Config::default()), + quote: quote::cli(), + ping: Ping::new( + PingConfig::new() + .with_keep_alive(false) + .with_interval(Duration::from_secs(86_400)), + ), + }; + let mut swarm = swarm::cli(identity, tor_socks5_port, behaviour).await?; + + let _ = swarm.dial_addr(rendezvous_node_addr.clone()); + + let event_loop = EventLoop::new( + swarm, + rendezvous_node_peer_id, + rendezvous_node_addr, + namespace, + ); + let sellers = event_loop.run().await; + + Ok(sellers) +} + +#[serde_as] +#[derive(Debug, Serialize)] +pub struct Seller { + #[serde_as(as = "DisplayFromStr")] + pub multiaddr: Multiaddr, + pub quote: BidQuote, +} + +#[derive(Debug)] +enum OutEvent { + Rendezvous(rendezvous::Event), + Quote(quote::OutEvent), + Ping(PingEvent), +} + +impl From for OutEvent { + fn from(event: rendezvous::Event) -> Self { + OutEvent::Rendezvous(event) + } +} + +impl From for OutEvent { + fn from(event: quote::OutEvent) -> Self { + OutEvent::Quote(event) + } +} + +#[derive(libp2p::NetworkBehaviour)] +#[behaviour(event_process = false)] +#[behaviour(out_event = "OutEvent")] +struct Behaviour { + rendezvous: Rendezvous, + quote: quote::Behaviour, + ping: Ping, +} + +#[derive(Debug)] +enum QuoteStatus { + Pending, + Received(BidQuote), +} + +enum State { + WaitForDiscovery, + WaitForQuoteCompletion, +} + +struct EventLoop { + swarm: Swarm, + rendezvous_peer_id: PeerId, + rendezvous_addr: Multiaddr, + namespace: XmrBtcNamespace, + asb_address: HashMap, + asb_quote_status: HashMap, + state: State, +} + +impl EventLoop { + fn new( + swarm: Swarm, + rendezvous_peer_id: PeerId, + rendezvous_addr: Multiaddr, + namespace: XmrBtcNamespace, + ) -> Self { + Self { + swarm, + rendezvous_peer_id, + rendezvous_addr, + namespace, + asb_address: Default::default(), + asb_quote_status: Default::default(), + state: State::WaitForDiscovery, + } + } + + async fn run(mut self) -> Vec { + loop { + tokio::select! { + swarm_event = self.swarm.select_next_some() => { + match swarm_event { + SwarmEvent::ConnectionEstablished { peer_id, endpoint, .. } => { + if peer_id == self.rendezvous_peer_id{ + tracing::info!( + "Connected to rendezvous point, discovering nodes in '{}' namespace ...", + self.namespace + ); + + self.swarm.behaviour_mut().rendezvous.discover( + Some(Namespace::new(self.namespace.to_string()).expect("our namespace to be a correct string")), + None, + None, + self.rendezvous_peer_id, + ); + } else { + let address = endpoint.get_remote_address(); + self.asb_address.insert(peer_id, address.clone()); + } + } + SwarmEvent::UnreachableAddr { peer_id, error, address, .. } => { + if address == self.rendezvous_addr { + tracing::error!( + "Failed to connect to rendezvous point at {}: {}", + address, + error + ); + + // if the rendezvous node is unreachable we just stop + return Vec::new(); + } else { + tracing::debug!( + "Failed to connect to peer at {}: {}", + address, + error + ); + + // if a different peer than the rendezvous node is unreachable (i.e. a seller) we remove that seller from the quote status state + self.asb_quote_status.remove(&peer_id); + } + } + SwarmEvent::Behaviour(OutEvent::Rendezvous( + rendezvous::Event::Discovered { registrations, .. }, + )) => { + self.state = State::WaitForQuoteCompletion; + + for registration in registrations { + let peer = registration.record.peer_id(); + for address in registration.record.addresses() { + tracing::info!("Discovered peer {} at {}", peer, address); + + let p2p_suffix = Protocol::P2p(*peer.as_ref()); + let _address_with_p2p = if !address + .ends_with(&Multiaddr::empty().with(p2p_suffix.clone())) + { + address.clone().with(p2p_suffix) + } else { + address.clone() + }; + + self.asb_quote_status.insert(peer, QuoteStatus::Pending); + + // add all external addresses of that peer to the quote behaviour + self.swarm.behaviour_mut().quote.add_address(&peer, address.clone()); + } + + // request the quote, if we are not connected to the peer it will be dialed automatically + let _request_id = self.swarm.behaviour_mut().quote.send_request(&peer, ()); + } + } + SwarmEvent::Behaviour(OutEvent::Quote(quote_response)) => { + match quote_response { + RequestResponseEvent::Message { peer, message } => { + match message { + RequestResponseMessage::Response { response, .. } => { + if self.asb_quote_status.insert(peer, QuoteStatus::Received(response)).is_none() { + tracing::error!(%peer, "Received bid quote from unexpected peer, this record will be removed!"); + self.asb_quote_status.remove(&peer); + } + } + RequestResponseMessage::Request { .. } => unreachable!() + } + } + RequestResponseEvent::OutboundFailure { peer, error, .. } => { + if peer == self.rendezvous_peer_id { + tracing::debug!(%peer, "Outbound failure when communicating with rendezvous node: {:#}", error); + } else { + tracing::debug!(%peer, "Ignoring seller, because unable to request quote: {:#}", error); + self.asb_quote_status.remove(&peer); + } + } + RequestResponseEvent::InboundFailure { peer, error, .. } => { + if peer == self.rendezvous_peer_id { + tracing::debug!(%peer, "Inbound failure when communicating with rendezvous node: {:#}", error); + } else { + tracing::debug!(%peer, "Ignoring seller, because unable to request quote: {:#}", error); + self.asb_quote_status.remove(&peer); + } + }, + RequestResponseEvent::ResponseSent { .. } => unreachable!() + } + } + _ => {} + } + } + } + + match self.state { + State::WaitForDiscovery => { + continue; + } + State::WaitForQuoteCompletion => { + let all_quotes_fetched = self + .asb_quote_status + .iter() + .map(|(peer_id, quote_status)| match quote_status { + QuoteStatus::Pending => Err(StillPending {}), + QuoteStatus::Received(quote) => { + let address = self + .asb_address + .get(&peer_id) + .expect("if we got a quote we must have stored an address"); + + Ok(Seller { + multiaddr: address.clone(), + quote: *quote, + }) + } + }) + .collect::, _>>(); + + match all_quotes_fetched { + Ok(sellers) => break sellers, + Err(StillPending {}) => continue, + } + } + } + } + } +} + +struct StillPending {} + +impl From for OutEvent { + fn from(event: PingEvent) -> Self { + OutEvent::Ping(event) + } +} diff --git a/swap/src/cli/tracing.rs b/swap/src/cli/tracing.rs index 733b263e..1b9620f5 100644 --- a/swap/src/cli/tracing.rs +++ b/swap/src/cli/tracing.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use std::option::Option::Some; use std::path::Path; use tracing::subscriber::set_global_default; use tracing::{Event, Level, Subscriber}; @@ -8,7 +9,7 @@ use tracing_subscriber::layer::{Context, SubscriberExt}; use tracing_subscriber::{fmt, EnvFilter, FmtSubscriber, Layer, Registry}; use uuid::Uuid; -pub fn init(debug: bool, json: bool, dir: impl AsRef, swap_id: Uuid) -> Result<()> { +pub fn init(debug: bool, json: bool, dir: impl AsRef, swap_id: Option) -> Result<()> { if json { let level = if debug { Level::DEBUG } else { Level::INFO }; @@ -24,7 +25,7 @@ pub fn init(debug: bool, json: bool, dir: impl AsRef, swap_id: Uuid) -> Re .init(); Ok(()) - } else { + } else if let Some(swap_id) = swap_id { let level_filter = EnvFilter::try_new("swap=debug")?; let registry = Registry::default().with(level_filter); @@ -45,6 +46,19 @@ pub fn init(debug: bool, json: bool, dir: impl AsRef, swap_id: Uuid) -> Re set_global_default(registry.with(file_logger).with(info_terminal_printer()))?; } + Ok(()) + } else { + let level = if debug { Level::DEBUG } else { Level::INFO }; + let is_terminal = atty::is(atty::Stream::Stderr); + + FmtSubscriber::builder() + .with_env_filter(format!("swap={}", level)) + .with_writer(std::io::stderr) + .with_ansi(is_terminal) + .with_timer(ChronoLocal::with_format("%F %T".to_owned())) + .with_target(false) + .init(); + Ok(()) } } diff --git a/swap/src/network.rs b/swap/src/network.rs index d02cddbd..420480af 100644 --- a/swap/src/network.rs +++ b/swap/src/network.rs @@ -7,6 +7,7 @@ pub mod encrypted_signature; pub mod json_pull_codec; pub mod quote; pub mod redial; +pub mod rendezvous; pub mod swap_setup; pub mod swarm; pub mod tor_transport; diff --git a/swap/src/network/quote.rs b/swap/src/network/quote.rs index 76c1ffc5..fc2d93f4 100644 --- a/swap/src/network/quote.rs +++ b/swap/src/network/quote.rs @@ -9,7 +9,7 @@ use libp2p::PeerId; use serde::{Deserialize, Serialize}; const PROTOCOL: &str = "/comit/xmr/btc/bid-quote/1.0.0"; -type OutEvent = RequestResponseEvent<(), BidQuote>; +pub type OutEvent = RequestResponseEvent<(), BidQuote>; type Message = RequestResponseMessage<(), BidQuote>; pub type Behaviour = RequestResponse>; @@ -24,7 +24,7 @@ impl ProtocolName for BidQuoteProtocol { } /// Represents a quote for buying XMR. -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)] pub struct BidQuote { /// The price at which the maker is willing to buy at. #[serde(with = "::bitcoin::util::amount::serde::as_sat")] diff --git a/swap/src/network/rendezvous.rs b/swap/src/network/rendezvous.rs new file mode 100644 index 00000000..9060f000 --- /dev/null +++ b/swap/src/network/rendezvous.rs @@ -0,0 +1,32 @@ +use libp2p::rendezvous::Namespace; +use std::fmt; + +pub const DEFAULT_RENDEZVOUS_ADDRESS: &str = + "/dnsaddr/rendezvous.coblox.tech/p2p/12D3KooWQUt9DkNZxEn2R5ymJzWj15MpG6mTW84kyd8vDaRZi46o"; + +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum XmrBtcNamespace { + Mainnet, + Testnet, +} + +const MAINNET: &str = "xmr-btc-swap-mainnet"; +const TESTNET: &str = "xmr-btc-swap-testnet"; + +impl fmt::Display for XmrBtcNamespace { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + XmrBtcNamespace::Mainnet => write!(f, "{}", MAINNET), + XmrBtcNamespace::Testnet => write!(f, "{}", TESTNET), + } + } +} + +impl From for Namespace { + fn from(namespace: XmrBtcNamespace) -> Self { + match namespace { + XmrBtcNamespace::Mainnet => Namespace::from_static(MAINNET), + XmrBtcNamespace::Testnet => Namespace::from_static(TESTNET), + } + } +} diff --git a/swap/src/network/test.rs b/swap/src/network/test.rs index ea4b1a8d..8c2eb037 100644 --- a/swap/src/network/test.rs +++ b/swap/src/network/test.rs @@ -1,19 +1,18 @@ -use futures::future; +use async_trait::async_trait; +use futures::stream::FusedStream; +use futures::{future, Future, Stream, StreamExt}; use libp2p::core::muxing::StreamMuxerBox; -use libp2p::core::transport::memory::MemoryTransport; -use libp2p::core::upgrade::{SelectUpgrade, Version}; -use libp2p::core::{Executor, Multiaddr}; +use libp2p::core::transport::upgrade::Version; +use libp2p::core::transport::MemoryTransport; +use libp2p::core::upgrade::SelectUpgrade; +use libp2p::core::{identity, Executor, Multiaddr, PeerId, Transport}; use libp2p::mplex::MplexConfig; -use libp2p::noise::{self, NoiseConfig, X25519Spec}; -use libp2p::swarm::{ - IntoProtocolsHandler, NetworkBehaviour, ProtocolsHandler, SwarmBuilder, SwarmEvent, -}; -use libp2p::{identity, yamux, PeerId, Swarm, Transport}; +use libp2p::noise::{Keypair, NoiseConfig, X25519Spec}; +use libp2p::swarm::{AddressScore, NetworkBehaviour, Swarm, SwarmBuilder, SwarmEvent}; +use libp2p::yamux::YamuxConfig; use std::fmt::Debug; -use std::future::Future; use std::pin::Pin; use std::time::Duration; -use tokio::time; /// An adaptor struct for libp2p that spawns futures into the current /// thread-local runtime. @@ -25,49 +24,18 @@ impl Executor for GlobalSpawnTokioExecutor { } } -#[allow(missing_debug_implementations)] -pub struct Actor { - pub swarm: Swarm, - pub addr: Multiaddr, - pub peer_id: PeerId, -} - -pub async fn new_connected_swarm_pair(behaviour_fn: F) -> (Actor, Actor) +pub fn new_swarm(behaviour_fn: F) -> Swarm where B: NetworkBehaviour, - F: Fn(PeerId, identity::Keypair) -> B + Clone, - <<::ProtocolsHandler as IntoProtocolsHandler>::Handler as ProtocolsHandler>::InEvent: Clone, -::OutEvent: Debug{ - let (swarm, addr, peer_id) = new_swarm(behaviour_fn.clone()); - let mut alice = Actor { - swarm, - addr, - peer_id, - }; - - let (swarm, addr, peer_id) = new_swarm(behaviour_fn); - let mut bob = Actor { - swarm, - addr, - peer_id, - }; - - connect(&mut alice.swarm, &mut bob.swarm).await; - - (alice, bob) -} - -pub fn new_swarm B>( - behaviour_fn: F, -) -> (Swarm, Multiaddr, PeerId) -where + ::OutEvent: Debug, B: NetworkBehaviour, + F: FnOnce(PeerId, identity::Keypair) -> B, { - let id_keys = identity::Keypair::generate_ed25519(); - let peer_id = PeerId::from(id_keys.public()); + let identity = identity::Keypair::generate_ed25519(); + let peer_id = PeerId::from(identity.public()); - let dh_keys = noise::Keypair::::new() - .into_authentic(&id_keys) + let dh_keys = Keypair::::new() + .into_authentic(&identity) .expect("failed to create dh_keys"); let noise = NoiseConfig::xx(dh_keys).into_authenticated(); @@ -75,88 +43,146 @@ where .upgrade(Version::V1) .authenticate(noise) .multiplex(SelectUpgrade::new( - yamux::YamuxConfig::default(), + YamuxConfig::default(), MplexConfig::new(), )) .timeout(Duration::from_secs(5)) .map(|(peer, muxer), _| (peer, StreamMuxerBox::new(muxer))) .boxed(); - let mut swarm: Swarm = SwarmBuilder::new(transport, behaviour_fn(peer_id, id_keys), peer_id) + SwarmBuilder::new(transport, behaviour_fn(peer_id, identity), peer_id) .executor(Box::new(GlobalSpawnTokioExecutor)) - .build(); + .build() +} +fn get_rand_memory_address() -> Multiaddr { let address_port = rand::random::(); let addr = format!("/memory/{}", address_port) .parse::() .unwrap(); - Swarm::listen_on(&mut swarm, addr.clone()).unwrap(); - - (swarm, addr, peer_id) + addr } -pub async fn await_events_or_timeout( - alice_event: impl Future, - bob_event: impl Future, -) -> (A, B) { - time::timeout( - Duration::from_secs(10), - future::join(alice_event, bob_event), +pub async fn await_events_or_timeout( + swarm_1: &mut (impl Stream> + FusedStream + Unpin), + swarm_2: &mut (impl Stream> + FusedStream + Unpin), +) -> (SwarmEvent, SwarmEvent) +where + SwarmEvent: Debug, + SwarmEvent: Debug, +{ + tokio::time::timeout( + Duration::from_secs(30), + future::join( + swarm_1 + .inspect(|event| tracing::debug!("Swarm1 emitted {:?}", event)) + .select_next_some(), + swarm_2 + .inspect(|event| tracing::debug!("Swarm2 emitted {:?}", event)) + .select_next_some(), + ), ) .await .expect("network behaviours to emit an event within 10 seconds") } -/// Connects two swarms with each other. -/// -/// This assumes the transport that is in use can be used by Bob to connect to -/// the listen address that is emitted by Alice. In other words, they have to be -/// on the same network. The memory transport used by the above `new_swarm` -/// function fulfills this. -/// -/// We also assume that the swarms don't emit any behaviour events during the -/// connection phase. Any event emitted is considered a bug from this functions -/// PoV because they would be lost. -pub async fn connect(alice: &mut Swarm, bob: &mut Swarm) +/// An extension trait for [`Swarm`] that makes it easier to set up a network of +/// [`Swarm`]s for tests. +#[async_trait] +pub trait SwarmExt { + /// Establishes a connection to the given [`Swarm`], polling both of them + /// until the connection is established. + async fn block_on_connection(&mut self, other: &mut Swarm) + where + T: NetworkBehaviour, + ::OutEvent: Debug; + + /// Listens on a random memory address, polling the [`Swarm`] until the + /// transport is ready to accept connections. + async fn listen_on_random_memory_address(&mut self) -> Multiaddr; +} + +#[async_trait] +impl SwarmExt for Swarm where - BA: NetworkBehaviour, - BB: NetworkBehaviour, - ::OutEvent: Debug, - ::OutEvent: Debug, + B: NetworkBehaviour, + ::OutEvent: Debug, { - let mut alice_connected = false; - let mut bob_connected = false; - - while !alice_connected && !bob_connected { - let (alice_event, bob_event) = future::join(alice.next_event(), bob.next_event()).await; - - match alice_event { - SwarmEvent::ConnectionEstablished { .. } => { - alice_connected = true; + async fn block_on_connection(&mut self, other: &mut Swarm) + where + T: NetworkBehaviour, + ::OutEvent: Debug, + { + let addr_to_dial = other.external_addresses().next().unwrap().addr.clone(); + + self.dial_addr(addr_to_dial.clone()).unwrap(); + + let mut dialer_done = false; + let mut listener_done = false; + + loop { + let dialer_event_fut = self.select_next_some(); + + tokio::select! { + dialer_event = dialer_event_fut => { + match dialer_event { + SwarmEvent::ConnectionEstablished { .. } => { + dialer_done = true; + } + SwarmEvent::UnknownPeerUnreachableAddr { address, error } if address == addr_to_dial => { + panic!("Failed to dial address {}: {}", addr_to_dial, error) + } + other => { + tracing::debug!("Ignoring {:?}", other); + } + } + }, + listener_event = other.select_next_some() => { + match listener_event { + SwarmEvent::ConnectionEstablished { .. } => { + listener_done = true; + } + SwarmEvent::IncomingConnectionError { error, .. } => { + panic!("Failure in incoming connection {}", error); + } + other => { + tracing::debug!("Ignoring {:?}", other); + } + } + } } - SwarmEvent::NewListenAddr(addr) => { - bob.dial_addr(addr).unwrap(); - } - SwarmEvent::Behaviour(event) => { - panic!( - "alice unexpectedly emitted a behaviour event during connection: {:?}", - event - ); + + if dialer_done && listener_done { + return; } - _ => {} } - match bob_event { - SwarmEvent::ConnectionEstablished { .. } => { - bob_connected = true; - } - SwarmEvent::Behaviour(event) => { - panic!( - "bob unexpectedly emitted a behaviour event during connection: {:?}", - event - ); + } + + async fn listen_on_random_memory_address(&mut self) -> Multiaddr { + let multiaddr = get_rand_memory_address(); + + self.listen_on(multiaddr.clone()).unwrap(); + + // block until we are actually listening + loop { + match self.select_next_some().await { + SwarmEvent::NewListenAddr(addr) if addr == multiaddr => { + break; + } + other => { + tracing::debug!( + "Ignoring {:?} while waiting for listening to succeed", + other + ); + } } - _ => {} } + + // Memory addresses are externally reachable because they all share the same + // memory-space. + self.add_external_address(multiaddr.clone(), AddressScore::Infinite); + + multiaddr } }