Compare commits

...

22 Commits

Author SHA1 Message Date
Daniel Karzel 7fba6e8fbf
Debug print for receiving quote from seller
3 years ago
Daniel Karzel 8d61407738
Add script to auto-swap with the ASB with best price
3 years ago
Daniel Karzel 9224134dee
Remove `--seller-peer-id` from `buy-xmr` command
3 years ago
Daniel Karzel 061208a063
Always print sellers as json
3 years ago
Daniel Karzel 23ee6c1bae
CLI prints on stdout and seller serializes to json
3 years ago
Daniel Karzel 2bdfc58ed7
Rebase fix
3 years ago
Daniel Karzel f0a217b1e4
Upgrade to latest libp2p branch rev
3 years ago
Daniel Karzel 5f70144c63
Change Ping intervals of list-sellers to 24h instead of 100y
3 years ago
Daniel Karzel 7c2c1319bc
Refuse unknown fields in config
3 years ago
Daniel Karzel bbd6991346
Latest specific rev of rendezvous branch
3 years ago
Daniel Karzel 6f5d83af78
fixup! Allow external addr to be specified in ASB CLI param
3 years ago
Daniel Karzel adc5e926ce
fixup! Add the `list-sellers` command to the CLI
3 years ago
Daniel Karzel dcf5923d3e
log when successfully registered
3 years ago
Daniel Karzel 01bfa93cf7
testnet is not mainnet
3 years ago
Daniel Karzel 863ab3ed11
fixup! Add the `list-sellers` command to the CLI
3 years ago
rishflab fa4cc82de0
Allow external addr to be specified in ASB CLI param
3 years ago
Daniel Karzel 92e77d1b71
Log error upon rendezvous registration failure
3 years ago
Daniel Karzel abde566b35
Re-registration
3 years ago
rishflab 76c0031253
WIP Integrate rendezvous protocol into ASB
3 years ago
Daniel Karzel 8fb20db33e
Add the `list-sellers` command to the CLI
3 years ago
Daniel Karzel 4ffb2f14be
Pass in CLI behaviour when creating swarm
3 years ago
Daniel Karzel 2e3601693e
Use our rendezvous libp2p fork
3 years ago

379
Cargo.lock generated

@ -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"
@ -611,7 +617,7 @@ dependencies = [
"ansi_term 0.11.0",
"atty",
"bitflags",
"strsim",
"strsim 0.8.0",
"textwrap",
"unicode-width",
"vec_map",
@ -838,6 +844,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"
@ -850,9 +891,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]]
@ -1032,9 +1073,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]]
@ -1175,9 +1216,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]]
@ -1414,13 +1455,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]]
@ -1525,6 +1566,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"
@ -1708,7 +1755,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]]
@ -1718,7 +1765,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]]
@ -1770,9 +1817,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?rev=4cacaf215b334d447faca8f4e361fe19c30d2162#4cacaf215b334d447faca8f4e361fe19c30d2162"
dependencies = [
"atomic",
"bytes 1.0.1",
@ -1783,13 +1829,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",
@ -1798,9 +1845,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?rev=4cacaf215b334d447faca8f4e361fe19c30d2162#4cacaf215b334d447faca8f4e361fe19c30d2162"
dependencies = [
"asn1_der",
"bs58",
@ -1812,9 +1858,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",
@ -1832,9 +1878,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?rev=4cacaf215b334d447faca8f4e361fe19c30d2162#4cacaf215b334d447faca8f4e361fe19c30d2162"
dependencies = [
"futures",
"libp2p-core",
@ -1845,9 +1890,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?rev=4cacaf215b334d447faca8f4e361fe19c30d2162#4cacaf215b334d447faca8f4e361fe19c30d2162"
dependencies = [
"asynchronous-codec",
"bytes 1.0.1",
@ -1863,9 +1907,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?rev=4cacaf215b334d447faca8f4e361fe19c30d2162#4cacaf215b334d447faca8f4e361fe19c30d2162"
dependencies = [
"bytes 1.0.1",
"curve25519-dalek",
@ -1885,9 +1928,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?rev=4cacaf215b334d447faca8f4e361fe19c30d2162#4cacaf215b334d447faca8f4e361fe19c30d2162"
dependencies = [
"futures",
"libp2p-core",
@ -1898,11 +1940,32 @@ dependencies = [
"wasm-timer",
]
[[package]]
name = "libp2p-rendezvous"
version = "0.1.0"
source = "git+https://github.com/comit-network/rust-libp2p?rev=4cacaf215b334d447faca8f4e361fe19c30d2162#4cacaf215b334d447faca8f4e361fe19c30d2162"
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?rev=4cacaf215b334d447faca8f4e361fe19c30d2162#4cacaf215b334d447faca8f4e361fe19c30d2162"
dependencies = [
"async-trait",
"bytes 1.0.1",
@ -1920,9 +1983,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?rev=4cacaf215b334d447faca8f4e361fe19c30d2162#4cacaf215b334d447faca8f4e361fe19c30d2162"
dependencies = [
"either",
"futures",
@ -1937,18 +1999,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?rev=4cacaf215b334d447faca8f4e361fe19c30d2162#4cacaf215b334d447faca8f4e361fe19c30d2162"
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?rev=4cacaf215b334d447faca8f4e361fe19c30d2162#4cacaf215b334d447faca8f4e361fe19c30d2162"
dependencies = [
"futures",
"futures-timer",
@ -1963,9 +2023,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?rev=4cacaf215b334d447faca8f4e361fe19c30d2162#4cacaf215b334d447faca8f4e361fe19c30d2162"
dependencies = [
"either",
"futures",
@ -1981,9 +2040,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?rev=4cacaf215b334d447faca8f4e361fe19c30d2162#4cacaf215b334d447faca8f4e361fe19c30d2162"
dependencies = [
"futures",
"libp2p-core",
@ -1994,20 +2052,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"
@ -2160,9 +2250,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]]
@ -2282,6 +2372,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"
@ -2303,9 +2411,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",
]
@ -2317,9 +2425,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?rev=4cacaf215b334d447faca8f4e361fe19c30d2162#4cacaf215b334d447faca8f4e361fe19c30d2162"
dependencies = [
"bytes 1.0.1",
"futures",
@ -2472,24 +2579,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"
@ -2605,9 +2694,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]]
@ -2616,9 +2705,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]]
@ -2719,9 +2808,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",
]
@ -2731,7 +2820,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",
]
@ -2759,9 +2848,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",
]
@ -2822,9 +2911,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]]
@ -2885,7 +2974,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]]
@ -3502,9 +3591,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]]
@ -3530,6 +3619,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"
@ -3547,9 +3659,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]]
@ -3791,11 +3903,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]]
@ -3805,13 +3917,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]]
@ -3826,6 +3938,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.21"
@ -3845,9 +3963,9 @@ checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90"
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]]
@ -3866,9 +3984,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]]
@ -3935,6 +4053,7 @@ dependencies = [
"serde",
"serde_cbor",
"serde_json",
"serde_with",
"sha2 0.9.5",
"sigma_fun",
"sled",
@ -3975,11 +4094,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",
]
@ -3990,9 +4109,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",
]
@ -4086,9 +4205,9 @@ version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d"
dependencies = [
"proc-macro2 1.0.24",
"proc-macro2 1.0.27",
"quote 1.0.9",
"syn 1.0.64",
"syn 1.0.73",
]
[[package]]
@ -4142,10 +4261,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]]
@ -4198,9 +4317,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]]
@ -4345,9 +4464,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]]
@ -4730,9 +4849,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",
]
@ -4764,9 +4883,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",
]
@ -4957,9 +5076,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",
]

@ -0,0 +1,31 @@
#!/bin/bash
CLI_PATH=$1
YOUR_MONERO_ADDR=$2
RENDEZVOUS_ADDR="/dnsaddr/rendezvous.coblox.tech"
RENDEZVOUS_PEER_ID="12D3KooWQUt9DkNZxEn2R5ymJzWj15MpG6mTW84kyd8vDaRZi46o"
# Since we always print json on stdout for `list-sellers` we don't need the `--json` flag
CLI_LIST_SELLERS="$CLI_PATH --testnet --debug list-sellers --rendezvous-node-peer-id $RENDEZVOUS_PEER_ID --rendezvous-node-addr $RENDEZVOUS_ADDR"
echo "Requesting sellers with command: $CLI_LIST_SELLERS"
echo
BEST_SELLER_ARR=$($CLI_LIST_SELLERS | jq -s -c 'sort_by(.quote .price)[]' | jq -r '.multiaddr, (.quote .price), (.quote .min_quantity), (.quote .max_quantity)')
read -a BEST_SELLER < <(echo $BEST_SELLER_ARR)
echo
echo "Seller with best price:"
echo " multiaddr : ${BEST_SELLER[0]}"
echo " price : ${BEST_SELLER[1]} sat"
echo " min_quantity: ${BEST_SELLER[2]} sat"
echo " max_quantity: ${BEST_SELLER[3]} sat"
echo
CLI_SWAP="$CLI_PATH --testnet --debug buy-xmr --receive-address $YOUR_MONERO_ADDR --seller-addr ${BEST_SELLER[0]}"
echo "Starting swap with best seller using command $CLI_SWAP"
echo
$CLI_SWAP

@ -29,7 +29,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", rev = "4cacaf215b334d447faca8f4e361fe19c30d2162", 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"

@ -6,9 +6,11 @@ use crate::network::swap_setup::alice::WalletSnapshot;
use crate::network::{encrypted_signature, quote, transfer_proof};
use crate::protocol::alice::State3;
use anyhow::{anyhow, Error};
use libp2p::identity::Keypair;
use libp2p::ping::{Ping, PingEvent};
use libp2p::rendezvous::{Event, Namespace, RegisterError};
use libp2p::request_response::{RequestId, ResponseChannel};
use libp2p::{NetworkBehaviour, PeerId};
use libp2p::{rendezvous, NetworkBehaviour, PeerId};
use uuid::Uuid;
#[derive(Debug)]
@ -42,6 +44,12 @@ pub enum OutEvent {
peer: PeerId,
error: Error,
},
Registered {
rendezvous_node: PeerId,
ttl: u64,
namespace: Namespace,
},
RegisterFailed(RegisterError),
/// "Fallback" variant that allows the event mapping code to swallow certain
/// events that we don't want the caller to deal with.
Other,
@ -71,6 +79,7 @@ pub struct Behaviour<LR>
where
LR: LatestRate + Send + 'static,
{
pub rendezvous: rendezvous::Rendezvous,
pub quote: quote::Behaviour,
pub swap_setup: alice::Behaviour<LR>,
pub transfer_proof: transfer_proof::Behaviour,
@ -92,8 +101,10 @@ where
latest_rate: LR,
resume_only: bool,
env_config: env::Config,
keypair: Keypair,
) -> Self {
Self {
rendezvous: rendezvous::Rendezvous::new(keypair, rendezvous::Config::default()),
quote: quote::asb(),
swap_setup: alice::Behaviour::new(
min_buy,
@ -114,3 +125,28 @@ impl From<PingEvent> for OutEvent {
OutEvent::Other
}
}
impl From<rendezvous::Event> for OutEvent {
fn from(event: rendezvous::Event) -> Self {
match event {
Event::Discovered { .. } => unreachable!("The ASB does not discover other nodes"),
Event::DiscoverFailed { .. } => unreachable!("The ASB does not discover other nodes"),
Event::Registered {
rendezvous_node,
ttl,
namespace,
} => OutEvent::Registered {
rendezvous_node,
ttl,
namespace,
},
Event::RegisterFailed(error) => OutEvent::RegisterFailed(error),
Event::DiscoverServed { .. } => unreachable!("ASB does not act as rendezvous node"),
Event::DiscoverNotServed { .. } => unreachable!("ASB does not act as rendezvous node"),
Event::PeerRegistered { .. } => unreachable!("ASB does not act as rendezvous node"),
Event::PeerNotRegistered { .. } => unreachable!("ASB does not act as rendezvous node"),
Event::PeerUnregistered { .. } => unreachable!("ASB does not act as rendezvous node"),
Event::RegistrationExpired(_) => unreachable!("ASB does not act as rendezvous node"),
}
}
}

@ -4,6 +4,7 @@ use crate::env;
use crate::env::GetConfig;
use anyhow::{bail, Result};
use bitcoin::Address;
use libp2p::Multiaddr;
use serde::Serialize;
use std::ffi::OsString;
use std::path::PathBuf;
@ -24,12 +25,18 @@ where
let command: RawCommand = args.cmd;
let arguments = match command {
RawCommand::Start { resume_only } => Arguments {
RawCommand::Start {
resume_only,
external_addr,
} => Arguments {
testnet: is_testnet,
json: is_json,
config_path: config_path(config, is_testnet)?,
env_config: env_config(is_testnet),
cmd: Command::Start { resume_only },
cmd: Command::Start {
resume_only,
external_addr,
},
},
RawCommand::History => Arguments {
testnet: is_testnet,
@ -167,6 +174,7 @@ pub struct Arguments {
pub enum Command {
Start {
resume_only: bool,
external_addr: Option<Multiaddr>,
},
History,
WithdrawBtc {
@ -234,6 +242,11 @@ pub enum RawCommand {
help = "For maintenance only. When set, no new swap requests will be accepted, but existing unfinished swaps will be resumed."
)]
resume_only: bool,
#[structopt(
long = "external-addr",
help = "Our external address to be used when registering our service with a rendezvous node"
)]
external_addr: Option<Multiaddr>,
},
#[structopt(about = "Prints swap-id and the state of each swap ever made.")]
History,
@ -338,7 +351,10 @@ mod tests {
json: false,
config_path: default_mainnet_conf_path.clone(),
env_config: mainnet_env_config,
cmd: Command::Start { resume_only: false },
cmd: Command::Start {
resume_only: false,
external_addr: None,
},
};
let args = parse_args(raw_ars).unwrap();
assert_eq!(expected_args, args);
@ -475,7 +491,10 @@ mod tests {
json: false,
config_path: default_testnet_conf_path.clone(),
env_config: testnet_env_config,
cmd: Command::Start { resume_only: false },
cmd: Command::Start {
resume_only: false,
external_addr: None,
},
};
let args = parse_args(raw_ars).unwrap();
assert_eq!(expected_args, args);

@ -6,9 +6,11 @@ use config::ConfigError;
use dialoguer::theme::ColorfulTheme;
use dialoguer::Input;
use libp2p::core::Multiaddr;
use libp2p::PeerId;
use rust_decimal::prelude::FromPrimitive;
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, DisplayFromStr};
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
@ -81,11 +83,19 @@ fn default_asb_data_dir() -> Result<PathBuf> {
.context("Could not generate default config file path")
}
// TODO: update this to the actual deployed rendezvous server
// Currently set to Staging ASB on raspi
const DEFAULT_RENDEZVOUS_PEER_ID: &str = "12D3KooWPZ69DRp4wbGB3wJsxxsg1XW1EVZ2evtVwcARCF3a1nrx";
// TODO: update this to the actual deployed rendezvous server
// Port still to be opened once running rendezvous node
const DEFAULT_RENDEZVOUS_ADDR: &str = "/ip4/141.168.172.35/tcp/7654";
const DEFAULT_MIN_BUY_AMOUNT: f64 = 0.002f64;
const DEFAULT_MAX_BUY_AMOUNT: f64 = 0.02f64;
const DEFAULT_SPREAD: f64 = 0.02f64;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct Config {
pub data: Data,
pub network: Network,
@ -93,6 +103,7 @@ pub struct Config {
pub monero: Monero,
pub tor: TorConf,
pub maker: Maker,
pub rendezvous_node: Rendezvous,
}
impl Config {
@ -157,6 +168,14 @@ pub struct Maker {
pub price_ticker_ws_url: Url,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct Rendezvous {
pub addr: Multiaddr,
#[serde_as(as = "DisplayFromStr")]
pub peer_id: PeerId,
}
impl Default for TorConf {
fn default() -> Self {
Self {
@ -285,6 +304,18 @@ pub fn query_user_for_initial_config(testnet: bool) -> Result<Config> {
}
let ask_spread = Decimal::from_f64(ask_spread).context("Unable to parse spread")?;
let rendezvous_peer_id_str = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Enter the peer id of the rendezvous node you wish to register with")
.default(DEFAULT_RENDEZVOUS_PEER_ID.to_string())
.interact_text()?;
let rendezvous_peer_id = PeerId::from_str(rendezvous_peer_id_str.as_str())?;
let rendezvous_addr_str = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Enter the multiaddress of the rendezvous node you wish to register with")
.default(DEFAULT_RENDEZVOUS_ADDR.to_string())
.interact_text()?;
let rendezvous_addr = Multiaddr::from_str(rendezvous_addr_str.as_str())?;
println!();
Ok(Config {
@ -313,6 +344,10 @@ pub fn query_user_for_initial_config(testnet: bool) -> Result<Config> {
ask_spread,
price_ticker_ws_url: defaults.price_ticker_ws_url,
},
rendezvous_node: Rendezvous {
addr: rendezvous_addr,
peer_id: rendezvous_peer_id,
},
})
}
@ -354,6 +389,10 @@ mod tests {
ask_spread: Decimal::from_f64(DEFAULT_SPREAD).unwrap(),
price_ticker_ws_url: defaults.price_ticker_ws_url,
},
rendezvous_node: Rendezvous {
addr: DEFAULT_RENDEZVOUS_ADDR.parse().unwrap(),
peer_id: PeerId::from_str(DEFAULT_RENDEZVOUS_PEER_ID).unwrap(),
},
};
initial_setup(config_path.clone(), expected.clone()).unwrap();
@ -395,6 +434,10 @@ mod tests {
ask_spread: Decimal::from_f64(DEFAULT_SPREAD).unwrap(),
price_ticker_ws_url: defaults.price_ticker_ws_url,
},
rendezvous_node: Rendezvous {
addr: DEFAULT_RENDEZVOUS_ADDR.parse().unwrap(),
peer_id: PeerId::from_str(DEFAULT_RENDEZVOUS_PEER_ID).unwrap(),
},
};
initial_setup(config_path.clone(), expected.clone()).unwrap();

@ -1,24 +1,26 @@
use crate::asb::behaviour::{Behaviour, OutEvent};
use crate::asb::Rate;
use crate::database::Database;
use crate::env::Config;
use crate::network::quote::BidQuote;
use crate::network::swap_setup::alice::WalletSnapshot;
use crate::network::transfer_proof;
use crate::protocol::alice::{AliceState, State3, Swap};
use crate::{bitcoin, kraken, monero};
use crate::rendezvous::XmrBtcNamespace;
use crate::{bitcoin, env, kraken, monero};
use anyhow::{Context, Result};
use futures::future;
use futures::future::{BoxFuture, FutureExt};
use futures::stream::{FuturesUnordered, StreamExt};
use libp2p::rendezvous::Namespace;
use libp2p::request_response::{RequestId, ResponseChannel};
use libp2p::swarm::SwarmEvent;
use libp2p::{PeerId, Swarm};
use libp2p::{Multiaddr, PeerId, Swarm};
use rust_decimal::Decimal;
use std::collections::HashMap;
use std::convert::Infallible;
use std::fmt::Debug;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::mpsc;
use uuid::Uuid;
@ -38,7 +40,7 @@ where
LR: LatestRate + Send + 'static + Debug + Clone,
{
swarm: libp2p::Swarm<Behaviour<LR>>,
env_config: Config,
env_config: env::Config,
bitcoin_wallet: Arc<bitcoin::Wallet>,
monero_wallet: Arc<monero::Wallet>,
db: Arc<Database>,
@ -61,6 +63,14 @@ where
/// Tracks [`transfer_proof::Request`]s which are currently inflight and
/// awaiting an acknowledgement.
inflight_transfer_proofs: HashMap<RequestId, bmrng::Responder<()>>,
// TODO: Potentially group together, potentially the whole rendezvous handling could go into a
// separate swarm? There is no dependency between swap and registration, so we could just
// build this completely separate.
rendezvous_node_peer_id: PeerId,
rendezvous_node_addr: Multiaddr,
rendezvous_namespace: XmrBtcNamespace,
rendezvous_reregister_timestamp: Option<Instant>,
}
impl<LR> EventLoop<LR>
@ -70,13 +80,16 @@ where
#[allow(clippy::too_many_arguments)]
pub fn new(
swarm: Swarm<Behaviour<LR>>,
env_config: Config,
env_config: env::Config,
bitcoin_wallet: Arc<bitcoin::Wallet>,
monero_wallet: Arc<monero::Wallet>,
db: Arc<Database>,
latest_rate: LR,
min_buy: bitcoin::Amount,
max_buy: bitcoin::Amount,
rendezvous_node_peer_id: PeerId,
rendezvous_node_addr: Multiaddr,
rendezvous_namespace: XmrBtcNamespace,
) -> Result<(Self, mpsc::Receiver<Swap>)> {
let swap_channel = MpscChannels::default();
@ -95,6 +108,10 @@ where
send_transfer_proof: Default::default(),
buffered_transfer_proofs: Default::default(),
inflight_transfer_proofs: Default::default(),
rendezvous_node_peer_id,
rendezvous_node_addr,
rendezvous_namespace,
rendezvous_reregister_timestamp: None,
};
Ok((event_loop, swap_channel.receiver))
}
@ -148,10 +165,34 @@ where
}
loop {
// rendezvous node re-registration
if let Some(rendezvous_reregister_timestamp) = self.rendezvous_reregister_timestamp {
if Instant::now() > rendezvous_reregister_timestamp {
if self.swarm.is_connected(&self.rendezvous_node_peer_id) {
self.swarm.behaviour_mut().rendezvous.register(
Namespace::new(self.rendezvous_namespace.to_string())
.expect("our namespace to be a correct string"),
self.rendezvous_node_peer_id,
None,
);
} else {
match Swarm::dial_addr(&mut self.swarm, self.rendezvous_node_addr.clone()) {
Ok(()) => {}
Err(error) => {
tracing::error!(
"Failed to redial rendezvous node for re-registration: {:#}",
error
);
}
}
}
}
}
tokio::select! {
swarm_event = self.swarm.next_event() => {
swarm_event = self.swarm.next() => {
match swarm_event {
SwarmEvent::Behaviour(OutEvent::SwapSetupInitiated { mut send_wallet_snapshot }) => {
Some(SwarmEvent::Behaviour(OutEvent::SwapSetupInitiated { mut send_wallet_snapshot })) => {
let (btc, responder) = match send_wallet_snapshot.recv().await {
Ok((btc, responder)) => (btc, responder),
@ -172,13 +213,13 @@ where
// Ignore result, we should never hit this because the receiver will alive as long as the connection is.
let _ = responder.respond(wallet_snapshot);
}
SwarmEvent::Behaviour(OutEvent::SwapSetupCompleted{peer_id, swap_id, state3}) => {
Some(SwarmEvent::Behaviour(OutEvent::SwapSetupCompleted{peer_id, swap_id, state3})) => {
let _ = self.handle_execution_setup_done(peer_id, swap_id, *state3).await;
}
SwarmEvent::Behaviour(OutEvent::SwapDeclined { peer, error }) => {
Some(SwarmEvent::Behaviour(OutEvent::SwapDeclined { peer, error })) => {
tracing::warn!(%peer, "Ignoring spot price request because: {}", error);
}
SwarmEvent::Behaviour(OutEvent::QuoteRequested { channel, peer }) => {
Some(SwarmEvent::Behaviour(OutEvent::QuoteRequested { channel, peer })) => {
let quote = match self.make_quote(self.min_buy, self.max_buy).await {
Ok(quote) => quote,
Err(error) => {
@ -191,13 +232,13 @@ where
tracing::debug!(%peer, "Failed to respond with quote");
}
}
SwarmEvent::Behaviour(OutEvent::TransferProofAcknowledged { peer, id }) => {
Some(SwarmEvent::Behaviour(OutEvent::TransferProofAcknowledged { peer, id })) => {
tracing::debug!(%peer, "Bob acknowledged transfer proof");
if let Some(responder) = self.inflight_transfer_proofs.remove(&id) {
let _ = responder.respond(());
}
}
SwarmEvent::Behaviour(OutEvent::EncryptedSignatureReceived{ msg, channel, peer }) => {
Some(SwarmEvent::Behaviour(OutEvent::EncryptedSignatureReceived{ msg, channel, peer })) => {
let swap_id = msg.swap_id;
let swap_peer = self.db.get_peer_id(swap_id);
@ -246,12 +287,33 @@ where
channel
}.boxed());
}
SwarmEvent::Behaviour(OutEvent::Failure {peer, error}) => {
Some(SwarmEvent::Behaviour(OutEvent::Registered { rendezvous_node, ttl, namespace })) => {
// TODO: this can most likely not happen at all, potentially remove these checks
if rendezvous_node != self.rendezvous_node_peer_id {
tracing::error!(peer_id=%rendezvous_node, "Ignoring message from unknown rendezvous node");
continue;
}
// TODO: Consider implementing From for Namespace and XmrBtcNamespace
if namespace.to_string() != self.rendezvous_namespace.to_string() {
tracing::error!(peer_id=%rendezvous_node, %namespace, "Ignoring message from rendezvous node for unknown namespace");
continue;
}
tracing::info!("Successfully registered with rendezvous node");
// record re-registration after half the ttl has expired
self.rendezvous_reregister_timestamp = Some(Instant::now() + Duration::from_secs(ttl) / 2);
}
Some(SwarmEvent::Behaviour(OutEvent::RegisterFailed(error))) => {
tracing::error!(rendezvous_node=%self.rendezvous_node_peer_id, "Registration with rendezvous node failed: {:#}", error);
}
Some(SwarmEvent::Behaviour(OutEvent::Failure {peer, error})) => {
tracing::error!(
%peer,
"Communication error. Error {:#}", error);
}
SwarmEvent::ConnectionEstablished { peer_id: peer, endpoint, .. } => {
Some(SwarmEvent::ConnectionEstablished { peer_id: peer, endpoint, .. }) => {
tracing::debug!(%peer, address = %endpoint.get_remote_address(), "New connection established");
if let Some(transfer_proofs) = self.buffered_transfer_proofs.remove(&peer) {
@ -262,17 +324,25 @@ where
self.inflight_transfer_proofs.insert(id, responder);
}
}
if peer == self.rendezvous_node_peer_id {
self
.swarm
.behaviour_mut()
.rendezvous
.register(Namespace::new(self.rendezvous_namespace.to_string()).expect("our namespace to be a correct string"), self.rendezvous_node_peer_id, None);
}
}
SwarmEvent::IncomingConnectionError { send_back_addr: address, error, .. } => {
Some(SwarmEvent::IncomingConnectionError { send_back_addr: address, error, .. }) => {
tracing::warn!(%address, "Failed to set up connection with peer. Error {:#}", error);
}
SwarmEvent::ConnectionClosed { peer_id: peer, num_established, endpoint, cause: Some(error) } if num_established == 0 => {
Some(SwarmEvent::ConnectionClosed { peer_id: peer, num_established, endpoint, cause: Some(error) }) if num_established == 0 => {
tracing::warn!(%peer, address = %endpoint.get_remote_address(), "Lost connection. Error {:#}", error);
}
SwarmEvent::ConnectionClosed { peer_id: peer, num_established, endpoint, cause: None } if num_established == 0 => {
Some(SwarmEvent::ConnectionClosed { peer_id: peer, num_established, endpoint, cause: None }) if num_established == 0 => {
tracing::info!(%peer, address = %endpoint.get_remote_address(), "Successfully closed connection");
}
SwarmEvent::NewListenAddr(address) => {
Some(SwarmEvent::NewListenAddr(address)) => {
tracing::info!(%address, "New listen address detected");
}
_ => {}

@ -15,6 +15,7 @@
use anyhow::{bail, Context, Result};
use libp2p::core::multiaddr::Protocol;
use libp2p::core::Multiaddr;
use libp2p::swarm::AddressScore;
use libp2p::Swarm;
use prettytable::{row, Table};
use std::env;
@ -31,6 +32,7 @@ use swap::database::Database;
use swap::monero::Amount;
use swap::network::swarm;
use swap::protocol::alice::run;
use swap::rendezvous::XmrBtcNamespace;
use swap::seed::Seed;
use swap::tor::AuthenticatedClient;
use swap::{asb, bitcoin, kraken, monero, tor};
@ -105,7 +107,10 @@ async fn main() -> Result<()> {
Seed::from_file_or_generate(&config.data.dir).expect("Could not retrieve/initialize seed");
match cmd {
Command::Start { resume_only } => {
Command::Start {
resume_only,
external_addr,
} => {
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
let monero_wallet = init_monero_wallet(&config, env_config).await?;
@ -124,7 +129,7 @@ async fn main() -> Result<()> {
info!(%monero_balance, "Initialized Monero wallet");
}
let kraken_price_updates = kraken::connect(config.maker.price_ticker_ws_url)?;
let kraken_price_updates = kraken::connect(config.maker.price_ticker_ws_url.clone())?;
// setup Tor hidden services
let tor_client =
@ -153,13 +158,33 @@ async fn main() -> Result<()> {
env_config,
)?;
for listen in config.network.listen {
for listen in config.network.listen.clone() {
Swarm::listen_on(&mut swarm, listen.clone())
.with_context(|| format!("Failed to listen on network interface {}", listen))?;
}
tracing::info!(peer_id = %swarm.local_peer_id(), "Network layer initialized");
// todo: Option<Multiaddr> is being used as a rendezvous feature toggle.
// The fact that rendezvous is an optional feature could be expressed better.
if let Some(addr) = external_addr {
let _ = Swarm::add_external_address(&mut swarm, addr, AddressScore::Infinite);
Swarm::dial_addr(&mut swarm, config.rendezvous_node.addr.clone()).with_context(
|| {
format!(
"Failed to dial rendezvous node addr {}",
config.rendezvous_node.addr
)
},
)?;
}
let namespace = if testnet {
XmrBtcNamespace::Testnet
} else {
XmrBtcNamespace::Mainnet
};
let (event_loop, mut swap_receiver) = EventLoop::new(
swarm,
env_config,
@ -169,6 +194,9 @@ async fn main() -> Result<()> {
kraken_rate.clone(),
config.maker.min_buy_btc,
config.maker.max_buy_btc,
config.rendezvous_node.peer_id,
config.rendezvous_node.addr,
namespace,
)
.unwrap();

@ -13,6 +13,8 @@
#![allow(non_snake_case)]
use anyhow::{bail, Context, Result};
use libp2p::core::multiaddr::Protocol;
use libp2p::PeerId;
use prettytable::{row, Table};
use qrcode::render::unicode;
use qrcode::QrCode;
@ -24,7 +26,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::network::quote::BidQuote;
@ -58,7 +60,6 @@ async fn main() -> Result<()> {
match cmd {
Command::BuyXmr {
seller_peer_id,
seller_addr,
bitcoin_electrum_rpc_url,
bitcoin_target_block,
@ -68,7 +69,24 @@ async fn main() -> Result<()> {
} => {
let swap_id = Uuid::new_v4();
cli::tracing::init(debug, json, data_dir.join("logs"), swap_id)?;
let seller_peer_id = match seller_addr.iter().last() {
None => {
bail!("Invalid multiaddress");
}
Some(protocol) => {
match protocol {
Protocol::P2p(hash) => {
match PeerId::from_multihash(hash) {
Ok(peer_id) => peer_id,
Err(_) => bail!("Multiaddress contains invalid peer-id")
}
}
_ => bail!("The given multiaddress does not end with the peer-id (i.e. /p2p/{peer-id}), make sure you provide the peer-id of the seller as part of the address.")
}
}
};
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())
@ -86,14 +104,9 @@ async fn main() -> Result<()> {
init_monero_wallet(data_dir, monero_daemon_address, env_config).await?;
let bitcoin_wallet = Arc::new(bitcoin_wallet);
let mut swarm = swarm::cli(
&seed,
seller_peer_id,
tor_socks5_port,
env_config,
bitcoin_wallet.clone(),
)
.await?;
let behaviour = cli::Behaviour::new(seller_peer_id, env_config, bitcoin_wallet.clone());
let mut swarm =
swarm::cli(seed.derive_libp2p_identity(), tor_socks5_port, behaviour).await?;
swarm
.behaviour_mut()
.add_address(seller_peer_id, seller_addr);
@ -164,7 +177,11 @@ async fn main() -> Result<()> {
monero_daemon_address,
tor_socks5_port,
} => {
cli::tracing::init(debug, json, data_dir.join("logs"), swap_id)?;
// TODO: Save complete seller address into database - the user might not know
// the seller that is traded with We assume that the seller address
// does not change.
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())
@ -188,14 +205,9 @@ async fn main() -> Result<()> {
let seller_peer_id = db.get_peer_id(swap_id)?;
let mut swarm = swarm::cli(
&seed,
seller_peer_id,
tor_socks5_port,
env_config,
bitcoin_wallet.clone(),
)
.await?;
let behaviour = cli::Behaviour::new(seller_peer_id, env_config, bitcoin_wallet.clone());
let mut swarm =
swarm::cli(seed.derive_libp2p_identity(), tor_socks5_port, behaviour).await?;
let our_peer_id = swarm.local_peer_id();
tracing::debug!(peer_id = %our_peer_id, "Initializing network module");
swarm
@ -231,7 +243,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())
@ -263,7 +275,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())
@ -280,6 +292,30 @@ async fn main() -> Result<()> {
cli::refund(swap_id, Arc::new(bitcoin_wallet), db, force).await??;
}
Command::ListSellers {
rendezvous_node_peer_id,
rendezvous_node_addr,
namespace,
tor_socks5_port,
} => {
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?;
for seller in sellers {
println!("{}", serde_json::to_string(&seller)?);
}
}
};
Ok(())
}

@ -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;

@ -1,5 +1,6 @@
use crate::env::GetConfig;
use crate::fs::system_data_dir;
use crate::rendezvous::XmrBtcNamespace;
use crate::{env, monero};
use anyhow::{Context, Result};
use libp2p::core::Multiaddr;
@ -68,7 +69,6 @@ where
let arguments = match args.cmd {
RawCommand::BuyXmr {
seller_peer_id,
seller_addr: SellerAddr { seller_addr },
bitcoin:
Bitcoin {
@ -87,7 +87,6 @@ where
json,
data_dir: data::data_dir_from(data, is_testnet)?,
cmd: Command::BuyXmr {
seller_peer_id,
seller_addr,
bitcoin_electrum_rpc_url: bitcoin_electrum_rpc_url_from(
bitcoin_electrum_rpc_url,
@ -193,6 +192,22 @@ where
bitcoin_target_block: bitcoin_target_block_from(bitcoin_target_block, is_testnet),
},
},
RawCommand::ListSellers {
rendezvous_node_peer_id,
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_peer_id,
rendezvous_node_addr,
namespace: rendezvous_namespace_from(is_testnet),
tor_socks5_port,
},
},
};
Ok(ParseResult::Arguments(arguments))
@ -201,7 +216,6 @@ where
#[derive(Debug, PartialEq)]
pub enum Command {
BuyXmr {
seller_peer_id: PeerId,
seller_addr: Multiaddr,
bitcoin_electrum_rpc_url: Url,
bitcoin_target_block: usize,
@ -231,6 +245,12 @@ pub enum Command {
bitcoin_electrum_rpc_url: Url,
bitcoin_target_block: usize,
},
ListSellers {
rendezvous_node_peer_id: PeerId,
rendezvous_node_addr: Multiaddr,
namespace: XmrBtcNamespace,
tor_socks5_port: u16,
},
}
#[derive(structopt::StructOpt, Debug)]
@ -268,9 +288,6 @@ pub struct RawArguments {
pub enum RawCommand {
/// Start a XMR for BTC swap
BuyXmr {
#[structopt(long = "seller-peer-id", help = "The seller's peer id")]
seller_peer_id: PeerId,
#[structopt(flatten)]
seller_addr: SellerAddr,
@ -324,6 +341,24 @@ pub enum RawCommand {
#[structopt(flatten)]
bitcoin: Bitcoin,
},
ListSellers {
// TODO: sane default value
#[structopt(
long,
help = "The peer-id of a rendezvous node that sellers register with"
)]
rendezvous_node_peer_id: PeerId,
// TODO: sane default value
#[structopt(
long,
help = "The multiaddr of a rendezvous node that sellers register with"
)]
rendezvous_node_addr: Multiaddr,
#[structopt(flatten)]
tor: Tor,
},
}
#[derive(structopt::StructOpt, Debug)]
@ -374,7 +409,10 @@ pub struct SwapId {
#[derive(structopt::StructOpt, Debug)]
pub struct SellerAddr {
#[structopt(long = "seller-addr", help = "The seller's multiaddress")]
#[structopt(
long = "seller-addr",
help = "The seller's multiaddress including the peer-id"
)]
pub seller_addr: Multiaddr,
}
@ -416,6 +454,14 @@ fn bitcoin_electrum_rpc_url_from(url: Option<Url>, testnet: bool) -> Result<Url>
}
}
fn rendezvous_namespace_from(is_testnet: bool) -> XmrBtcNamespace {
if is_testnet {
XmrBtcNamespace::Testnet
} else {
XmrBtcNamespace::Mainnet
}
}
fn bitcoin_target_block_from(target_block: Option<usize>, testnet: bool) -> usize {
if let Some(target_block) = target_block {
target_block
@ -938,7 +984,6 @@ mod tests {
json: false,
data_dir: data_dir_path_cli().join(TESTNET),
cmd: Command::BuyXmr {
seller_peer_id: PeerId::from_str(PEER_ID).unwrap(),
seller_addr: Multiaddr::from_str(MUTLI_ADDRESS).unwrap(),
bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL_TESTNET)
.unwrap(),
@ -958,7 +1003,6 @@ mod tests {
json: false,
data_dir: data_dir_path_cli().join(MAINNET),
cmd: Command::BuyXmr {
seller_peer_id: PeerId::from_str(PEER_ID).unwrap(),
seller_addr: Multiaddr::from_str(MUTLI_ADDRESS).unwrap(),
bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(),
bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET,

@ -94,19 +94,19 @@ 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.next().fuse() => {
match swarm_event {
SwarmEvent::Behaviour(OutEvent::QuoteReceived { id, response }) => {
Some(SwarmEvent::Behaviour(OutEvent::QuoteReceived { id, response })) => {
if let Some(responder) = self.inflight_quote_requests.remove(&id) {
let _ = responder.respond(response);
}
}
SwarmEvent::Behaviour(OutEvent::SwapSetupCompleted(response)) => {
Some(SwarmEvent::Behaviour(OutEvent::SwapSetupCompleted(response))) => {
if let Some(responder) = self.inflight_swap_setup.take() {
let _ = responder.respond(*response);
}
}
SwarmEvent::Behaviour(OutEvent::TransferProofReceived { msg, channel, peer }) => {
Some(SwarmEvent::Behaviour(OutEvent::TransferProofReceived { msg, channel, peer })) => {
let swap_id = msg.swap_id;
if peer != self.alice_peer_id {
@ -142,34 +142,34 @@ impl EventLoop {
channel
}.boxed()));
}
SwarmEvent::Behaviour(OutEvent::EncryptedSignatureAcknowledged { id }) => {
Some(SwarmEvent::Behaviour(OutEvent::EncryptedSignatureAcknowledged { id })) => {
if let Some(responder) = self.inflight_encrypted_signature_requests.remove(&id) {
let _ = responder.respond(());
}
}
SwarmEvent::Behaviour(OutEvent::AllRedialAttemptsExhausted { peer }) if peer == self.alice_peer_id => {
Some(SwarmEvent::Behaviour(OutEvent::AllRedialAttemptsExhausted { peer })) if peer == self.alice_peer_id => {
tracing::error!("Exhausted all re-dial attempts to Alice");
return;
}
SwarmEvent::Behaviour(OutEvent::Failure { peer, error }) => {
Some(SwarmEvent::Behaviour(OutEvent::Failure { peer, error })) => {
tracing::warn!(%peer, "Communication error: {:#}", error);
return;
}
SwarmEvent::ConnectionEstablished { peer_id, endpoint, .. } if peer_id == self.alice_peer_id => {
Some(SwarmEvent::ConnectionEstablished { peer_id, endpoint, .. }) if peer_id == self.alice_peer_id => {
tracing::info!("Connected to Alice at {}", endpoint.get_remote_address());
}
SwarmEvent::Dialing(peer_id) if peer_id == self.alice_peer_id => {
Some(SwarmEvent::Dialing(peer_id)) if peer_id == self.alice_peer_id => {
tracing::debug!("Dialling Alice at {}", peer_id);
}
SwarmEvent::ConnectionClosed { peer_id, endpoint, num_established, cause: Some(error) } if peer_id == self.alice_peer_id && num_established == 0 => {
Some(SwarmEvent::ConnectionClosed { peer_id, endpoint, num_established, cause: Some(error) }) if peer_id == self.alice_peer_id && num_established == 0 => {
tracing::warn!("Lost connection to Alice at {}, cause: {}", endpoint.get_remote_address(), error);
}
SwarmEvent::ConnectionClosed { peer_id, num_established, cause: None, .. } if peer_id == self.alice_peer_id && num_established == 0 => {
Some(SwarmEvent::ConnectionClosed { peer_id, num_established, cause: None, .. }) if peer_id == self.alice_peer_id && num_established == 0 => {
// no error means the disconnection was requested
tracing::info!("Successfully closed connection to Alice");
return;
}
SwarmEvent::UnreachableAddr { peer_id, address, attempts_remaining, error } if peer_id == self.alice_peer_id && attempts_remaining == 0 => {
Some(SwarmEvent::UnreachableAddr { peer_id, address, attempts_remaining, error }) if peer_id == self.alice_peer_id && attempts_remaining == 0 => {
tracing::warn!(%address, "Failed to dial Alice: {}", error);
if let Some(duration) = self.swarm.behaviour_mut().redial.until_next_redial() {

@ -0,0 +1,277 @@
use crate::network::quote::BidQuote;
use crate::network::{quote, swarm};
use crate::rendezvous::XmrBtcNamespace;
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<Vec<Seller>> {
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 peer_id: PeerId,
pub multiaddr: Multiaddr,
pub quote: BidQuote,
}
#[derive(Debug)]
pub enum OutEvent {
Rendezvous(rendezvous::Event),
Quote(quote::OutEvent),
Ping(PingEvent),
}
impl From<rendezvous::Event> for OutEvent {
fn from(event: rendezvous::Event) -> Self {
OutEvent::Rendezvous(event)
}
}
impl From<quote::OutEvent> for OutEvent {
fn from(event: quote::OutEvent) -> Self {
OutEvent::Quote(event)
}
}
#[derive(libp2p::NetworkBehaviour)]
#[behaviour(event_process = false)]
#[behaviour(out_event = "OutEvent")]
pub struct Behaviour {
pub rendezvous: Rendezvous,
pub quote: quote::Behaviour,
pub ping: Ping,
}
#[derive(Debug)]
enum QuoteStatus {
Pending,
Received(BidQuote),
}
enum State {
WaitForDiscovery,
WaitForQuoteCompletion,
}
pub struct EventLoop {
swarm: Swarm<Behaviour>,
rendezvous_peer_id: PeerId,
rendezvous_addr: Multiaddr,
namespace: XmrBtcNamespace,
asb_address: HashMap<PeerId, Multiaddr>,
asb_quote_status: HashMap<PeerId, QuoteStatus>,
state: State,
}
impl EventLoop {
pub fn new(
swarm: Swarm<Behaviour>,
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,
}
}
pub async fn run(mut self) -> Vec<Seller> {
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_some() {
tracing::debug!(%peer, "Received bid quote {:?} from peer {}", response, peer);
} else {
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 {
peer_id: *peer_id,
multiaddr: address.clone(),
quote: *quote,
})
}
})
.collect::<Result<Vec<_>, _>>();
match all_quotes_fetched {
Ok(sellers) => break sellers,
Err(StillPending {}) => continue,
}
}
}
}
}
}
struct StillPending {}
impl From<PingEvent> for OutEvent {
fn from(event: PingEvent) -> Self {
OutEvent::Ping(event)
}
}

@ -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<Path>, swap_id: Uuid) -> Result<()> {
pub fn init(debug: bool, json: bool, dir: impl AsRef<Path>, swap_id: Option<Uuid>) -> 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<Path>, 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<Path>, 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(())
}
}

@ -26,6 +26,7 @@ pub mod kraken;
pub mod monero;
pub mod network;
pub mod protocol;
pub mod rendezvous;
pub mod seed;
pub mod tor;

@ -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<JsonPullCodec<BidQuoteProtocol, BidQuote>>;
@ -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")]

@ -2,10 +2,9 @@ use crate::asb::LatestRate;
use crate::seed::Seed;
use crate::{asb, bitcoin, cli, env, tor};
use anyhow::Result;
use libp2p::swarm::SwarmBuilder;
use libp2p::{PeerId, Swarm};
use libp2p::swarm::{NetworkBehaviour, SwarmBuilder};
use libp2p::{identity, Swarm};
use std::fmt::Debug;
use std::sync::Arc;
#[allow(clippy::too_many_arguments)]
pub fn asb<LR>(
@ -19,9 +18,17 @@ pub fn asb<LR>(
where
LR: LatestRate + Send + 'static + Debug + Clone,
{
let behaviour = asb::Behaviour::new(min_buy, max_buy, latest_rate, resume_only, env_config);
let identity = seed.derive_libp2p_identity();
let behaviour = asb::Behaviour::new(
min_buy,
max_buy,
latest_rate,
resume_only,
env_config,
identity.clone(),
);
let transport = asb::transport::new(&identity)?;
let peer_id = identity.public().into_peer_id();
@ -34,21 +41,19 @@ where
Ok(swarm)
}
pub async fn cli(
seed: &Seed,
alice: PeerId,
pub async fn cli<T>(
identity: identity::Keypair,
tor_socks5_port: u16,
env_config: env::Config,
bitcoin_wallet: Arc<bitcoin::Wallet>,
) -> Result<Swarm<cli::Behaviour>> {
behaviour: T,
) -> Result<Swarm<T>>
where
T: NetworkBehaviour,
{
let maybe_tor_socks5_port = match tor::Client::new(tor_socks5_port).assert_tor_running().await {
Ok(()) => Some(tor_socks5_port),
Err(_) => None,
};
let behaviour = cli::Behaviour::new(alice, env_config, bitcoin_wallet);
let identity = seed.derive_libp2p_identity();
let transport = cli::transport::new(&identity, maybe_tor_socks5_port)?;
let peer_id = identity.public().into_peer_id();

@ -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<B: NetworkBehaviour> {
pub swarm: Swarm<B>,
pub addr: Multiaddr,
pub peer_id: PeerId,
}
pub async fn new_connected_swarm_pair<B, F>(behaviour_fn: F) -> (Actor<B>, Actor<B>)
pub fn new_swarm<B, F>(behaviour_fn: F) -> Swarm<B>
where
B: NetworkBehaviour,
F: Fn(PeerId, identity::Keypair) -> B + Clone,
<<<B as NetworkBehaviour>::ProtocolsHandler as IntoProtocolsHandler>::Handler as ProtocolsHandler>::InEvent: Clone,
<B as NetworkBehaviour>::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: NetworkBehaviour, F: Fn(PeerId, identity::Keypair) -> B>(
behaviour_fn: F,
) -> (Swarm<B>, Multiaddr, PeerId)
where
<B as NetworkBehaviour>::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::<X25519Spec>::new()
.into_authentic(&id_keys)
let dh_keys = Keypair::<X25519Spec>::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<B> = 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::<u64>();
let addr = format!("/memory/{}", address_port)
.parse::<Multiaddr>()
.unwrap();
Swarm::listen_on(&mut swarm, addr.clone()).unwrap();
(swarm, addr, peer_id)
addr
}
pub async fn await_events_or_timeout<A, B>(
alice_event: impl Future<Output = A>,
bob_event: impl Future<Output = B>,
) -> (A, B) {
time::timeout(
Duration::from_secs(10),
future::join(alice_event, bob_event),
pub async fn await_events_or_timeout<A, B, E1, E2>(
swarm_1: &mut (impl Stream<Item = SwarmEvent<A, E1>> + FusedStream + Unpin),
swarm_2: &mut (impl Stream<Item = SwarmEvent<B, E2>> + FusedStream + Unpin),
) -> (SwarmEvent<A, E1>, SwarmEvent<B, E2>)
where
SwarmEvent<A, E1>: Debug,
SwarmEvent<B, E2>: 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<BA, BB>(alice: &mut Swarm<BA>, bob: &mut Swarm<BB>)
/// 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<T>(&mut self, other: &mut Swarm<T>)
where
T: NetworkBehaviour,
<T as 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<B> SwarmExt for Swarm<B>
where
BA: NetworkBehaviour,
BB: NetworkBehaviour,
<BA as NetworkBehaviour>::OutEvent: Debug,
<BB as NetworkBehaviour>::OutEvent: Debug,
B: NetworkBehaviour,
<B as 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<T>(&mut self, other: &mut Swarm<T>)
where
T: NetworkBehaviour,
<T as 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
}
}

@ -0,0 +1,16 @@
use std::fmt::{Display, Formatter};
#[derive(Debug, PartialEq)]
pub enum XmrBtcNamespace {
Mainnet,
Testnet,
}
impl Display for XmrBtcNamespace {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
XmrBtcNamespace::Mainnet => write!(f, "xmr-btc-swap-mainnet"),
XmrBtcNamespace::Testnet => write!(f, "xmr-btc-swap-testnet"),
}
}
}

@ -22,6 +22,7 @@ use swap::network::swarm;
use swap::protocol::alice::{AliceState, Swap};
use swap::protocol::bob::BobState;
use swap::protocol::{alice, bob};
use swap::rendezvous::XmrBtcNamespace;
use swap::seed::Seed;
use swap::{asb, bitcoin, cli, env, monero};
use tempfile::tempdir;
@ -249,6 +250,9 @@ async fn start_alice(
FixedRate::default(),
min_buy,
max_buy,
PeerId::random(),
Multiaddr::empty(),
XmrBtcNamespace::Testnet,
)
.unwrap();
@ -445,12 +449,16 @@ impl BobParams {
) -> Result<(cli::EventLoop, cli::EventLoopHandle)> {
let tor_socks5_port = get_port()
.expect("We don't care about Tor in the tests so we get a free port to disable it.");
let mut swarm = swarm::cli(
&self.seed,
let behaviour = cli::Behaviour::new(
self.alice_peer_id,
tor_socks5_port,
self.env_config,
self.bitcoin_wallet.clone(),
);
let mut swarm = swarm::cli(
self.seed.derive_libp2p_identity(),
tor_socks5_port,
behaviour,
)
.await?;
swarm

Loading…
Cancel
Save