mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-05-03 15:24:53 -04:00
refactor(swap): Upgrade libp2p to 0.53.2, reliable retry mechanisms (#109)
Our libp2p version is out of date, and we need to tackle the upgrade even though it's a significant undertaking. This'll also fix some other [issues](https://github.com/UnstoppableSwap/core/issues/95). ## This PR includes the following changes: - Breaking network protocol change: The libp2p version has been upgraded to 0.53 which includes breaking network protocol changes. ASBs and CLIs will not be able to swap if one of them is on the old version. - ASB: Transfer proofs will be repeatedly sent until they are acknowledged by the other party. This fixes a bug where it'd seem to Bob as if the Alice never locked the Monero. Forcing the swap to be refunded. - CLI: Encrypted signatures will be repeatedly sent until they are acknowledged by the other party - CLI+ASB: Libp2p network errors in request-response protocols are now propagated throught the event loop channels. This allows the caller to retry if an error occurs (e.g timeout) Closes https://github.com/UnstoppableSwap/core/issues/101, https://github.com/UnstoppableSwap/core/issues/95
This commit is contained in:
parent
a116c27785
commit
c027e51087
41 changed files with 4197 additions and 7643 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
target
|
||||
.vscode
|
|
@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
- Breaking network protocol change: The libp2p version has been upgraded to 0.53 which includes breaking network protocol changes. ASBs and CLIs will not be able to swap if one of them is on the old version.
|
||||
- ASB: Transfer proofs will be repeatedly sent until they are acknowledged by the other party. This fixes a bug where it'd seem to Bob as if the Alice never locked the Monero. Forcing the swap to be refunded.
|
||||
- CLI: Encrypted signatures will be repeatedly sent until they are acknowledged by the other party
|
||||
- ASB: We now retry indefinitely to lock Monero funds until the swap is cancelled. This fixes an issue where we would fail to lock Monero on the first try (e.g., due to the daemon not being fully synced) and would never try again, forcing the swap to be refunded.
|
||||
- ASB + CLI: You can now use the `logs` command to retrieve logs stored in the past, redacting addresses and id's using `logs --redact`.
|
||||
- ASB: The `--disable-timestamp` flag has been removed
|
||||
|
|
4482
Cargo.lock
generated
4482
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -13,7 +13,7 @@ WORKDIR /build/swap
|
|||
|
||||
RUN cargo build --release --bin=asb
|
||||
|
||||
FROM debian:bullseye-slim
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
WORKDIR /data
|
||||
|
||||
|
|
|
@ -9,11 +9,11 @@ import HistoryPage from "./pages/history/HistoryPage";
|
|||
import SwapPage from "./pages/swap/SwapPage";
|
||||
import WalletPage from "./pages/wallet/WalletPage";
|
||||
import GlobalSnackbarProvider from "./snackbar/GlobalSnackbarProvider";
|
||||
import UpdaterDialog from "./modal/updater/UpdaterDialog";
|
||||
import { initEventListeners } from "renderer/rpc";
|
||||
import { useEffect } from "react";
|
||||
import { fetchProvidersViaHttp, fetchAlertsViaHttp, fetchXmrPrice, fetchBtcPrice, fetchXmrBtcRate } from "renderer/api";
|
||||
import { initEventListeners } from "renderer/rpc";
|
||||
import { store } from "renderer/store/storeRenderer";
|
||||
import UpdaterDialog from "./modal/updater/UpdaterDialog";
|
||||
import { setAlerts } from "store/features/alertsSlice";
|
||||
import { setRegistryProviders, registryConnectionFailed } from "store/features/providersSlice";
|
||||
import { setXmrPrice, setBtcPrice, setXmrBtcRate } from "store/features/ratesSlice";
|
||||
|
|
|
@ -16,7 +16,6 @@ import {
|
|||
fetchXmrPrice,
|
||||
} from "./api";
|
||||
import App from "./components/App";
|
||||
import { initEventListeners } from "./rpc";
|
||||
import { persistor, store } from "./store/storeRenderer";
|
||||
|
||||
const container = document.getElementById("root");
|
||||
|
|
4346
src-tauri/Cargo.lock
generated
4346
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -15,13 +15,14 @@ tauri = [ "dep:tauri" ]
|
|||
anyhow = "1"
|
||||
async-compression = { version = "0.3", features = [ "bzip2", "tokio" ] }
|
||||
async-trait = "0.1"
|
||||
asynchronous-codec = "0.7.0"
|
||||
atty = "0.2"
|
||||
backoff = { version = "0.4", features = [ "tokio" ] }
|
||||
base64 = "0.22"
|
||||
bdk = "0.28"
|
||||
big-bytes = "1"
|
||||
bitcoin = { version = "0.29", features = [ "rand", "serde" ] }
|
||||
bmrng = "0.5"
|
||||
bmrng = "0.5.2"
|
||||
comfy-table = "7.1"
|
||||
config = { version = "0.14", default-features = false, features = [ "toml" ] }
|
||||
conquer-once = "0.4"
|
||||
|
@ -43,19 +44,7 @@ hyper = "0.14.20"
|
|||
itertools = "0.13"
|
||||
jsonrpsee = { version = "0.16.2", features = [ "server" ] }
|
||||
jsonrpsee-core = "0.16.2"
|
||||
libp2p = { version = "0.42.2", default-features = false, features = [
|
||||
"tcp-tokio",
|
||||
"yamux",
|
||||
"mplex",
|
||||
"dns-tokio",
|
||||
"noise",
|
||||
"request-response",
|
||||
"websocket",
|
||||
"ping",
|
||||
"rendezvous",
|
||||
"identify",
|
||||
"serde",
|
||||
] }
|
||||
libp2p = { version = "0.53.2", features = [ "tcp", "yamux", "dns", "noise", "request-response", "ping", "rendezvous", "identify", "macros", "cbor", "json", "tokio", "serde", "rsa" ] }
|
||||
monero = { version = "0.12", features = [ "serde_support" ] }
|
||||
monero-rpc = { path = "../monero-rpc" }
|
||||
once_cell = "1.19"
|
||||
|
@ -128,6 +117,7 @@ tracing-subscriber = { version = "0.3", default-features = false, features = [
|
|||
"json",
|
||||
] }
|
||||
typeshare = "1.0.3"
|
||||
unsigned-varint = { version = "0.8.0", features = [ "codec", "asynchronous_codec" ] }
|
||||
url = { version = "2", features = [ "serde" ] }
|
||||
uuid = { version = "1.9", features = [ "serde", "v4" ] }
|
||||
void = "1"
|
||||
|
|
|
@ -23,7 +23,6 @@ pub struct Defaults {
|
|||
pub config_path: PathBuf,
|
||||
data_dir: PathBuf,
|
||||
listen_address_tcp: Multiaddr,
|
||||
listen_address_ws: Multiaddr,
|
||||
electrum_rpc_url: Url,
|
||||
monero_wallet_rpc_url: Url,
|
||||
price_ticker_ws_url: Url,
|
||||
|
@ -38,7 +37,6 @@ impl GetDefaults for Testnet {
|
|||
.join("config.toml"),
|
||||
data_dir: default_asb_data_dir()?.join("testnet"),
|
||||
listen_address_tcp: Multiaddr::from_str("/ip4/0.0.0.0/tcp/9939")?,
|
||||
listen_address_ws: Multiaddr::from_str("/ip4/0.0.0.0/tcp/9940/ws")?,
|
||||
electrum_rpc_url: Url::parse("ssl://electrum.blockstream.info:60002")?,
|
||||
monero_wallet_rpc_url: Url::parse("http://127.0.0.1:38083/json_rpc")?,
|
||||
price_ticker_ws_url: Url::parse("wss://ws.kraken.com")?,
|
||||
|
@ -57,7 +55,6 @@ impl GetDefaults for Mainnet {
|
|||
.join("config.toml"),
|
||||
data_dir: default_asb_data_dir()?.join("mainnet"),
|
||||
listen_address_tcp: Multiaddr::from_str("/ip4/0.0.0.0/tcp/9939")?,
|
||||
listen_address_ws: Multiaddr::from_str("/ip4/0.0.0.0/tcp/9940/ws")?,
|
||||
electrum_rpc_url: Url::parse("ssl://blockstream.info:700")?,
|
||||
monero_wallet_rpc_url: Url::parse("http://127.0.0.1:18083/json_rpc")?,
|
||||
price_ticker_ws_url: Url::parse("wss://ws.kraken.com")?,
|
||||
|
@ -299,7 +296,7 @@ pub fn query_user_for_initial_config(testnet: bool) -> Result<Config> {
|
|||
|
||||
let listen_addresses = Input::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("Enter multiaddresses (comma separated) on which asb should list for peer-to-peer communications or hit return to use default")
|
||||
.default( format!("{},{}", defaults.listen_address_tcp, defaults.listen_address_ws))
|
||||
.default( format!("{}", defaults.listen_address_tcp))
|
||||
.interact_text()?;
|
||||
let listen_addresses = listen_addresses
|
||||
.split(',')
|
||||
|
@ -429,7 +426,7 @@ mod tests {
|
|||
network: bitcoin::Network::Testnet,
|
||||
},
|
||||
network: Network {
|
||||
listen: vec![defaults.listen_address_tcp, defaults.listen_address_ws],
|
||||
listen: vec![defaults.listen_address_tcp],
|
||||
rendezvous_point: vec![],
|
||||
external_addresses: vec![],
|
||||
},
|
||||
|
@ -473,7 +470,7 @@ mod tests {
|
|||
network: bitcoin::Network::Bitcoin,
|
||||
},
|
||||
network: Network {
|
||||
listen: vec![defaults.listen_address_tcp, defaults.listen_address_ws],
|
||||
listen: vec![defaults.listen_address_tcp],
|
||||
rendezvous_point: vec![],
|
||||
external_addresses: vec![],
|
||||
},
|
||||
|
|
|
@ -5,14 +5,15 @@ use crate::network::cooperative_xmr_redeem_after_punish::Response::{Fullfilled,
|
|||
use crate::network::quote::BidQuote;
|
||||
use crate::network::swap_setup::alice::WalletSnapshot;
|
||||
use crate::network::transfer_proof;
|
||||
use crate::protocol::alice::swap::has_already_processed_enc_sig;
|
||||
use crate::protocol::alice::{AliceState, State3, Swap};
|
||||
use crate::protocol::{Database, State};
|
||||
use crate::{bitcoin, env, kraken, monero};
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use futures::future;
|
||||
use futures::future::{BoxFuture, FutureExt};
|
||||
use futures::stream::{FuturesUnordered, StreamExt};
|
||||
use libp2p::request_response::{RequestId, ResponseChannel};
|
||||
use libp2p::request_response::{OutboundFailure, OutboundRequestId, ResponseChannel};
|
||||
use libp2p::swarm::SwarmEvent;
|
||||
use libp2p::{PeerId, Swarm};
|
||||
use rust_decimal::Decimal;
|
||||
|
@ -20,19 +21,10 @@ use std::collections::HashMap;
|
|||
use std::convert::{Infallible, TryInto};
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// A future that resolves to a tuple of `PeerId`, `transfer_proof::Request` and
|
||||
/// `Responder`.
|
||||
///
|
||||
/// When this future resolves, the `transfer_proof::Request` shall be sent to
|
||||
/// the peer identified by the `PeerId`. Once the request has been acknowledged
|
||||
/// by the peer, i.e. a `()` response has been received, the `Responder` shall
|
||||
/// be used to let the original sender know about the successful transfer.
|
||||
type OutgoingTransferProof =
|
||||
BoxFuture<'static, Result<(PeerId, transfer_proof::Request, bmrng::Responder<()>)>>;
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct EventLoop<LR>
|
||||
where
|
||||
|
@ -50,19 +42,70 @@ where
|
|||
|
||||
swap_sender: mpsc::Sender<Swap>,
|
||||
|
||||
/// Stores incoming [`EncryptedSignature`]s per swap.
|
||||
/// Stores where to send [`EncryptedSignature`]s to
|
||||
/// The corresponding receiver for this channel is stored in the EventLoopHandle
|
||||
/// that is responsible for the swap.
|
||||
///
|
||||
/// Once a [`EncryptedSignature`] has been sent to the EventLoopHandle,
|
||||
/// the sender is removed from this map.
|
||||
recv_encrypted_signature: HashMap<Uuid, bmrng::RequestSender<bitcoin::EncryptedSignature, ()>>,
|
||||
|
||||
/// Once we receive an [`EncryptedSignature`] from Bob, we forward it to the EventLoopHandle.
|
||||
/// Once the EventLoopHandle acknowledges the receipt of the [`EncryptedSignature`], we need to confirm this to Bob.
|
||||
/// When the EventLoopHandle acknowledges the receipt, a future in this collection resolves and returns the libp2p channel
|
||||
/// which we use to confirm to Bob that we have received the [`EncryptedSignature`].
|
||||
///
|
||||
/// Flow:
|
||||
/// 1. When signature forwarded via recv_encrypted_signature sender
|
||||
/// 2. New future pushed here to await EventLoopHandle's acknowledgement
|
||||
/// 3. When future completes, the EventLoop uses the ResponseChannel to send an acknowledgment to Bob
|
||||
/// 4. Future is removed from this collection
|
||||
inflight_encrypted_signatures: FuturesUnordered<BoxFuture<'static, ResponseChannel<()>>>,
|
||||
|
||||
send_transfer_proof: FuturesUnordered<OutgoingTransferProof>,
|
||||
/// Channel for sending transfer proofs to Bobs. The sender is shared with every EventLoopHandle.
|
||||
/// The receiver is polled by the event loop to send transfer proofs over the network to Bob.
|
||||
///
|
||||
/// Flow:
|
||||
/// 1. EventLoopHandle sends (PeerId, Request, Responder) through sender
|
||||
/// 2. Event loop receives and attempts to send to peer
|
||||
/// 3. Result (Ok or network failure) is sent back to EventLoopHandle
|
||||
#[allow(clippy::type_complexity)]
|
||||
outgoing_transfer_proofs_requests: tokio::sync::mpsc::UnboundedReceiver<(
|
||||
PeerId,
|
||||
transfer_proof::Request,
|
||||
oneshot::Sender<Result<(), OutboundFailure>>,
|
||||
)>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
outgoing_transfer_proofs_sender: tokio::sync::mpsc::UnboundedSender<(
|
||||
PeerId,
|
||||
transfer_proof::Request,
|
||||
oneshot::Sender<Result<(), OutboundFailure>>,
|
||||
)>,
|
||||
|
||||
/// Tracks [`transfer_proof::Request`]s which could not yet be sent because
|
||||
/// we are currently disconnected from the peer.
|
||||
buffered_transfer_proofs: HashMap<PeerId, Vec<(transfer_proof::Request, bmrng::Responder<()>)>>,
|
||||
/// Temporarily stores transfer proof requests for peers that are currently disconnected.
|
||||
///
|
||||
/// When a transfer proof cannot be sent because there's no connection to the peer:
|
||||
/// 1. It is moved from [`outgoing_transfer_proofs_requests`] to this buffer
|
||||
/// 2. Once a connection is established with the peer, the proof is send back into the [`outgoing_transfer_proofs_sender`]
|
||||
/// 3. The buffered request is then removed from this collection
|
||||
#[allow(clippy::type_complexity)]
|
||||
buffered_transfer_proofs: HashMap<
|
||||
PeerId,
|
||||
Vec<(
|
||||
transfer_proof::Request,
|
||||
oneshot::Sender<Result<(), OutboundFailure>>,
|
||||
)>,
|
||||
>,
|
||||
|
||||
/// Tracks [`transfer_proof::Request`]s which are currently inflight and
|
||||
/// awaiting an acknowledgement.
|
||||
inflight_transfer_proofs: HashMap<RequestId, bmrng::Responder<()>>,
|
||||
/// Tracks [`transfer_proof::Request`]s which are currently inflight and awaiting an acknowledgement from Bob
|
||||
///
|
||||
/// When a transfer proof is sent to Bob:
|
||||
/// 1. A unique request ID is generated by libp2p
|
||||
/// 2. The response channel is stored in this map with the request ID as key
|
||||
/// 3. When Bob acknowledges the proof, we use the stored channel to notify the EventLoopHandle
|
||||
/// 4. The entry is then removed from this map
|
||||
inflight_transfer_proofs:
|
||||
HashMap<OutboundRequestId, oneshot::Sender<Result<(), OutboundFailure>>>,
|
||||
}
|
||||
|
||||
impl<LR> EventLoop<LR>
|
||||
|
@ -82,6 +125,8 @@ where
|
|||
external_redeem_address: Option<bitcoin::Address>,
|
||||
) -> Result<(Self, mpsc::Receiver<Swap>)> {
|
||||
let swap_channel = MpscChannels::default();
|
||||
let (outgoing_transfer_proofs_sender, outgoing_transfer_proofs_requests) =
|
||||
tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
let event_loop = EventLoop {
|
||||
swarm,
|
||||
|
@ -96,7 +141,8 @@ where
|
|||
external_redeem_address,
|
||||
recv_encrypted_signature: Default::default(),
|
||||
inflight_encrypted_signatures: Default::default(),
|
||||
send_transfer_proof: Default::default(),
|
||||
outgoing_transfer_proofs_requests,
|
||||
outgoing_transfer_proofs_sender,
|
||||
buffered_transfer_proofs: Default::default(),
|
||||
inflight_transfer_proofs: Default::default(),
|
||||
};
|
||||
|
@ -110,7 +156,6 @@ where
|
|||
pub async fn run(mut self) {
|
||||
// ensure that these streams are NEVER empty, otherwise it will
|
||||
// terminate forever.
|
||||
self.send_transfer_proof.push(future::pending().boxed());
|
||||
self.inflight_encrypted_signatures
|
||||
.push(future::pending().boxed());
|
||||
|
||||
|
@ -201,8 +246,9 @@ where
|
|||
}
|
||||
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(());
|
||||
let _ = responder.send(Ok(()));
|
||||
}
|
||||
}
|
||||
SwarmEvent::Behaviour(OutEvent::EncryptedSignatureReceived{ msg, channel, peer }) => {
|
||||
|
@ -231,10 +277,33 @@ where
|
|||
continue;
|
||||
}
|
||||
|
||||
// Immediately acknowledge if we've already processed this encrypted signature
|
||||
// This handles the case where Bob didn't receive our previous acknowledgment
|
||||
// and is retrying sending the encrypted signature
|
||||
if let Ok(state) = self.db.get_state(swap_id).await {
|
||||
let state: AliceState = state.try_into()
|
||||
.expect("Alices database only contains Alice states");
|
||||
|
||||
// Check if we have already processed the encrypted signature
|
||||
if has_already_processed_enc_sig(&state) {
|
||||
tracing::warn!(%swap_id, "Received encrypted signature for swap in state {}. We have already processed this encrypted signature. Acknowledging immediately.", state);
|
||||
|
||||
// We push create a future that will resolve immediately, and returns the channel
|
||||
// This will be resolved in the next iteration of the event loop, and the acknowledgment will be sent to Bob
|
||||
self.inflight_encrypted_signatures.push(async move {
|
||||
channel
|
||||
}.boxed());
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let sender = match self.recv_encrypted_signature.remove(&swap_id) {
|
||||
Some(sender) => sender,
|
||||
None => {
|
||||
// TODO: Don't just drop encsig if we currently don't have a running swap for it, save in db
|
||||
// 1. Save the encrypted signature in the database
|
||||
// 2. Acknowledge the receipt of the encrypted signature
|
||||
tracing::warn!(%swap_id, "No sender for encrypted signature, maybe already handled?");
|
||||
continue;
|
||||
}
|
||||
|
@ -310,8 +379,28 @@ where
|
|||
SwarmEvent::Behaviour(OutEvent::Rendezvous(libp2p::rendezvous::client::Event::Registered { rendezvous_node, ttl, namespace })) => {
|
||||
tracing::info!("Successfully registered with rendezvous node: {} with namespace: {} and TTL: {:?}", rendezvous_node, namespace, ttl);
|
||||
}
|
||||
SwarmEvent::Behaviour(OutEvent::Rendezvous(libp2p::rendezvous::client::Event::RegisterFailed(error))) => {
|
||||
tracing::error!("Registration with rendezvous node failed: {:?}", error);
|
||||
SwarmEvent::Behaviour(OutEvent::Rendezvous(libp2p::rendezvous::client::Event::RegisterFailed { rendezvous_node, namespace, error })) => {
|
||||
tracing::error!("Registration with rendezvous node {} failed for namespace {}: {:?}", rendezvous_node, namespace, error);
|
||||
}
|
||||
SwarmEvent::Behaviour(OutEvent::OutboundRequestResponseFailure {peer, error, request_id, protocol}) => {
|
||||
tracing::error!(
|
||||
%peer,
|
||||
%request_id,
|
||||
%error,
|
||||
%protocol,
|
||||
"Failed to send request-response request to peer");
|
||||
|
||||
if let Some(responder) = self.inflight_transfer_proofs.remove(&request_id) {
|
||||
let _ = responder.send(Err(error));
|
||||
}
|
||||
}
|
||||
SwarmEvent::Behaviour(OutEvent::InboundRequestResponseFailure {peer, error, request_id, protocol}) => {
|
||||
tracing::error!(
|
||||
%peer,
|
||||
%request_id,
|
||||
%error,
|
||||
%protocol,
|
||||
"Failed to receive request-response request from peer");
|
||||
}
|
||||
SwarmEvent::Behaviour(OutEvent::Failure {peer, error}) => {
|
||||
tracing::error!(
|
||||
|
@ -321,23 +410,27 @@ where
|
|||
SwarmEvent::ConnectionEstablished { peer_id: peer, endpoint, .. } => {
|
||||
tracing::debug!(%peer, address = %endpoint.get_remote_address(), "New connection established");
|
||||
|
||||
// If we have buffered transfer proofs for this peer, we can now send them
|
||||
if let Some(transfer_proofs) = self.buffered_transfer_proofs.remove(&peer) {
|
||||
for (transfer_proof, responder) in transfer_proofs {
|
||||
tracing::debug!(%peer, "Found buffered transfer proof for peer");
|
||||
|
||||
let id = self.swarm.behaviour_mut().transfer_proof.send_request(&peer, transfer_proof);
|
||||
self.inflight_transfer_proofs.insert(id, responder);
|
||||
// We have an established connection to the peer, so we can add the transfer proof to the queue
|
||||
// This is then polled in the next iteration of the event loop, and attempted to be sent to the peer
|
||||
if let Err(e) = self.outgoing_transfer_proofs_sender.send((peer, transfer_proof, responder)) {
|
||||
tracing::error!(%peer, error = %e, "Failed to forward buffered transfer proof to event loop channel");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SwarmEvent::IncomingConnectionError { send_back_addr: address, error, .. } => {
|
||||
tracing::warn!(%address, "Failed to set up connection with peer: {:#}", error);
|
||||
}
|
||||
SwarmEvent::ConnectionClosed { peer_id: peer, num_established: 0, endpoint, cause: Some(error) } => {
|
||||
tracing::debug!(%peer, address = %endpoint.get_remote_address(), "Lost connection to peer: {:#}", error);
|
||||
SwarmEvent::ConnectionClosed { peer_id: peer, num_established: 0, endpoint, cause: Some(error), connection_id } => {
|
||||
tracing::debug!(%peer, address = %endpoint.get_remote_address(), %connection_id, "Lost connection to peer: {:#}", error);
|
||||
}
|
||||
SwarmEvent::ConnectionClosed { peer_id: peer, num_established: 0, endpoint, cause: None } => {
|
||||
tracing::info!(%peer, address = %endpoint.get_remote_address(), "Successfully closed connection");
|
||||
SwarmEvent::ConnectionClosed { peer_id: peer, num_established: 0, endpoint, cause: None, connection_id } => {
|
||||
tracing::info!(%peer, address = %endpoint.get_remote_address(), %connection_id, "Successfully closed connection");
|
||||
}
|
||||
SwarmEvent::NewListenAddr{address, ..} => {
|
||||
tracing::info!(%address, "New listen address reported");
|
||||
|
@ -345,26 +438,18 @@ where
|
|||
_ => {}
|
||||
}
|
||||
},
|
||||
next_transfer_proof = self.send_transfer_proof.next() => {
|
||||
match next_transfer_proof {
|
||||
Some(Ok((peer, transfer_proof, responder))) => {
|
||||
Some((peer, transfer_proof, responder)) = self.outgoing_transfer_proofs_requests.recv() => {
|
||||
// If we are not connected to the peer, we buffer the transfer proof
|
||||
if !self.swarm.behaviour_mut().transfer_proof.is_connected(&peer) {
|
||||
tracing::warn!(%peer, "No active connection to peer, buffering transfer proof");
|
||||
self.buffered_transfer_proofs.entry(peer).or_default().push((transfer_proof, responder));
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we are connected to the peer, we attempt to send the transfer proof
|
||||
let id = self.swarm.behaviour_mut().transfer_proof.send_request(&peer, transfer_proof);
|
||||
self.inflight_transfer_proofs.insert(id, responder);
|
||||
},
|
||||
Some(Err(error)) => {
|
||||
tracing::debug!("A swap stopped without sending a transfer proof: {:#}", error);
|
||||
}
|
||||
None => {
|
||||
unreachable!("stream of transfer proof receivers must never terminate")
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(response_channel) = self.inflight_encrypted_signatures.next() => {
|
||||
let _ = self.swarm.behaviour_mut().encrypted_signature.send_response(response_channel, ());
|
||||
}
|
||||
|
@ -387,13 +472,20 @@ where
|
|||
let balance = self.monero_wallet.get_balance().await?;
|
||||
|
||||
// use unlocked monero balance for quote
|
||||
let xmr = Amount::from_piconero(balance.unlocked_balance);
|
||||
let xmr_balance = Amount::from_piconero(balance.unlocked_balance);
|
||||
|
||||
let max_bitcoin_for_monero = xmr.max_bitcoin_for_price(ask_price).ok_or_else(|| {
|
||||
anyhow::anyhow!("Bitcoin price ({}) x Monero ({}) overflow", ask_price, xmr)
|
||||
let max_bitcoin_for_monero =
|
||||
xmr_balance
|
||||
.max_bitcoin_for_price(ask_price)
|
||||
.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"Bitcoin price ({}) x Monero ({}) overflow",
|
||||
ask_price,
|
||||
xmr_balance
|
||||
)
|
||||
})?;
|
||||
|
||||
tracing::debug!(%ask_price, %xmr, %max_bitcoin_for_monero);
|
||||
tracing::debug!(%ask_price, %xmr_balance, %max_bitcoin_for_monero, "Computed quote");
|
||||
|
||||
if min_buy > max_bitcoin_for_monero {
|
||||
tracing::warn!(
|
||||
|
@ -467,32 +559,23 @@ where
|
|||
/// Create a new [`EventLoopHandle`] that is scoped for communication with
|
||||
/// the given peer.
|
||||
fn new_handle(&mut self, peer: PeerId, swap_id: Uuid) -> EventLoopHandle {
|
||||
// we deliberately don't put timeouts on these channels because the swap always
|
||||
// races these futures against a timelock
|
||||
|
||||
let (transfer_proof_sender, mut transfer_proof_receiver) = bmrng::channel(1);
|
||||
let encrypted_signature = bmrng::channel(1);
|
||||
// Create a new channel for receiving encrypted signatures from Bob
|
||||
// The channel has a capacity of 1 since we only expect one signature per swap
|
||||
let (encrypted_signature_sender, encrypted_signature_receiver) = bmrng::channel(1);
|
||||
|
||||
// The sender is stored in the EventLoop
|
||||
// The receiver is stored in the EventLoopHandle
|
||||
// When a signature is received, the EventLoop uses the sender to notify the EventLoopHandle
|
||||
self.recv_encrypted_signature
|
||||
.insert(swap_id, encrypted_signature.0);
|
||||
.insert(swap_id, encrypted_signature_sender);
|
||||
|
||||
self.send_transfer_proof.push(
|
||||
async move {
|
||||
let (transfer_proof, responder) = transfer_proof_receiver.recv().await?;
|
||||
|
||||
let request = transfer_proof::Request {
|
||||
swap_id,
|
||||
tx_lock_proof: transfer_proof,
|
||||
};
|
||||
|
||||
Ok((peer, request, responder))
|
||||
}
|
||||
.boxed(),
|
||||
);
|
||||
let transfer_proof_sender = self.outgoing_transfer_proofs_sender.clone();
|
||||
|
||||
EventLoopHandle {
|
||||
recv_encrypted_signature: Some(encrypted_signature.1),
|
||||
send_transfer_proof: Some(transfer_proof_sender),
|
||||
swap_id,
|
||||
peer,
|
||||
recv_encrypted_signature: Some(encrypted_signature_receiver),
|
||||
transfer_proof_sender: Some(transfer_proof_sender),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -561,33 +644,98 @@ impl LatestRate for KrakenRate {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct EventLoopHandle {
|
||||
swap_id: Uuid,
|
||||
peer: PeerId,
|
||||
recv_encrypted_signature: Option<bmrng::RequestReceiver<bitcoin::EncryptedSignature, ()>>,
|
||||
send_transfer_proof: Option<bmrng::RequestSender<monero::TransferProof, ()>>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
transfer_proof_sender: Option<
|
||||
tokio::sync::mpsc::UnboundedSender<(
|
||||
PeerId,
|
||||
transfer_proof::Request,
|
||||
oneshot::Sender<Result<(), OutboundFailure>>,
|
||||
)>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl EventLoopHandle {
|
||||
pub async fn recv_encrypted_signature(&mut self) -> Result<bitcoin::EncryptedSignature> {
|
||||
let (tx_redeem_encsig, responder) = self
|
||||
.recv_encrypted_signature
|
||||
.take()
|
||||
.context("Encrypted signature was already received")?
|
||||
.recv()
|
||||
.await?;
|
||||
fn build_transfer_proof_request(
|
||||
&self,
|
||||
transfer_proof: monero::TransferProof,
|
||||
) -> transfer_proof::Request {
|
||||
transfer_proof::Request {
|
||||
swap_id: self.swap_id,
|
||||
tx_lock_proof: transfer_proof,
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait for an encrypted signature from Bob
|
||||
pub async fn recv_encrypted_signature(&mut self) -> Result<bitcoin::EncryptedSignature> {
|
||||
let receiver = self
|
||||
.recv_encrypted_signature
|
||||
.as_mut()
|
||||
.context("Encrypted signature was already received")?;
|
||||
|
||||
let (tx_redeem_encsig, responder) = receiver.recv().await?;
|
||||
|
||||
// Acknowledge receipt of the encrypted signature
|
||||
// This notifies the EventLoop that the signature has been processed
|
||||
// The EventLoop can then send an acknowledgement back to Bob over the network
|
||||
responder
|
||||
.respond(())
|
||||
.context("Failed to acknowledge receipt of encrypted signature")?;
|
||||
|
||||
// Only take after successful receipt and acknowledgement
|
||||
self.recv_encrypted_signature.take();
|
||||
|
||||
Ok(tx_redeem_encsig)
|
||||
}
|
||||
|
||||
/// Send a transfer proof to Bob
|
||||
///
|
||||
/// This function will retry indefinitely until the transfer proof is sent successfully
|
||||
/// and acknowledged by Bob
|
||||
///
|
||||
/// This will fail if
|
||||
/// 1. the transfer proof has already been sent once
|
||||
/// 2. there is an error with the bmrng channel
|
||||
pub async fn send_transfer_proof(&mut self, msg: monero::TransferProof) -> Result<()> {
|
||||
self.send_transfer_proof
|
||||
.take()
|
||||
.context("Transfer proof was already sent")?
|
||||
.send_receive(msg)
|
||||
.await
|
||||
.context("Failed to send transfer proof")?;
|
||||
let sender = self
|
||||
.transfer_proof_sender
|
||||
.as_ref()
|
||||
.context("Transfer proof was already sent")?;
|
||||
|
||||
// We will retry indefinitely until we succeed
|
||||
let backoff = backoff::ExponentialBackoffBuilder::new()
|
||||
.with_max_elapsed_time(None)
|
||||
.with_max_interval(Duration::from_secs(60))
|
||||
.build();
|
||||
|
||||
let transfer_proof = self.build_transfer_proof_request(msg);
|
||||
|
||||
backoff::future::retry(backoff, || async {
|
||||
// Create a oneshot channel to receive the acknowledgment of the transfer proof
|
||||
let (singular_sender, singular_receiver) = oneshot::channel();
|
||||
|
||||
if let Err(err) = sender.send((self.peer, transfer_proof.clone(), singular_sender)) {
|
||||
let err = anyhow!(err).context("Failed to communicate transfer proof through event loop channel");
|
||||
tracing::error!(%err, swap_id = %self.swap_id, "Failed to send transfer proof");
|
||||
return Err(backoff::Error::permanent(err));
|
||||
}
|
||||
|
||||
match singular_receiver.await {
|
||||
Ok(Ok(())) => Ok(()),
|
||||
Ok(Err(err)) => {
|
||||
tracing::warn!(%err, "Failed to send transfer proof due to a network error. We will retry");
|
||||
Err(backoff::Error::transient(anyhow!(err)))
|
||||
}
|
||||
Err(_) => {
|
||||
Err(backoff::Error::permanent(anyhow!("The sender channel should never be closed without sending a response")))
|
||||
}
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
|
||||
self.transfer_proof_sender.take();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -11,42 +11,36 @@ use crate::network::{
|
|||
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::identify::{Identify, IdentifyConfig, IdentifyEvent};
|
||||
use libp2p::ping::{Ping, PingConfig, PingEvent};
|
||||
use libp2p::request_response::{RequestId, ResponseChannel};
|
||||
use libp2p::request_response::ResponseChannel;
|
||||
use libp2p::swarm::dial_opts::PeerCondition;
|
||||
use libp2p::swarm::{
|
||||
IntoProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, PollParameters,
|
||||
ProtocolsHandler,
|
||||
};
|
||||
use libp2p::tcp::TokioTcpConfig;
|
||||
use libp2p::websocket::WsConfig;
|
||||
use libp2p::{identity, Multiaddr, NetworkBehaviour, PeerId, Transport};
|
||||
use libp2p::swarm::NetworkBehaviour;
|
||||
use libp2p::{Multiaddr, PeerId};
|
||||
use std::task::Poll;
|
||||
use std::time::Duration;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub mod transport {
|
||||
use libp2p::{dns, identity, tcp, 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 tcp = tcp::tokio::Transport::new(tcp::Config::new().nodelay(true));
|
||||
let tcp_with_dns = dns::tokio::Transport::system(tcp)?;
|
||||
|
||||
let transport = tcp_with_dns.or_transport(websocket_with_dns).boxed();
|
||||
|
||||
authenticate_and_multiplex(transport, identity)
|
||||
authenticate_and_multiplex(tcp_with_dns.boxed(), identity)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod behaviour {
|
||||
use libp2p::swarm::behaviour::toggle::Toggle;
|
||||
use libp2p::{
|
||||
identify, identity, ping,
|
||||
request_response::{InboundFailure, InboundRequestId, OutboundFailure, OutboundRequestId},
|
||||
swarm::behaviour::toggle::Toggle,
|
||||
};
|
||||
|
||||
use super::{rendezvous::RendezvousNode, *};
|
||||
|
||||
|
@ -71,7 +65,7 @@ pub mod behaviour {
|
|||
},
|
||||
TransferProofAcknowledged {
|
||||
peer: PeerId,
|
||||
id: RequestId,
|
||||
id: OutboundRequestId,
|
||||
},
|
||||
EncryptedSignatureReceived {
|
||||
msg: encrypted_signature::Request,
|
||||
|
@ -84,6 +78,18 @@ pub mod behaviour {
|
|||
peer: PeerId,
|
||||
},
|
||||
Rendezvous(libp2p::rendezvous::client::Event),
|
||||
OutboundRequestResponseFailure {
|
||||
peer: PeerId,
|
||||
error: OutboundFailure,
|
||||
request_id: OutboundRequestId,
|
||||
protocol: String,
|
||||
},
|
||||
InboundRequestResponseFailure {
|
||||
peer: PeerId,
|
||||
error: InboundFailure,
|
||||
request_id: InboundRequestId,
|
||||
protocol: String,
|
||||
},
|
||||
Failure {
|
||||
peer: PeerId,
|
||||
error: Error,
|
||||
|
@ -108,7 +114,6 @@ pub mod behaviour {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A `NetworkBehaviour` that represents an XMR/BTC swap node as Alice.
|
||||
#[derive(NetworkBehaviour)]
|
||||
#[behaviour(out_event = "OutEvent", event_process = false)]
|
||||
|
@ -123,12 +128,12 @@ pub mod behaviour {
|
|||
pub transfer_proof: transfer_proof::Behaviour,
|
||||
pub cooperative_xmr_redeem: cooperative_xmr_redeem_after_punish::Behaviour,
|
||||
pub encrypted_signature: encrypted_signature::Behaviour,
|
||||
pub identify: Identify,
|
||||
pub identify: identify::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,
|
||||
ping: ping::Behaviour,
|
||||
}
|
||||
|
||||
impl<LR> Behaviour<LR>
|
||||
|
@ -147,9 +152,12 @@ pub mod behaviour {
|
|||
let (identity, namespace) = identify_params;
|
||||
let agent_version = format!("asb/{} ({})", env!("CARGO_PKG_VERSION"), namespace);
|
||||
let protocol_version = "/comit/xmr/btc/1.0.0".to_string();
|
||||
let identifyConfig = IdentifyConfig::new(protocol_version, identity.public())
|
||||
|
||||
let identifyConfig = identify::Config::new(protocol_version, identity.public())
|
||||
.with_agent_version(agent_version);
|
||||
|
||||
let pingConfig = ping::Config::new().with_timeout(Duration::from_secs(60));
|
||||
|
||||
let behaviour = if rendezvous_nodes.is_empty() {
|
||||
None
|
||||
} else {
|
||||
|
@ -169,20 +177,20 @@ pub mod behaviour {
|
|||
transfer_proof: transfer_proof::alice(),
|
||||
encrypted_signature: encrypted_signature::alice(),
|
||||
cooperative_xmr_redeem: cooperative_xmr_redeem_after_punish::alice(),
|
||||
ping: Ping::new(PingConfig::new().with_keep_alive(true)),
|
||||
identify: Identify::new(identifyConfig),
|
||||
ping: ping::Behaviour::new(pingConfig),
|
||||
identify: identify::Behaviour::new(identifyConfig),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PingEvent> for OutEvent {
|
||||
fn from(_: PingEvent) -> Self {
|
||||
impl From<ping::Event> for OutEvent {
|
||||
fn from(_: ping::Event) -> Self {
|
||||
OutEvent::Other
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IdentifyEvent> for OutEvent {
|
||||
fn from(_: IdentifyEvent) -> Self {
|
||||
impl From<identify::Event> for OutEvent {
|
||||
fn from(_: identify::Event) -> Self {
|
||||
OutEvent::Other
|
||||
}
|
||||
}
|
||||
|
@ -196,10 +204,16 @@ pub mod behaviour {
|
|||
|
||||
pub mod rendezvous {
|
||||
use super::*;
|
||||
use libp2p::identity;
|
||||
use libp2p::rendezvous::client::RegisterError;
|
||||
use libp2p::swarm::dial_opts::DialOpts;
|
||||
use libp2p::swarm::DialError;
|
||||
use libp2p::swarm::{
|
||||
ConnectionDenied, ConnectionId, FromSwarm, THandler, THandlerInEvent, THandlerOutEvent,
|
||||
ToSwarm,
|
||||
};
|
||||
use std::collections::VecDeque;
|
||||
use std::pin::Pin;
|
||||
use std::task::Context;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
enum ConnectionStatus {
|
||||
|
@ -222,6 +236,7 @@ pub mod rendezvous {
|
|||
to_dial: VecDeque<PeerId>,
|
||||
}
|
||||
|
||||
/// A node running the rendezvous server protocol.
|
||||
pub struct RendezvousNode {
|
||||
pub address: Multiaddr,
|
||||
connection_status: ConnectionStatus,
|
||||
|
@ -266,99 +281,151 @@ pub mod rendezvous {
|
|||
}
|
||||
}
|
||||
|
||||
/// Calls the rendezvous register method of the node at node_index in the Vec of rendezvous nodes
|
||||
fn register(&mut self, node_index: usize) {
|
||||
let node = &self.rendezvous_nodes[node_index];
|
||||
self.inner
|
||||
.register(node.namespace.into(), node.peer_id, node.registration_ttl);
|
||||
/// Registers the rendezvous node at the given index.
|
||||
/// Also sets the registration status to [`RegistrationStatus::Pending`].
|
||||
pub fn register(&mut self, node_index: usize) -> Result<(), RegisterError> {
|
||||
let node = &mut self.rendezvous_nodes[node_index];
|
||||
node.set_registration(RegistrationStatus::Pending);
|
||||
let (namespace, peer_id, ttl) =
|
||||
(node.namespace.into(), node.peer_id, node.registration_ttl);
|
||||
self.inner.register(namespace, peer_id, ttl)
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkBehaviour for Behaviour {
|
||||
type ProtocolsHandler =
|
||||
<libp2p::rendezvous::client::Behaviour as NetworkBehaviour>::ProtocolsHandler;
|
||||
type OutEvent = libp2p::rendezvous::client::Event;
|
||||
type ConnectionHandler =
|
||||
<libp2p::rendezvous::client::Behaviour as NetworkBehaviour>::ConnectionHandler;
|
||||
type ToSwarm = libp2p::rendezvous::client::Event;
|
||||
|
||||
fn new_handler(&mut self) -> Self::ProtocolsHandler {
|
||||
self.inner.new_handler()
|
||||
}
|
||||
|
||||
fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec<Multiaddr> {
|
||||
for node in self.rendezvous_nodes.iter() {
|
||||
if peer_id == &node.peer_id {
|
||||
return vec![node.address.clone()];
|
||||
}
|
||||
}
|
||||
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn inject_connected(&mut self, peer_id: &PeerId) {
|
||||
for i in 0..self.rendezvous_nodes.len() {
|
||||
if peer_id == &self.rendezvous_nodes[i].peer_id {
|
||||
self.rendezvous_nodes[i].set_connection(ConnectionStatus::Connected);
|
||||
match &self.rendezvous_nodes[i].registration_status {
|
||||
RegistrationStatus::RegisterOnNextConnection => {
|
||||
self.register(i);
|
||||
self.rendezvous_nodes[i].set_registration(RegistrationStatus::Pending);
|
||||
}
|
||||
RegistrationStatus::Registered { .. } => {}
|
||||
RegistrationStatus::Pending => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_disconnected(&mut self, peer_id: &PeerId) {
|
||||
for i in 0..self.rendezvous_nodes.len() {
|
||||
let node = &mut self.rendezvous_nodes[i];
|
||||
if peer_id == &node.peer_id {
|
||||
node.connection_status = ConnectionStatus::Disconnected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_event(
|
||||
fn handle_established_inbound_connection(
|
||||
&mut self,
|
||||
peer_id: PeerId,
|
||||
connection: ConnectionId,
|
||||
event: <<Self::ProtocolsHandler as IntoProtocolsHandler>::Handler as ProtocolsHandler>::OutEvent,
|
||||
) {
|
||||
self.inner.inject_event(peer_id, connection, event)
|
||||
connection_id: ConnectionId,
|
||||
peer: PeerId,
|
||||
local_addr: &Multiaddr,
|
||||
remote_addr: &Multiaddr,
|
||||
) -> Result<THandler<Self>, ConnectionDenied> {
|
||||
self.inner.handle_established_inbound_connection(
|
||||
connection_id,
|
||||
peer,
|
||||
local_addr,
|
||||
remote_addr,
|
||||
)
|
||||
}
|
||||
|
||||
fn inject_dial_failure(
|
||||
fn handle_established_outbound_connection(
|
||||
&mut self,
|
||||
peer_id: Option<PeerId>,
|
||||
_handler: Self::ProtocolsHandler,
|
||||
_error: &DialError,
|
||||
) {
|
||||
for i in 0..self.rendezvous_nodes.len() {
|
||||
let node = &mut self.rendezvous_nodes[i];
|
||||
if let Some(id) = peer_id {
|
||||
if id == node.peer_id {
|
||||
node.connection_status = ConnectionStatus::Disconnected;
|
||||
}
|
||||
}
|
||||
}
|
||||
connection_id: ConnectionId,
|
||||
peer: PeerId,
|
||||
addr: &Multiaddr,
|
||||
role_override: libp2p::core::Endpoint,
|
||||
) -> Result<THandler<Self>, ConnectionDenied> {
|
||||
self.inner.handle_established_outbound_connection(
|
||||
connection_id,
|
||||
peer,
|
||||
addr,
|
||||
role_override,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn poll(
|
||||
fn handle_pending_outbound_connection(
|
||||
&mut self,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
params: &mut impl PollParameters,
|
||||
) -> Poll<NetworkBehaviourAction<Self::OutEvent, Self::ProtocolsHandler>> {
|
||||
if let Some(peer_id) = self.to_dial.pop_front() {
|
||||
return Poll::Ready(NetworkBehaviourAction::Dial {
|
||||
opts: DialOpts::peer_id(peer_id)
|
||||
.condition(PeerCondition::Disconnected)
|
||||
.build(),
|
||||
connection_id: ConnectionId,
|
||||
maybe_peer: Option<PeerId>,
|
||||
addresses: &[Multiaddr],
|
||||
effective_role: libp2p::core::Endpoint,
|
||||
) -> std::result::Result<Vec<Multiaddr>, ConnectionDenied> {
|
||||
self.inner.handle_pending_outbound_connection(
|
||||
connection_id,
|
||||
maybe_peer,
|
||||
addresses,
|
||||
effective_role,
|
||||
)
|
||||
}
|
||||
|
||||
handler: Self::ProtocolsHandler::new(Duration::from_secs(30)),
|
||||
fn on_swarm_event(&mut self, event: FromSwarm<'_>) {
|
||||
match event {
|
||||
FromSwarm::ConnectionEstablished(peer) => {
|
||||
let peer_id = peer.peer_id;
|
||||
|
||||
// Find the rendezvous node that matches the peer id, else do nothing.
|
||||
if let Some(index) = self
|
||||
.rendezvous_nodes
|
||||
.iter_mut()
|
||||
.position(|node| node.peer_id == peer_id)
|
||||
{
|
||||
let rendezvous_node = &mut self.rendezvous_nodes[index];
|
||||
rendezvous_node.set_connection(ConnectionStatus::Connected);
|
||||
|
||||
if let RegistrationStatus::RegisterOnNextConnection =
|
||||
rendezvous_node.registration_status
|
||||
{
|
||||
let _ = self.register(index).inspect_err(|err| {
|
||||
tracing::error!(
|
||||
error=%err,
|
||||
rendezvous_node=%peer_id,
|
||||
"Failed to register with rendezvous node");
|
||||
});
|
||||
}
|
||||
// check the status of each rendezvous node
|
||||
}
|
||||
}
|
||||
FromSwarm::ConnectionClosed(peer) => {
|
||||
// Update the connection status of the rendezvous node that disconnected.
|
||||
if let Some(node) = self
|
||||
.rendezvous_nodes
|
||||
.iter_mut()
|
||||
.find(|node| node.peer_id == peer.peer_id)
|
||||
{
|
||||
node.set_connection(ConnectionStatus::Disconnected);
|
||||
}
|
||||
}
|
||||
FromSwarm::DialFailure(peer) => {
|
||||
// Update the connection status of the rendezvous node that failed to connect.
|
||||
if let Some(peer_id) = peer.peer_id {
|
||||
if let Some(node) = self
|
||||
.rendezvous_nodes
|
||||
.iter_mut()
|
||||
.find(|node| node.peer_id == peer_id)
|
||||
{
|
||||
node.set_connection(ConnectionStatus::Disconnected);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.inner.on_swarm_event(event);
|
||||
}
|
||||
|
||||
fn on_connection_handler_event(
|
||||
&mut self,
|
||||
peer_id: PeerId,
|
||||
connection_id: ConnectionId,
|
||||
event: THandlerOutEvent<Self>,
|
||||
) {
|
||||
self.inner
|
||||
.on_connection_handler_event(peer_id, connection_id, event)
|
||||
}
|
||||
|
||||
fn poll(
|
||||
&mut self,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> {
|
||||
if let Some(peer_id) = self.to_dial.pop_front() {
|
||||
return Poll::Ready(ToSwarm::Dial {
|
||||
opts: DialOpts::peer_id(peer_id)
|
||||
.addresses(vec![self
|
||||
.rendezvous_nodes
|
||||
.iter()
|
||||
.find(|node| node.peer_id == peer_id)
|
||||
.map(|node| node.address.clone())
|
||||
.expect("We should have a rendezvous node for the peer id")])
|
||||
.condition(PeerCondition::Disconnected)
|
||||
// TODO: this makes the behaviour call `NetworkBehaviour::handle_pending_outbound_connection`
|
||||
// but we don't implement it
|
||||
.extend_addresses_through_behaviour()
|
||||
.build(),
|
||||
});
|
||||
}
|
||||
// Check the status of each rendezvous node
|
||||
for i in 0..self.rendezvous_nodes.len() {
|
||||
let connection_status = self.rendezvous_nodes[i].connection_status.clone();
|
||||
match &mut self.rendezvous_nodes[i].registration_status {
|
||||
|
@ -369,17 +436,19 @@ pub mod rendezvous {
|
|||
}
|
||||
ConnectionStatus::Dialling => {}
|
||||
ConnectionStatus::Connected => {
|
||||
self.rendezvous_nodes[i].set_registration(RegistrationStatus::Pending);
|
||||
self.register(i);
|
||||
let _ = self.register(i);
|
||||
}
|
||||
},
|
||||
RegistrationStatus::Registered { re_register_in } => {
|
||||
if let Poll::Ready(()) = re_register_in.poll_unpin(cx) {
|
||||
match connection_status {
|
||||
ConnectionStatus::Connected => {
|
||||
self.rendezvous_nodes[i]
|
||||
.set_registration(RegistrationStatus::Pending);
|
||||
self.register(i);
|
||||
let _ = self.register(i).inspect_err(|err| {
|
||||
tracing::error!(
|
||||
error=%err,
|
||||
rendezvous_node=%self.rendezvous_nodes[i].peer_id,
|
||||
"Failed to register with rendezvous node");
|
||||
});
|
||||
}
|
||||
ConnectionStatus::Disconnected => {
|
||||
self.rendezvous_nodes[i].set_registration(
|
||||
|
@ -395,10 +464,10 @@ pub mod rendezvous {
|
|||
}
|
||||
}
|
||||
|
||||
let inner_poll = self.inner.poll(cx, params);
|
||||
let inner_poll = self.inner.poll(cx);
|
||||
|
||||
// reset the timer for the specific rendezvous node if we successfully registered
|
||||
if let Poll::Ready(NetworkBehaviourAction::GenerateEvent(
|
||||
if let Poll::Ready(ToSwarm::GenerateEvent(
|
||||
libp2p::rendezvous::client::Event::Registered {
|
||||
ttl,
|
||||
rendezvous_node,
|
||||
|
@ -434,7 +503,7 @@ pub mod rendezvous {
|
|||
#[tokio::test]
|
||||
async fn given_no_initial_connection_when_constructed_asb_connects_and_registers_with_rendezvous_node(
|
||||
) {
|
||||
let mut rendezvous_node = new_swarm(|_, _| {
|
||||
let mut rendezvous_node = new_swarm(|_| {
|
||||
rendezvous::server::Behaviour::new(rendezvous::server::Config::default())
|
||||
});
|
||||
let address = rendezvous_node.listen_on_random_memory_address().await;
|
||||
|
@ -445,7 +514,7 @@ pub mod rendezvous {
|
|||
None,
|
||||
);
|
||||
|
||||
let mut asb = new_swarm(|_, identity| {
|
||||
let mut asb = new_swarm(|identity| {
|
||||
super::rendezvous::Behaviour::new(identity, vec![rendezvous_point])
|
||||
});
|
||||
asb.listen_on_random_memory_address().await; // this adds an external address
|
||||
|
@ -473,7 +542,7 @@ pub mod rendezvous {
|
|||
|
||||
#[tokio::test]
|
||||
async fn asb_automatically_re_registers() {
|
||||
let mut rendezvous_node = new_swarm(|_, _| {
|
||||
let mut rendezvous_node = new_swarm(|_| {
|
||||
rendezvous::server::Behaviour::new(
|
||||
rendezvous::server::Config::default().with_min_ttl(2),
|
||||
)
|
||||
|
@ -486,7 +555,7 @@ pub mod rendezvous {
|
|||
Some(5),
|
||||
);
|
||||
|
||||
let mut asb = new_swarm(|_, identity| {
|
||||
let mut asb = new_swarm(|identity| {
|
||||
super::rendezvous::Behaviour::new(identity, vec![rendezvous_point])
|
||||
});
|
||||
asb.listen_on_random_memory_address().await; // this adds an external address
|
||||
|
@ -525,7 +594,7 @@ pub mod rendezvous {
|
|||
let mut registrations = HashMap::new();
|
||||
// register with 5 rendezvous nodes
|
||||
for _ in 0..5 {
|
||||
let mut rendezvous = new_swarm(|_, _| {
|
||||
let mut rendezvous = new_swarm(|_| {
|
||||
rendezvous::server::Behaviour::new(
|
||||
rendezvous::server::Config::default().with_min_ttl(2),
|
||||
)
|
||||
|
@ -546,9 +615,8 @@ pub mod rendezvous {
|
|||
});
|
||||
}
|
||||
|
||||
let mut asb = new_swarm(|_, identity| {
|
||||
super::rendezvous::Behaviour::new(identity, rendezvous_nodes)
|
||||
});
|
||||
let mut asb =
|
||||
new_swarm(|identity| super::rendezvous::Behaviour::new(identity, rendezvous_nodes));
|
||||
asb.listen_on_random_memory_address().await; // this adds an external address
|
||||
|
||||
let handle = tokio::spawn(async move {
|
||||
|
|
|
@ -16,7 +16,6 @@ use anyhow::{bail, Context, Result};
|
|||
use comfy_table::Table;
|
||||
use libp2p::core::multiaddr::Protocol;
|
||||
use libp2p::core::Multiaddr;
|
||||
use libp2p::swarm::AddressScore;
|
||||
use libp2p::Swarm;
|
||||
use std::convert::TryInto;
|
||||
use std::env;
|
||||
|
@ -186,18 +185,15 @@ pub async fn main() -> Result<()> {
|
|||
)?;
|
||||
|
||||
for listen in config.network.listen.clone() {
|
||||
Swarm::listen_on(&mut swarm, listen.clone())
|
||||
.with_context(|| format!("Failed to listen on network interface {}", listen))?;
|
||||
if let Err(e) = Swarm::listen_on(&mut swarm, listen.clone()) {
|
||||
tracing::warn!("Failed to listen on network interface {}: {}. Consider removing it from the config.", listen, e);
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!(peer_id = %swarm.local_peer_id(), "Network layer initialized");
|
||||
|
||||
for external_address in config.network.external_addresses {
|
||||
let _ = Swarm::add_external_address(
|
||||
&mut swarm,
|
||||
external_address,
|
||||
AddressScore::Infinite,
|
||||
);
|
||||
Swarm::add_external_address(&mut swarm, external_address);
|
||||
}
|
||||
|
||||
let (event_loop, mut swap_receiver) = EventLoop::new(
|
||||
|
|
|
@ -874,6 +874,11 @@ impl EstimateFeeRate for Client {
|
|||
// https://github.com/romanz/electrs/blob/f9cf5386d1b5de6769ee271df5eef324aa9491bc/src/rpc.rs#L213
|
||||
// Returned estimated fees are per BTC/kb.
|
||||
let fee_per_byte = self.electrum.estimate_fee(target_block.into())?;
|
||||
|
||||
if fee_per_byte < 0.0 {
|
||||
bail!("Fee per byte returned by electrum server is negative: {}. This may indicate that fee estimation is not supported by this server", fee_per_byte);
|
||||
}
|
||||
|
||||
// we do not expect fees being that high.
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
Ok(FeeRate::from_btc_per_kvb(fee_per_byte as f32))
|
||||
|
|
133
swap/src/cli.rs
133
swap/src/cli.rs
|
@ -23,12 +23,15 @@ mod tests {
|
|||
use crate::network::rendezvous::XmrBtcNamespace;
|
||||
use crate::network::test::{new_swarm, SwarmExt};
|
||||
use futures::StreamExt;
|
||||
use libp2p::core::Endpoint;
|
||||
use libp2p::multiaddr::Protocol;
|
||||
use libp2p::request_response::RequestResponseEvent;
|
||||
use libp2p::swarm::{AddressScore, NetworkBehaviourEventProcess};
|
||||
use libp2p::{identity, rendezvous, Multiaddr, PeerId};
|
||||
use libp2p::swarm::{
|
||||
ConnectionDenied, ConnectionId, FromSwarm, THandlerInEvent, THandlerOutEvent, ToSwarm,
|
||||
};
|
||||
use libp2p::{identity, rendezvous, request_response, Multiaddr, PeerId};
|
||||
use std::collections::HashSet;
|
||||
use std::iter::FromIterator;
|
||||
use std::task::Poll;
|
||||
use std::time::Duration;
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -57,7 +60,7 @@ mod tests {
|
|||
}
|
||||
|
||||
async fn setup_rendezvous_point() -> (Multiaddr, PeerId) {
|
||||
let mut rendezvous_node = new_swarm(|_, _| RendezvousPointBehaviour::default());
|
||||
let mut rendezvous_node = new_swarm(|_| RendezvousPointBehaviour::default());
|
||||
let rendezvous_address = rendezvous_node.listen_on_tcp_localhost().await;
|
||||
let rendezvous_peer_id = *rendezvous_node.local_peer_id();
|
||||
|
||||
|
@ -81,22 +84,23 @@ mod tests {
|
|||
max_quantity: bitcoin::Amount::from_sat(9001),
|
||||
};
|
||||
|
||||
let mut asb = new_swarm(|_, identity| {
|
||||
let mut asb = new_swarm(|identity| {
|
||||
let rendezvous_node =
|
||||
RendezvousNode::new(rendezvous_address, rendezvous_peer_id, namespace, None);
|
||||
let rendezvous = asb::rendezvous::Behaviour::new(identity, vec![rendezvous_node]);
|
||||
|
||||
StaticQuoteAsbBehaviour {
|
||||
inner: StaticQuoteAsbBehaviourInner {
|
||||
rendezvous,
|
||||
ping: Default::default(),
|
||||
quote: quote::asb(),
|
||||
},
|
||||
static_quote,
|
||||
registered: false,
|
||||
}
|
||||
});
|
||||
|
||||
let asb_address = asb.listen_on_tcp_localhost().await;
|
||||
asb.add_external_address(asb_address.clone(), AddressScore::Infinite);
|
||||
asb.add_external_address(asb_address.clone());
|
||||
|
||||
let asb_peer_id = *asb.local_peer_id();
|
||||
|
||||
|
@ -113,62 +117,114 @@ mod tests {
|
|||
});
|
||||
|
||||
Seller {
|
||||
multiaddr: asb_address.with(Protocol::P2p(asb_peer_id.into())),
|
||||
multiaddr: asb_address.with(Protocol::P2p(asb_peer_id)),
|
||||
status: Status::Online(static_quote),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(libp2p::NetworkBehaviour)]
|
||||
#[behaviour(event_process = true)]
|
||||
struct StaticQuoteAsbBehaviour {
|
||||
#[derive(libp2p::swarm::NetworkBehaviour)]
|
||||
struct StaticQuoteAsbBehaviourInner {
|
||||
rendezvous: asb::rendezvous::Behaviour,
|
||||
// Support `Ping` as a workaround until https://github.com/libp2p/rust-libp2p/issues/2109 is fixed.
|
||||
ping: libp2p::ping::Ping,
|
||||
quote: quote::Behaviour,
|
||||
}
|
||||
|
||||
#[behaviour(ignore)]
|
||||
struct StaticQuoteAsbBehaviour {
|
||||
inner: StaticQuoteAsbBehaviourInner,
|
||||
static_quote: BidQuote,
|
||||
#[behaviour(ignore)]
|
||||
registered: bool,
|
||||
}
|
||||
impl NetworkBehaviourEventProcess<rendezvous::client::Event> for StaticQuoteAsbBehaviour {
|
||||
fn inject_event(&mut self, event: rendezvous::client::Event) {
|
||||
if let rendezvous::client::Event::Registered { .. } = event {
|
||||
|
||||
impl libp2p::swarm::NetworkBehaviour for StaticQuoteAsbBehaviour {
|
||||
type ConnectionHandler =
|
||||
<StaticQuoteAsbBehaviourInner as libp2p::swarm::NetworkBehaviour>::ConnectionHandler;
|
||||
type ToSwarm = <StaticQuoteAsbBehaviourInner as libp2p::swarm::NetworkBehaviour>::ToSwarm;
|
||||
|
||||
fn handle_established_inbound_connection(
|
||||
&mut self,
|
||||
connection_id: ConnectionId,
|
||||
peer: PeerId,
|
||||
local_addr: &Multiaddr,
|
||||
remote_addr: &Multiaddr,
|
||||
) -> Result<libp2p::swarm::THandler<Self>, ConnectionDenied> {
|
||||
self.inner.handle_established_inbound_connection(
|
||||
connection_id,
|
||||
peer,
|
||||
local_addr,
|
||||
remote_addr,
|
||||
)
|
||||
}
|
||||
|
||||
fn handle_established_outbound_connection(
|
||||
&mut self,
|
||||
connection_id: ConnectionId,
|
||||
peer: PeerId,
|
||||
addr: &Multiaddr,
|
||||
role_override: Endpoint,
|
||||
) -> Result<libp2p::swarm::THandler<Self>, ConnectionDenied> {
|
||||
self.inner.handle_established_outbound_connection(
|
||||
connection_id,
|
||||
peer,
|
||||
addr,
|
||||
role_override,
|
||||
)
|
||||
}
|
||||
|
||||
fn on_swarm_event(&mut self, event: FromSwarm<'_>) {
|
||||
self.inner.on_swarm_event(event);
|
||||
}
|
||||
|
||||
fn on_connection_handler_event(
|
||||
&mut self,
|
||||
peer_id: PeerId,
|
||||
connection_id: ConnectionId,
|
||||
event: THandlerOutEvent<Self>,
|
||||
) {
|
||||
self.inner
|
||||
.on_connection_handler_event(peer_id, connection_id, event);
|
||||
}
|
||||
|
||||
fn poll(
|
||||
&mut self,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> {
|
||||
match self.inner.poll(cx) {
|
||||
Poll::Ready(ToSwarm::GenerateEvent(event)) => match event {
|
||||
StaticQuoteAsbBehaviourInnerEvent::Rendezvous(rendezvous_event) => {
|
||||
if let rendezvous::client::Event::Registered { .. } = rendezvous_event {
|
||||
self.registered = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkBehaviourEventProcess<libp2p::ping::PingEvent> for StaticQuoteAsbBehaviour {
|
||||
fn inject_event(&mut self, _: libp2p::ping::PingEvent) {}
|
||||
Poll::Ready(ToSwarm::GenerateEvent(
|
||||
StaticQuoteAsbBehaviourInnerEvent::Rendezvous(rendezvous_event),
|
||||
))
|
||||
}
|
||||
impl NetworkBehaviourEventProcess<quote::OutEvent> for StaticQuoteAsbBehaviour {
|
||||
fn inject_event(&mut self, event: quote::OutEvent) {
|
||||
if let RequestResponseEvent::Message {
|
||||
StaticQuoteAsbBehaviourInnerEvent::Quote(quote_event) => {
|
||||
if let request_response::Event::Message {
|
||||
message: quote::Message::Request { channel, .. },
|
||||
..
|
||||
} = event
|
||||
} = quote_event
|
||||
{
|
||||
self.quote
|
||||
self.inner
|
||||
.quote
|
||||
.send_response(channel, self.static_quote)
|
||||
.unwrap();
|
||||
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
Poll::Ready(ToSwarm::GenerateEvent(
|
||||
StaticQuoteAsbBehaviourInnerEvent::Quote(quote_event),
|
||||
))
|
||||
}
|
||||
},
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(libp2p::NetworkBehaviour)]
|
||||
#[behaviour(event_process = true)]
|
||||
#[derive(libp2p::swarm::NetworkBehaviour)]
|
||||
struct RendezvousPointBehaviour {
|
||||
rendezvous: rendezvous::server::Behaviour,
|
||||
// Support `Ping` as a workaround until https://github.com/libp2p/rust-libp2p/issues/2109 is fixed.
|
||||
ping: libp2p::ping::Ping,
|
||||
}
|
||||
|
||||
impl NetworkBehaviourEventProcess<rendezvous::server::Event> for RendezvousPointBehaviour {
|
||||
fn inject_event(&mut self, _: rendezvous::server::Event) {}
|
||||
}
|
||||
impl NetworkBehaviourEventProcess<libp2p::ping::PingEvent> for RendezvousPointBehaviour {
|
||||
fn inject_event(&mut self, _: libp2p::ping::PingEvent) {}
|
||||
}
|
||||
|
||||
impl Default for RendezvousPointBehaviour {
|
||||
|
@ -177,7 +233,6 @@ mod tests {
|
|||
rendezvous: rendezvous::server::Behaviour::new(
|
||||
rendezvous::server::Config::default(),
|
||||
),
|
||||
ping: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -626,7 +626,7 @@ pub async fn buy_xmr(
|
|||
)
|
||||
.await?;
|
||||
|
||||
swarm.behaviour_mut().add_address(seller_peer_id, seller);
|
||||
swarm.add_peer_address(seller_peer_id, seller);
|
||||
|
||||
context
|
||||
.db
|
||||
|
@ -816,9 +816,7 @@ pub async fn resume_swap(
|
|||
|
||||
// Fetch the seller's addresses from the database and add them to the swarm
|
||||
for seller_address in seller_addresses {
|
||||
swarm
|
||||
.behaviour_mut()
|
||||
.add_address(seller_peer_id, seller_address);
|
||||
swarm.add_peer_address(seller_peer_id, seller_address);
|
||||
}
|
||||
|
||||
let (event_loop, event_loop_handle) =
|
||||
|
@ -1222,9 +1220,17 @@ where
|
|||
}
|
||||
|
||||
loop {
|
||||
println!("max_giveable: {}", max_giveable);
|
||||
println!("bid_quote.min_quantity: {}", bid_quote.min_quantity);
|
||||
let min_outstanding = bid_quote.min_quantity - max_giveable;
|
||||
println!("min_outstanding: {}", min_outstanding);
|
||||
let min_bitcoin_lock_tx_fee = estimate_fee(min_outstanding).await?;
|
||||
println!("min_bitcoin_lock_tx_fee: {}", min_bitcoin_lock_tx_fee);
|
||||
let min_deposit_until_swap_will_start = min_outstanding + min_bitcoin_lock_tx_fee;
|
||||
println!(
|
||||
"min_deposit_until_swap_will_start: {}",
|
||||
min_deposit_until_swap_will_start
|
||||
);
|
||||
let max_deposit_until_maximum_amount_is_reached =
|
||||
maximum_amount - max_giveable + min_bitcoin_lock_tx_fee;
|
||||
|
||||
|
|
|
@ -9,18 +9,18 @@ use crate::network::{
|
|||
use crate::protocol::bob::State2;
|
||||
use crate::{bitcoin, env};
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use libp2p::core::Multiaddr;
|
||||
use libp2p::identify::{Identify, IdentifyConfig, IdentifyEvent};
|
||||
use libp2p::ping::{Ping, PingConfig, PingEvent};
|
||||
use libp2p::request_response::{RequestId, ResponseChannel};
|
||||
use libp2p::{identity, NetworkBehaviour, PeerId};
|
||||
use libp2p::request_response::{
|
||||
InboundFailure, InboundRequestId, OutboundFailure, OutboundRequestId, ResponseChannel,
|
||||
};
|
||||
use libp2p::swarm::NetworkBehaviour;
|
||||
use libp2p::{identify, identity, ping, PeerId};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OutEvent {
|
||||
QuoteReceived {
|
||||
id: RequestId,
|
||||
id: OutboundRequestId,
|
||||
response: BidQuote,
|
||||
},
|
||||
SwapSetupCompleted(Box<Result<State2>>),
|
||||
|
@ -30,25 +30,34 @@ pub enum OutEvent {
|
|||
peer: PeerId,
|
||||
},
|
||||
EncryptedSignatureAcknowledged {
|
||||
id: RequestId,
|
||||
id: OutboundRequestId,
|
||||
},
|
||||
CooperativeXmrRedeemFulfilled {
|
||||
id: RequestId,
|
||||
id: OutboundRequestId,
|
||||
s_a: Scalar,
|
||||
swap_id: uuid::Uuid,
|
||||
},
|
||||
CooperativeXmrRedeemRejected {
|
||||
id: RequestId,
|
||||
id: OutboundRequestId,
|
||||
reason: CooperativeXmrRedeemRejectReason,
|
||||
swap_id: uuid::Uuid,
|
||||
},
|
||||
AllRedialAttemptsExhausted {
|
||||
peer: PeerId,
|
||||
},
|
||||
Failure {
|
||||
peer: PeerId,
|
||||
error: Error,
|
||||
},
|
||||
OutboundRequestResponseFailure {
|
||||
peer: PeerId,
|
||||
error: OutboundFailure,
|
||||
request_id: OutboundRequestId,
|
||||
protocol: String,
|
||||
},
|
||||
InboundRequestResponseFailure {
|
||||
peer: PeerId,
|
||||
error: InboundFailure,
|
||||
request_id: InboundRequestId,
|
||||
protocol: String,
|
||||
},
|
||||
/// "Fallback" variant that allows the event mapping code to swallow certain
|
||||
/// events that we don't want the caller to deal with.
|
||||
Other,
|
||||
|
@ -72,7 +81,7 @@ impl OutEvent {
|
|||
|
||||
/// A `NetworkBehaviour` that represents an XMR/BTC swap node as Bob.
|
||||
#[derive(NetworkBehaviour)]
|
||||
#[behaviour(out_event = "OutEvent", event_process = false)]
|
||||
#[behaviour(to_swarm = "OutEvent")]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Behaviour {
|
||||
pub quote: quote::Behaviour,
|
||||
|
@ -81,12 +90,12 @@ pub struct Behaviour {
|
|||
pub cooperative_xmr_redeem: cooperative_xmr_redeem_after_punish::Behaviour,
|
||||
pub encrypted_signature: encrypted_signature::Behaviour,
|
||||
pub redial: redial::Behaviour,
|
||||
pub identify: Identify,
|
||||
pub identify: identify::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,
|
||||
ping: ping::Behaviour,
|
||||
}
|
||||
|
||||
impl Behaviour {
|
||||
|
@ -98,37 +107,37 @@ impl Behaviour {
|
|||
) -> Self {
|
||||
let agentVersion = format!("cli/{} ({})", env!("CARGO_PKG_VERSION"), identify_params.1);
|
||||
let protocolVersion = "/comit/xmr/btc/1.0.0".to_string();
|
||||
let identifyConfig = IdentifyConfig::new(protocolVersion, identify_params.0.public())
|
||||
|
||||
let identifyConfig = identify::Config::new(protocolVersion, identify_params.0.public())
|
||||
.with_agent_version(agentVersion);
|
||||
|
||||
let pingConfig = ping::Config::new().with_timeout(Duration::from_secs(60));
|
||||
|
||||
Self {
|
||||
quote: quote::cli(),
|
||||
swap_setup: bob::Behaviour::new(env_config, bitcoin_wallet),
|
||||
transfer_proof: transfer_proof::bob(),
|
||||
encrypted_signature: encrypted_signature::bob(),
|
||||
cooperative_xmr_redeem: cooperative_xmr_redeem_after_punish::bob(),
|
||||
redial: redial::Behaviour::new(alice, Duration::from_secs(2)),
|
||||
ping: Ping::new(PingConfig::new().with_keep_alive(true)),
|
||||
identify: Identify::new(identifyConfig),
|
||||
redial: redial::Behaviour::new(
|
||||
alice,
|
||||
Duration::from_secs(2),
|
||||
Duration::from_secs(5 * 60),
|
||||
),
|
||||
ping: ping::Behaviour::new(pingConfig),
|
||||
identify: identify::Behaviour::new(identifyConfig),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a known address for the given peer
|
||||
pub fn add_address(&mut self, peer_id: PeerId, address: Multiaddr) {
|
||||
self.quote.add_address(&peer_id, address.clone());
|
||||
self.transfer_proof.add_address(&peer_id, address.clone());
|
||||
self.encrypted_signature.add_address(&peer_id, address);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PingEvent> for OutEvent {
|
||||
fn from(_: PingEvent) -> Self {
|
||||
impl From<ping::Event> for OutEvent {
|
||||
fn from(_: ping::Event) -> Self {
|
||||
OutEvent::Other
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IdentifyEvent> for OutEvent {
|
||||
fn from(_: IdentifyEvent) -> Self {
|
||||
impl From<identify::Event> for OutEvent {
|
||||
fn from(_: identify::Event) -> Self {
|
||||
OutEvent::Other
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
use crate::bitcoin::EncryptedSignature;
|
||||
use crate::cli::behaviour::{Behaviour, OutEvent};
|
||||
use crate::monero;
|
||||
use crate::network::cooperative_xmr_redeem_after_punish::{Request, Response};
|
||||
use crate::network::cooperative_xmr_redeem_after_punish::{self, Request, Response};
|
||||
use crate::network::encrypted_signature;
|
||||
use crate::network::quote::BidQuote;
|
||||
use crate::network::swap_setup::bob::NewSwap;
|
||||
use crate::protocol::bob::State2;
|
||||
use crate::protocol::bob::swap::has_already_processed_transfer_proof;
|
||||
use crate::protocol::bob::{BobState, State2};
|
||||
use crate::protocol::Database;
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use futures::future::{BoxFuture, OptionFuture};
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use libp2p::request_response::{RequestId, ResponseChannel};
|
||||
use libp2p::request_response::{OutboundFailure, OutboundRequestId, ResponseChannel};
|
||||
use libp2p::swarm::dial_opts::DialOpts;
|
||||
use libp2p::swarm::SwarmEvent;
|
||||
use libp2p::{PeerId, Swarm};
|
||||
|
@ -19,6 +20,9 @@ use std::sync::Arc;
|
|||
use std::time::Duration;
|
||||
use uuid::Uuid;
|
||||
|
||||
static REQUEST_RESPONSE_PROTOCOL_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
static EXECUTION_SETUP_PROTOCOL_TIMEOUT: Duration = Duration::from_secs(120);
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct EventLoop {
|
||||
swap_id: Uuid,
|
||||
|
@ -26,21 +30,35 @@ pub struct EventLoop {
|
|||
alice_peer_id: PeerId,
|
||||
db: Arc<dyn Database + Send + Sync>,
|
||||
|
||||
// these streams represents outgoing requests that we have to make
|
||||
quote_requests: bmrng::RequestReceiverStream<(), BidQuote>,
|
||||
cooperative_xmr_redeem_requests: bmrng::RequestReceiverStream<Uuid, Response>,
|
||||
encrypted_signatures: bmrng::RequestReceiverStream<EncryptedSignature, ()>,
|
||||
swap_setup_requests: bmrng::RequestReceiverStream<NewSwap, Result<State2>>,
|
||||
// These streams represents outgoing requests that we have to make
|
||||
// These are essentially queues of requests that we will send to Alice once we are connected to her.
|
||||
quote_requests: bmrng::RequestReceiverStream<(), Result<BidQuote, OutboundFailure>>,
|
||||
cooperative_xmr_redeem_requests: bmrng::RequestReceiverStream<
|
||||
(),
|
||||
Result<cooperative_xmr_redeem_after_punish::Response, OutboundFailure>,
|
||||
>,
|
||||
encrypted_signatures_requests:
|
||||
bmrng::RequestReceiverStream<EncryptedSignature, Result<(), OutboundFailure>>,
|
||||
execution_setup_requests: bmrng::RequestReceiverStream<NewSwap, Result<State2>>,
|
||||
|
||||
// these represents requests that are currently in-flight.
|
||||
// once we get a response to a matching [`RequestId`], we will use the responder to relay the
|
||||
// These represents requests that are currently in-flight.
|
||||
// Meaning that we have sent them to Alice, but we have not yet received a response.
|
||||
// Once we get a response to a matching [`RequestId`], we will use the responder to relay the
|
||||
// response.
|
||||
inflight_quote_requests: HashMap<RequestId, bmrng::Responder<BidQuote>>,
|
||||
inflight_encrypted_signature_requests: HashMap<RequestId, bmrng::Responder<()>>,
|
||||
inflight_quote_requests:
|
||||
HashMap<OutboundRequestId, bmrng::Responder<Result<BidQuote, OutboundFailure>>>,
|
||||
inflight_encrypted_signature_requests:
|
||||
HashMap<OutboundRequestId, bmrng::Responder<Result<(), OutboundFailure>>>,
|
||||
inflight_swap_setup: Option<bmrng::Responder<Result<State2>>>,
|
||||
inflight_cooperative_xmr_redeem_requests: HashMap<RequestId, bmrng::Responder<Response>>,
|
||||
/// The sender we will use to relay incoming transfer proofs.
|
||||
transfer_proof: bmrng::RequestSender<monero::TransferProof, ()>,
|
||||
inflight_cooperative_xmr_redeem_requests: HashMap<
|
||||
OutboundRequestId,
|
||||
bmrng::Responder<Result<cooperative_xmr_redeem_after_punish::Response, OutboundFailure>>,
|
||||
>,
|
||||
|
||||
/// The sender we will use to relay incoming transfer proofs to the EventLoopHandle
|
||||
/// The corresponding receiver is stored in the EventLoopHandle
|
||||
transfer_proof_sender: bmrng::RequestSender<monero::TransferProof, ()>,
|
||||
|
||||
/// The future representing the successful handling of an incoming transfer
|
||||
/// proof.
|
||||
///
|
||||
|
@ -58,20 +76,26 @@ impl EventLoop {
|
|||
alice_peer_id: PeerId,
|
||||
db: Arc<dyn Database + Send + Sync>,
|
||||
) -> Result<(Self, EventLoopHandle)> {
|
||||
let execution_setup = bmrng::channel_with_timeout(1, Duration::from_secs(60));
|
||||
let transfer_proof = bmrng::channel_with_timeout(1, Duration::from_secs(60));
|
||||
let encrypted_signature = bmrng::channel(1);
|
||||
let quote = bmrng::channel_with_timeout(1, Duration::from_secs(60));
|
||||
let cooperative_xmr_redeem = bmrng::channel_with_timeout(1, Duration::from_secs(60));
|
||||
// We still use a timeout here, because this protocol does not dial Alice itself
|
||||
// and we want to fail if we cannot reach Alice
|
||||
let (execution_setup_sender, execution_setup_receiver) =
|
||||
bmrng::channel_with_timeout(1, EXECUTION_SETUP_PROTOCOL_TIMEOUT);
|
||||
|
||||
// It is okay to not have a timeout here, as timeouts are enforced by the request-response protocol
|
||||
let (transfer_proof_sender, transfer_proof_receiver) = bmrng::channel(1);
|
||||
let (encrypted_signature_sender, encrypted_signature_receiver) = bmrng::channel(1);
|
||||
let (quote_sender, quote_receiver) = bmrng::channel(1);
|
||||
let (cooperative_xmr_redeem_sender, cooperative_xmr_redeem_receiver) = bmrng::channel(1);
|
||||
|
||||
let event_loop = EventLoop {
|
||||
swap_id,
|
||||
swarm,
|
||||
alice_peer_id,
|
||||
swap_setup_requests: execution_setup.1.into(),
|
||||
transfer_proof: transfer_proof.0,
|
||||
encrypted_signatures: encrypted_signature.1.into(),
|
||||
cooperative_xmr_redeem_requests: cooperative_xmr_redeem.1.into(),
|
||||
quote_requests: quote.1.into(),
|
||||
execution_setup_requests: execution_setup_receiver.into(),
|
||||
transfer_proof_sender,
|
||||
encrypted_signatures_requests: encrypted_signature_receiver.into(),
|
||||
cooperative_xmr_redeem_requests: cooperative_xmr_redeem_receiver.into(),
|
||||
quote_requests: quote_receiver.into(),
|
||||
inflight_quote_requests: HashMap::default(),
|
||||
inflight_swap_setup: None,
|
||||
inflight_encrypted_signature_requests: HashMap::default(),
|
||||
|
@ -81,11 +105,11 @@ impl EventLoop {
|
|||
};
|
||||
|
||||
let handle = EventLoopHandle {
|
||||
swap_setup: execution_setup.0,
|
||||
transfer_proof: transfer_proof.1,
|
||||
encrypted_signature: encrypted_signature.0,
|
||||
cooperative_xmr_redeem: cooperative_xmr_redeem.0,
|
||||
quote: quote.0,
|
||||
execution_setup_sender,
|
||||
transfer_proof_receiver,
|
||||
encrypted_signature_sender,
|
||||
cooperative_xmr_redeem_sender,
|
||||
quote_sender,
|
||||
};
|
||||
|
||||
Ok((event_loop, handle))
|
||||
|
@ -107,7 +131,7 @@ impl EventLoop {
|
|||
match swarm_event {
|
||||
SwarmEvent::Behaviour(OutEvent::QuoteReceived { id, response }) => {
|
||||
if let Some(responder) = self.inflight_quote_requests.remove(&id) {
|
||||
let _ = responder.respond(response);
|
||||
let _ = responder.respond(Ok(response));
|
||||
}
|
||||
}
|
||||
SwarmEvent::Behaviour(OutEvent::SwapSetupCompleted(response)) => {
|
||||
|
@ -128,7 +152,27 @@ impl EventLoop {
|
|||
continue;
|
||||
}
|
||||
|
||||
let mut responder = match self.transfer_proof.send(msg.tx_lock_proof).await {
|
||||
// Immediately acknowledge if we've already processed this transfer proof
|
||||
// This handles the case where Alice didn't receive our previous acknowledgment
|
||||
// and is retrying sending the transfer proof
|
||||
if let Ok(state) = self.db.get_state(swap_id).await {
|
||||
let state: BobState = state.try_into()
|
||||
.expect("Bobs database only contains Bob states");
|
||||
|
||||
if has_already_processed_transfer_proof(&state) {
|
||||
tracing::warn!("Received transfer proof for swap {} but we are already in state {}. Acknowledging immediately. Alice most likely did not receive the acknowledgment when we sent it before", swap_id, state);
|
||||
|
||||
// We set this to a future that will resolve immediately, and returns the channel
|
||||
// This will be resolved in the next iteration of the event loop, and a response will be sent to Alice
|
||||
self.pending_transfer_proof = OptionFuture::from(Some(async move {
|
||||
channel
|
||||
}.boxed()));
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let mut responder = match self.transfer_proof_sender.send(msg.tx_lock_proof).await {
|
||||
Ok(responder) => responder,
|
||||
Err(e) => {
|
||||
tracing::warn!("Failed to pass on transfer proof: {:#}", e);
|
||||
|
@ -178,23 +222,19 @@ impl EventLoop {
|
|||
}
|
||||
SwarmEvent::Behaviour(OutEvent::EncryptedSignatureAcknowledged { id }) => {
|
||||
if let Some(responder) = self.inflight_encrypted_signature_requests.remove(&id) {
|
||||
let _ = responder.respond(());
|
||||
let _ = responder.respond(Ok(()));
|
||||
}
|
||||
}
|
||||
SwarmEvent::Behaviour(OutEvent::CooperativeXmrRedeemFulfilled { id, swap_id, s_a }) => {
|
||||
if let Some(responder) = self.inflight_cooperative_xmr_redeem_requests.remove(&id) {
|
||||
let _ = responder.respond(Response::Fullfilled { s_a, swap_id });
|
||||
let _ = responder.respond(Ok(Response::Fullfilled { s_a, swap_id }));
|
||||
}
|
||||
}
|
||||
SwarmEvent::Behaviour(OutEvent::CooperativeXmrRedeemRejected { id, swap_id, reason }) => {
|
||||
if let Some(responder) = self.inflight_cooperative_xmr_redeem_requests.remove(&id) {
|
||||
let _ = responder.respond(Response::Rejected { reason, swap_id });
|
||||
let _ = responder.respond(Ok(Response::Rejected { reason, swap_id }));
|
||||
}
|
||||
}
|
||||
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 }) => {
|
||||
tracing::warn!(%peer, err = %error, "Communication error");
|
||||
return;
|
||||
|
@ -202,40 +242,75 @@ impl EventLoop {
|
|||
SwarmEvent::ConnectionEstablished { peer_id, endpoint, .. } if peer_id == self.alice_peer_id => {
|
||||
tracing::info!(peer_id = %endpoint.get_remote_address(), "Connected to Alice");
|
||||
}
|
||||
SwarmEvent::Dialing(peer_id) if peer_id == self.alice_peer_id => {
|
||||
tracing::debug!(%peer_id, "Dialling Alice");
|
||||
SwarmEvent::Dialing { peer_id: Some(alice_peer_id), connection_id } if alice_peer_id == self.alice_peer_id => {
|
||||
tracing::debug!(%alice_peer_id, %connection_id, "Dialing Alice");
|
||||
}
|
||||
SwarmEvent::ConnectionClosed { peer_id, endpoint, num_established, cause: Some(error), connection_id } if peer_id == self.alice_peer_id && num_established == 0 => {
|
||||
tracing::warn!(peer_id = %endpoint.get_remote_address(), cause = %error, %connection_id, "Lost connection to Alice");
|
||||
|
||||
if let Some(duration) = self.swarm.behaviour_mut().redial.until_next_redial() {
|
||||
tracing::info!(seconds_until_next_redial = %duration.as_secs(), "Waiting for next redial attempt");
|
||||
}
|
||||
SwarmEvent::ConnectionClosed { peer_id, endpoint, num_established, cause: Some(error) } if peer_id == self.alice_peer_id && num_established == 0 => {
|
||||
tracing::warn!(peer_id = %endpoint.get_remote_address(), cause = %error, "Lost connection to Alice");
|
||||
}
|
||||
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::OutgoingConnectionError { peer_id: Some(alice_peer_id), error } if alice_peer_id == self.alice_peer_id => {
|
||||
tracing::warn!(%error, "Failed to dial Alice");
|
||||
SwarmEvent::OutgoingConnectionError { peer_id: Some(alice_peer_id), error, connection_id } if alice_peer_id == self.alice_peer_id => {
|
||||
tracing::warn!(%alice_peer_id, %connection_id, %error, "Failed to connect to Alice");
|
||||
|
||||
if let Some(duration) = self.swarm.behaviour_mut().redial.until_next_redial() {
|
||||
tracing::info!(seconds_until_next_redial = %duration.as_secs(), "Waiting for next redial attempt");
|
||||
}
|
||||
}
|
||||
SwarmEvent::Behaviour(OutEvent::OutboundRequestResponseFailure {peer, error, request_id, protocol}) => {
|
||||
tracing::error!(
|
||||
%peer,
|
||||
%request_id,
|
||||
%error,
|
||||
%protocol,
|
||||
"Failed to send request-response request to peer");
|
||||
|
||||
// If we fail to send a request-response request, we should notify the responder that the request failed
|
||||
// We will remove the responder from the inflight requests and respond with an error
|
||||
|
||||
// Check for encrypted signature requests
|
||||
if let Some(responder) = self.inflight_encrypted_signature_requests.remove(&request_id) {
|
||||
let _ = responder.respond(Err(error));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for quote requests
|
||||
if let Some(responder) = self.inflight_quote_requests.remove(&request_id) {
|
||||
let _ = responder.respond(Err(error));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for cooperative xmr redeem requests
|
||||
if let Some(responder) = self.inflight_cooperative_xmr_redeem_requests.remove(&request_id) {
|
||||
let _ = responder.respond(Err(error));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
SwarmEvent::Behaviour(OutEvent::InboundRequestResponseFailure {peer, error, request_id, protocol}) => {
|
||||
tracing::error!(
|
||||
%peer,
|
||||
%request_id,
|
||||
%error,
|
||||
%protocol,
|
||||
"Failed to receive request-response request from peer");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
|
||||
// Handle to-be-sent requests for all our network protocols.
|
||||
// Use `self.is_connected_to_alice` as a guard to "buffer" requests until we are connected.
|
||||
Some(((), responder)) = self.quote_requests.next().fuse(), if self.is_connected_to_alice() => {
|
||||
// Handle to-be-sent outgoing requests for all our network protocols.
|
||||
Some(((), responder)) = self.quote_requests.next().fuse() => {
|
||||
let id = self.swarm.behaviour_mut().quote.send_request(&self.alice_peer_id, ());
|
||||
self.inflight_quote_requests.insert(id, responder);
|
||||
},
|
||||
Some((swap, responder)) = self.swap_setup_requests.next().fuse(), if self.is_connected_to_alice() => {
|
||||
self.swarm.behaviour_mut().swap_setup.start(self.alice_peer_id, swap).await;
|
||||
self.inflight_swap_setup = Some(responder);
|
||||
},
|
||||
Some((tx_redeem_encsig, responder)) = self.encrypted_signatures.next().fuse(), if self.is_connected_to_alice() => {
|
||||
Some((tx_redeem_encsig, responder)) = self.encrypted_signatures_requests.next().fuse() => {
|
||||
let request = encrypted_signature::Request {
|
||||
swap_id: self.swap_id,
|
||||
tx_redeem_encsig
|
||||
|
@ -244,19 +319,33 @@ impl EventLoop {
|
|||
let id = self.swarm.behaviour_mut().encrypted_signature.send_request(&self.alice_peer_id, request);
|
||||
self.inflight_encrypted_signature_requests.insert(id, responder);
|
||||
},
|
||||
|
||||
Some(response_channel) = &mut self.pending_transfer_proof => {
|
||||
let _ = self.swarm.behaviour_mut().transfer_proof.send_response(response_channel, ());
|
||||
|
||||
self.pending_transfer_proof = OptionFuture::from(None);
|
||||
},
|
||||
|
||||
Some((swap_id, responder)) = self.cooperative_xmr_redeem_requests.next().fuse(), if self.is_connected_to_alice() => {
|
||||
Some((_, responder)) = self.cooperative_xmr_redeem_requests.next().fuse() => {
|
||||
let id = self.swarm.behaviour_mut().cooperative_xmr_redeem.send_request(&self.alice_peer_id, Request {
|
||||
swap_id
|
||||
swap_id: self.swap_id
|
||||
});
|
||||
self.inflight_cooperative_xmr_redeem_requests.insert(id, responder);
|
||||
},
|
||||
|
||||
// We use `self.is_connected_to_alice` as a guard to "buffer" requests until we are connected.
|
||||
// because the protocol does not dial Alice itself
|
||||
// (unlike request-response above)
|
||||
Some((swap, responder)) = self.execution_setup_requests.next().fuse(), if self.is_connected_to_alice() => {
|
||||
self.swarm.behaviour_mut().swap_setup.start(self.alice_peer_id, swap).await;
|
||||
self.inflight_swap_setup = Some(responder);
|
||||
},
|
||||
|
||||
// Send an acknowledgement to Alice once the EventLoopHandle has processed a received transfer proof
|
||||
// We use `self.is_connected_to_alice` as a guard to "buffer" requests until we are connected.
|
||||
//
|
||||
// Why do we do this here but not for the other request-response channels?
|
||||
// This is the only request, we don't have a retry mechanism for. We lazily send this.
|
||||
Some(response_channel) = &mut self.pending_transfer_proof, if self.is_connected_to_alice() => {
|
||||
if self.swarm.behaviour_mut().transfer_proof.send_response(response_channel, ()).is_err() {
|
||||
tracing::warn!("Failed to send acknowledgment to Alice that we have received the transfer proof");
|
||||
} else {
|
||||
self.pending_transfer_proof = OptionFuture::from(None);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -268,24 +357,84 @@ impl EventLoop {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct EventLoopHandle {
|
||||
swap_setup: bmrng::RequestSender<NewSwap, Result<State2>>,
|
||||
transfer_proof: bmrng::RequestReceiver<monero::TransferProof, ()>,
|
||||
encrypted_signature: bmrng::RequestSender<EncryptedSignature, ()>,
|
||||
quote: bmrng::RequestSender<(), BidQuote>,
|
||||
cooperative_xmr_redeem: bmrng::RequestSender<Uuid, Response>,
|
||||
/// When a NewSwap object is sent into this channel, the EventLoop will:
|
||||
/// 1. Trigger the swap setup protocol with Alice to negotiate the swap parameters
|
||||
/// 2. Return the resulting State2 if successful
|
||||
/// 3. Return an anyhow error if the request fails
|
||||
execution_setup_sender: bmrng::RequestSender<NewSwap, Result<State2>>,
|
||||
|
||||
/// Receiver for incoming Monero transfer proofs from Alice.
|
||||
/// When a proof is received, we process it and acknowledge receipt back to the EventLoop
|
||||
/// The EventLoop will then send an acknowledgment back to Alice over the network
|
||||
transfer_proof_receiver: bmrng::RequestReceiver<monero::TransferProof, ()>,
|
||||
|
||||
/// When an encrypted signature is sent into this channel, the EventLoop will:
|
||||
/// 1. Send the encrypted signature to Alice over the network
|
||||
/// 2. Return Ok(()) if Alice acknowledges receipt, or
|
||||
/// 3. Return an OutboundFailure error if the request fails
|
||||
encrypted_signature_sender:
|
||||
bmrng::RequestSender<EncryptedSignature, Result<(), OutboundFailure>>,
|
||||
|
||||
/// When a () is sent into this channel, the EventLoop will:
|
||||
/// 1. Request a price quote from Alice
|
||||
/// 2. Return the quote if successful
|
||||
/// 3. Return an OutboundFailure error if the request fails
|
||||
quote_sender: bmrng::RequestSender<(), Result<BidQuote, OutboundFailure>>,
|
||||
|
||||
/// When a () is sent into this channel, the EventLoop will:
|
||||
/// 1. Request Alice's cooperation in redeeming the Monero
|
||||
/// 2. Return the a response object (Fullfilled or Rejected), if the network request is successful
|
||||
/// The Fullfilled object contains the keys required to redeem the Monero
|
||||
/// 3. Return an OutboundFailure error if the network request fails
|
||||
cooperative_xmr_redeem_sender: bmrng::RequestSender<
|
||||
(),
|
||||
Result<cooperative_xmr_redeem_after_punish::Response, OutboundFailure>,
|
||||
>,
|
||||
}
|
||||
|
||||
impl EventLoopHandle {
|
||||
fn create_retry_config(max_elapsed_time: Duration) -> backoff::ExponentialBackoff {
|
||||
backoff::ExponentialBackoffBuilder::new()
|
||||
.with_max_elapsed_time(max_elapsed_time.into())
|
||||
.with_max_interval(Duration::from_secs(5))
|
||||
.build()
|
||||
}
|
||||
|
||||
pub async fn setup_swap(&mut self, swap: NewSwap) -> Result<State2> {
|
||||
self.swap_setup.send_receive(swap).await?
|
||||
tracing::debug!(swap = ?swap, "Sending swap setup request");
|
||||
|
||||
let backoff = Self::create_retry_config(EXECUTION_SETUP_PROTOCOL_TIMEOUT);
|
||||
|
||||
backoff::future::retry(backoff, || async {
|
||||
match self.execution_setup_sender.send_receive(swap.clone()).await {
|
||||
Ok(Ok(state2)) => Ok(state2),
|
||||
// These are errors thrown by the swap_setup/bob behaviour
|
||||
Ok(Err(err)) => {
|
||||
tracing::warn!(%err, "Failed to setup swap. Will retry");
|
||||
Err(backoff::Error::transient(err))
|
||||
}
|
||||
// This will happen if we don't establish a connection to Alice within the timeout of the MPSC channel
|
||||
// The protocol does not dial Alice it self
|
||||
// This is handled by redial behaviour
|
||||
Err(bmrng::error::RequestError::RecvTimeoutError) => {
|
||||
Err(backoff::Error::permanent(anyhow!("We failed to setup the swap in the allotted time by the event loop channel")))
|
||||
}
|
||||
Err(_) => {
|
||||
unreachable!("We never drop the receiver of the execution setup channel, so this should never happen")
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.context("Failed to setup swap after retries")
|
||||
}
|
||||
|
||||
pub async fn recv_transfer_proof(&mut self) -> Result<monero::TransferProof> {
|
||||
let (transfer_proof, responder) = self
|
||||
.transfer_proof
|
||||
.transfer_proof_receiver
|
||||
.recv()
|
||||
.await
|
||||
.context("Failed to receive transfer proof")?;
|
||||
|
||||
responder
|
||||
.respond(())
|
||||
.context("Failed to acknowledge receipt of transfer proof")?;
|
||||
|
@ -295,18 +444,71 @@ impl EventLoopHandle {
|
|||
|
||||
pub async fn request_quote(&mut self) -> Result<BidQuote> {
|
||||
tracing::debug!("Requesting quote");
|
||||
Ok(self.quote.send_receive(()).await?)
|
||||
|
||||
let backoff = Self::create_retry_config(REQUEST_RESPONSE_PROTOCOL_TIMEOUT);
|
||||
|
||||
backoff::future::retry(backoff, || async {
|
||||
match self.quote_sender.send_receive(()).await {
|
||||
Ok(Ok(quote)) => Ok(quote),
|
||||
Ok(Err(err)) => {
|
||||
tracing::warn!(%err, "Failed to request quote due to network error. Will retry");
|
||||
Err(backoff::Error::transient(err))
|
||||
}
|
||||
pub async fn request_cooperative_xmr_redeem(&mut self, swap_id: Uuid) -> Result<Response> {
|
||||
Ok(self.cooperative_xmr_redeem.send_receive(swap_id).await?)
|
||||
Err(_) => {
|
||||
unreachable!("We initiate the quote channel without a timeout and store both the sender and receiver in the same struct, so this should never happen");
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.context("Failed to request quote after retries")
|
||||
}
|
||||
|
||||
pub async fn request_cooperative_xmr_redeem(&mut self) -> Result<Response> {
|
||||
tracing::debug!("Requesting cooperative XMR redeem");
|
||||
|
||||
let backoff = Self::create_retry_config(REQUEST_RESPONSE_PROTOCOL_TIMEOUT);
|
||||
|
||||
backoff::future::retry(backoff, || async {
|
||||
match self.cooperative_xmr_redeem_sender.send_receive(()).await {
|
||||
Ok(Ok(response)) => Ok(response),
|
||||
Ok(Err(err)) => {
|
||||
tracing::warn!(%err, "Failed to request cooperative XMR redeem due to network error. Will retry");
|
||||
Err(backoff::Error::transient(err))
|
||||
}
|
||||
Err(_) => {
|
||||
unreachable!("We initiate the cooperative xmr redeem channel without a timeout and store both the sender and receiver in the same struct, so this should never happen");
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.context("Failed to request cooperative XMR redeem after retries")
|
||||
}
|
||||
|
||||
pub async fn send_encrypted_signature(
|
||||
&mut self,
|
||||
tx_redeem_encsig: EncryptedSignature,
|
||||
) -> Result<(), bmrng::error::RequestError<EncryptedSignature>> {
|
||||
self.encrypted_signature
|
||||
.send_receive(tx_redeem_encsig)
|
||||
) -> Result<()> {
|
||||
tracing::debug!("Sending encrypted signature");
|
||||
|
||||
// We will retry indefinitely until we succeed
|
||||
let backoff = backoff::ExponentialBackoffBuilder::new()
|
||||
.with_max_elapsed_time(None)
|
||||
.with_max_interval(REQUEST_RESPONSE_PROTOCOL_TIMEOUT)
|
||||
.build();
|
||||
|
||||
backoff::future::retry(backoff, || async {
|
||||
match self.encrypted_signature_sender.send_receive(tx_redeem_encsig.clone()).await {
|
||||
Ok(Ok(_)) => Ok(()),
|
||||
Ok(Err(err)) => {
|
||||
tracing::warn!(%err, "Failed to send encrypted signature due to a network error. Will retry");
|
||||
Err(backoff::Error::transient(err))
|
||||
}
|
||||
Err(_) => {
|
||||
unreachable!("We initiate the encrypted signature channel without a timeout and store both the sender and receiver in the same struct, so this should never happen");
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.context("Failed to send encrypted signature after retries")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,10 @@ use crate::network::{quote, swarm};
|
|||
use anyhow::{Context, Result};
|
||||
use futures::StreamExt;
|
||||
use libp2p::multiaddr::Protocol;
|
||||
use libp2p::ping::{Ping, PingConfig, PingEvent};
|
||||
use libp2p::request_response::{RequestResponseEvent, RequestResponseMessage};
|
||||
use libp2p::request_response;
|
||||
use libp2p::swarm::dial_opts::DialOpts;
|
||||
use libp2p::swarm::SwarmEvent;
|
||||
use libp2p::{identity, rendezvous, Multiaddr, PeerId, Swarm};
|
||||
use libp2p::swarm::{NetworkBehaviour, SwarmEvent};
|
||||
use libp2p::{identity, ping, rendezvous, Multiaddr, PeerId, Swarm};
|
||||
use serde::Serialize;
|
||||
use serde_with::{serde_as, DisplayFromStr};
|
||||
use std::collections::hash_map::Entry;
|
||||
|
@ -32,18 +31,11 @@ pub async fn list_sellers(
|
|||
let behaviour = Behaviour {
|
||||
rendezvous: rendezvous::client::Behaviour::new(identity.clone()),
|
||||
quote: quote::cli(),
|
||||
ping: Ping::new(
|
||||
PingConfig::new()
|
||||
.with_keep_alive(false)
|
||||
.with_interval(Duration::from_secs(86_400)),
|
||||
),
|
||||
ping: ping::Behaviour::new(ping::Config::new().with_timeout(Duration::from_secs(60))),
|
||||
};
|
||||
let mut swarm = swarm::cli(identity, tor_socks5_port, behaviour).await?;
|
||||
|
||||
swarm
|
||||
.behaviour_mut()
|
||||
.quote
|
||||
.add_address(&rendezvous_node_peer_id, rendezvous_node_addr.clone());
|
||||
swarm.add_peer_address(rendezvous_node_peer_id, rendezvous_node_addr.clone());
|
||||
|
||||
swarm
|
||||
.dial(DialOpts::from(rendezvous_node_peer_id))
|
||||
|
@ -83,7 +75,7 @@ pub enum Status {
|
|||
enum OutEvent {
|
||||
Rendezvous(rendezvous::client::Event),
|
||||
Quote(quote::OutEvent),
|
||||
Ping(PingEvent),
|
||||
Ping(ping::Event),
|
||||
}
|
||||
|
||||
impl From<rendezvous::client::Event> for OutEvent {
|
||||
|
@ -98,13 +90,13 @@ impl From<quote::OutEvent> for OutEvent {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(libp2p::NetworkBehaviour)]
|
||||
#[derive(NetworkBehaviour)]
|
||||
#[behaviour(event_process = false)]
|
||||
#[behaviour(out_event = "OutEvent")]
|
||||
struct Behaviour {
|
||||
rendezvous: rendezvous::client::Behaviour,
|
||||
quote: quote::Behaviour,
|
||||
ping: Ping,
|
||||
ping: ping::Behaviour,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -173,7 +165,7 @@ impl EventLoop {
|
|||
self.reachable_asb_address.insert(peer_id, address.clone());
|
||||
}
|
||||
}
|
||||
SwarmEvent::OutgoingConnectionError { peer_id, error } => {
|
||||
SwarmEvent::OutgoingConnectionError { peer_id, error, .. } => {
|
||||
if let Some(peer_id) = peer_id {
|
||||
if peer_id == self.rendezvous_peer_id {
|
||||
tracing::error!(
|
||||
|
@ -216,7 +208,7 @@ impl EventLoop {
|
|||
for address in registration.record.addresses() {
|
||||
tracing::info!(peer_id=%peer, address=%address, "Discovered peer");
|
||||
|
||||
let p2p_suffix = Protocol::P2p(*peer.as_ref());
|
||||
let p2p_suffix = Protocol::P2p(peer);
|
||||
let _address_with_p2p = if !address
|
||||
.ends_with(&Multiaddr::empty().with(p2p_suffix.clone()))
|
||||
{
|
||||
|
@ -228,7 +220,7 @@ impl EventLoop {
|
|||
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());
|
||||
self.swarm.add_peer_address(peer, address.clone());
|
||||
}
|
||||
|
||||
// request the quote, if we are not connected to the peer it will be dialed automatically
|
||||
|
@ -237,18 +229,18 @@ impl EventLoop {
|
|||
}
|
||||
SwarmEvent::Behaviour(OutEvent::Quote(quote_response)) => {
|
||||
match quote_response {
|
||||
RequestResponseEvent::Message { peer, message } => {
|
||||
request_response::Event::Message { peer, message } => {
|
||||
match message {
|
||||
RequestResponseMessage::Response { response, .. } => {
|
||||
request_response::Message::Response { response, .. } => {
|
||||
if self.asb_quote_status.insert(peer, QuoteStatus::Received(Status::Online(response))).is_none() {
|
||||
tracing::error!(%peer, "Received bid quote from unexpected peer, this record will be removed!");
|
||||
self.asb_quote_status.remove(&peer);
|
||||
}
|
||||
}
|
||||
RequestResponseMessage::Request { .. } => unreachable!()
|
||||
request_response::Message::Request { .. } => unreachable!()
|
||||
}
|
||||
}
|
||||
RequestResponseEvent::OutboundFailure { peer, error, .. } => {
|
||||
request_response::Event::OutboundFailure { peer, error, .. } => {
|
||||
if peer == self.rendezvous_peer_id {
|
||||
tracing::debug!(%peer, "Outbound failure when communicating with rendezvous node: {:#}", error);
|
||||
} else {
|
||||
|
@ -256,7 +248,7 @@ impl EventLoop {
|
|||
self.asb_quote_status.remove(&peer);
|
||||
}
|
||||
}
|
||||
RequestResponseEvent::InboundFailure { peer, error, .. } => {
|
||||
request_response::Event::InboundFailure { peer, error, .. } => {
|
||||
if peer == self.rendezvous_peer_id {
|
||||
tracing::debug!(%peer, "Inbound failure when communicating with rendezvous node: {:#}", error);
|
||||
} else {
|
||||
|
@ -264,7 +256,7 @@ impl EventLoop {
|
|||
self.asb_quote_status.remove(&peer);
|
||||
}
|
||||
},
|
||||
RequestResponseEvent::ResponseSent { .. } => unreachable!()
|
||||
request_response::Event::ResponseSent { .. } => unreachable!()
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -323,8 +315,8 @@ impl EventLoop {
|
|||
#[derive(Debug)]
|
||||
struct StillPending {}
|
||||
|
||||
impl From<PingEvent> for OutEvent {
|
||||
fn from(event: PingEvent) -> Self {
|
||||
impl From<ping::Event> for OutEvent {
|
||||
fn from(event: ping::Event) -> Self {
|
||||
OutEvent::Ping(event)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ use crate::network::transport::authenticate_and_multiplex;
|
|||
use anyhow::Result;
|
||||
use libp2p::core::muxing::StreamMuxerBox;
|
||||
use libp2p::core::transport::{Boxed, OptionalTransport};
|
||||
use libp2p::dns::TokioDnsConfig;
|
||||
use libp2p::tcp::TokioTcpConfig;
|
||||
use libp2p::dns;
|
||||
use libp2p::tcp;
|
||||
use libp2p::{identity, PeerId, Transport};
|
||||
|
||||
/// Creates the libp2p transport for the swap CLI.
|
||||
|
@ -19,8 +19,9 @@ pub fn new(
|
|||
identity: &identity::Keypair,
|
||||
maybe_tor_socks5_port: Option<u16>,
|
||||
) -> Result<Boxed<(PeerId, StreamMuxerBox)>> {
|
||||
let tcp = TokioTcpConfig::new().nodelay(true);
|
||||
let tcp_with_dns = TokioDnsConfig::system(tcp)?;
|
||||
let tcp = tcp::tokio::Transport::new(tcp::Config::new().nodelay(true));
|
||||
let tcp_with_dns = dns::tokio::Transport::system(tcp)?;
|
||||
|
||||
let maybe_tor_transport = match maybe_tor_socks5_port {
|
||||
Some(port) => OptionalTransport::some(TorDialOnlyTransport::new(port)),
|
||||
None => OptionalTransport::none(),
|
||||
|
|
|
@ -8,7 +8,7 @@ pub trait MultiAddrExt {
|
|||
impl MultiAddrExt for Multiaddr {
|
||||
fn extract_peer_id(&self) -> Option<PeerId> {
|
||||
match self.iter().last()? {
|
||||
Protocol::P2p(multihash) => PeerId::from_multihash(multihash).ok(),
|
||||
Protocol::P2p(peer_id) => Some(peer_id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
mod impl_from_rr_event;
|
||||
|
||||
pub mod cbor_request_response;
|
||||
pub mod cooperative_xmr_redeem_after_punish;
|
||||
pub mod encrypted_signature;
|
||||
pub mod json_pull_codec;
|
||||
pub mod quote;
|
||||
pub mod redial;
|
||||
pub mod rendezvous;
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
use async_trait::async_trait;
|
||||
use futures::prelude::*;
|
||||
use libp2p::core::upgrade;
|
||||
use libp2p::request_response::{ProtocolName, RequestResponseCodec};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use std::fmt::Debug;
|
||||
use std::io;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Message receive buffer.
|
||||
pub const BUF_SIZE: usize = 1024 * 1024;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct CborCodec<P, Req, Res> {
|
||||
phantom: PhantomData<(P, Req, Res)>,
|
||||
}
|
||||
|
||||
impl<P, Req, Res> Default for CborCodec<P, Req, Res> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P, Req, Res> RequestResponseCodec for CborCodec<P, Req, Res>
|
||||
where
|
||||
P: ProtocolName + Send + Sync + Clone,
|
||||
Req: DeserializeOwned + Serialize + Send,
|
||||
Res: DeserializeOwned + Serialize + Send,
|
||||
{
|
||||
type Protocol = P;
|
||||
type Request = Req;
|
||||
type Response = Res;
|
||||
|
||||
async fn read_request<T>(&mut self, _: &Self::Protocol, io: &mut T) -> io::Result<Self::Request>
|
||||
where
|
||||
T: AsyncRead + Unpin + Send,
|
||||
{
|
||||
let message = upgrade::read_length_prefixed(io, BUF_SIZE).await?;
|
||||
let mut de = serde_cbor::Deserializer::from_slice(&message);
|
||||
let msg = Req::deserialize(&mut de)
|
||||
.map_err(|error| io::Error::new(io::ErrorKind::Other, error))?;
|
||||
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
async fn read_response<T>(
|
||||
&mut self,
|
||||
_: &Self::Protocol,
|
||||
io: &mut T,
|
||||
) -> io::Result<Self::Response>
|
||||
where
|
||||
T: AsyncRead + Unpin + Send,
|
||||
{
|
||||
let message = upgrade::read_length_prefixed(io, BUF_SIZE).await?;
|
||||
let mut de = serde_cbor::Deserializer::from_slice(&message);
|
||||
let msg = Res::deserialize(&mut de)
|
||||
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
|
||||
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
async fn write_request<T>(
|
||||
&mut self,
|
||||
_: &Self::Protocol,
|
||||
io: &mut T,
|
||||
req: Self::Request,
|
||||
) -> io::Result<()>
|
||||
where
|
||||
T: AsyncWrite + Unpin + Send,
|
||||
{
|
||||
let bytes =
|
||||
serde_cbor::to_vec(&req).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
||||
|
||||
upgrade::write_length_prefixed(io, &bytes).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_response<T>(
|
||||
&mut self,
|
||||
_: &Self::Protocol,
|
||||
io: &mut T,
|
||||
res: Self::Response,
|
||||
) -> io::Result<()>
|
||||
where
|
||||
T: AsyncWrite + Unpin + Send,
|
||||
{
|
||||
let bytes = serde_cbor::to_vec(&res)
|
||||
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
|
||||
upgrade::write_length_prefixed(io, &bytes).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,27 +1,23 @@
|
|||
use crate::monero::Scalar;
|
||||
use crate::network::cbor_request_response::CborCodec;
|
||||
use crate::{asb, cli};
|
||||
use libp2p::core::ProtocolName;
|
||||
use libp2p::request_response::{
|
||||
ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent,
|
||||
RequestResponseMessage,
|
||||
};
|
||||
use libp2p::PeerId;
|
||||
use libp2p::request_response::ProtocolSupport;
|
||||
use libp2p::{request_response, PeerId, StreamProtocol};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use uuid::Uuid;
|
||||
|
||||
const PROTOCOL: &str = "/comit/xmr/btc/cooperative_xmr_redeem_after_punish/1.0.0";
|
||||
type OutEvent = RequestResponseEvent<Request, Response>;
|
||||
type Message = RequestResponseMessage<Request, Response>;
|
||||
type OutEvent = request_response::Event<Request, Response>;
|
||||
type Message = request_response::Message<Request, Response>;
|
||||
|
||||
pub type Behaviour = RequestResponse<CborCodec<CooperativeXmrRedeemProtocol, Request, Response>>;
|
||||
pub type Behaviour = request_response::cbor::Behaviour<Request, Response>;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct CooperativeXmrRedeemProtocol;
|
||||
|
||||
impl ProtocolName for CooperativeXmrRedeemProtocol {
|
||||
fn protocol_name(&self) -> &[u8] {
|
||||
PROTOCOL.as_bytes()
|
||||
impl AsRef<str> for CooperativeXmrRedeemProtocol {
|
||||
fn as_ref(&self) -> &str {
|
||||
PROTOCOL
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,19 +47,24 @@ pub enum Response {
|
|||
reason: CooperativeXmrRedeemRejectReason,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn alice() -> Behaviour {
|
||||
Behaviour::new(
|
||||
CborCodec::default(),
|
||||
vec![(CooperativeXmrRedeemProtocol, ProtocolSupport::Inbound)],
|
||||
RequestResponseConfig::default(),
|
||||
vec![(
|
||||
StreamProtocol::new(CooperativeXmrRedeemProtocol.as_ref()),
|
||||
ProtocolSupport::Inbound,
|
||||
)],
|
||||
request_response::Config::default().with_request_timeout(Duration::from_secs(60)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn bob() -> Behaviour {
|
||||
Behaviour::new(
|
||||
CborCodec::default(),
|
||||
vec![(CooperativeXmrRedeemProtocol, ProtocolSupport::Outbound)],
|
||||
RequestResponseConfig::default(),
|
||||
vec![(
|
||||
StreamProtocol::new(CooperativeXmrRedeemProtocol.as_ref()),
|
||||
ProtocolSupport::Outbound,
|
||||
)],
|
||||
request_response::Config::default().with_request_timeout(Duration::from_secs(60)),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,26 +1,22 @@
|
|||
use crate::network::cbor_request_response::CborCodec;
|
||||
use crate::{asb, cli};
|
||||
use libp2p::core::ProtocolName;
|
||||
use libp2p::request_response::{
|
||||
ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent,
|
||||
RequestResponseMessage,
|
||||
};
|
||||
use libp2p::PeerId;
|
||||
use libp2p::request_response::{self};
|
||||
use libp2p::{PeerId, StreamProtocol};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use uuid::Uuid;
|
||||
|
||||
const PROTOCOL: &str = "/comit/xmr/btc/encrypted_signature/1.0.0";
|
||||
type OutEvent = RequestResponseEvent<Request, ()>;
|
||||
type Message = RequestResponseMessage<Request, ()>;
|
||||
type OutEvent = request_response::Event<Request, ()>;
|
||||
type Message = request_response::Message<Request, ()>;
|
||||
|
||||
pub type Behaviour = RequestResponse<CborCodec<EncryptedSignatureProtocol, Request, ()>>;
|
||||
pub type Behaviour = request_response::cbor::Behaviour<Request, ()>;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct EncryptedSignatureProtocol;
|
||||
|
||||
impl ProtocolName for EncryptedSignatureProtocol {
|
||||
fn protocol_name(&self) -> &[u8] {
|
||||
PROTOCOL.as_bytes()
|
||||
impl AsRef<str> for EncryptedSignatureProtocol {
|
||||
fn as_ref(&self) -> &str {
|
||||
PROTOCOL
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,17 +28,21 @@ pub struct Request {
|
|||
|
||||
pub fn alice() -> Behaviour {
|
||||
Behaviour::new(
|
||||
CborCodec::default(),
|
||||
vec![(EncryptedSignatureProtocol, ProtocolSupport::Inbound)],
|
||||
RequestResponseConfig::default(),
|
||||
vec![(
|
||||
StreamProtocol::new(EncryptedSignatureProtocol.as_ref()),
|
||||
request_response::ProtocolSupport::Inbound,
|
||||
)],
|
||||
request_response::Config::default().with_request_timeout(Duration::from_secs(60)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn bob() -> Behaviour {
|
||||
Behaviour::new(
|
||||
CborCodec::default(),
|
||||
vec![(EncryptedSignatureProtocol, ProtocolSupport::Outbound)],
|
||||
RequestResponseConfig::default(),
|
||||
vec![(
|
||||
StreamProtocol::new(EncryptedSignatureProtocol.as_ref()),
|
||||
request_response::ProtocolSupport::Outbound,
|
||||
)],
|
||||
request_response::Config::default().with_request_timeout(Duration::from_secs(60)),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/// Helper macro to map a [`RequestResponseEvent`] to our [`OutEvent`].
|
||||
/// Helper macro to map a [`request_response::Event`] to our [`OutEvent`].
|
||||
///
|
||||
/// This is primarily a macro and not a regular function because we use it for
|
||||
/// Alice and Bob and they have different [`OutEvent`]s that just happen to
|
||||
|
@ -8,59 +8,33 @@ macro_rules! impl_from_rr_event {
|
|||
($protocol_event:ty, $behaviour_out_event:ty, $protocol:ident) => {
|
||||
impl From<$protocol_event> for $behaviour_out_event {
|
||||
fn from(event: $protocol_event) -> Self {
|
||||
use ::libp2p::request_response::RequestResponseEvent::*;
|
||||
use anyhow::anyhow;
|
||||
use ::libp2p::request_response::Event::*;
|
||||
|
||||
match event {
|
||||
Message { message, peer, .. } => Self::from((peer, message)),
|
||||
ResponseSent { .. } => Self::Other,
|
||||
InboundFailure { peer, error, .. } => {
|
||||
use libp2p::request_response::InboundFailure::*;
|
||||
|
||||
match error {
|
||||
Timeout => {
|
||||
Self::Failure {
|
||||
error: anyhow!("{} failed because of an inbound timeout", $protocol),
|
||||
InboundFailure {
|
||||
peer,
|
||||
}
|
||||
}
|
||||
ConnectionClosed => {
|
||||
Self::Failure {
|
||||
error: anyhow!("{} failed because the connection was closed before a response could be sent", $protocol),
|
||||
error,
|
||||
request_id,
|
||||
} => Self::InboundRequestResponseFailure {
|
||||
peer,
|
||||
}
|
||||
}
|
||||
UnsupportedProtocols => Self::Other, // TODO: Report this and disconnected / ban the peer?
|
||||
ResponseOmission => Self::Other
|
||||
}
|
||||
}
|
||||
OutboundFailure { peer, error, .. } => {
|
||||
use libp2p::request_response::OutboundFailure::*;
|
||||
|
||||
match error {
|
||||
Timeout => {
|
||||
Self::Failure {
|
||||
error: anyhow!("{} failed because we did not receive a response within the configured timeout", $protocol),
|
||||
error,
|
||||
request_id,
|
||||
protocol: $protocol.to_string(),
|
||||
},
|
||||
OutboundFailure {
|
||||
peer,
|
||||
}
|
||||
}
|
||||
ConnectionClosed => {
|
||||
Self::Failure {
|
||||
error: anyhow!("{} failed because the connection was closed we received a response", $protocol),
|
||||
peer,
|
||||
}
|
||||
}
|
||||
UnsupportedProtocols => Self::Other, // TODO: Report this and disconnected / ban the peer?
|
||||
DialFailure => {
|
||||
Self::Failure {
|
||||
error: anyhow!("{} failed because we failed to dial", $protocol),
|
||||
error,
|
||||
request_id,
|
||||
} => Self::OutboundRequestResponseFailure {
|
||||
peer,
|
||||
error,
|
||||
request_id,
|
||||
protocol: $protocol.to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
use async_trait::async_trait;
|
||||
use futures::prelude::*;
|
||||
use libp2p::core::upgrade;
|
||||
use libp2p::request_response::{ProtocolName, RequestResponseCodec};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use std::fmt::Debug;
|
||||
use std::io;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Message receive buffer.
|
||||
pub const BUF_SIZE: usize = 1024 * 1024;
|
||||
|
||||
/// A [`RequestResponseCodec`] for pull-based protocols where the response is
|
||||
/// encoded using JSON.
|
||||
///
|
||||
/// A pull-based protocol is a protocol where the dialer doesn't send any
|
||||
/// message and expects the listener to directly send the response as the
|
||||
/// substream is opened.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct JsonPullCodec<P, Res> {
|
||||
phantom: PhantomData<(P, Res)>,
|
||||
}
|
||||
|
||||
impl<P, Res> Default for JsonPullCodec<P, Res> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<P, Res> RequestResponseCodec for JsonPullCodec<P, Res>
|
||||
where
|
||||
P: ProtocolName + Send + Sync + Clone,
|
||||
Res: DeserializeOwned + Serialize + Send,
|
||||
{
|
||||
type Protocol = P;
|
||||
type Request = ();
|
||||
type Response = Res;
|
||||
|
||||
async fn read_request<T>(&mut self, _: &Self::Protocol, _: &mut T) -> io::Result<Self::Request>
|
||||
where
|
||||
T: AsyncRead + Unpin + Send,
|
||||
{
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn read_response<T>(
|
||||
&mut self,
|
||||
_: &Self::Protocol,
|
||||
io: &mut T,
|
||||
) -> io::Result<Self::Response>
|
||||
where
|
||||
T: AsyncRead + Unpin + Send,
|
||||
{
|
||||
let message = upgrade::read_length_prefixed(io, BUF_SIZE)
|
||||
.await
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
||||
let mut de = serde_json::Deserializer::from_slice(&message);
|
||||
let msg = Res::deserialize(&mut de)
|
||||
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
|
||||
|
||||
Ok(msg)
|
||||
}
|
||||
|
||||
async fn write_request<T>(
|
||||
&mut self,
|
||||
_: &Self::Protocol,
|
||||
_: &mut T,
|
||||
_: Self::Request,
|
||||
) -> io::Result<()>
|
||||
where
|
||||
T: AsyncWrite + Unpin + Send,
|
||||
{
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_response<T>(
|
||||
&mut self,
|
||||
_: &Self::Protocol,
|
||||
io: &mut T,
|
||||
res: Self::Response,
|
||||
) -> io::Result<()>
|
||||
where
|
||||
T: AsyncWrite + Unpin + Send,
|
||||
{
|
||||
let bytes = serde_json::to_vec(&res)
|
||||
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
|
||||
upgrade::write_length_prefixed(io, &bytes).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,26 +1,23 @@
|
|||
use crate::network::json_pull_codec::JsonPullCodec;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{asb, bitcoin, cli};
|
||||
use libp2p::core::ProtocolName;
|
||||
use libp2p::request_response::{
|
||||
ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent,
|
||||
RequestResponseMessage,
|
||||
};
|
||||
use libp2p::PeerId;
|
||||
use libp2p::request_response::{self, ProtocolSupport};
|
||||
use libp2p::{PeerId, StreamProtocol};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
const PROTOCOL: &str = "/comit/xmr/btc/bid-quote/1.0.0";
|
||||
pub type OutEvent = RequestResponseEvent<(), BidQuote>;
|
||||
pub type Message = RequestResponseMessage<(), BidQuote>;
|
||||
pub type OutEvent = request_response::Event<(), BidQuote>;
|
||||
pub type Message = request_response::Message<(), BidQuote>;
|
||||
|
||||
pub type Behaviour = RequestResponse<JsonPullCodec<BidQuoteProtocol, BidQuote>>;
|
||||
pub type Behaviour = request_response::json::Behaviour<(), BidQuote>;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct BidQuoteProtocol;
|
||||
|
||||
impl ProtocolName for BidQuoteProtocol {
|
||||
fn protocol_name(&self) -> &[u8] {
|
||||
PROTOCOL.as_bytes()
|
||||
impl AsRef<str> for BidQuoteProtocol {
|
||||
fn as_ref(&self) -> &str {
|
||||
PROTOCOL
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,9 +50,8 @@ pub struct ZeroQuoteReceived;
|
|||
/// handing out quotes.
|
||||
pub fn asb() -> Behaviour {
|
||||
Behaviour::new(
|
||||
JsonPullCodec::default(),
|
||||
vec![(BidQuoteProtocol, ProtocolSupport::Inbound)],
|
||||
RequestResponseConfig::default(),
|
||||
vec![(StreamProtocol::new(PROTOCOL), ProtocolSupport::Inbound)],
|
||||
request_response::Config::default().with_request_timeout(Duration::from_secs(60)),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -65,9 +61,8 @@ pub fn asb() -> Behaviour {
|
|||
/// requesting quotes.
|
||||
pub fn cli() -> Behaviour {
|
||||
Behaviour::new(
|
||||
JsonPullCodec::default(),
|
||||
vec![(BidQuoteProtocol, ProtocolSupport::Outbound)],
|
||||
RequestResponseConfig::default(),
|
||||
vec![(StreamProtocol::new(PROTOCOL), ProtocolSupport::Outbound)],
|
||||
request_response::Config::default().with_request_timeout(Duration::from_secs(60)),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
use crate::cli;
|
||||
use backoff::backoff::Backoff;
|
||||
use backoff::ExponentialBackoff;
|
||||
use futures::future::FutureExt;
|
||||
use libp2p::core::connection::ConnectionId;
|
||||
use libp2p::core::Multiaddr;
|
||||
use libp2p::swarm::dial_opts::{DialOpts, PeerCondition};
|
||||
use libp2p::swarm::protocols_handler::DummyProtocolsHandler;
|
||||
use libp2p::swarm::{NetworkBehaviour, NetworkBehaviourAction, PollParameters};
|
||||
use libp2p::swarm::{NetworkBehaviour, ToSwarm};
|
||||
use libp2p::PeerId;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
@ -14,9 +11,7 @@ use std::time::Duration;
|
|||
use tokio::time::{Instant, Sleep};
|
||||
use void::Void;
|
||||
|
||||
pub enum OutEvent {
|
||||
AllAttemptsExhausted { peer: PeerId },
|
||||
}
|
||||
use crate::cli;
|
||||
|
||||
/// A [`NetworkBehaviour`] that tracks whether we are connected to the given
|
||||
/// peer and attempts to re-establish a connection with an exponential backoff
|
||||
|
@ -31,15 +26,15 @@ pub struct Behaviour {
|
|||
}
|
||||
|
||||
impl Behaviour {
|
||||
pub fn new(peer: PeerId, interval: Duration) -> Self {
|
||||
pub fn new(peer: PeerId, interval: Duration, max_interval: Duration) -> Self {
|
||||
Self {
|
||||
peer,
|
||||
sleep: None,
|
||||
backoff: ExponentialBackoff {
|
||||
initial_interval: interval,
|
||||
current_interval: interval,
|
||||
// give up dialling after 5 minutes
|
||||
max_elapsed_time: Some(Duration::from_secs(5 * 60)),
|
||||
max_interval,
|
||||
max_elapsed_time: None, // We never give up on re-dialling
|
||||
..ExponentialBackoff::default()
|
||||
},
|
||||
}
|
||||
|
@ -57,44 +52,57 @@ impl Behaviour {
|
|||
}
|
||||
|
||||
impl NetworkBehaviour for Behaviour {
|
||||
type ProtocolsHandler = DummyProtocolsHandler;
|
||||
type OutEvent = OutEvent;
|
||||
type ConnectionHandler = libp2p::swarm::dummy::ConnectionHandler;
|
||||
type ToSwarm = ();
|
||||
|
||||
fn new_handler(&mut self) -> Self::ProtocolsHandler {
|
||||
DummyProtocolsHandler::default()
|
||||
}
|
||||
|
||||
fn addresses_of_peer(&mut self, _: &PeerId) -> Vec<Multiaddr> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn inject_connected(&mut self, peer_id: &PeerId) {
|
||||
if peer_id != &self.peer {
|
||||
return;
|
||||
}
|
||||
|
||||
// established a connection to the desired peer, cancel any active re-dialling
|
||||
fn handle_established_inbound_connection(
|
||||
&mut self,
|
||||
_connection_id: libp2p::swarm::ConnectionId,
|
||||
peer: PeerId,
|
||||
_local_addr: &Multiaddr,
|
||||
_remote_addr: &Multiaddr,
|
||||
) -> Result<libp2p::swarm::THandler<Self>, libp2p::swarm::ConnectionDenied> {
|
||||
// We establish an inbound connection to the peer we are interested in.
|
||||
// We stop re-dialling.
|
||||
// Reset the backoff state to start with the initial interval again once we disconnect again
|
||||
if peer == self.peer {
|
||||
self.backoff.reset();
|
||||
self.sleep = None;
|
||||
}
|
||||
|
||||
fn inject_disconnected(&mut self, peer_id: &PeerId) {
|
||||
if peer_id != &self.peer {
|
||||
return;
|
||||
Ok(Self::ConnectionHandler {})
|
||||
}
|
||||
|
||||
// lost connection to the configured peer, trigger re-dialling with an
|
||||
// exponential backoff
|
||||
self.backoff.reset();
|
||||
self.sleep = Some(Box::pin(tokio::time::sleep(self.backoff.initial_interval)));
|
||||
}
|
||||
|
||||
fn inject_event(&mut self, _: PeerId, _: ConnectionId, _: Void) {}
|
||||
|
||||
fn poll(
|
||||
fn handle_established_outbound_connection(
|
||||
&mut self,
|
||||
cx: &mut Context<'_>,
|
||||
_: &mut impl PollParameters,
|
||||
) -> Poll<NetworkBehaviourAction<Self::OutEvent, Self::ProtocolsHandler>> {
|
||||
_connection_id: libp2p::swarm::ConnectionId,
|
||||
peer: PeerId,
|
||||
_addr: &Multiaddr,
|
||||
_role_override: libp2p::core::Endpoint,
|
||||
) -> Result<libp2p::swarm::THandler<Self>, libp2p::swarm::ConnectionDenied> {
|
||||
// We establish an outbound connection to the peer we are interested in.
|
||||
// We stop re-dialling.
|
||||
// Reset the backoff state to start with the initial interval again once we disconnect again
|
||||
if peer == self.peer {
|
||||
self.backoff.reset();
|
||||
self.sleep = None;
|
||||
}
|
||||
Ok(Self::ConnectionHandler {})
|
||||
}
|
||||
|
||||
fn on_swarm_event(&mut self, event: libp2p::swarm::FromSwarm<'_>) {
|
||||
let redial = match event {
|
||||
libp2p::swarm::FromSwarm::ConnectionClosed(e) if e.peer_id == self.peer => true,
|
||||
libp2p::swarm::FromSwarm::DialFailure(e) if e.peer_id == Some(self.peer) => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if redial && self.sleep.is_none() {
|
||||
self.sleep = Some(Box::pin(tokio::time::sleep(self.backoff.initial_interval)));
|
||||
tracing::info!(seconds_until_next_redial = %self.until_next_redial().unwrap().as_secs(), "Waiting for next redial attempt");
|
||||
}
|
||||
}
|
||||
|
||||
fn poll(&mut self, cx: &mut Context<'_>) -> std::task::Poll<ToSwarm<Self::ToSwarm, Void>> {
|
||||
let sleep = match self.sleep.as_mut() {
|
||||
None => return Poll::Pending, // early exit if we shouldn't be re-dialling
|
||||
Some(future) => future,
|
||||
|
@ -105,29 +113,31 @@ impl NetworkBehaviour for Behaviour {
|
|||
let next_dial_in = match self.backoff.next_backoff() {
|
||||
Some(next_dial_in) => next_dial_in,
|
||||
None => {
|
||||
return Poll::Ready(NetworkBehaviourAction::GenerateEvent(
|
||||
OutEvent::AllAttemptsExhausted { peer: self.peer },
|
||||
));
|
||||
unreachable!("The backoff should never run out of attempts");
|
||||
}
|
||||
};
|
||||
|
||||
self.sleep = Some(Box::pin(tokio::time::sleep(next_dial_in)));
|
||||
|
||||
Poll::Ready(NetworkBehaviourAction::Dial {
|
||||
Poll::Ready(ToSwarm::Dial {
|
||||
opts: DialOpts::peer_id(self.peer)
|
||||
.condition(PeerCondition::Disconnected)
|
||||
.build(),
|
||||
handler: Self::ProtocolsHandler::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn on_connection_handler_event(
|
||||
&mut self,
|
||||
_peer_id: PeerId,
|
||||
_connection_id: libp2p::swarm::ConnectionId,
|
||||
_event: libp2p::swarm::THandlerOutEvent<Self>,
|
||||
) {
|
||||
unreachable!("The re-dial dummy connection handler does not produce any events");
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OutEvent> for cli::OutEvent {
|
||||
fn from(event: OutEvent) -> Self {
|
||||
match event {
|
||||
OutEvent::AllAttemptsExhausted { peer } => {
|
||||
cli::OutEvent::AllRedialAttemptsExhausted { peer }
|
||||
}
|
||||
}
|
||||
impl From<()> for cli::OutEvent {
|
||||
fn from(_: ()) -> Self {
|
||||
Self::Other
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,39 +1,36 @@
|
|||
use crate::monero;
|
||||
use anyhow::{Context, Result};
|
||||
use libp2p::core::upgrade;
|
||||
use libp2p::swarm::NegotiatedSubstream;
|
||||
use asynchronous_codec::{Bytes, Framed};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
|
||||
use libp2p::swarm::Stream;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod alice;
|
||||
pub mod bob;
|
||||
mod vendor_from_fn;
|
||||
|
||||
pub const BUF_SIZE: usize = 1024 * 1024;
|
||||
|
||||
pub mod protocol {
|
||||
use futures::future;
|
||||
use libp2p::core::upgrade::{from_fn, FromFnUpgrade};
|
||||
use libp2p::core::Endpoint;
|
||||
use libp2p::swarm::NegotiatedSubstream;
|
||||
use libp2p::swarm::Stream;
|
||||
use void::Void;
|
||||
|
||||
use super::vendor_from_fn::{from_fn, FromFnUpgrade};
|
||||
|
||||
pub fn new() -> SwapSetup {
|
||||
from_fn(
|
||||
b"/comit/xmr/btc/swap_setup/1.0.0",
|
||||
"/comit/xmr/btc/swap_setup/1.0.0",
|
||||
Box::new(|socket, _| future::ready(Ok(socket))),
|
||||
)
|
||||
}
|
||||
|
||||
pub type SwapSetup = FromFnUpgrade<
|
||||
&'static [u8],
|
||||
Box<
|
||||
dyn Fn(
|
||||
NegotiatedSubstream,
|
||||
Endpoint,
|
||||
) -> future::Ready<Result<NegotiatedSubstream, Void>>
|
||||
+ Send
|
||||
+ 'static,
|
||||
>,
|
||||
&'static str,
|
||||
Box<dyn Fn(Stream, Endpoint) -> future::Ready<Result<Stream, Void>> + Send + 'static>,
|
||||
>;
|
||||
}
|
||||
|
||||
|
@ -86,13 +83,23 @@ pub enum SpotPriceError {
|
|||
Other,
|
||||
}
|
||||
|
||||
pub async fn read_cbor_message<T>(substream: &mut NegotiatedSubstream) -> Result<T>
|
||||
fn codec() -> unsigned_varint::codec::UviBytes<Bytes> {
|
||||
let mut codec = unsigned_varint::codec::UviBytes::<Bytes>::default();
|
||||
codec.set_max_len(BUF_SIZE);
|
||||
codec
|
||||
}
|
||||
|
||||
pub async fn read_cbor_message<T>(stream: &mut Stream) -> Result<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let bytes = upgrade::read_length_prefixed(substream, BUF_SIZE)
|
||||
let mut frame = Framed::new(stream, codec());
|
||||
|
||||
let bytes = frame
|
||||
.next()
|
||||
.await
|
||||
.context("Failed to read length-prefixed message from stream")?;
|
||||
.context("Failed to read length-prefixed message from stream")??;
|
||||
|
||||
let mut de = serde_cbor::Deserializer::from_slice(&bytes);
|
||||
let message =
|
||||
T::deserialize(&mut de).context("Failed to deserialize bytes into message using CBOR")?;
|
||||
|
@ -100,13 +107,17 @@ where
|
|||
Ok(message)
|
||||
}
|
||||
|
||||
pub async fn write_cbor_message<T>(substream: &mut NegotiatedSubstream, message: T) -> Result<()>
|
||||
pub async fn write_cbor_message<T>(stream: &mut Stream, message: T) -> Result<()>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
let bytes =
|
||||
serde_cbor::to_vec(&message).context("Failed to serialize message as bytes using CBOR")?;
|
||||
upgrade::write_length_prefixed(substream, &bytes)
|
||||
|
||||
let mut frame = Framed::new(stream, codec());
|
||||
|
||||
frame
|
||||
.send(Bytes::from(bytes))
|
||||
.await
|
||||
.context("Failed to write bytes as length-prefixed message")?;
|
||||
|
||||
|
|
|
@ -9,20 +9,18 @@ use crate::protocol::{Message0, Message2, Message4};
|
|||
use crate::{asb, bitcoin, env, monero};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use futures::future::{BoxFuture, OptionFuture};
|
||||
use futures::{AsyncWriteExt, FutureExt};
|
||||
use libp2p::core::connection::ConnectionId;
|
||||
use futures::AsyncWriteExt;
|
||||
use futures::FutureExt;
|
||||
use libp2p::core::upgrade;
|
||||
use libp2p::swarm::{
|
||||
KeepAlive, NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, PollParameters,
|
||||
ProtocolsHandler, ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr, SubstreamProtocol,
|
||||
};
|
||||
use libp2p::swarm::handler::ConnectionEvent;
|
||||
use libp2p::swarm::{ConnectionHandler, ConnectionId};
|
||||
use libp2p::swarm::{ConnectionHandlerEvent, NetworkBehaviour, SubstreamProtocol, ToSwarm};
|
||||
use libp2p::{Multiaddr, PeerId};
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Debug;
|
||||
use std::task::Poll;
|
||||
use std::time::{Duration, Instant};
|
||||
use uuid::Uuid;
|
||||
use void::Void;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
|
@ -147,28 +145,59 @@ impl<LR> NetworkBehaviour for Behaviour<LR>
|
|||
where
|
||||
LR: LatestRate + Send + 'static + Clone,
|
||||
{
|
||||
type ProtocolsHandler = Handler<LR>;
|
||||
type OutEvent = OutEvent;
|
||||
type ConnectionHandler = Handler<LR>;
|
||||
type ToSwarm = OutEvent;
|
||||
|
||||
fn new_handler(&mut self) -> Self::ProtocolsHandler {
|
||||
Handler::new(
|
||||
fn handle_established_inbound_connection(
|
||||
&mut self,
|
||||
_connection_id: libp2p::swarm::ConnectionId,
|
||||
_peer: PeerId,
|
||||
_local_addr: &Multiaddr,
|
||||
_remote_addr: &Multiaddr,
|
||||
) -> std::result::Result<libp2p::swarm::THandler<Self>, libp2p::swarm::ConnectionDenied> {
|
||||
// A new inbound connection has been established by Bob
|
||||
// He wants to negotiate a swap setup with us
|
||||
// We create a new Handler to handle the negotiation
|
||||
let handler = Handler::new(
|
||||
self.min_buy,
|
||||
self.max_buy,
|
||||
self.env_config,
|
||||
self.latest_rate.clone(),
|
||||
self.resume_only,
|
||||
)
|
||||
);
|
||||
|
||||
Ok(handler)
|
||||
}
|
||||
|
||||
fn addresses_of_peer(&mut self, _: &PeerId) -> Vec<Multiaddr> {
|
||||
Vec::new()
|
||||
fn handle_established_outbound_connection(
|
||||
&mut self,
|
||||
_connection_id: libp2p::swarm::ConnectionId,
|
||||
_peer: PeerId,
|
||||
_addr: &Multiaddr,
|
||||
_role_override: libp2p::core::Endpoint,
|
||||
) -> std::result::Result<libp2p::swarm::THandler<Self>, libp2p::swarm::ConnectionDenied> {
|
||||
// A new outbound connection has been established (probably to a rendezvous node because we dont dial Bob)
|
||||
// We still return a handler, because we dont want to close the connection
|
||||
let handler = Handler::new(
|
||||
self.min_buy,
|
||||
self.max_buy,
|
||||
self.env_config,
|
||||
self.latest_rate.clone(),
|
||||
self.resume_only,
|
||||
);
|
||||
|
||||
Ok(handler)
|
||||
}
|
||||
|
||||
fn inject_connected(&mut self, _: &PeerId) {}
|
||||
|
||||
fn inject_disconnected(&mut self, _: &PeerId) {}
|
||||
|
||||
fn inject_event(&mut self, peer_id: PeerId, _: ConnectionId, event: HandlerOutEvent) {
|
||||
fn on_connection_handler_event(
|
||||
&mut self,
|
||||
peer_id: PeerId,
|
||||
_: ConnectionId,
|
||||
event: HandlerOutEvent,
|
||||
) {
|
||||
// Here we receive events from the Handler, add some context and forward them to the swarm
|
||||
// This is done by pushing the event to the [`events`] queue
|
||||
// The queue is then polled in the [`poll`] function, and the events are sent to the swarm
|
||||
match event {
|
||||
HandlerOutEvent::Initiated(send_wallet_snapshot) => {
|
||||
self.events.push_back(OutEvent::Initiated {
|
||||
|
@ -188,17 +217,18 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn poll(
|
||||
&mut self,
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
_params: &mut impl PollParameters,
|
||||
) -> Poll<NetworkBehaviourAction<Self::OutEvent, Self::ProtocolsHandler>> {
|
||||
fn poll(&mut self, _cx: &mut std::task::Context<'_>) -> Poll<ToSwarm<Self::ToSwarm, ()>> {
|
||||
// Poll events from the queue and send them to the swarm
|
||||
if let Some(event) = self.events.pop_front() {
|
||||
return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event));
|
||||
return Poll::Ready(ToSwarm::GenerateEvent(event));
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
|
||||
fn on_swarm_event(&mut self, _event: libp2p::swarm::FromSwarm<'_>) {
|
||||
// We do not need to handle any swarm events here
|
||||
}
|
||||
}
|
||||
|
||||
type InboundStream = BoxFuture<'static, Result<(Uuid, State3)>>;
|
||||
|
@ -214,8 +244,12 @@ pub struct Handler<LR> {
|
|||
latest_rate: LR,
|
||||
resume_only: bool,
|
||||
|
||||
timeout: Duration,
|
||||
keep_alive: KeepAlive,
|
||||
// This is the timeout for the negotiation phase where Alice and Bob exchange messages
|
||||
negotiation_timeout: Duration,
|
||||
|
||||
// If set to None, we will keep the connection alive indefinitely
|
||||
// If set to Some, we will keep the connection alive until the given instant
|
||||
keep_alive_until: Option<Instant>,
|
||||
}
|
||||
|
||||
impl<LR> Handler<LR> {
|
||||
|
@ -234,8 +268,8 @@ impl<LR> Handler<LR> {
|
|||
env_config,
|
||||
latest_rate,
|
||||
resume_only,
|
||||
timeout: Duration::from_secs(120),
|
||||
keep_alive: KeepAlive::Until(Instant::now() + Duration::from_secs(10)),
|
||||
negotiation_timeout: Duration::from_secs(120),
|
||||
keep_alive_until: Some(Instant::now() + Duration::from_secs(30)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -247,13 +281,12 @@ pub enum HandlerOutEvent {
|
|||
Completed(Result<(Uuid, State3)>),
|
||||
}
|
||||
|
||||
impl<LR> ProtocolsHandler for Handler<LR>
|
||||
impl<LR> ConnectionHandler for Handler<LR>
|
||||
where
|
||||
LR: LatestRate + Send + 'static,
|
||||
{
|
||||
type InEvent = ();
|
||||
type OutEvent = HandlerOutEvent;
|
||||
type Error = Error;
|
||||
type FromBehaviour = ();
|
||||
type ToBehaviour = HandlerOutEvent;
|
||||
type InboundProtocol = protocol::SwapSetup;
|
||||
type OutboundProtocol = upgrade::DeniedUpgrade;
|
||||
type InboundOpenInfo = ();
|
||||
|
@ -263,24 +296,35 @@ where
|
|||
SubstreamProtocol::new(protocol::new(), ())
|
||||
}
|
||||
|
||||
fn inject_fully_negotiated_inbound(
|
||||
fn on_connection_event(
|
||||
&mut self,
|
||||
mut substream: NegotiatedSubstream,
|
||||
_: Self::InboundOpenInfo,
|
||||
event: libp2p::swarm::handler::ConnectionEvent<
|
||||
'_,
|
||||
Self::InboundProtocol,
|
||||
Self::OutboundProtocol,
|
||||
Self::InboundOpenInfo,
|
||||
Self::OutboundOpenInfo,
|
||||
>,
|
||||
) {
|
||||
self.keep_alive = KeepAlive::Yes;
|
||||
match event {
|
||||
ConnectionEvent::FullyNegotiatedInbound(substream) => {
|
||||
self.keep_alive_until = None;
|
||||
|
||||
let mut substream = substream.protocol;
|
||||
|
||||
let (sender, receiver) = bmrng::channel_with_timeout::<
|
||||
bitcoin::Amount,
|
||||
WalletSnapshot,
|
||||
>(1, Duration::from_secs(60));
|
||||
|
||||
let (sender, receiver) = bmrng::channel_with_timeout::<bitcoin::Amount, WalletSnapshot>(
|
||||
1,
|
||||
Duration::from_secs(5),
|
||||
);
|
||||
let resume_only = self.resume_only;
|
||||
let min_buy = self.min_buy;
|
||||
let max_buy = self.max_buy;
|
||||
let latest_rate = self.latest_rate.latest_rate();
|
||||
let env_config = self.env_config;
|
||||
|
||||
let protocol = tokio::time::timeout(self.timeout, async move {
|
||||
// We wrap the entire handshake in a timeout future
|
||||
let protocol = tokio::time::timeout(self.negotiation_timeout, async move {
|
||||
let request = swap_setup::read_cbor_message::<SpotPriceRequest>(&mut substream)
|
||||
.await
|
||||
.context("Failed to read spot price request")?;
|
||||
|
@ -325,12 +369,15 @@ where
|
|||
});
|
||||
}
|
||||
|
||||
let rate = latest_rate.map_err(|e| Error::LatestRateFetchFailed(Box::new(e)))?;
|
||||
let rate =
|
||||
latest_rate.map_err(|e| Error::LatestRateFetchFailed(Box::new(e)))?;
|
||||
let xmr = rate
|
||||
.sell_quote(btc)
|
||||
.map_err(Error::SellQuoteCalculationFailed)?;
|
||||
|
||||
let unlocked = Amount::from_piconero(wallet_snapshot.balance.unlocked_balance);
|
||||
let unlocked =
|
||||
Amount::from_piconero(wallet_snapshot.balance.unlocked_balance);
|
||||
|
||||
if unlocked < xmr + wallet_snapshot.lock_fee {
|
||||
return Err(Error::BalanceTooLow {
|
||||
balance: wallet_snapshot.balance,
|
||||
|
@ -404,7 +451,7 @@ where
|
|||
Ok((swap_id, state3))
|
||||
});
|
||||
|
||||
let max_seconds = self.timeout.as_secs();
|
||||
let max_seconds = self.negotiation_timeout.as_secs();
|
||||
self.inbound_stream = OptionFuture::from(Some(
|
||||
async move {
|
||||
protocol.await.with_context(|| {
|
||||
|
@ -416,25 +463,27 @@ where
|
|||
|
||||
self.events.push_back(HandlerOutEvent::Initiated(receiver));
|
||||
}
|
||||
|
||||
fn inject_fully_negotiated_outbound(&mut self, _: Void, _: Self::OutboundOpenInfo) {
|
||||
unreachable!("Alice does not support outbound in the handler")
|
||||
ConnectionEvent::DialUpgradeError(..) => {
|
||||
unreachable!("Alice does not dial")
|
||||
}
|
||||
ConnectionEvent::FullyNegotiatedOutbound(..) => {
|
||||
unreachable!("Alice does not support outbound connections")
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn inject_event(&mut self, _: Self::InEvent) {
|
||||
fn on_behaviour_event(&mut self, _event: Self::FromBehaviour) {
|
||||
unreachable!("Alice does not receive events from the Behaviour in the handler")
|
||||
}
|
||||
|
||||
fn inject_dial_upgrade_error(
|
||||
&mut self,
|
||||
_: Self::OutboundOpenInfo,
|
||||
_: ProtocolsHandlerUpgrErr<Void>,
|
||||
) {
|
||||
unreachable!("Alice does not dial")
|
||||
fn connection_keep_alive(&self) -> bool {
|
||||
// If keep_alive_until is None, we keep the connection alive indefinitely
|
||||
// If keep_alive_until is Some, we keep the connection alive until the given instant
|
||||
match self.keep_alive_until {
|
||||
None => true,
|
||||
Some(keep_alive_until) => Instant::now() < keep_alive_until,
|
||||
}
|
||||
|
||||
fn connection_keep_alive(&self) -> KeepAlive {
|
||||
self.keep_alive
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
|
@ -442,22 +491,21 @@ where
|
|||
&mut self,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> Poll<
|
||||
ProtocolsHandlerEvent<
|
||||
Self::OutboundProtocol,
|
||||
Self::OutboundOpenInfo,
|
||||
Self::OutEvent,
|
||||
Self::Error,
|
||||
>,
|
||||
ConnectionHandlerEvent<Self::OutboundProtocol, Self::OutboundOpenInfo, Self::ToBehaviour>,
|
||||
> {
|
||||
// Send events in the queue to the behaviour
|
||||
// This is currently only used to notify the behaviour that the negotiation phase has been initiated
|
||||
if let Some(event) = self.events.pop_front() {
|
||||
return Poll::Ready(ProtocolsHandlerEvent::Custom(event));
|
||||
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(event));
|
||||
}
|
||||
|
||||
if let Some(result) = futures::ready!(self.inbound_stream.poll_unpin(cx)) {
|
||||
self.inbound_stream = OptionFuture::from(None);
|
||||
return Poll::Ready(ProtocolsHandlerEvent::Custom(HandlerOutEvent::Completed(
|
||||
result,
|
||||
)));
|
||||
|
||||
// Notify the behaviour that the negotiation phase has been completed
|
||||
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(
|
||||
HandlerOutEvent::Completed(result),
|
||||
));
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
|
|
|
@ -1,27 +1,24 @@
|
|||
use crate::network::swap_setup::{
|
||||
protocol, read_cbor_message, write_cbor_message, BlockchainNetwork, SpotPriceError,
|
||||
SpotPriceRequest, SpotPriceResponse,
|
||||
};
|
||||
use crate::network::swap_setup::{protocol, BlockchainNetwork, SpotPriceError, SpotPriceResponse};
|
||||
use crate::protocol::bob::{State0, State2};
|
||||
use crate::protocol::{Message1, Message3};
|
||||
use crate::{bitcoin, cli, env, monero};
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use futures::future::{BoxFuture, OptionFuture};
|
||||
use futures::{AsyncWriteExt, FutureExt};
|
||||
use libp2p::core::connection::ConnectionId;
|
||||
use futures::AsyncWriteExt;
|
||||
use futures::FutureExt;
|
||||
use libp2p::core::upgrade;
|
||||
use libp2p::swarm::{
|
||||
KeepAlive, NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler,
|
||||
PollParameters, ProtocolsHandler, ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr,
|
||||
SubstreamProtocol,
|
||||
ConnectionDenied, ConnectionHandler, ConnectionHandlerEvent, ConnectionId, FromSwarm,
|
||||
NetworkBehaviour, SubstreamProtocol, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm,
|
||||
};
|
||||
use libp2p::{Multiaddr, PeerId};
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use std::task::Poll;
|
||||
use std::time::Duration;
|
||||
use uuid::Uuid;
|
||||
use void::Void;
|
||||
|
||||
use super::{read_cbor_message, write_cbor_message, SpotPriceRequest};
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Behaviour {
|
||||
|
@ -53,38 +50,56 @@ impl From<Completed> for cli::OutEvent {
|
|||
}
|
||||
|
||||
impl NetworkBehaviour for Behaviour {
|
||||
type ProtocolsHandler = Handler;
|
||||
type OutEvent = Completed;
|
||||
type ConnectionHandler = Handler;
|
||||
type ToSwarm = Completed;
|
||||
|
||||
fn new_handler(&mut self) -> Self::ProtocolsHandler {
|
||||
Handler::new(self.env_config, self.bitcoin_wallet.clone())
|
||||
fn handle_established_inbound_connection(
|
||||
&mut self,
|
||||
_connection_id: ConnectionId,
|
||||
_peer: PeerId,
|
||||
_local_addr: &Multiaddr,
|
||||
_remote_addr: &Multiaddr,
|
||||
) -> Result<THandler<Self>, ConnectionDenied> {
|
||||
Ok(Handler::new(self.env_config, self.bitcoin_wallet.clone()))
|
||||
}
|
||||
|
||||
fn addresses_of_peer(&mut self, _: &PeerId) -> Vec<Multiaddr> {
|
||||
Vec::new()
|
||||
fn handle_established_outbound_connection(
|
||||
&mut self,
|
||||
_connection_id: ConnectionId,
|
||||
_peer: PeerId,
|
||||
_addr: &Multiaddr,
|
||||
_role_override: libp2p::core::Endpoint,
|
||||
) -> Result<THandler<Self>, ConnectionDenied> {
|
||||
Ok(Handler::new(self.env_config, self.bitcoin_wallet.clone()))
|
||||
}
|
||||
|
||||
fn inject_connected(&mut self, _: &PeerId) {}
|
||||
fn on_swarm_event(&mut self, _event: FromSwarm<'_>) {
|
||||
// We do not need to handle swarm events
|
||||
}
|
||||
|
||||
fn inject_disconnected(&mut self, _: &PeerId) {}
|
||||
|
||||
fn inject_event(&mut self, peer: PeerId, _: ConnectionId, completed: Completed) {
|
||||
self.completed_swaps.push_back((peer, completed));
|
||||
fn on_connection_handler_event(
|
||||
&mut self,
|
||||
peer_id: PeerId,
|
||||
_connection_id: libp2p::swarm::ConnectionId,
|
||||
event: THandlerOutEvent<Self>,
|
||||
) {
|
||||
self.completed_swaps.push_back((peer_id, event));
|
||||
}
|
||||
|
||||
fn poll(
|
||||
&mut self,
|
||||
_cx: &mut Context<'_>,
|
||||
_params: &mut impl PollParameters,
|
||||
) -> Poll<NetworkBehaviourAction<Self::OutEvent, Self::ProtocolsHandler>> {
|
||||
if let Some((_, event)) = self.completed_swaps.pop_front() {
|
||||
return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event));
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> {
|
||||
// Forward completed swaps from the connection handler to the swarm
|
||||
if let Some((_peer, completed)) = self.completed_swaps.pop_front() {
|
||||
return Poll::Ready(ToSwarm::GenerateEvent(completed));
|
||||
}
|
||||
|
||||
// If there is a new swap to be started, send it to the connection handler
|
||||
if let Some((peer, event)) = self.new_swaps.pop_front() {
|
||||
return Poll::Ready(NetworkBehaviourAction::NotifyHandler {
|
||||
return Poll::Ready(ToSwarm::NotifyHandler {
|
||||
peer_id: peer,
|
||||
handler: NotifyHandler::Any,
|
||||
handler: libp2p::swarm::NotifyHandler::Any,
|
||||
event,
|
||||
});
|
||||
}
|
||||
|
@ -93,7 +108,7 @@ impl NetworkBehaviour for Behaviour {
|
|||
}
|
||||
}
|
||||
|
||||
type OutboundStream = BoxFuture<'static, Result<State2>>;
|
||||
type OutboundStream = BoxFuture<'static, Result<State2, Error>>;
|
||||
|
||||
pub struct Handler {
|
||||
outbound_stream: OptionFuture<OutboundStream>,
|
||||
|
@ -101,7 +116,7 @@ pub struct Handler {
|
|||
timeout: Duration,
|
||||
new_swaps: VecDeque<NewSwap>,
|
||||
bitcoin_wallet: Arc<bitcoin::Wallet>,
|
||||
keep_alive: KeepAlive,
|
||||
keep_alive: bool,
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
|
@ -112,12 +127,12 @@ impl Handler {
|
|||
timeout: Duration::from_secs(120),
|
||||
new_swaps: VecDeque::default(),
|
||||
bitcoin_wallet,
|
||||
keep_alive: KeepAlive::Yes,
|
||||
keep_alive: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NewSwap {
|
||||
pub swap_id: Uuid,
|
||||
pub btc: bitcoin::Amount,
|
||||
|
@ -129,123 +144,173 @@ pub struct NewSwap {
|
|||
#[derive(Debug)]
|
||||
pub struct Completed(Result<State2>);
|
||||
|
||||
impl ProtocolsHandler for Handler {
|
||||
type InEvent = NewSwap;
|
||||
type OutEvent = Completed;
|
||||
type Error = Void;
|
||||
impl ConnectionHandler for Handler {
|
||||
type FromBehaviour = NewSwap;
|
||||
type ToBehaviour = Completed;
|
||||
type InboundProtocol = upgrade::DeniedUpgrade;
|
||||
type OutboundProtocol = protocol::SwapSetup;
|
||||
type InboundOpenInfo = ();
|
||||
type OutboundOpenInfo = NewSwap;
|
||||
|
||||
fn listen_protocol(&self) -> SubstreamProtocol<Self::InboundProtocol, Self::InboundOpenInfo> {
|
||||
// Bob does not support inbound substreams
|
||||
SubstreamProtocol::new(upgrade::DeniedUpgrade, ())
|
||||
}
|
||||
|
||||
fn inject_fully_negotiated_inbound(&mut self, _: Void, _: Self::InboundOpenInfo) {
|
||||
fn on_connection_event(
|
||||
&mut self,
|
||||
event: libp2p::swarm::handler::ConnectionEvent<
|
||||
'_,
|
||||
Self::InboundProtocol,
|
||||
Self::OutboundProtocol,
|
||||
Self::InboundOpenInfo,
|
||||
Self::OutboundOpenInfo,
|
||||
>,
|
||||
) {
|
||||
match event {
|
||||
libp2p::swarm::handler::ConnectionEvent::FullyNegotiatedInbound(_) => {
|
||||
unreachable!("Bob does not support inbound substreams")
|
||||
}
|
||||
libp2p::swarm::handler::ConnectionEvent::FullyNegotiatedOutbound(outbound) => {
|
||||
let mut substream = outbound.protocol;
|
||||
let new_swap_request = outbound.info;
|
||||
|
||||
fn inject_fully_negotiated_outbound(
|
||||
&mut self,
|
||||
mut substream: NegotiatedSubstream,
|
||||
info: Self::OutboundOpenInfo,
|
||||
) {
|
||||
let bitcoin_wallet = self.bitcoin_wallet.clone();
|
||||
let env_config = self.env_config;
|
||||
|
||||
let protocol = tokio::time::timeout(self.timeout, async move {
|
||||
let result = async {
|
||||
// Here we request the spot price from Alice
|
||||
write_cbor_message(
|
||||
&mut substream,
|
||||
SpotPriceRequest {
|
||||
btc: info.btc,
|
||||
btc: new_swap_request.btc,
|
||||
blockchain_network: BlockchainNetwork {
|
||||
bitcoin: env_config.bitcoin_network,
|
||||
monero: env_config.monero_network,
|
||||
},
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.context("Failed to send spot price request to Alice")?;
|
||||
|
||||
let xmr = Result::from(read_cbor_message::<SpotPriceResponse>(&mut substream).await?)?;
|
||||
// Here we read the spot price response from Alice
|
||||
// The outer ? checks if Alice responded with an error (SpotPriceError)
|
||||
let xmr = Result::from(
|
||||
// The inner ? is for the read_cbor_message function
|
||||
// It will return an error if the deserialization fails
|
||||
read_cbor_message::<SpotPriceResponse>(&mut substream)
|
||||
.await
|
||||
.context("Failed to read spot price response from Alice")?,
|
||||
)?;
|
||||
|
||||
let state0 = State0::new(
|
||||
info.swap_id,
|
||||
new_swap_request.swap_id,
|
||||
&mut rand::thread_rng(),
|
||||
info.btc,
|
||||
new_swap_request.btc,
|
||||
xmr,
|
||||
env_config.bitcoin_cancel_timelock,
|
||||
env_config.bitcoin_punish_timelock,
|
||||
info.bitcoin_refund_address,
|
||||
new_swap_request.bitcoin_refund_address,
|
||||
env_config.monero_finality_confirmations,
|
||||
info.tx_refund_fee,
|
||||
info.tx_cancel_fee,
|
||||
new_swap_request.tx_refund_fee,
|
||||
new_swap_request.tx_cancel_fee,
|
||||
);
|
||||
|
||||
write_cbor_message(&mut substream, state0.next_message()).await?;
|
||||
let message1 = read_cbor_message::<Message1>(&mut substream).await?;
|
||||
let state1 = state0.receive(bitcoin_wallet.as_ref(), message1).await?;
|
||||
write_cbor_message(&mut substream, state0.next_message())
|
||||
.await
|
||||
.context("Failed to send state0 message to Alice")?;
|
||||
let message1 = read_cbor_message::<Message1>(&mut substream)
|
||||
.await
|
||||
.context("Failed to read message1 from Alice")?;
|
||||
let state1 = state0
|
||||
.receive(bitcoin_wallet.as_ref(), message1)
|
||||
.await
|
||||
.context("Failed to receive state1")?;
|
||||
write_cbor_message(&mut substream, state1.next_message())
|
||||
.await
|
||||
.context("Failed to send state1 message")?;
|
||||
let message3 = read_cbor_message::<Message3>(&mut substream)
|
||||
.await
|
||||
.context("Failed to read message3 from Alice")?;
|
||||
let state2 = state1
|
||||
.receive(message3)
|
||||
.context("Failed to receive state2")?;
|
||||
|
||||
write_cbor_message(&mut substream, state1.next_message()).await?;
|
||||
let message3 = read_cbor_message::<Message3>(&mut substream).await?;
|
||||
let state2 = state1.receive(message3)?;
|
||||
write_cbor_message(&mut substream, state2.next_message())
|
||||
.await
|
||||
.context("Failed to send state2 message")?;
|
||||
|
||||
write_cbor_message(&mut substream, state2.next_message()).await?;
|
||||
|
||||
substream.flush().await?;
|
||||
substream.close().await?;
|
||||
substream
|
||||
.flush()
|
||||
.await
|
||||
.context("Failed to flush substream")?;
|
||||
substream
|
||||
.close()
|
||||
.await
|
||||
.context("Failed to close substream")?;
|
||||
|
||||
Ok(state2)
|
||||
}
|
||||
.await;
|
||||
|
||||
result.map_err(|e: anyhow::Error| {
|
||||
tracing::error!("Error occurred during swap setup protocol: {:#}", e);
|
||||
Error::Other
|
||||
})
|
||||
});
|
||||
|
||||
let max_seconds = self.timeout.as_secs();
|
||||
self.outbound_stream = OptionFuture::from(Some(
|
||||
async move {
|
||||
|
||||
self.outbound_stream = OptionFuture::from(Some(Box::pin(async move {
|
||||
protocol.await.map_err(|_| Error::Timeout {
|
||||
seconds: max_seconds,
|
||||
})?
|
||||
})
|
||||
as OutboundStream));
|
||||
|
||||
// Once the outbound stream is created, we keep the connection alive
|
||||
self.keep_alive = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
.boxed(),
|
||||
));
|
||||
}
|
||||
|
||||
fn inject_event(&mut self, new_swap: Self::InEvent) {
|
||||
fn on_behaviour_event(&mut self, new_swap: Self::FromBehaviour) {
|
||||
self.new_swaps.push_back(new_swap);
|
||||
}
|
||||
|
||||
fn inject_dial_upgrade_error(
|
||||
&mut self,
|
||||
_: Self::OutboundOpenInfo,
|
||||
_: ProtocolsHandlerUpgrErr<Void>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn connection_keep_alive(&self) -> KeepAlive {
|
||||
fn connection_keep_alive(&self) -> bool {
|
||||
self.keep_alive
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn poll(
|
||||
&mut self,
|
||||
cx: &mut Context<'_>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> Poll<
|
||||
ProtocolsHandlerEvent<
|
||||
Self::OutboundProtocol,
|
||||
Self::OutboundOpenInfo,
|
||||
Self::OutEvent,
|
||||
Self::Error,
|
||||
>,
|
||||
ConnectionHandlerEvent<Self::OutboundProtocol, Self::OutboundOpenInfo, Self::ToBehaviour>,
|
||||
> {
|
||||
// Check if there is a new swap to be started
|
||||
if let Some(new_swap) = self.new_swaps.pop_front() {
|
||||
self.keep_alive = KeepAlive::Yes;
|
||||
return Poll::Ready(ProtocolsHandlerEvent::OutboundSubstreamRequest {
|
||||
self.keep_alive = true;
|
||||
|
||||
// We instruct the swarm to start a new outbound substream
|
||||
return Poll::Ready(ConnectionHandlerEvent::OutboundSubstreamRequest {
|
||||
protocol: SubstreamProtocol::new(protocol::new(), new_swap),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(result) = futures::ready!(self.outbound_stream.poll_unpin(cx)) {
|
||||
self.outbound_stream = OptionFuture::from(None);
|
||||
return Poll::Ready(ProtocolsHandlerEvent::Custom(Completed(result)));
|
||||
// Check if the outbound stream has completed
|
||||
if let Poll::Ready(Some(result)) = self.outbound_stream.poll_unpin(cx) {
|
||||
self.outbound_stream = None.into();
|
||||
|
||||
// Once the outbound stream is completed, we no longer keep the connection alive
|
||||
self.keep_alive = false;
|
||||
|
||||
// We notify the swarm that the swap setup is completed / failed
|
||||
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(Completed(
|
||||
result.map_err(anyhow::Error::from),
|
||||
)));
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
|
|
117
swap/src/network/swap_setup/vendor_from_fn.rs
Normal file
117
swap/src/network/swap_setup/vendor_from_fn.rs
Normal file
|
@ -0,0 +1,117 @@
|
|||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// These functions have been vendored from https://github.com/libp2p/rust-libp2p/blob/v0.51.0/core/src/upgrade/from_fn.rs because they were removed from the library itself
|
||||
// This is recommended, see: https://github.com/libp2p/rust-libp2p/pull/3747
|
||||
// We replaced ProtocolName with AsRef<str>. See: https://github.com/libp2p/rust-libp2p/pull/3746/files
|
||||
|
||||
use futures::prelude::*;
|
||||
use libp2p::{
|
||||
core::{Endpoint, UpgradeInfo},
|
||||
InboundUpgrade, OutboundUpgrade,
|
||||
};
|
||||
use std::iter;
|
||||
|
||||
/// Initializes a new [`FromFnUpgrade`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use libp2p_core::transport::{Transport, MemoryTransport, memory::Channel};
|
||||
/// # use libp2p_core::{upgrade, Negotiated};
|
||||
/// # use std::io;
|
||||
/// # use futures::AsyncWriteExt;
|
||||
/// let _transport = MemoryTransport::default()
|
||||
/// .and_then(move |out, cp| {
|
||||
/// upgrade::apply(out, upgrade::from_fn("/foo/1", move |mut sock: Negotiated<Channel<Vec<u8>>>, endpoint| async move {
|
||||
/// if endpoint.is_dialer() {
|
||||
/// upgrade::write_length_prefixed(&mut sock, "some handshake data").await?;
|
||||
/// sock.close().await?;
|
||||
/// } else {
|
||||
/// let handshake_data = upgrade::read_length_prefixed(&mut sock, 1024).await?;
|
||||
/// if handshake_data != b"some handshake data" {
|
||||
/// return Err(io::Error::new(io::ErrorKind::Other, "bad handshake"));
|
||||
/// }
|
||||
/// }
|
||||
/// Ok(sock)
|
||||
/// }), cp, upgrade::Version::V1)
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
pub fn from_fn<P, F, C, Fut, Out, Err>(protocol_name: P, fun: F) -> FromFnUpgrade<P, F>
|
||||
where
|
||||
// Note: these bounds are there in order to help the compiler infer types
|
||||
P: AsRef<str> + Clone,
|
||||
F: FnOnce(C, Endpoint) -> Fut,
|
||||
Fut: Future<Output = Result<Out, Err>>,
|
||||
{
|
||||
FromFnUpgrade { protocol_name, fun }
|
||||
}
|
||||
|
||||
/// Implements the `UpgradeInfo`, `InboundUpgrade` and `OutboundUpgrade` traits.
|
||||
///
|
||||
/// The upgrade consists in calling the function passed when creating this struct.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FromFnUpgrade<P, F> {
|
||||
protocol_name: P,
|
||||
fun: F,
|
||||
}
|
||||
|
||||
impl<P, F> UpgradeInfo for FromFnUpgrade<P, F>
|
||||
where
|
||||
P: AsRef<str> + Clone,
|
||||
{
|
||||
type Info = P;
|
||||
type InfoIter = iter::Once<P>;
|
||||
|
||||
fn protocol_info(&self) -> Self::InfoIter {
|
||||
iter::once(self.protocol_name.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, P, F, Fut, Err, Out> InboundUpgrade<C> for FromFnUpgrade<P, F>
|
||||
where
|
||||
P: AsRef<str> + Clone,
|
||||
F: FnOnce(C, Endpoint) -> Fut,
|
||||
Fut: Future<Output = Result<Out, Err>>,
|
||||
{
|
||||
type Output = Out;
|
||||
type Error = Err;
|
||||
type Future = Fut;
|
||||
|
||||
fn upgrade_inbound(self, sock: C, _: Self::Info) -> Self::Future {
|
||||
(self.fun)(sock, Endpoint::Listener)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, P, F, Fut, Err, Out> OutboundUpgrade<C> for FromFnUpgrade<P, F>
|
||||
where
|
||||
P: AsRef<str> + Clone,
|
||||
F: FnOnce(C, Endpoint) -> Fut,
|
||||
Fut: Future<Output = Result<Out, Err>>,
|
||||
{
|
||||
type Output = Out;
|
||||
type Error = Err;
|
||||
type Future = Fut;
|
||||
|
||||
fn upgrade_outbound(self, sock: C, _: Self::Info) -> Self::Future {
|
||||
(self.fun)(sock, Endpoint::Dialer)
|
||||
}
|
||||
}
|
|
@ -4,9 +4,11 @@ use crate::network::rendezvous::XmrBtcNamespace;
|
|||
use crate::seed::Seed;
|
||||
use crate::{asb, bitcoin, cli, env, tor};
|
||||
use anyhow::Result;
|
||||
use libp2p::swarm::{NetworkBehaviour, SwarmBuilder};
|
||||
use libp2p::swarm::NetworkBehaviour;
|
||||
use libp2p::SwarmBuilder;
|
||||
use libp2p::{identity, Multiaddr, Swarm};
|
||||
use std::fmt::Debug;
|
||||
use std::time::Duration;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn asb<LR>(
|
||||
|
@ -46,12 +48,12 @@ where
|
|||
);
|
||||
|
||||
let transport = asb::transport::new(&identity)?;
|
||||
let peer_id = identity.public().into();
|
||||
|
||||
let swarm = SwarmBuilder::new(transport, behaviour, peer_id)
|
||||
.executor(Box::new(|f| {
|
||||
tokio::spawn(f);
|
||||
}))
|
||||
let swarm = SwarmBuilder::with_existing_identity(identity)
|
||||
.with_tokio()
|
||||
.with_other_transport(|_| transport)?
|
||||
.with_behaviour(|_| behaviour)?
|
||||
.with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::MAX))
|
||||
.build();
|
||||
|
||||
Ok(swarm)
|
||||
|
@ -71,12 +73,12 @@ where
|
|||
};
|
||||
|
||||
let transport = cli::transport::new(&identity, maybe_tor_socks5_port)?;
|
||||
let peer_id = identity.public().into();
|
||||
|
||||
let swarm = SwarmBuilder::new(transport, behaviour, peer_id)
|
||||
.executor(Box::new(|f| {
|
||||
tokio::spawn(f);
|
||||
}))
|
||||
let swarm = SwarmBuilder::with_existing_identity(identity)
|
||||
.with_tokio()
|
||||
.with_other_transport(|_| transport)?
|
||||
.with_behaviour(|_| behaviour)?
|
||||
.with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::MAX))
|
||||
.build();
|
||||
|
||||
Ok(swarm)
|
||||
|
|
|
@ -1,59 +1,45 @@
|
|||
use async_trait::async_trait;
|
||||
use futures::stream::FusedStream;
|
||||
use futures::{future, Future, StreamExt};
|
||||
use futures::StreamExt;
|
||||
use libp2p::core::muxing::StreamMuxerBox;
|
||||
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::{Keypair, NoiseConfig, X25519Spec};
|
||||
use libp2p::swarm::{AddressScore, NetworkBehaviour, Swarm, SwarmBuilder, SwarmEvent};
|
||||
use libp2p::tcp::TokioTcpConfig;
|
||||
use libp2p::yamux::YamuxConfig;
|
||||
use libp2p::core::{Multiaddr, Transport};
|
||||
use libp2p::identity;
|
||||
use libp2p::noise;
|
||||
use libp2p::swarm::dial_opts::DialOpts;
|
||||
use libp2p::swarm::{NetworkBehaviour, Swarm, SwarmEvent};
|
||||
use libp2p::tcp;
|
||||
use libp2p::yamux;
|
||||
use libp2p::SwarmBuilder;
|
||||
use std::fmt::Debug;
|
||||
use std::pin::Pin;
|
||||
use std::time::Duration;
|
||||
|
||||
/// An adaptor struct for libp2p that spawns futures into the current
|
||||
/// thread-local runtime.
|
||||
struct GlobalSpawnTokioExecutor;
|
||||
|
||||
impl Executor for GlobalSpawnTokioExecutor {
|
||||
fn exec(&self, future: Pin<Box<dyn Future<Output = ()> + Send>>) {
|
||||
tokio::spawn(future);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_swarm<B, F>(behaviour_fn: F) -> Swarm<B>
|
||||
where
|
||||
B: NetworkBehaviour,
|
||||
<B as NetworkBehaviour>::OutEvent: Debug,
|
||||
<B as NetworkBehaviour>::ToSwarm: Debug,
|
||||
B: NetworkBehaviour,
|
||||
F: FnOnce(PeerId, identity::Keypair) -> B,
|
||||
F: FnOnce(identity::Keypair) -> B,
|
||||
{
|
||||
let identity = identity::Keypair::generate_ed25519();
|
||||
let peer_id = PeerId::from(identity.public());
|
||||
let noise = noise::Config::new(&identity).unwrap();
|
||||
let tcp = tcp::tokio::Transport::new(tcp::Config::new());
|
||||
|
||||
let dh_keys = Keypair::<X25519Spec>::new()
|
||||
.into_authentic(&identity)
|
||||
.expect("failed to create dh_keys");
|
||||
let noise = NoiseConfig::xx(dh_keys).into_authenticated();
|
||||
|
||||
let transport = MemoryTransport
|
||||
.or_transport(TokioTcpConfig::new())
|
||||
let transport = MemoryTransport::new()
|
||||
.or_transport(tcp)
|
||||
.upgrade(Version::V1)
|
||||
.authenticate(noise)
|
||||
.multiplex(SelectUpgrade::new(
|
||||
YamuxConfig::default(),
|
||||
MplexConfig::new(),
|
||||
))
|
||||
.multiplex(yamux::Config::default())
|
||||
.timeout(Duration::from_secs(5))
|
||||
.map(|(peer, muxer), _| (peer, StreamMuxerBox::new(muxer)))
|
||||
.boxed();
|
||||
|
||||
SwarmBuilder::new(transport, behaviour_fn(peer_id, identity), peer_id)
|
||||
.executor(Box::new(GlobalSpawnTokioExecutor))
|
||||
SwarmBuilder::with_existing_identity(identity)
|
||||
.with_tokio()
|
||||
.with_other_transport(|_| Ok(transport))
|
||||
.unwrap()
|
||||
.with_behaviour(|keypair| Ok(behaviour_fn(keypair.clone())))
|
||||
.unwrap()
|
||||
.build()
|
||||
}
|
||||
|
||||
|
@ -74,29 +60,6 @@ async fn get_local_tcp_address() -> Multiaddr {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn await_events_or_timeout<A, B, E1, E2>(
|
||||
swarm_1: &mut (impl FusedStream<Item = SwarmEvent<A, E1>> + FusedStream + Unpin),
|
||||
swarm_2: &mut (impl FusedStream<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")
|
||||
}
|
||||
|
||||
/// An extension trait for [`Swarm`] that makes it easier to set up a network of
|
||||
/// [`Swarm`]s for tests.
|
||||
#[async_trait]
|
||||
|
@ -105,8 +68,8 @@ pub trait SwarmExt {
|
|||
/// 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;
|
||||
T: NetworkBehaviour + Send,
|
||||
<T as NetworkBehaviour>::ToSwarm: Debug;
|
||||
|
||||
/// Listens on a random memory address, polling the [`Swarm`] until the
|
||||
/// transport is ready to accept connections.
|
||||
|
@ -120,18 +83,24 @@ pub trait SwarmExt {
|
|||
#[async_trait]
|
||||
impl<B> SwarmExt for Swarm<B>
|
||||
where
|
||||
B: NetworkBehaviour,
|
||||
<B as NetworkBehaviour>::OutEvent: Debug,
|
||||
B: NetworkBehaviour + Send,
|
||||
<B as NetworkBehaviour>::ToSwarm: Debug,
|
||||
{
|
||||
async fn block_on_connection<T>(&mut self, other: &mut Swarm<T>)
|
||||
where
|
||||
T: NetworkBehaviour,
|
||||
<T as NetworkBehaviour>::OutEvent: Debug,
|
||||
T: NetworkBehaviour + Send,
|
||||
<T as NetworkBehaviour>::ToSwarm: Debug,
|
||||
{
|
||||
let addr_to_dial = other.external_addresses().next().unwrap().addr.clone();
|
||||
let addr_to_dial = other.external_addresses().next().unwrap().clone();
|
||||
let local_peer_id = *other.local_peer_id();
|
||||
|
||||
self.dial(addr_to_dial).unwrap();
|
||||
self.dial(
|
||||
DialOpts::peer_id(local_peer_id)
|
||||
.addresses(vec![addr_to_dial])
|
||||
.extend_addresses_through_behaviour()
|
||||
.build(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut dialer_done = false;
|
||||
let mut listener_done = false;
|
||||
|
@ -145,7 +114,7 @@ where
|
|||
SwarmEvent::ConnectionEstablished { .. } => {
|
||||
dialer_done = true;
|
||||
}
|
||||
SwarmEvent::OutgoingConnectionError { peer_id, error } if matches!(peer_id, Some(alice_peer_id) if alice_peer_id == local_peer_id) => {
|
||||
SwarmEvent::OutgoingConnectionError { peer_id, error, .. } if matches!(peer_id, Some(alice_peer_id) if alice_peer_id == local_peer_id) => {
|
||||
panic!("Failed to dial address {}: {}", peer_id.unwrap(), error)
|
||||
}
|
||||
other => {
|
||||
|
@ -182,7 +151,7 @@ where
|
|||
|
||||
// Memory addresses are externally reachable because they all share the same
|
||||
// memory-space.
|
||||
self.add_external_address(multiaddr.clone(), AddressScore::Infinite);
|
||||
self.add_external_address(multiaddr.clone());
|
||||
|
||||
multiaddr
|
||||
}
|
||||
|
@ -200,7 +169,7 @@ where
|
|||
async fn block_until_listening_on<B>(swarm: &mut Swarm<B>, multiaddr: &Multiaddr)
|
||||
where
|
||||
B: NetworkBehaviour,
|
||||
<B as NetworkBehaviour>::OutEvent: Debug,
|
||||
<B as NetworkBehaviour>::ToSwarm: Debug,
|
||||
{
|
||||
loop {
|
||||
match swarm.select_next_some().await {
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
use anyhow::Result;
|
||||
use data_encoding::BASE32;
|
||||
use futures::future::{BoxFuture, FutureExt, Ready};
|
||||
use futures::future::{BoxFuture, Ready};
|
||||
use libp2p::core::multiaddr::{Multiaddr, Protocol};
|
||||
use libp2p::core::transport::TransportError;
|
||||
use libp2p::core::transport::{ListenerId, TransportError};
|
||||
use libp2p::core::Transport;
|
||||
use libp2p::tcp::tokio::{Tcp, TcpStream};
|
||||
use libp2p::tcp::TcpListenStream;
|
||||
use libp2p::tcp::tokio::TcpStream;
|
||||
use std::borrow::Cow;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::{fmt, io};
|
||||
|
@ -26,61 +25,87 @@ impl TorDialOnlyTransport {
|
|||
impl Transport for TorDialOnlyTransport {
|
||||
type Output = TcpStream;
|
||||
type Error = io::Error;
|
||||
type Listener = TcpListenStream<Tcp>;
|
||||
type ListenerUpgrade = Ready<Result<Self::Output, Self::Error>>;
|
||||
type Dial = BoxFuture<'static, Result<Self::Output, Self::Error>>;
|
||||
|
||||
fn listen_on(self, addr: Multiaddr) -> Result<Self::Listener, TransportError<Self::Error>> {
|
||||
fn listen_on(
|
||||
&mut self,
|
||||
_id: ListenerId,
|
||||
addr: Multiaddr,
|
||||
) -> Result<(), TransportError<Self::Error>> {
|
||||
Err(TransportError::MultiaddrNotSupported(addr))
|
||||
}
|
||||
|
||||
fn dial(self, addr: Multiaddr) -> Result<Self::Dial, TransportError<Self::Error>> {
|
||||
fn dial(&mut self, addr: Multiaddr) -> Result<Self::Dial, TransportError<Self::Error>> {
|
||||
let address = TorCompatibleAddress::from_multiaddr(Cow::Borrowed(&addr))?;
|
||||
|
||||
if address.is_certainly_not_reachable_via_tor_daemon() {
|
||||
return Err(TransportError::MultiaddrNotSupported(addr));
|
||||
}
|
||||
|
||||
let dial_future = async move {
|
||||
let socks_port = self.socks_port;
|
||||
|
||||
Ok(Box::pin(async move {
|
||||
tracing::debug!(address = %addr, "Establishing connection through Tor proxy");
|
||||
|
||||
let stream =
|
||||
Socks5Stream::connect((Ipv4Addr::LOCALHOST, self.socks_port), address.to_string())
|
||||
Socks5Stream::connect((Ipv4Addr::LOCALHOST, socks_port), address.to_string())
|
||||
.await
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::ConnectionRefused, e))?;
|
||||
|
||||
tracing::debug!("Connection through Tor established");
|
||||
|
||||
Ok(TcpStream(stream.into_inner()))
|
||||
};
|
||||
|
||||
Ok(dial_future.boxed())
|
||||
}))
|
||||
}
|
||||
|
||||
fn address_translation(&self, _: &Multiaddr, _: &Multiaddr) -> Option<Multiaddr> {
|
||||
None
|
||||
}
|
||||
fn dial_as_listener(self, addr: Multiaddr) -> Result<Self::Dial, TransportError<Self::Error>> {
|
||||
|
||||
fn dial_as_listener(
|
||||
&mut self,
|
||||
addr: Multiaddr,
|
||||
) -> Result<Self::Dial, TransportError<Self::Error>> {
|
||||
let address = TorCompatibleAddress::from_multiaddr(Cow::Borrowed(&addr))?;
|
||||
|
||||
if address.is_certainly_not_reachable_via_tor_daemon() {
|
||||
return Err(TransportError::MultiaddrNotSupported(addr));
|
||||
}
|
||||
|
||||
let dial_future = async move {
|
||||
let socks_port = self.socks_port;
|
||||
|
||||
Ok(Box::pin(async move {
|
||||
tracing::debug!(address = %addr, "Establishing connection through Tor proxy");
|
||||
|
||||
let stream =
|
||||
Socks5Stream::connect((Ipv4Addr::LOCALHOST, self.socks_port), address.to_string())
|
||||
Socks5Stream::connect((Ipv4Addr::LOCALHOST, socks_port), address.to_string())
|
||||
.await
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::ConnectionRefused, e))?;
|
||||
|
||||
tracing::debug!("Connection through Tor established");
|
||||
|
||||
Ok(TcpStream(stream.into_inner()))
|
||||
};
|
||||
}))
|
||||
}
|
||||
|
||||
Ok(dial_future.boxed())
|
||||
fn remove_listener(&mut self, _id: ListenerId) -> bool {
|
||||
// TODO(Libp2p Migration): What do we need to do here?
|
||||
// I believe nothing because we are not using the transport to listen.
|
||||
false
|
||||
}
|
||||
|
||||
fn poll(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<libp2p::core::transport::TransportEvent<Self::ListenerUpgrade, Self::Error>>
|
||||
{
|
||||
// TODO(Libp2p Migration): What do we need to do here?
|
||||
// See: https://github.com/libp2p/rust-libp2p/pull/2652
|
||||
// I believe we do not need to do anything here because we are not using the transport to listen.
|
||||
// But we need to verify this before merging.
|
||||
|
||||
std::task::Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,26 +1,23 @@
|
|||
use crate::network::cbor_request_response::CborCodec;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{asb, cli, monero};
|
||||
use libp2p::core::ProtocolName;
|
||||
use libp2p::request_response::{
|
||||
ProtocolSupport, RequestResponse, RequestResponseConfig, RequestResponseEvent,
|
||||
RequestResponseMessage,
|
||||
};
|
||||
use libp2p::PeerId;
|
||||
use libp2p::request_response::{self, ProtocolSupport};
|
||||
use libp2p::{PeerId, StreamProtocol};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
const PROTOCOL: &str = "/comit/xmr/btc/transfer_proof/1.0.0";
|
||||
type OutEvent = RequestResponseEvent<Request, ()>;
|
||||
type Message = RequestResponseMessage<Request, ()>;
|
||||
type OutEvent = request_response::Event<Request, ()>;
|
||||
type Message = request_response::Message<Request, ()>;
|
||||
|
||||
pub type Behaviour = RequestResponse<CborCodec<TransferProofProtocol, Request, ()>>;
|
||||
pub type Behaviour = request_response::cbor::Behaviour<Request, ()>;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct TransferProofProtocol;
|
||||
|
||||
impl ProtocolName for TransferProofProtocol {
|
||||
fn protocol_name(&self) -> &[u8] {
|
||||
PROTOCOL.as_bytes()
|
||||
impl AsRef<str> for TransferProofProtocol {
|
||||
fn as_ref(&self) -> &str {
|
||||
PROTOCOL
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,17 +29,15 @@ pub struct Request {
|
|||
|
||||
pub fn alice() -> Behaviour {
|
||||
Behaviour::new(
|
||||
CborCodec::default(),
|
||||
vec![(TransferProofProtocol, ProtocolSupport::Outbound)],
|
||||
RequestResponseConfig::default(),
|
||||
vec![(StreamProtocol::new(PROTOCOL), ProtocolSupport::Outbound)],
|
||||
request_response::Config::default().with_request_timeout(Duration::from_secs(60)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn bob() -> Behaviour {
|
||||
Behaviour::new(
|
||||
CborCodec::default(),
|
||||
vec![(TransferProofProtocol, ProtocolSupport::Inbound)],
|
||||
RequestResponseConfig::default(),
|
||||
vec![(StreamProtocol::new(PROTOCOL), ProtocolSupport::Inbound)],
|
||||
request_response::Config::default().with_request_timeout(Duration::from_secs(60)),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -57,6 +52,7 @@ impl From<(PeerId, Message)> for asb::OutEvent {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
crate::impl_from_rr_event!(OutEvent, asb::OutEvent, PROTOCOL);
|
||||
|
||||
impl From<(PeerId, Message)> for cli::OutEvent {
|
||||
|
|
|
@ -2,9 +2,8 @@ use anyhow::Result;
|
|||
use futures::{AsyncRead, AsyncWrite};
|
||||
use libp2p::core::muxing::StreamMuxerBox;
|
||||
use libp2p::core::transport::Boxed;
|
||||
use libp2p::core::upgrade::{SelectUpgrade, Version};
|
||||
use libp2p::mplex::MplexConfig;
|
||||
use libp2p::noise::{self, NoiseConfig, X25519Spec};
|
||||
use libp2p::core::upgrade::Version;
|
||||
use libp2p::noise;
|
||||
use libp2p::{identity, yamux, PeerId, Transport};
|
||||
use std::time::Duration;
|
||||
|
||||
|
@ -21,11 +20,8 @@ pub fn authenticate_and_multiplex<T>(
|
|||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
{
|
||||
let auth_upgrade = {
|
||||
let noise_identity = noise::Keypair::<X25519Spec>::new().into_authentic(identity)?;
|
||||
NoiseConfig::xx(noise_identity).into_authenticated()
|
||||
};
|
||||
let multiplex_upgrade = SelectUpgrade::new(yamux::YamuxConfig::default(), MplexConfig::new());
|
||||
let auth_upgrade = noise::Config::new(identity)?;
|
||||
let multiplex_upgrade = yamux::Config::default();
|
||||
|
||||
let transport = transport
|
||||
.upgrade(Version::V1)
|
||||
|
|
|
@ -226,6 +226,11 @@ where
|
|||
state3,
|
||||
}
|
||||
},
|
||||
// TODO: We should already listen for the encrypted signature here.
|
||||
//
|
||||
// If we send Bob the transfer proof, but for whatever reason we do not receive an acknoledgement from him
|
||||
// we would be stuck in this state forever (deadlock). By listening for the encrypted signature here we
|
||||
// can still proceed to the next state even if Bob does not respond with an acknoledgement.
|
||||
result = tx_lock_status.wait_until_confirmed_with(state3.cancel_timelock) => {
|
||||
result?;
|
||||
AliceState::CancelTimelockExpired {
|
||||
|
@ -245,7 +250,6 @@ where
|
|||
|
||||
select! {
|
||||
biased; // make sure the cancel timelock expiry future is polled first
|
||||
|
||||
result = tx_lock_status.wait_until_confirmed_with(state3.cancel_timelock) => {
|
||||
result?;
|
||||
AliceState::CancelTimelockExpired {
|
||||
|
@ -275,6 +279,7 @@ where
|
|||
ExpiredTimelocks::None { .. } => {
|
||||
let tx_lock_status = bitcoin_wallet.subscribe_to(state3.tx_lock.clone()).await;
|
||||
match state3.signed_redeem_transaction(*encrypted_signature) {
|
||||
// TODO: We should retry publishing the redeem transaction if it fails
|
||||
Ok(tx) => match bitcoin_wallet.broadcast(tx, "redeem").await {
|
||||
Ok((_, subscription)) => match subscription.wait_until_seen().await {
|
||||
Ok(_) => AliceState::BtcRedeemTransactionPublished { state3 },
|
||||
|
@ -457,3 +462,15 @@ pub(crate) fn is_complete(state: &AliceState) -> bool {
|
|||
| AliceState::SafelyAborted
|
||||
)
|
||||
}
|
||||
|
||||
/// This function is used to check if Alice is in a state where it is clear that she has already received the encrypted signature from Bob.
|
||||
/// This allows us to acknowledge the encrypted signature multiple times
|
||||
/// If our acknowledgement does not reach Bob, he might send the encrypted signature again.
|
||||
pub(crate) fn has_already_processed_enc_sig(state: &AliceState) -> bool {
|
||||
matches!(
|
||||
state,
|
||||
AliceState::EncSigLearned { .. }
|
||||
| AliceState::BtcRedeemTransactionPublished { .. }
|
||||
| AliceState::BtcRedeemed
|
||||
)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,22 @@ pub fn is_complete(state: &BobState) -> bool {
|
|||
)
|
||||
}
|
||||
|
||||
/// Identifies states that have already processed the transfer proof.
|
||||
/// This is used to be able to acknowledge the transfer proof multiple times (if it was already processed).
|
||||
/// This is necessary because sometimes our acknowledgement might not reach Alice.
|
||||
pub fn has_already_processed_transfer_proof(state: &BobState) -> bool {
|
||||
// This match statement MUST match all states which Bob can enter after receiving the transfer proof.
|
||||
// We do not match any of the cancel / refund states because in those, the swap cannot be successfull anymore.
|
||||
matches!(
|
||||
state,
|
||||
BobState::XmrLockProofReceived { .. }
|
||||
| BobState::XmrLocked(..)
|
||||
| BobState::EncSigSent(..)
|
||||
| BobState::BtcRedeemed(..)
|
||||
| BobState::XmrRedeemed { .. }
|
||||
)
|
||||
}
|
||||
|
||||
// Identifies states that should be run at most once before exiting.
|
||||
// This is used to prevent infinite retry loops while still allowing manual resumption.
|
||||
//
|
||||
|
@ -334,8 +350,10 @@ async fn next_state(
|
|||
result = event_loop_handle.send_encrypted_signature(state.tx_redeem_encsig()) => {
|
||||
match result {
|
||||
Ok(_) => BobState::EncSigSent(state),
|
||||
Err(bmrng::error::RequestError::RecvError | bmrng::error::RequestError::SendError(_)) => bail!("Failed to communicate encrypted signature through event loop channel"),
|
||||
Err(bmrng::error::RequestError::RecvTimeoutError) => unreachable!("We construct the channel with no timeout"),
|
||||
Err(err) => {
|
||||
tracing::error!(%err, "Failed to send encrypted signature to Alice");
|
||||
bail!("Failed to send encrypted signature to Alice");
|
||||
}
|
||||
}
|
||||
},
|
||||
result = tx_lock_status.wait_until_confirmed_with(state.cancel_timelock) => {
|
||||
|
@ -457,9 +475,7 @@ async fn next_state(
|
|||
);
|
||||
|
||||
tracing::info!("Attempting to cooperatively redeem XMR after being punished");
|
||||
let response = event_loop_handle
|
||||
.request_cooperative_xmr_redeem(swap_id)
|
||||
.await;
|
||||
let response = event_loop_handle.request_cooperative_xmr_redeem().await;
|
||||
|
||||
match response {
|
||||
Ok(Fullfilled { s_a, .. }) => {
|
||||
|
|
|
@ -43,9 +43,8 @@ impl Seed {
|
|||
|
||||
pub fn derive_libp2p_identity(&self) -> identity::Keypair {
|
||||
let bytes = self.derive(b"NETWORK").derive(b"LIBP2P_IDENTITY").bytes();
|
||||
let key = identity::ed25519::SecretKey::from_bytes(bytes).expect("we always pass 32 bytes");
|
||||
|
||||
identity::Keypair::Ed25519(key.into())
|
||||
identity::Keypair::ed25519_from_bytes(bytes).expect("we always pass 32 bytes")
|
||||
}
|
||||
|
||||
pub fn derive_torv3_key(&self) -> TorSecretKeyV3 {
|
||||
|
|
|
@ -420,7 +420,7 @@ impl BobParams {
|
|||
format!(
|
||||
"{}/p2p/{}",
|
||||
self.alice_address.clone(),
|
||||
self.alice_peer_id.clone().to_base58()
|
||||
self.alice_peer_id.to_base58()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -505,9 +505,7 @@ impl BobParams {
|
|||
(identity.clone(), XmrBtcNamespace::Testnet),
|
||||
);
|
||||
let mut swarm = swarm::cli(identity.clone(), tor_socks5_port, behaviour).await?;
|
||||
swarm
|
||||
.behaviour_mut()
|
||||
.add_address(self.alice_peer_id, self.alice_address.clone());
|
||||
swarm.add_peer_address(self.alice_peer_id, self.alice_address.clone());
|
||||
|
||||
cli::EventLoop::new(swap_id, swarm, self.alice_peer_id, db.clone())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue