Compare commits
4 Commits
master
...
rendezvous
Author | SHA1 | Date |
---|---|---|
Daniel Karzel | 0e3e0fb4cb | 3 years ago |
Daniel Karzel | a679cdee34 | 3 years ago |
Daniel Karzel | 96a5aaf758 | 3 years ago |
Daniel Karzel | 18eb1ab511 | 3 years ago |
@ -1,24 +0,0 @@
|
||||
# Contribution guidelines
|
||||
|
||||
Thank you for wanting to contribute to this project!
|
||||
|
||||
## Contributing code
|
||||
|
||||
There are a couple of things we are going to look out for in PRs and knowing them upfront is going to reduce the number of times we will be going back and forth, making things more efficient.
|
||||
|
||||
1. We have CI checks in place that validate formatting and code style.
|
||||
Make sure `dprint check` and `cargo clippy` both finish without any warnings or errors.
|
||||
If you don't already have it installed, you can obtain in [various ways](https://dprint.dev/install/).
|
||||
2. All text document (`CHANGELOG.md`, `README.md`, etc) should follow the [semantic linebreaks](https://sembr.org/) specification.
|
||||
3. We strive for atomic commits with good commit messages.
|
||||
As an inspiration, read [this](https://chris.beams.io/posts/git-commit/) blogpost.
|
||||
An atomic commit is a cohesive diff with formatting checks, linter and build passing.
|
||||
Ideally, all tests are passing as well but we acknowledge that this is not always possible depending on the change you are making.
|
||||
4. If you are making any user visible changes, include a changelog entry.
|
||||
|
||||
## Contributing issues
|
||||
|
||||
When contributing a feature request, please focus on your _problem_ as much as possible.
|
||||
It is okay to include ideas on how the feature should be implemented but they should be 2nd nature of your request.
|
||||
|
||||
For more loosely-defined problems and ideas, consider starting a [discussion](https://github.com/comit-network/xmr-btc-swap/discussions/new) instead of opening an issue.
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +0,0 @@
|
||||
# Documentation
|
||||
|
||||
This directory hosts various pieces of documentation.
|
||||
|
||||
- [`swap` CLI](./cli/README.md)
|
||||
- [`asb` service](./asb/README.md)
|
@ -1,139 +0,0 @@
|
||||
# Swap CLI
|
||||
|
||||
The CLI defaults to **mainnet** (from version 0.6.0 onwards).
|
||||
For testing and to familiarise yourself with the tool, we recommend you to try it on testnet first.
|
||||
To do that, pass the `--testnet` flag with the actual command:
|
||||
|
||||
```shell
|
||||
swap --testnet <SUBCOMMAND>
|
||||
```
|
||||
|
||||
The two main commands of the CLI are:
|
||||
|
||||
- `buy-xmr`: for swapping BTC to XMR with a particular seller
|
||||
- `list-sellers`: for discovering available sellers through a rendezvous point
|
||||
|
||||
Running `swap --help` gives us roughly the following output:
|
||||
|
||||
```
|
||||
swap 0.8.0
|
||||
The COMIT guys <hello@comit.network>
|
||||
CLI for swapping BTC for XMR
|
||||
|
||||
USAGE:
|
||||
swap [FLAGS] [OPTIONS] <SUBCOMMAND>
|
||||
|
||||
FLAGS:
|
||||
--debug Activate debug logging
|
||||
-h, --help Prints help information
|
||||
-j, --json Outputs all logs in JSON format instead of plain text
|
||||
--testnet Swap on testnet and assume testnet defaults for data-dir and the blockchain related parameters
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
--data-base-dir <data> The base data directory to be used for mainnet / testnet specific data like database, wallets etc
|
||||
|
||||
SUBCOMMANDS:
|
||||
buy-xmr Start a BTC for XMR swap
|
||||
list-sellers Discover and list sellers (i.e. ASB providers)
|
||||
|
||||
cancel Try to cancel an ongoing swap (expert users only)
|
||||
help Prints this message or the help of the given subcommand(s)
|
||||
history Show a list of past, ongoing and completed swaps
|
||||
refund Try to cancel a swap and refund the BTC (expert users only)
|
||||
resume Resume a swap
|
||||
```
|
||||
|
||||
## Swapping BTC for XMR
|
||||
|
||||
Running `swap buy-xmr --help` gives us roughly the following output:
|
||||
|
||||
```
|
||||
swap-buy-xmr 0.8.0
|
||||
Start a BTC for XMR swap
|
||||
|
||||
USAGE:
|
||||
swap buy-xmr [FLAGS] [OPTIONS] --change-address <bitcoin-change-address> --receive-address <monero-receive-address> --seller <seller>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
--testnet Swap on testnet and assume testnet defaults for data-dir and the blockchain related parameters
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
--change-address <bitcoin-change-address> The bitcoin address where any form of change or excess funds should be sent to
|
||||
--receive-address <monero-receive-address> The monero address where you would like to receive monero
|
||||
--seller <seller> The seller's address. Must include a peer ID part, i.e. `/p2p/`
|
||||
|
||||
--electrum-rpc <bitcoin-electrum-rpc-url> Provide the Bitcoin Electrum RPC URL
|
||||
--bitcoin-target-block <bitcoin-target-block> Estimate Bitcoin fees such that transactions are confirmed within the specified number of blocks
|
||||
--monero-daemon-address <monero-daemon-address> Specify to connect to a monero daemon of your choice: <host>:<port>
|
||||
--tor-socks5-port <tor-socks5-port> Your local Tor socks5 proxy port [default: 9050]
|
||||
```
|
||||
|
||||
This command has three core options:
|
||||
|
||||
- `--change-address`: A Bitcoin address you control. Will be used for refunds of any kind.
|
||||
- `--receive-address`: A Monero address you control. This is where you will receive the Monero after the swap.
|
||||
- `--seller`: The multiaddress of the seller you want to swap with.
|
||||
|
||||
## Discovering sellers
|
||||
|
||||
Running `swap list-sellers --help` gives us roughly the following output:
|
||||
|
||||
```
|
||||
swap-list-sellers 0.8.0
|
||||
Discover and list sellers (i.e. ASB providers)
|
||||
|
||||
USAGE:
|
||||
swap list-sellers [FLAGS] [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
--testnet Swap on testnet and assume testnet defaults for data-dir and the blockchain related parameters
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
--rendezvous-point <rendezvous-point> Address of the rendezvous point you want to use to discover ASBs
|
||||
--tor-socks5-port <tor-socks5-port> Your local Tor socks5 proxy port [default: 9050]
|
||||
```
|
||||
|
||||
Running `swap --testnet list-sellers --rendezvous-point /dnsaddr/rendezvous.coblox.tech/p2p/12D3KooWQUt9DkNZxEn2R5ymJzWj15MpG6mTW84kyd8vDaRZi46o` will give you something like:
|
||||
|
||||
```
|
||||
Connected to rendezvous point, discovering nodes in 'xmr-btc-swap-testnet' namespace ...
|
||||
Discovered peer 12D3KooWPZ69DRp4wbGB3wJsxxsg1XW1EVZ2evtVwcARCF3a1nrx at /dns4/ac4hgzmsmekwekjbdl77brufqqbylddugzze4tel6qsnlympgmr46iid.onion/tcp/8765
|
||||
+----------------+----------------+----------------+--------+----------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| PRICE | MIN_QUANTITY | MAX_QUANTITY | STATUS | ADDRESS |
|
||||
+====================================================================================================================================================================================================+
|
||||
| 0.00665754 BTC | 0.00010000 BTC | 0.00100000 BTC | Online | /dns4/ac4hgzmsmekwekjbdl77brufqqbylddugzze4tel6qsnlympgmr46iid.onion/tcp/8765/p2p/12D3KooWPZ69DRp4wbGB3wJsxxsg1XW1EVZ2evtVwcARCF3a1nrx |
|
||||
+----------------+----------------+----------------+--------+----------------------------------------------------------------------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
or this if a node is not reachable:
|
||||
|
||||
```
|
||||
Connected to rendezvous point, discovering nodes in 'xmr-btc-swap-testnet' namespace ...
|
||||
Discovered peer 12D3KooWPZ69DRp4wbGB3wJsxxsg1XW1EVZ2evtVwcARCF3a1nrx at /dns4/ac4hgzmsmekwekjbdl77brufqqbylddugzze4tel6qsnlympgmr46iid.onion/tcp/8765
|
||||
+-------+--------------+--------------+-------------+----------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| PRICE | MIN_QUANTITY | MAX_QUANTITY | STATUS | ADDRESS |
|
||||
+============================================================================================================================================================================================+
|
||||
| ??? | ??? | ??? | Unreachable | /dns4/ac4hgzmsmekwekjbdl77brufqqbylddugzze4tel6qsnlympgmr46iid.onion/tcp/8765/p2p/12D3KooWPZ69DRp4wbGB3wJsxxsg1XW1EVZ2evtVwcARCF3a1nrx |
|
||||
+-------+--------------+--------------+-------------+----------------------------------------------------------------------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
## Automating discover and swapping
|
||||
|
||||
The `buy-xmr` and `list-sellers` command have been designed to be composed.
|
||||
[This script](./discover_and_take.sh) is example of what can be done.
|
||||
Deciding on the seller to use is non-trivial to automate which is why it is not implemented as part of the tool.
|
||||
|
||||
## Tor
|
||||
|
||||
By default, the CLI will look for Tor at the default socks port `9050` and automatically route all traffic with a seller through Tor.
|
||||
This allows swapping with sellers that are only reachable with an onion address.
|
||||
|
||||
Disclaimer:
|
||||
Communication with public blockchain explorers (Electrum, public XMR nodes) currently goes through clearnet.
|
||||
For complete anonymity it is recommended to run your own blockchain nodes.
|
||||
Use `swap buy-xmr --help` to see configuration options.
|
@ -1,39 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This is a utility script to showcase how the swap CLI can discover sellers and then trigger a swap using the discovered sellers
|
||||
#
|
||||
# 1st param: Path to the "swap" binary (aka the swap CLI)
|
||||
# 2nd param: Multiaddress of the rendezvous node to be used for discovery
|
||||
# 3rd param: Your Monero stagenet address where the XMR will be received
|
||||
# 4th param: Your bech32 Bitcoin testnet address that will be used for any change output (e.g. refund scenario or when swapping an amount smaller than the transferred BTC)
|
||||
#
|
||||
# Example usage:
|
||||
# discover_and_take.sh "PATH/TO/swap" "/dnsaddr/rendezvous.coblox.tech/p2p/12D3KooWQUt9DkNZxEn2R5ymJzWj15MpG6mTW84kyd8vDaRZi46o" "YOUR_XMR_STAGENET_ADDRESS" "YOUR_BECH32_BITCOIN_TESTNET_ADDRESS"
|
||||
|
||||
CLI_PATH=$1
|
||||
RENDEZVOUS_POINT=$2
|
||||
YOUR_MONERO_ADDR=$3
|
||||
YOUR_BITCOIN_ADDR=$4
|
||||
|
||||
CLI_LIST_SELLERS="$CLI_PATH --testnet --json --debug list-sellers --rendezvous-point $RENDEZVOUS_POINT"
|
||||
echo "Requesting sellers with command: $CLI_LIST_SELLERS"
|
||||
echo
|
||||
|
||||
BEST_SELLER=$($CLI_LIST_SELLERS | jq -s -c 'min_by(.status .Online .price)' | jq -r '.multiaddr, (.status .Online .price), (.status .Online .min_quantity), (.status .Online .max_quantity)')
|
||||
read ADDR PRICE MIN MAX < <(echo $BEST_SELLER)
|
||||
|
||||
echo
|
||||
|
||||
echo "Seller with best price:"
|
||||
echo " multiaddr : $ADDR"
|
||||
echo " price : $PRICE sat"
|
||||
echo " min_quantity: $MIN sat"
|
||||
echo " max_quantity: $MAX sat"
|
||||
|
||||
echo
|
||||
|
||||
CLI_SWAP="$CLI_PATH --testnet --debug buy-xmr --receive-address $YOUR_MONERO_ADDR --change-address $YOUR_BITCOIN_ADDR --seller $ADDR"
|
||||
|
||||
echo "Starting swap with best seller using command $CLI_SWAP"
|
||||
echo
|
||||
$CLI_SWAP
|
@ -1,9 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use vergen::{vergen, Config, SemverKind};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut config = Config::default();
|
||||
*config.git_mut().semver_kind_mut() = SemverKind::Lightweight;
|
||||
|
||||
vergen(config)
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
# Seeds for failure cases proptest has generated in the past. It is
|
||||
# automatically read and these particular cases re-run before any
|
||||
# novel cases are generated.
|
||||
#
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc 849f8b01f49fc9a913100203698a9151d8de8a37564e1d3b1e3b4169e192f58a # shrinks to funding_amount = 290250686, num_utxos = 3, sats_per_vb = 75.35638, key = ExtendedPrivKey { network: Regtest, depth: 0, parent_fingerprint: 00000000, child_number: Normal { index: 0 }, private_key: [private key data], chain_code: 0b7a29ca6990bbc9b9187c1d1a07e2cf68e32f5ce55d2df01edf8a4ac2ee2a4b }, alice = Point<Normal,Public,NonZero>(0299a8c6a662e2e9e8ee7c6889b75a51c432812b4bf70c1d76eace63abc1bdfb1b), bob = Point<Normal,Public,NonZero>(027165b1f9924030c90d38c511da0f4397766078687997ed34d6ef2743d2a7bbed)
|
@ -0,0 +1,116 @@
|
||||
use crate::asb::event_loop::LatestRate;
|
||||
use crate::env;
|
||||
use crate::network::quote::BidQuote;
|
||||
use crate::network::swap_setup::alice;
|
||||
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::ping::{Ping, PingEvent};
|
||||
use libp2p::request_response::{RequestId, ResponseChannel};
|
||||
use libp2p::{NetworkBehaviour, PeerId};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OutEvent {
|
||||
SwapSetupInitiated {
|
||||
send_wallet_snapshot: bmrng::RequestReceiver<bitcoin::Amount, WalletSnapshot>,
|
||||
},
|
||||
SwapSetupCompleted {
|
||||
peer_id: PeerId,
|
||||
swap_id: Uuid,
|
||||
state3: Box<State3>,
|
||||
},
|
||||
SwapDeclined {
|
||||
peer: PeerId,
|
||||
error: alice::Error,
|
||||
},
|
||||
QuoteRequested {
|
||||
channel: ResponseChannel<BidQuote>,
|
||||
peer: PeerId,
|
||||
},
|
||||
TransferProofAcknowledged {
|
||||
peer: PeerId,
|
||||
id: RequestId,
|
||||
},
|
||||
EncryptedSignatureReceived {
|
||||
msg: Box<encrypted_signature::Request>,
|
||||
channel: ResponseChannel<()>,
|
||||
peer: PeerId,
|
||||
},
|
||||
Failure {
|
||||
peer: PeerId,
|
||||
error: Error,
|
||||
},
|
||||
/// "Fallback" variant that allows the event mapping code to swallow certain
|
||||
/// events that we don't want the caller to deal with.
|
||||
Other,
|
||||
}
|
||||
|
||||
impl OutEvent {
|
||||
pub fn unexpected_request(peer: PeerId) -> OutEvent {
|
||||
OutEvent::Failure {
|
||||
peer,
|
||||
error: anyhow!("Unexpected request received"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unexpected_response(peer: PeerId) -> OutEvent {
|
||||
OutEvent::Failure {
|
||||
peer,
|
||||
error: anyhow!("Unexpected response received"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A `NetworkBehaviour` that represents an XMR/BTC swap node as Alice.
|
||||
#[derive(NetworkBehaviour)]
|
||||
#[behaviour(out_event = "OutEvent", event_process = false)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Behaviour<LR>
|
||||
where
|
||||
LR: LatestRate + Send + 'static,
|
||||
{
|
||||
pub quote: quote::Behaviour,
|
||||
pub swap_setup: alice::Behaviour<LR>,
|
||||
pub transfer_proof: transfer_proof::Behaviour,
|
||||
pub encrypted_signature: encrypted_signature::Behaviour,
|
||||
|
||||
/// Ping behaviour that ensures that the underlying network connection is
|
||||
/// still alive. If the ping fails a connection close event will be
|
||||
/// emitted that is picked up as swarm event.
|
||||
ping: Ping,
|
||||
}
|
||||
|
||||
impl<LR> Behaviour<LR>
|
||||
where
|
||||
LR: LatestRate + Send + 'static,
|
||||
{
|
||||
pub fn new(
|
||||
min_buy: bitcoin::Amount,
|
||||
max_buy: bitcoin::Amount,
|
||||
latest_rate: LR,
|
||||
resume_only: bool,
|
||||
env_config: env::Config,
|
||||
) -> Self {
|
||||
Self {
|
||||
quote: quote::asb(),
|
||||
swap_setup: alice::Behaviour::new(
|
||||
min_buy,
|
||||
max_buy,
|
||||
env_config,
|
||||
latest_rate,
|
||||
resume_only,
|
||||
),
|
||||
transfer_proof: transfer_proof::alice(),
|
||||
encrypted_signature: encrypted_signature::alice(),
|
||||
ping: Ping::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PingEvent> for OutEvent {
|
||||
fn from(_: PingEvent) -> Self {
|
||||
OutEvent::Other
|
||||
}
|
||||
}
|
@ -1,441 +0,0 @@
|
||||
use crate::asb::event_loop::LatestRate;
|
||||
use crate::env;
|
||||
use crate::network::quote::BidQuote;
|
||||
use crate::network::rendezvous::XmrBtcNamespace;
|
||||
use crate::network::swap_setup::alice;
|
||||
use crate::network::swap_setup::alice::WalletSnapshot;
|
||||
use crate::network::transport::authenticate_and_multiplex;
|
||||
use crate::network::{encrypted_signature, quote, transfer_proof};
|
||||
use crate::protocol::alice::State3;
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use futures::FutureExt;
|
||||
use libp2p::core::connection::ConnectionId;
|
||||
use libp2p::core::muxing::StreamMuxerBox;
|
||||
use libp2p::core::transport::Boxed;
|
||||
use libp2p::dns::TokioDnsConfig;
|
||||
use libp2p::ping::{Ping, PingConfig, PingEvent};
|
||||
use libp2p::request_response::{RequestId, ResponseChannel};
|
||||
use libp2p::swarm::{
|
||||
DialPeerCondition, IntoProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction,
|
||||
PollParameters, ProtocolsHandler,
|
||||
};
|
||||
use libp2p::tcp::TokioTcpConfig;
|
||||
use libp2p::websocket::WsConfig;
|
||||
use libp2p::{identity, Multiaddr, NetworkBehaviour, PeerId, Transport};
|
||||
use std::task::Poll;
|
||||
use std::time::Duration;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub mod transport {
|
||||
use super::*;
|
||||
|
||||
/// Creates the libp2p transport for the ASB.
|
||||
pub fn new(identity: &identity::Keypair) -> Result<Boxed<(PeerId, StreamMuxerBox)>> {
|
||||
let tcp = TokioTcpConfig::new().nodelay(true);
|
||||
let tcp_with_dns = TokioDnsConfig::system(tcp)?;
|
||||
let websocket_with_dns = WsConfig::new(tcp_with_dns.clone());
|
||||
|
||||
let transport = tcp_with_dns.or_transport(websocket_with_dns).boxed();
|
||||
|
||||
authenticate_and_multiplex(transport, identity)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod behaviour {
|
||||
use super::*;
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug)]
|
||||
pub enum OutEvent {
|
||||
SwapSetupInitiated {
|
||||
send_wallet_snapshot: bmrng::RequestReceiver<bitcoin::Amount, WalletSnapshot>,
|
||||
},
|
||||
SwapSetupCompleted {
|
||||
peer_id: PeerId,
|
||||
swap_id: Uuid,
|
||||
state3: State3,
|
||||
},
|
||||
SwapDeclined {
|
||||
peer: PeerId,
|
||||
error: alice::Error,
|
||||
},
|
||||
QuoteRequested {
|
||||
channel: ResponseChannel<BidQuote>,
|
||||
peer: PeerId,
|
||||
},
|
||||
TransferProofAcknowledged {
|
||||
peer: PeerId,
|
||||
id: RequestId,
|
||||
},
|
||||
EncryptedSignatureReceived {
|
||||
msg: encrypted_signature::Request,
|
||||
channel: ResponseChannel<()>,
|
||||
peer: PeerId,
|
||||
},
|
||||
Rendezvous(libp2p::rendezvous::Event),
|
||||
Failure {
|
||||
peer: PeerId,
|
||||
error: Error,
|
||||
},
|
||||
/// "Fallback" variant that allows the event mapping code to swallow
|
||||
/// certain events that we don't want the caller to deal with.
|
||||
Other,
|
||||
}
|
||||
|
||||
impl OutEvent {
|
||||
pub fn unexpected_request(peer: PeerId) -> OutEvent {
|
||||
OutEvent::Failure {
|
||||
peer,
|
||||
error: anyhow!("Unexpected request received"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unexpected_response(peer: PeerId) -> OutEvent {
|
||||
OutEvent::Failure {
|
||||
peer,
|
||||
error: anyhow!("Unexpected response received"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A `NetworkBehaviour` that represents an XMR/BTC swap node as Alice.
|
||||
#[derive(NetworkBehaviour)]
|
||||
#[behaviour(out_event = "OutEvent", event_process = false)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Behaviour<LR>
|
||||
where
|
||||
LR: LatestRate + Send + 'static,
|
||||
{
|
||||
pub rendezvous: libp2p::swarm::toggle::Toggle<rendezous::Behaviour>,
|
||||
pub quote: quote::Behaviour,
|
||||
pub swap_setup: alice::Behaviour<LR>,
|
||||
pub transfer_proof: transfer_proof::Behaviour,
|
||||
pub encrypted_signature: encrypted_signature::Behaviour,
|
||||
|
||||
/// Ping behaviour that ensures that the underlying network connection
|
||||
/// is still alive. If the ping fails a connection close event
|
||||
/// will be emitted that is picked up as swarm event.
|
||||
ping: Ping,
|
||||
}
|
||||
|
||||
impl<LR> Behaviour<LR>
|
||||
where
|
||||
LR: LatestRate + Send + 'static,
|
||||
{
|
||||
pub fn new(
|
||||
min_buy: bitcoin::Amount,
|
||||
max_buy: bitcoin::Amount,
|
||||
latest_rate: LR,
|
||||
resume_only: bool,
|
||||
env_config: env::Config,
|
||||
rendezvous_params: Option<(identity::Keypair, PeerId, Multiaddr, XmrBtcNamespace)>,
|
||||
) -> Self {
|
||||
Self {
|
||||
rendezvous: libp2p::swarm::toggle::Toggle::from(rendezvous_params.map(
|
||||
|(identity, rendezvous_peer_id, rendezvous_address, namespace)| {
|
||||
rendezous::Behaviour::new(
|
||||
identity,
|
||||
rendezvous_peer_id,
|
||||
rendezvous_address,
|
||||
namespace,
|
||||
None, // use default ttl on rendezvous point
|
||||
)
|
||||
},
|
||||
)),
|
||||
quote: quote::asb(),
|
||||
swap_setup: alice::Behaviour::new(
|
||||
min_buy,
|
||||
max_buy,
|
||||
env_config,
|
||||
latest_rate,
|
||||
resume_only,
|
||||
),
|
||||
transfer_proof: transfer_proof::alice(),
|
||||
encrypted_signature: encrypted_signature::alice(),
|
||||
ping: Ping::new(PingConfig::new().with_keep_alive(true)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PingEvent> for OutEvent {
|
||||
fn from(_: PingEvent) -> Self {
|
||||
OutEvent::Other
|
||||
}
|
||||
}
|
||||
|
||||
impl From<libp2p::rendezvous::Event> for OutEvent {
|
||||
fn from(event: libp2p::rendezvous::Event) -> Self {
|
||||
OutEvent::Rendezvous(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod rendezous {
|
||||
use super::*;
|
||||
use std::pin::Pin;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum ConnectionStatus {
|
||||
Disconnected,
|
||||
Dialling,
|
||||
Connected,
|
||||
}
|
||||
|
||||
enum RegistrationStatus {
|
||||
RegisterOnNextConnection,
|
||||
Pending,
|
||||
Registered {
|
||||
re_register_in: Pin<Box<tokio::time::Sleep>>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct Behaviour {
|
||||
inner: libp2p::rendezvous::Rendezvous,
|
||||
rendezvous_point: Multiaddr,
|
||||
rendezvous_peer_id: PeerId,
|
||||
namespace: XmrBtcNamespace,
|
||||
registration_status: RegistrationStatus,
|
||||
connection_status: ConnectionStatus,
|
||||
registration_ttl: Option<u64>,
|
||||
}
|
||||
|
||||
impl Behaviour {
|
||||
pub fn new(
|
||||
identity: identity::Keypair,
|
||||
rendezvous_peer_id: PeerId,
|
||||
rendezvous_address: Multiaddr,
|
||||
namespace: XmrBtcNamespace,
|
||||
registration_ttl: Option<u64>,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: libp2p::rendezvous::Rendezvous::new(
|
||||
identity,
|
||||
libp2p::rendezvous::Config::default(),
|
||||
),
|
||||
rendezvous_point: rendezvous_address,
|
||||
rendezvous_peer_id,
|
||||
namespace,
|
||||
registration_status: RegistrationStatus::RegisterOnNextConnection,
|
||||
connection_status: ConnectionStatus::Disconnected,
|
||||
registration_ttl,
|
||||
}
|
||||
}
|
||||
|
||||
fn register(&mut self) {
|
||||
self.inner.register(
|
||||
self.namespace.into(),
|
||||
self.rendezvous_peer_id,
|
||||
self.registration_ttl,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkBehaviour for Behaviour {
|
||||
type ProtocolsHandler =
|
||||
<libp2p::rendezvous::Rendezvous as NetworkBehaviour>::ProtocolsHandler;
|
||||
type OutEvent = libp2p::rendezvous::Event;
|
||||
|
||||
fn new_handler(&mut self) -> Self::ProtocolsHandler {
|
||||
self.inner.new_handler()
|
||||
}
|
||||
|
||||
fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec<Multiaddr> {
|
||||
if peer_id == &self.rendezvous_peer_id {
|
||||
return vec![self.rendezvous_point.clone()];
|
||||
}
|
||||
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn inject_connected(&mut self, peer_id: &PeerId) {
|
||||
if peer_id == &self.rendezvous_peer_id {
|
||||
self.connection_status = ConnectionStatus::Connected;
|
||||
|
||||
match &self.registration_status {
|
||||
RegistrationStatus::RegisterOnNextConnection => {
|
||||
self.register();
|
||||
self.registration_status = RegistrationStatus::Pending;
|
||||
}
|
||||
RegistrationStatus::Registered { .. } => {}
|
||||
RegistrationStatus::Pending => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_disconnected(&mut self, peer_id: &PeerId) {
|
||||
if peer_id == &self.rendezvous_peer_id {
|
||||
self.connection_status = ConnectionStatus::Disconnected;
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_event(
|
||||
&mut self,
|
||||
peer_id: PeerId,
|
||||
connection: ConnectionId,
|
||||
event: <<Self::ProtocolsHandler as IntoProtocolsHandler>::Handler as ProtocolsHandler>::OutEvent,
|
||||
) {
|
||||
self.inner.inject_event(peer_id, connection, event)
|
||||
}
|
||||
|
||||
fn inject_dial_failure(&mut self, peer_id: &PeerId) {
|
||||
if peer_id == &self.rendezvous_peer_id {
|
||||
self.connection_status = ConnectionStatus::Disconnected;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn poll(&mut self, cx: &mut std::task::Context<'_>, params: &mut impl PollParameters) -> Poll<NetworkBehaviourAction<<<Self::ProtocolsHandler as IntoProtocolsHandler>::Handler as ProtocolsHandler>::InEvent, Self::OutEvent>>{
|
||||
match &mut self.registration_status {
|
||||
RegistrationStatus::RegisterOnNextConnection => match self.connection_status {
|
||||
ConnectionStatus::Disconnected => {
|
||||
self.connection_status = ConnectionStatus::Dialling;
|
||||
|
||||
return Poll::Ready(NetworkBehaviourAction::DialPeer {
|
||||
peer_id: self.rendezvous_peer_id,
|
||||
condition: DialPeerCondition::Disconnected,
|
||||
});
|
||||
}
|
||||
ConnectionStatus::Dialling => {}
|
||||
ConnectionStatus::Connected => {
|
||||
self.registration_status = RegistrationStatus::Pending;
|
||||
self.register();
|
||||
}
|
||||
},
|
||||
RegistrationStatus::Registered { re_register_in } => {
|
||||
if let Poll::Ready(()) = re_register_in.poll_unpin(cx) {
|
||||
match self.connection_status {
|
||||
ConnectionStatus::Connected => {
|
||||
self.registration_status = RegistrationStatus::Pending;
|
||||
self.register();
|
||||
}
|
||||
ConnectionStatus::Disconnected => {
|
||||
self.registration_status =
|
||||
RegistrationStatus::RegisterOnNextConnection;
|
||||
|
||||
return Poll::Ready(NetworkBehaviourAction::DialPeer {
|
||||
peer_id: self.rendezvous_peer_id,
|
||||
condition: DialPeerCondition::Disconnected,
|
||||
});
|
||||
}
|
||||
ConnectionStatus::Dialling => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
RegistrationStatus::Pending => {}
|
||||
}
|
||||
|
||||
let inner_poll = self.inner.poll(cx, params);
|
||||
|
||||
// reset the timer if we successfully registered
|
||||
if let Poll::Ready(NetworkBehaviourAction::GenerateEvent(
|
||||
libp2p::rendezvous::Event::Registered { ttl, .. },
|
||||
)) = &inner_poll
|
||||
{
|
||||
let half_of_ttl = Duration::from_secs(*ttl) / 2;
|
||||
|
||||
self.registration_status = RegistrationStatus::Registered {
|
||||
re_register_in: Box::pin(tokio::time::sleep(half_of_ttl)),
|
||||
};
|
||||
}
|
||||
|
||||
inner_poll
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::network::test::{new_swarm, SwarmExt};
|
||||
use futures::StreamExt;
|
||||
use libp2p::swarm::SwarmEvent;
|
||||
|
||||
#[tokio::test]
|
||||
async fn given_no_initial_connection_when_constructed_asb_connects_and_registers_with_rendezvous_node(
|
||||
) {
|
||||
let mut rendezvous_node = new_swarm(|_, identity| {
|
||||
libp2p::rendezvous::Rendezvous::new(identity, libp2p::rendezvous::Config::default())
|
||||
});
|
||||
let rendezvous_address = rendezvous_node.listen_on_random_memory_address().await;
|
||||
|
||||
let mut asb = new_swarm(|_, identity| {
|
||||
rendezous::Behaviour::new(
|
||||
identity,
|
||||
*rendezvous_node.local_peer_id(),
|
||||
rendezvous_address,
|
||||
XmrBtcNamespace::Testnet,
|
||||
None,
|
||||
)
|
||||
});
|
||||
asb.listen_on_random_memory_address().await; // this adds an external address
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
rendezvous_node.next().await;
|
||||
}
|
||||
});
|
||||
let asb_registered = tokio::spawn(async move {
|
||||
loop {
|
||||
if let SwarmEvent::Behaviour(libp2p::rendezvous::Event::Registered { .. }) =
|
||||
asb.select_next_some().await
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tokio::time::timeout(Duration::from_secs(10), asb_registered)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn asb_automatically_re_registers() {
|
||||
let min_ttl = 5;
|
||||
let mut rendezvous_node = new_swarm(|_, identity| {
|
||||
libp2p::rendezvous::Rendezvous::new(
|
||||
identity,
|
||||
libp2p::rendezvous::Config::default().with_min_ttl(min_ttl),
|
||||
)
|
||||
});
|
||||
let rendezvous_address = rendezvous_node.listen_on_random_memory_address().await;
|
||||
|
||||
let mut asb = new_swarm(|_, identity| {
|
||||
rendezous::Behaviour::new(
|
||||
identity,
|
||||
*rendezvous_node.local_peer_id(),
|
||||
rendezvous_address,
|
||||
XmrBtcNamespace::Testnet,
|
||||
Some(5),
|
||||
)
|
||||
});
|
||||
asb.listen_on_random_memory_address().await; // this adds an external address
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
rendezvous_node.next().await;
|
||||
}
|
||||
});
|
||||
let asb_registered_three_times = tokio::spawn(async move {
|
||||
let mut number_of_registrations = 0;
|
||||
|
||||
loop {
|
||||
if let SwarmEvent::Behaviour(libp2p::rendezvous::Event::Registered { .. }) =
|
||||
asb.select_next_some().await
|
||||
{
|
||||
number_of_registrations += 1
|
||||
}
|
||||
|
||||
if number_of_registrations == 3 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tokio::time::timeout(Duration::from_secs(30), asb_registered_three_times)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
use crate::network::transport::authenticate_and_multiplex;
|
||||
use anyhow::Result;
|
||||
use libp2p::core::muxing::StreamMuxerBox;
|
||||
use libp2p::core::transport::Boxed;
|
||||
use libp2p::dns::TokioDnsConfig;
|
||||
use libp2p::tcp::TokioTcpConfig;
|
||||
use libp2p::websocket::WsConfig;
|
||||
use libp2p::{identity, PeerId, Transport};
|
||||
|
||||
/// Creates the libp2p transport for the ASB.
|
||||
pub fn new(identity: &identity::Keypair) -> Result<Boxed<(PeerId, StreamMuxerBox)>> {
|
||||
let tcp = TokioTcpConfig::new().nodelay(true);
|
||||
let tcp_with_dns = TokioDnsConfig::system(tcp)?;
|
||||
let websocket_with_dns = WsConfig::new(tcp_with_dns.clone());
|
||||
|
||||
let transport = tcp_with_dns.or_transport(websocket_with_dns).boxed();
|
||||
|
||||
authenticate_and_multiplex(transport, identity)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,15 +0,0 @@
|
||||
use libp2p::multiaddr::Protocol;
|
||||
use libp2p::{Multiaddr, PeerId};
|
||||
|
||||
pub trait MultiAddrExt {
|
||||
fn extract_peer_id(&self) -> Option<PeerId>;
|
||||
}
|
||||
|
||||
impl MultiAddrExt for Multiaddr {
|
||||
fn extract_peer_id(&self) -> Option<PeerId> {
|
||||
match self.iter().last()? {
|
||||
Protocol::P2p(multihash) => PeerId::from_multihash(multihash).ok(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
|
@ -0,0 +1 @@
|
||||
|
@ -1,29 +0,0 @@
|
||||
use libp2p::rendezvous::Namespace;
|
||||
use std::fmt;
|
||||
|
||||
#[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<XmrBtcNamespace> for Namespace {
|
||||
fn from(namespace: XmrBtcNamespace) -> Self {
|
||||
match namespace {
|
||||
XmrBtcNamespace::Mainnet => Namespace::from_static(MAINNET),
|
||||
XmrBtcNamespace::Testnet => Namespace::from_static(TESTNET),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
use proptest::prelude::*;
|
||||
|
||||
pub mod ecdsa_fun {
|
||||
use super::*;
|
||||
use ::ecdsa_fun::fun::marker::{Mark, NonZero, Normal};
|
||||
use ::ecdsa_fun::fun::{Point, Scalar, G};
|
||||
|
||||
pub fn point() -> impl Strategy<Value = Point> {
|
||||
scalar().prop_map(|mut scalar| Point::from_scalar_mul(&G, &mut scalar).mark::<Normal>())
|
||||
}
|
||||
|
||||
pub fn scalar() -> impl Strategy<Value = Scalar> {
|
||||
prop::array::uniform32(0..255u8).prop_filter_map("generated the 0 element", |bytes| {
|
||||
Scalar::from_bytes_mod_order(bytes).mark::<NonZero>()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub mod bitcoin {
|
||||
use super::*;
|
||||
use ::bitcoin::util::bip32::ExtendedPrivKey;
|
||||
use ::bitcoin::Network;
|
||||
|
||||
pub fn extended_priv_key() -> impl Strategy<Value = ExtendedPrivKey> {
|
||||
prop::array::uniform8(0..255u8).prop_filter_map("invalid secret key generated", |bytes| {
|
||||
ExtendedPrivKey::new_master(Network::Regtest, &bytes).ok()
|
||||
})
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
#![allow(clippy::unwrap_used)] // This is only meant to be used in tests.
|
||||
|
||||
use std::io;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tracing::subscriber;
|
||||
use tracing_subscriber::filter::LevelFilter;
|
||||
use tracing_subscriber::fmt::MakeWriter;
|
||||
|
||||
/// Setup tracing with a capturing writer, allowing assertions on the log
|
||||
/// messages.
|
||||
///
|
||||
/// Time and ANSI are disabled to make the output more predictable and
|
||||
/// readable.
|
||||
pub fn capture_logs(min_level: LevelFilter) -> MakeCapturingWriter {
|
||||
let make_writer = MakeCapturingWriter::default();
|
||||
|
||||
let guard = subscriber::set_default(
|
||||
tracing_subscriber::fmt()
|
||||
.with_ansi(false)
|
||||
.without_time()
|
||||
.with_writer(make_writer.clone())
|
||||
.with_env_filter(format!("{}", min_level))
|
||||
.finish(),
|
||||
);
|
||||
// don't clean up guard we stay initialized
|
||||
std::mem::forget(guard);
|
||||
|
||||
make_writer
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct MakeCapturingWriter {
|
||||
writer: CapturingWriter,
|
||||
}
|
||||
|
||||
impl MakeCapturingWriter {
|
||||
pub fn captured(&self) -> String {
|
||||
let captured = &self.writer.captured;
|
||||
let cursor = captured.lock().unwrap();
|
||||
String::from_utf8(cursor.clone().into_inner()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl MakeWriter for MakeCapturingWriter {
|
||||
type Writer = CapturingWriter;
|
||||
|
||||
fn make_writer(&self) -> Self::Writer {
|
||||
self.writer.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct CapturingWriter {
|
||||
captured: Arc<Mutex<io::Cursor<Vec<u8>>>>,
|
||||
}
|
||||
|
||||
impl io::Write for CapturingWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.captured.lock().unwrap().write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in new issue