Merge branch 'master' into rpc-server

This commit is contained in:
binarybaron 2023-08-09 22:52:14 +02:00
commit 75bc64d248
34 changed files with 942 additions and 800 deletions

View File

@ -45,12 +45,12 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Checkout tagged commit
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.5.3
with:
ref: ${{ github.event.release.target_commitish }}
token: ${{ secrets.BOTTY_GITHUB_TOKEN }}
- uses: Swatinem/rust-cache@v2.2.0
- uses: Swatinem/rust-cache@v2.6.0
- uses: dtolnay/rust-toolchain@master
with:

View File

@ -13,12 +13,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.5.3
- uses: Swatinem/rust-cache@v2.2.0
- uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.67
components: clippy,rustfmt
- uses: Swatinem/rust-cache@v2.6.0
- name: Check formatting
uses: dprint/check@v2.1
uses: dprint/check@v2.2
with:
dprint-version: 0.39.1
- name: Run clippy with default features
run: cargo clippy --workspace --all-targets -- -D warnings
@ -30,9 +37,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.5.3
- uses: Swatinem/rust-cache@v2.0.2
- uses: Swatinem/rust-cache@v2.6.0
- name: Build swap
run: cargo build --bin swap
@ -44,9 +51,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.5.3
- uses: Swatinem/rust-cache@v2.0.2
- uses: Swatinem/rust-cache@v2.6.0
- name: Install sqlx-cli
run: cargo install sqlx-cli
@ -71,13 +78,13 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.5.3
- uses: Swatinem/rust-cache@v2.2.0
- uses: Swatinem/rust-cache@v2.6.0
- uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.63
toolchain: 1.67
targets: armv7-unknown-linux-gnueabihf
- name: Build binary
@ -111,9 +118,9 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Checkout sources
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.5.3
- uses: Swatinem/rust-cache@v2.2.0
- uses: Swatinem/rust-cache@v2.6.0
- name: Build tests
run: cargo build --tests --workspace --all-features
@ -148,9 +155,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3.3.0
uses: actions/checkout@v3.5.3
- uses: Swatinem/rust-cache@v2.2.0
- uses: Swatinem/rust-cache@v2.6.0
- name: Run test ${{ matrix.test_name }}
run: cargo test --package swap --all-features --test ${{ matrix.test_name }} -- --nocapture

View File

@ -11,7 +11,7 @@ jobs:
if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.3.0
- uses: actions/checkout@v3.5.3
- name: Extract version from branch name
id: extract-version

View File

@ -12,7 +12,7 @@ jobs:
name: "Draft a new release"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.3.0
- uses: actions/checkout@v3.5.3
with:
token: ${{ secrets.BOTTY_GITHUB_TOKEN }}
@ -41,8 +41,12 @@ jobs:
- name: Commit changelog and manifest files
id: make-commit
env:
DPRINT_VERSION: 0.39.1
RUST_TOOLCHAIN: 1.67
run: |
curl -fsSL https://dprint.dev/install.sh | sh
rustup component add rustfmt --toolchain "$RUST_TOOLCHAIN-x86_64-unknown-linux-gnu"
curl -fsSL https://dprint.dev/install.sh | sh -s $DPRINT_VERSION
/home/runner/.dprint/bin/dprint fmt
git add CHANGELOG.md Cargo.lock swap/Cargo.toml
@ -54,7 +58,7 @@ jobs:
run: git push origin release/${{ github.event.inputs.version }} --force
- name: Create pull request
uses: thomaseizinger/create-pull-request@1.3.0
uses: thomaseizinger/create-pull-request@1.3.1
with:
GITHUB_TOKEN: ${{ secrets.BOTTY_GITHUB_TOKEN }}
head: release/${{ github.event.inputs.version }}

View File

@ -10,7 +10,7 @@ jobs:
name: Create preview release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.3.0
- uses: actions/checkout@v3.5.3
- name: Delete 'preview' release
uses: larryjoelane/delete-release-action@v1.0.24

View File

@ -7,9 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.12.2] - 2023-08-08
### Changed
- Minimum Supported Rust Version (MSRV) bumped to 1.63
- Minimum Supported Rust Version (MSRV) bumped to 1.67
- ASB can now register with multiple rendezvous nodes. The `rendezvous_point` option in `config.toml` can be a string with comma separated addresses, or a toml array of address strings.
## [0.12.1] - 2023-01-09
@ -342,7 +345,8 @@ It is possible to migrate critical data from the old db to the sqlite but there
- Fixed an issue where Alice would not verify if Bob's Bitcoin lock transaction is semantically correct, i.e. pays the agreed upon amount to an output owned by both of them.
Fixing this required a **breaking change** on the network layer and hence old versions are not compatible with this version.
[unreleased]: https://github.com/comit-network/xmr-btc-swap/compare/0.12.1...HEAD
[Unreleased]: https://github.com/comit-network/xmr-btc-swap/compare/0.12.2...HEAD
[0.12.2]: https://github.com/comit-network/xmr-btc-swap/compare/0.12.1...0.12.2
[0.12.1]: https://github.com/comit-network/xmr-btc-swap/compare/0.12.0...0.12.1
[0.12.0]: https://github.com/comit-network/xmr-btc-swap/compare/0.11.0...0.12.0
[0.11.0]: https://github.com/comit-network/xmr-btc-swap/compare/0.10.2...0.11.0

940
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -44,13 +44,13 @@ It is not recommended to bump fees when swapping because it can have unpredictab
## Contributing
We are encourage community contributions whether it be a bug fix or an improvement to the documentation.
We encourage community contributions whether it be a bug fix or an improvement to the documentation.
Please have a look at the [contribution guidelines](./CONTRIBUTING.md).
## Rust Version Support
Please note that only the latest stable Rust toolchain is supported.
All stable toolchains since 1.63 _should_ work.
All stable toolchains since 1.67 _should_ work.
## Contact

View File

@ -42,13 +42,16 @@ Since the ASB is a long running task we specify the person running an ASB as ser
The ASB daemon supports the libp2p [rendezvous-protocol](https://github.com/libp2p/specs/tree/master/rendezvous).
Usage of the rendezvous functionality is entirely optional.
You can configure a rendezvous point in the `[network]` section of your config file.
You can configure one or more rendezvous points in the `[network]` section of your config file.
For the registration to be successful, you also need to configure the externally reachable addresses within the `[network]` section.
For example:
```toml
[network]
rendezvous_point = "/dns4/discover.unstoppableswap.net/tcp/8888/p2p/12D3KooWA6cnqJpVnreBVnoro8midDL9Lpzmg8oJPoAGi7YYaamE"
rendezvous_point = [
"/dns4/discover.unstoppableswap.net/tcp/8888/p2p/12D3KooWA6cnqJpVnreBVnoro8midDL9Lpzmg8oJPoAGi7YYaamE",
"/dns4/eratosthen.es/tcp/7798/p2p/12D3KooWAh7EXXa2ZyegzLGdjvj1W4G3EXrTGrf6trraoT1MEobs",
]
external_addresses = ["/dns4/example.com/tcp/9939"]
```

View File

@ -3,22 +3,16 @@
"projectType": "openSource",
"incremental": true,
"markdown": {},
"rustfmt": {
"edition": 2021,
"condense_wildcard_suffixes": true,
"format_macro_matchers": true,
"imports_granularity": "Module",
"use_field_init_shorthand": true,
"format_code_in_doc_comments": true,
"normalize_comments": true,
"wrap_comments": true,
"overflow_delimited_expr": true
"exec": {
"associations": "**/*.{rs}",
"rustfmt": "rustfmt --edition 2021",
"rustfmt.associations": "**/*.rs"
},
"includes": ["**/*.{md}", "**/*.{toml}", "**/*.{rs}"],
"excludes": ["target/"],
"plugins": [
"https://plugins.dprint.dev/markdown-0.13.1.wasm",
"https://github.com/thomaseizinger/dprint-plugin-cargo-toml/releases/download/0.1.0/cargo-toml-0.1.0.wasm",
"https://plugins.dprint.dev/rustfmt-0.6.1.exe-plugin@99b89a0599fd3a63e597e03436862157901f3facae2f0c2fbd0b9f656cdbc2a5"
"https://plugins.dprint.dev/exec-0.3.5.json@d687dda57be0fe9a0088ccdaefa5147649ff24127d8b3ea227536c68ee7abeab"
]
}

View File

@ -249,7 +249,7 @@ impl<'c> Monerod {
/// address
pub async fn start_miner(&self, miner_wallet_address: &str) -> Result<()> {
let monerod = self.client().clone();
let _ = tokio::spawn(mine(monerod, miner_wallet_address.to_string()));
tokio::spawn(mine(monerod, miner_wallet_address.to_string()));
Ok(())
}
}

View File

@ -19,5 +19,5 @@ serde_json = "1.0"
tracing = "0.1"
[dev-dependencies]
hex-literal = "0.3"
hex-literal = "0.4"
tokio = { version = "1", features = [ "full" ] }

View File

@ -47,9 +47,10 @@ impl Client {
}
pub async fn get_o_indexes(&self, txid: Hash) -> Result<GetOIndexesResponse> {
self.binary_request(self.get_o_indexes_bin_url.clone(), GetOIndexesPayload {
txid,
})
self.binary_request(
self.get_o_indexes_bin_url.clone(),
GetOIndexesPayload { txid },
)
.await
}

View File

@ -1,4 +1,4 @@
[toolchain]
channel = "1.63" # also update this in the readme, changelog, and github actions
channel = "1.67" # also update this in the readme, changelog, and github actions
components = ["clippy"]
targets = ["armv7-unknown-linux-gnueabihf"]

View File

@ -1,6 +1,6 @@
[package]
name = "swap"
version = "0.12.1"
version = "0.12.2"
authors = [ "The COMIT guys <hello@comit.network>" ]
edition = "2021"
description = "XMR/BTC trustless atomic swaps."
@ -14,16 +14,16 @@ async-compression = { version = "0.3", features = [ "bzip2", "tokio" ] }
async-trait = "0.1"
atty = "0.2"
backoff = { version = "0.4", features = [ "tokio" ] }
base64 = "0.20"
bdk = "0.25"
base64 = "0.21"
bdk = "0.28"
big-bytes = "1"
bitcoin = { version = "0.29", features = [ "rand", "serde" ] }
bmrng = "0.5"
comfy-table = "6.1"
config = { version = "0.13", default-features = false, features = [ "toml" ] }
conquer-once = "0.3"
conquer-once = "0.4"
curve25519-dalek = { package = "curve25519-dalek-ng", version = "4" }
data-encoding = "2.3"
data-encoding = "2.4"
dialoguer = "0.10"
directories-next = "2"
ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "libsecp_compat", "serde", "adaptor" ] }
@ -66,7 +66,7 @@ tracing-appender = "0.2"
tracing-futures = { version = "0.2", features = [ "std-future", "futures-03" ] }
tracing-subscriber = { version = "0.3", default-features = false, features = [ "fmt", "ansi", "env-filter", "time", "tracing-log", "json" ] }
url = { version = "2", features = [ "serde" ] }
uuid = { version = "1.2", features = [ "serde", "v4" ] }
uuid = { version = "1.4", features = [ "serde", "v4" ] }
void = "1"
[target.'cfg(not(windows))'.dependencies]
@ -86,11 +86,11 @@ port_check = "0.1"
proptest = "1"
sequential-test = "0.2.4"
serde_cbor = "0.11"
serial_test = "0.10"
serial_test = "2.0"
spectral = "0.6"
tempfile = "3"
testcontainers = "0.12"
[build-dependencies]
anyhow = "1"
vergen = { version = "7", default-features = false, features = [ "git", "build" ] }
vergen = { version = "7.5", default-features = false, features = [ "git", "build" ] }

View File

@ -8,6 +8,7 @@ pub mod tracing;
pub use event_loop::{EventLoop, EventLoopHandle, FixedRate, KrakenRate, LatestRate};
pub use network::behaviour::{Behaviour, OutEvent};
pub use network::rendezvous::RendezvousNode;
pub use network::transport;
pub use rate::Rate;
pub use recovery::cancel::cancel;
@ -18,4 +19,4 @@ pub use recovery::safely_abort::safely_abort;
pub use recovery::{cancel, refund};
#[cfg(test)]
pub use network::rendezous;
pub use network::rendezvous;

View File

@ -134,8 +134,8 @@ pub struct Data {
pub struct Network {
#[serde(deserialize_with = "addr_list::deserialize")]
pub listen: Vec<Multiaddr>,
#[serde(default)]
pub rendezvous_point: Option<Multiaddr>,
#[serde(default, deserialize_with = "addr_list::deserialize")]
pub rendezvous_point: Vec<Multiaddr>,
#[serde(default, deserialize_with = "addr_list::deserialize")]
pub external_addresses: Vec<Multiaddr>,
}
@ -156,7 +156,7 @@ mod addr_list {
let list: Result<Vec<_>, _> = s
.split(',')
.filter(|s| !s.is_empty())
.map(|s| s.parse().map_err(de::Error::custom))
.map(|s| s.trim().parse().map_err(de::Error::custom))
.collect();
Ok(list?)
}
@ -165,7 +165,7 @@ mod addr_list {
.iter()
.map(|v| {
if let Value::String(s) = v {
s.parse().map_err(de::Error::custom)
s.trim().parse().map_err(de::Error::custom)
} else {
Err(de::Error::custom("expected a string"))
}
@ -347,10 +347,27 @@ pub fn query_user_for_initial_config(testnet: bool) -> Result<Config> {
}
let ask_spread = Decimal::from_f64(ask_spread).context("Unable to parse spread")?;
let rendezvous_point = Input::<Multiaddr>::with_theme(&ColorfulTheme::default())
.with_prompt("Do you want to advertise your ASB instance with a rendezvous node? Enter an empty string if not.")
let mut number = 1;
let mut done = false;
let mut rendezvous_points = Vec::new();
println!("ASB can register with multiple rendezvous nodes for discoverability. This can also be edited in the config file later.");
while !done {
let prompt = format!(
"Enter the address for rendezvous node ({number}). Or just hit Enter to continue."
);
let rendezvous_addr = Input::<Multiaddr>::with_theme(&ColorfulTheme::default())
.with_prompt(prompt)
.allow_empty(true)
.interact_text()?;
if rendezvous_addr.is_empty() {
done = true;
} else if rendezvous_points.contains(&rendezvous_addr) {
println!("That rendezvous address is already in the list.");
} else {
rendezvous_points.push(rendezvous_addr);
number += 1;
}
}
println!();
@ -358,11 +375,7 @@ pub fn query_user_for_initial_config(testnet: bool) -> Result<Config> {
data: Data { dir: data_dir },
network: Network {
listen: listen_addresses,
rendezvous_point: if rendezvous_point.is_empty() {
None
} else {
Some(rendezvous_point)
},
rendezvous_point: rendezvous_points, // keeping the singular key name for backcompat
external_addresses: vec![],
},
bitcoin: Bitcoin {
@ -417,7 +430,7 @@ mod tests {
},
network: Network {
listen: vec![defaults.listen_address_tcp, defaults.listen_address_ws],
rendezvous_point: None,
rendezvous_point: vec![],
external_addresses: vec![],
},
monero: Monero {
@ -461,7 +474,7 @@ mod tests {
},
network: Network {
listen: vec![defaults.listen_address_tcp, defaults.listen_address_ws],
rendezvous_point: None,
rendezvous_point: vec![],
external_addresses: vec![],
},
monero: Monero {
@ -515,7 +528,7 @@ mod tests {
},
network: Network {
listen,
rendezvous_point: None,
rendezvous_point: vec![],
external_addresses,
},
monero: Monero {

View File

@ -253,8 +253,8 @@ where
channel
}.boxed());
}
SwarmEvent::Behaviour(OutEvent::Rendezvous(libp2p::rendezvous::client::Event::Registered { .. })) => {
tracing::info!("Successfully registered with rendezvous node");
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);

View File

@ -44,7 +44,9 @@ pub mod transport {
}
pub mod behaviour {
use super::*;
use libp2p::swarm::behaviour::toggle::Toggle;
use super::{rendezvous::RendezvousNode, *};
#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
@ -108,7 +110,7 @@ pub mod behaviour {
where
LR: LatestRate + Send + 'static,
{
pub rendezvous: libp2p::swarm::behaviour::toggle::Toggle<rendezous::Behaviour>,
pub rendezvous: Toggle<rendezvous::Behaviour>,
pub quote: quote::Behaviour,
pub swap_setup: alice::Behaviour<LR>,
pub transfer_proof: transfer_proof::Behaviour,
@ -132,25 +134,22 @@ pub mod behaviour {
resume_only: bool,
env_config: env::Config,
identify_params: (identity::Keypair, XmrBtcNamespace),
rendezvous_params: Option<(identity::Keypair, PeerId, Multiaddr, XmrBtcNamespace)>,
rendezvous_nodes: Vec<RendezvousNode>,
) -> Self {
let agentVersion = format!("asb/{} ({})", 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())
.with_agent_version(agentVersion);
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())
.with_agent_version(agent_version);
let behaviour = if rendezvous_nodes.is_empty() {
None
} else {
Some(rendezvous::Behaviour::new(identity, rendezvous_nodes))
};
Self {
rendezvous: libp2p::swarm::behaviour::toggle::Toggle::from(rendezvous_params.map(
|(identity, rendezvous_peer_id, rendezvous_address, namespace)| {
rendezous::Behaviour::new(
identity,
rendezvous_peer_id,
rendezvous_address,
namespace,
None, // use default ttl on rendezvous point
)
},
)),
rendezvous: Toggle::from(behaviour),
quote: quote::asb(),
swap_setup: alice::Behaviour::new(
min_buy,
@ -186,13 +185,14 @@ pub mod behaviour {
}
}
pub mod rendezous {
pub mod rendezvous {
use super::*;
use libp2p::swarm::dial_opts::DialOpts;
use libp2p::swarm::DialError;
use std::collections::VecDeque;
use std::pin::Pin;
#[derive(PartialEq)]
#[derive(Clone, PartialEq)]
enum ConnectionStatus {
Disconnected,
Dialling,
@ -209,39 +209,59 @@ pub mod rendezous {
pub struct Behaviour {
inner: libp2p::rendezvous::client::Behaviour,
rendezvous_point: Multiaddr,
rendezvous_peer_id: PeerId,
namespace: XmrBtcNamespace,
registration_status: RegistrationStatus,
connection_status: ConnectionStatus,
registration_ttl: Option<u64>,
rendezvous_nodes: Vec<RendezvousNode>,
to_dial: VecDeque<PeerId>,
}
impl Behaviour {
pub struct RendezvousNode {
pub address: Multiaddr,
connection_status: ConnectionStatus,
pub peer_id: PeerId,
registration_status: RegistrationStatus,
pub registration_ttl: Option<u64>,
pub namespace: XmrBtcNamespace,
}
impl RendezvousNode {
pub fn new(
identity: identity::Keypair,
rendezvous_peer_id: PeerId,
rendezvous_address: Multiaddr,
address: &Multiaddr,
peer_id: PeerId,
namespace: XmrBtcNamespace,
registration_ttl: Option<u64>,
) -> Self {
Self {
inner: libp2p::rendezvous::client::Behaviour::new(identity),
rendezvous_point: rendezvous_address,
rendezvous_peer_id,
namespace,
registration_status: RegistrationStatus::RegisterOnNextConnection,
address: address.to_owned(),
connection_status: ConnectionStatus::Disconnected,
namespace,
peer_id,
registration_status: RegistrationStatus::RegisterOnNextConnection,
registration_ttl,
}
}
fn register(&mut self) {
self.inner.register(
self.namespace.into(),
self.rendezvous_peer_id,
self.registration_ttl,
);
fn set_connection(&mut self, status: ConnectionStatus) {
self.connection_status = status;
}
fn set_registration(&mut self, status: RegistrationStatus) {
self.registration_status = status;
}
}
impl Behaviour {
pub fn new(identity: identity::Keypair, rendezvous_nodes: Vec<RendezvousNode>) -> Self {
Self {
inner: libp2p::rendezvous::client::Behaviour::new(identity),
rendezvous_nodes,
to_dial: VecDeque::new(),
}
}
/// 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);
}
}
@ -255,31 +275,37 @@ pub mod rendezous {
}
fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec<Multiaddr> {
if peer_id == &self.rendezvous_peer_id {
return vec![self.rendezvous_point.clone()];
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) {
if peer_id == &self.rendezvous_peer_id {
self.connection_status = ConnectionStatus::Connected;
match &self.registration_status {
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();
self.registration_status = RegistrationStatus::Pending;
self.register(i);
self.rendezvous_nodes[i].set_registration(RegistrationStatus::Pending);
}
RegistrationStatus::Registered { .. } => {}
RegistrationStatus::Pending => {}
}
}
}
}
fn inject_disconnected(&mut self, peer_id: &PeerId) {
if peer_id == &self.rendezvous_peer_id {
self.connection_status = ConnectionStatus::Disconnected;
for i in 0..self.rendezvous_nodes.len() {
let mut node = &mut self.rendezvous_nodes[i];
if peer_id == &node.peer_id {
node.connection_status = ConnectionStatus::Disconnected;
}
}
}
@ -298,9 +324,12 @@ pub mod rendezous {
_handler: Self::ProtocolsHandler,
_error: &DialError,
) {
for i in 0..self.rendezvous_nodes.len() {
let mut node = &mut self.rendezvous_nodes[i];
if let Some(id) = peer_id {
if id == self.rendezvous_peer_id {
self.connection_status = ConnectionStatus::Disconnected;
if id == node.peer_id {
node.connection_status = ConnectionStatus::Disconnected;
}
}
}
}
@ -311,42 +340,43 @@ pub mod rendezous {
cx: &mut std::task::Context<'_>,
params: &mut impl PollParameters,
) -> Poll<NetworkBehaviourAction<Self::OutEvent, Self::ProtocolsHandler>> {
match &mut self.registration_status {
RegistrationStatus::RegisterOnNextConnection => match self.connection_status {
ConnectionStatus::Disconnected => {
self.connection_status = ConnectionStatus::Dialling;
if let Some(peer_id) = self.to_dial.pop_front() {
return Poll::Ready(NetworkBehaviourAction::Dial {
opts: DialOpts::peer_id(self.rendezvous_peer_id)
opts: DialOpts::peer_id(peer_id)
.condition(PeerCondition::Disconnected)
.build(),
handler: Self::ProtocolsHandler::new(Duration::from_secs(30)),
});
}
// 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 {
RegistrationStatus::RegisterOnNextConnection => match connection_status {
ConnectionStatus::Disconnected => {
self.rendezvous_nodes[i].set_connection(ConnectionStatus::Dialling);
self.to_dial.push_back(self.rendezvous_nodes[i].peer_id);
}
ConnectionStatus::Dialling => {}
ConnectionStatus::Connected => {
self.registration_status = RegistrationStatus::Pending;
self.register();
self.rendezvous_nodes[i].set_registration(RegistrationStatus::Pending);
self.register(i);
}
},
RegistrationStatus::Registered { re_register_in } => {
if let Poll::Ready(()) = re_register_in.poll_unpin(cx) {
match self.connection_status {
match connection_status {
ConnectionStatus::Connected => {
self.registration_status = RegistrationStatus::Pending;
self.register();
self.rendezvous_nodes[i]
.set_registration(RegistrationStatus::Pending);
self.register(i);
}
ConnectionStatus::Disconnected => {
self.registration_status =
RegistrationStatus::RegisterOnNextConnection;
return Poll::Ready(NetworkBehaviourAction::Dial {
opts: DialOpts::peer_id(self.rendezvous_peer_id)
.condition(PeerCondition::Disconnected)
.build(),
handler: Self::ProtocolsHandler::new(Duration::from_secs(30)),
});
self.rendezvous_nodes[i].set_registration(
RegistrationStatus::RegisterOnNextConnection,
);
self.to_dial.push_back(self.rendezvous_nodes[i].peer_id);
}
ConnectionStatus::Dialling => {}
}
@ -354,19 +384,29 @@ pub mod rendezous {
}
RegistrationStatus::Pending => {}
}
}
let inner_poll = self.inner.poll(cx, params);
// reset the timer if we successfully registered
// reset the timer for the specific rendezvous node if we successfully registered
if let Poll::Ready(NetworkBehaviourAction::GenerateEvent(
libp2p::rendezvous::client::Event::Registered { ttl, .. },
libp2p::rendezvous::client::Event::Registered {
ttl,
rendezvous_node,
..
},
)) = &inner_poll
{
if let Some(i) = self
.rendezvous_nodes
.iter()
.position(|n| &n.peer_id == rendezvous_node)
{
let half_of_ttl = Duration::from_secs(*ttl) / 2;
self.registration_status = RegistrationStatus::Registered {
re_register_in: Box::pin(tokio::time::sleep(half_of_ttl)),
};
let re_register_in = Box::pin(tokio::time::sleep(half_of_ttl));
let status = RegistrationStatus::Registered { re_register_in };
self.rendezvous_nodes[i].set_registration(status);
}
}
inner_poll
@ -380,6 +420,7 @@ pub mod rendezous {
use futures::StreamExt;
use libp2p::rendezvous;
use libp2p::swarm::SwarmEvent;
use std::collections::HashMap;
#[tokio::test]
async fn given_no_initial_connection_when_constructed_asb_connects_and_registers_with_rendezvous_node(
@ -387,16 +428,16 @@ pub mod rendezous {
let mut rendezvous_node = new_swarm(|_, _| {
rendezvous::server::Behaviour::new(rendezvous::server::Config::default())
});
let rendezvous_address = rendezvous_node.listen_on_random_memory_address().await;
let mut asb = new_swarm(|_, identity| {
rendezous::Behaviour::new(
identity,
*rendezvous_node.local_peer_id(),
rendezvous_address,
let address = rendezvous_node.listen_on_random_memory_address().await;
let rendezvous_point = RendezvousNode::new(
&address,
rendezvous_node.local_peer_id().to_owned(),
XmrBtcNamespace::Testnet,
None,
)
);
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
@ -428,16 +469,16 @@ pub mod rendezous {
rendezvous::server::Config::default().with_min_ttl(2),
)
});
let rendezvous_address = rendezvous_node.listen_on_random_memory_address().await;
let mut asb = new_swarm(|_, identity| {
rendezous::Behaviour::new(
identity,
*rendezvous_node.local_peer_id(),
rendezvous_address,
let address = rendezvous_node.listen_on_random_memory_address().await;
let rendezvous_point = RendezvousNode::new(
&address,
rendezvous_node.local_peer_id().to_owned(),
XmrBtcNamespace::Testnet,
Some(5),
)
);
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
@ -467,5 +508,62 @@ pub mod rendezous {
.unwrap()
.unwrap();
}
#[tokio::test]
async fn asb_registers_multiple() {
let registration_ttl = Some(10);
let mut rendezvous_nodes = Vec::new();
let mut registrations = HashMap::new();
// register with 5 rendezvous nodes
for _ in 0..5 {
let mut rendezvous = new_swarm(|_, _| {
rendezvous::server::Behaviour::new(
rendezvous::server::Config::default().with_min_ttl(2),
)
});
let address = rendezvous.listen_on_random_memory_address().await;
let id = *rendezvous.local_peer_id();
registrations.insert(id, 0);
rendezvous_nodes.push(RendezvousNode::new(
&address,
*rendezvous.local_peer_id(),
XmrBtcNamespace::Testnet,
registration_ttl,
));
tokio::spawn(async move {
loop {
rendezvous.next().await;
}
});
}
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 {
loop {
if let SwarmEvent::Behaviour(rendezvous::client::Event::Registered {
rendezvous_node,
..
}) = asb.select_next_some().await
{
registrations
.entry(rendezvous_node)
.and_modify(|counter| *counter += 1);
}
if registrations.iter().all(|(_, &count)| count >= 4) {
break;
}
}
});
tokio::time::timeout(Duration::from_secs(30), handle)
.await
.unwrap()
.unwrap();
}
}
}

View File

@ -102,6 +102,19 @@ async fn main() -> Result<()> {
match cmd {
Command::Start { resume_only } => {
// check and warn for duplicate rendezvous points
let mut rendezvous_addrs = config.network.rendezvous_point.clone();
let prev_len = rendezvous_addrs.len();
rendezvous_addrs.sort();
rendezvous_addrs.dedup();
let new_len = rendezvous_addrs.len();
if new_len < prev_len {
tracing::warn!(
"`rendezvous_point` config has {} duplicate entries, they are being ignored.",
prev_len - new_len
);
}
let monero_wallet = init_monero_wallet(&config, env_config).await?;
let monero_address = monero_wallet.get_main_address();
tracing::info!(%monero_address, "Monero wallet address");
@ -161,7 +174,7 @@ async fn main() -> Result<()> {
resume_only,
env_config,
namespace,
config.network.rendezvous_point,
&rendezvous_addrs,
)?;
for listen in config.network.listen.clone() {

View File

@ -210,14 +210,20 @@ impl TxCancel {
};
// The order in which these are inserted doesn't matter
satisfier.insert(A, ::bitcoin::EcdsaSig {
satisfier.insert(
A,
::bitcoin::EcdsaSig {
sig: sig_a.into(),
hash_ty: EcdsaSighashType::All,
});
satisfier.insert(B, ::bitcoin::EcdsaSig {
},
);
satisfier.insert(
B,
::bitcoin::EcdsaSig {
sig: sig_b.into(),
hash_ty: EcdsaSighashType::All,
});
},
);
satisfier
};

View File

@ -65,14 +65,20 @@ impl TxPunish {
let B = B.try_into()?;
// The order in which these are inserted doesn't matter
satisfier.insert(A, ::bitcoin::EcdsaSig {
satisfier.insert(
A,
::bitcoin::EcdsaSig {
sig: sig_a.into(),
hash_ty: EcdsaSighashType::All,
});
satisfier.insert(B, ::bitcoin::EcdsaSig {
},
);
satisfier.insert(
B,
::bitcoin::EcdsaSig {
sig: sig_b.into(),
hash_ty: EcdsaSighashType::All,
});
},
);
satisfier
};

View File

@ -87,14 +87,20 @@ impl TxRedeem {
};
// The order in which these are inserted doesn't matter
satisfier.insert(A, ::bitcoin::EcdsaSig {
satisfier.insert(
A,
::bitcoin::EcdsaSig {
sig: sig_a.into(),
hash_ty: EcdsaSighashType::All,
});
satisfier.insert(B, ::bitcoin::EcdsaSig {
},
);
satisfier.insert(
B,
::bitcoin::EcdsaSig {
sig: sig_b.into(),
hash_ty: EcdsaSighashType::All,
});
},
);
satisfier
};

View File

@ -70,14 +70,20 @@ impl TxRefund {
};
// The order in which these are inserted doesn't matter
satisfier.insert(A, ::bitcoin::EcdsaSig {
satisfier.insert(
A,
::bitcoin::EcdsaSig {
sig: sig_a.into(),
hash_ty: EcdsaSighashType::All,
});
satisfier.insert(B, ::bitcoin::EcdsaSig {
},
);
satisfier.insert(
B,
::bitcoin::EcdsaSig {
sig: sig_b.into(),
hash_ty: EcdsaSighashType::All,
});
},
);
satisfier
};

View File

@ -738,12 +738,15 @@ impl Client {
let client = bdk::electrum_client::Client::new(electrum_rpc_url.as_str())
.context("Failed to initialize Electrum RPC client")?;
let blockchain = ElectrumBlockchain::from(client);
let last_sync = Instant::now()
.checked_sub(interval)
.expect("no underflow since block time is only 600 secs");
Ok(Self {
electrum,
blockchain,
latest_block_height: BlockHeight::try_from(latest_block)?,
last_sync: Instant::now() - interval,
last_sync,
sync_interval: interval,
script_history: Default::default(),
subscriptions: Default::default(),

View File

@ -15,6 +15,7 @@ pub use list_sellers::{list_sellers, Seller, Status as SellerStatus};
mod tests {
use super::*;
use crate::asb;
use crate::asb::rendezvous::RendezvousNode;
use crate::cli::list_sellers::{Seller, Status};
use crate::network::quote;
use crate::network::quote::BidQuote;
@ -33,10 +34,8 @@ mod tests {
async fn list_sellers_should_report_all_registered_asbs_with_a_quote() {
let namespace = XmrBtcNamespace::Mainnet;
let (rendezvous_address, rendezvous_peer_id) = setup_rendezvous_point().await;
let expected_seller_1 =
setup_asb(rendezvous_peer_id, rendezvous_address.clone(), namespace).await;
let expected_seller_2 =
setup_asb(rendezvous_peer_id, rendezvous_address.clone(), namespace).await;
let expected_seller_1 = setup_asb(rendezvous_peer_id, &rendezvous_address, namespace).await;
let expected_seller_2 = setup_asb(rendezvous_peer_id, &rendezvous_address, namespace).await;
let list_sellers = list_sellers(
rendezvous_peer_id,
@ -72,7 +71,7 @@ mod tests {
async fn setup_asb(
rendezvous_peer_id: PeerId,
rendezvous_address: Multiaddr,
rendezvous_address: &Multiaddr,
namespace: XmrBtcNamespace,
) -> Seller {
let static_quote = BidQuote {
@ -81,18 +80,18 @@ mod tests {
max_quantity: bitcoin::Amount::from_sat(9001),
};
let mut asb = new_swarm(|_, identity| StaticQuoteAsbBehaviour {
rendezvous: asb::rendezous::Behaviour::new(
identity,
rendezvous_peer_id,
rendezvous_address,
namespace,
None,
),
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 {
rendezvous,
ping: Default::default(),
quote: quote::asb(),
static_quote,
registered: false,
}
});
let asb_address = asb.listen_on_tcp_localhost().await;
@ -121,7 +120,7 @@ mod tests {
#[derive(libp2p::NetworkBehaviour)]
#[behaviour(event_process = true)]
struct StaticQuoteAsbBehaviour {
rendezvous: asb::rendezous::Behaviour,
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,

View File

@ -350,7 +350,9 @@ mod tests {
list.sort();
assert_eq!(list, vec![
assert_eq!(
list,
vec![
Seller {
multiaddr: "/ip4/127.0.0.1/tcp/5678".parse().unwrap(),
status: Status::Online(BidQuote {
@ -367,6 +369,7 @@ mod tests {
multiaddr: "/ip4/127.0.0.1/tcp/1234".parse().unwrap(),
status: Status::Unreachable
},
])
]
)
}
}

View File

@ -155,13 +155,16 @@ impl ProtocolsHandler for Handler {
let env_config = self.env_config;
let protocol = tokio::time::timeout(self.timeout, async move {
write_cbor_message(&mut substream, SpotPriceRequest {
write_cbor_message(
&mut substream,
SpotPriceRequest {
btc: info.btc,
blockchain_network: BlockchainNetwork {
bitcoin: env_config.bitcoin_network,
monero: env_config.monero_network,
},
})
},
)
.await?;
let xmr = Result::from(read_cbor_message::<SpotPriceResponse>(&mut substream).await?)?;

View File

@ -1,9 +1,9 @@
use crate::asb::LatestRate;
use crate::asb::{LatestRate, RendezvousNode};
use crate::libp2p_ext::MultiAddrExt;
use crate::network::rendezvous::XmrBtcNamespace;
use crate::seed::Seed;
use crate::{asb, bitcoin, cli, env, tor};
use anyhow::{Context, Result};
use anyhow::Result;
use libp2p::swarm::{NetworkBehaviour, SwarmBuilder};
use libp2p::{identity, Multiaddr, Swarm};
use std::fmt::Debug;
@ -17,22 +17,23 @@ pub fn asb<LR>(
resume_only: bool,
env_config: env::Config,
namespace: XmrBtcNamespace,
rendezvous_point: Option<Multiaddr>,
rendezvous_addrs: &[Multiaddr],
) -> Result<Swarm<asb::Behaviour<LR>>>
where
LR: LatestRate + Send + 'static + Debug + Clone,
{
let identity = seed.derive_libp2p_identity();
let rendezvous_params = if let Some(address) = rendezvous_point {
let peer_id = address
let rendezvous_nodes = rendezvous_addrs
.iter()
.map(|addr| {
let peer_id = addr
.extract_peer_id()
.context("Rendezvous node address must contain peer ID")?;
.expect("Rendezvous node address must contain peer ID");
Some((identity.clone(), peer_id, address, namespace))
} else {
None
};
RendezvousNode::new(addr, peer_id, namespace, None)
})
.collect();
let behaviour = asb::Behaviour::new(
min_buy,
@ -41,7 +42,7 @@ where
resume_only,
env_config,
(identity.clone(), namespace),
rendezvous_params,
rendezvous_nodes,
);
let transport = asb::transport::new(&identity)?;

View File

@ -21,7 +21,7 @@ struct GlobalSpawnTokioExecutor;
impl Executor for GlobalSpawnTokioExecutor {
fn exec(&self, future: Pin<Box<dyn Future<Output = ()> + Send>>) {
let _ = tokio::spawn(future);
tokio::spawn(future);
}
}

View File

@ -184,7 +184,9 @@ impl State0 {
let v = self.v_a + msg.v_b;
Ok((msg.swap_id, State1 {
Ok((
msg.swap_id,
State1 {
a: self.a,
B: msg.B,
s_a: self.s_a,
@ -206,7 +208,8 @@ impl State0 {
tx_punish_fee: self.tx_punish_fee,
tx_refund_fee: msg.tx_refund_fee,
tx_cancel_fee: msg.tx_cancel_fee,
}))
},
))
}
}

View File

@ -187,6 +187,9 @@ mod tests {
#[test]
fn seed_from_pem_works() {
use base64::engine::general_purpose;
use base64::Engine;
let payload: &str = "syl9wSYaruvgxg9P5Q1qkZaq5YkM6GvXkxe+VYrL/XM=";
// 32 bytes base64 encoded.
@ -195,7 +198,7 @@ syl9wSYaruvgxg9P5Q1qkZaq5YkM6GvXkxe+VYrL/XM=
-----END SEED-----
";
let want = base64::decode(payload).unwrap();
let want = general_purpose::STANDARD.decode(payload).unwrap();
let pem = pem::parse(pem_string).unwrap();
let got = Seed::from_pem(pem).unwrap();

View File

@ -8,7 +8,7 @@ async fn ensure_same_swap_id_for_alice_and_bob() {
harness::setup_test(SlowCancelConfig, |mut ctx| async move {
let (bob_swap, _) = ctx.bob_swap().await;
let bob_swap_id = bob_swap.id;
let _ = tokio::spawn(bob::run(bob_swap));
tokio::spawn(bob::run(bob_swap));
// once Bob's swap is spawned we can retrieve Alice's swap and assert on the
// swap ID

View File

@ -158,13 +158,16 @@ async fn init_containers(cli: &Cli) -> (Monero, Containers<'_>) {
.await
.unwrap();
(monero, Containers {
(
monero,
Containers {
bitcoind_url,
bitcoind,
monerod_container,
monero_wallet_rpc_containers,
electrs,
})
},
)
}
async fn init_bitcoind_container(
@ -245,7 +248,7 @@ async fn start_alice(
resume_only,
env_config,
XmrBtcNamespace::Testnet,
None,
&[],
)
.unwrap();
swarm.listen_on(listen_address).unwrap();
@ -925,7 +928,7 @@ async fn init_bitcoind(node_url: Url, spendable_quantity: u32) -> Result<Client>
bitcoind_client
.generatetoaddress(101 + spendable_quantity, reward_address.clone())
.await?;
let _ = tokio::spawn(mine(bitcoind_client.clone(), reward_address));
tokio::spawn(mine(bitcoind_client.clone(), reward_address));
Ok(bitcoind_client)
}