mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-12-25 07:29:32 -05:00
Merge branch 'master' into fix-fail-to-fetch
This commit is contained in:
commit
b4fbf41419
8
.github/workflows/build-release-binaries.yml
vendored
8
.github/workflows/build-release-binaries.yml
vendored
@ -45,16 +45,16 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout tagged commit
|
- name: Checkout tagged commit
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v4.1.1
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.release.target_commitish }}
|
ref: ${{ github.event.release.target_commitish }}
|
||||||
token: ${{ secrets.BOTTY_GITHUB_TOKEN }}
|
token: ${{ secrets.BOTTY_GITHUB_TOKEN }}
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2.2.0
|
- uses: Swatinem/rust-cache@v2.7.3
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@master
|
- uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: 1.63
|
toolchain: "1.70"
|
||||||
targets: armv7-unknown-linux-gnueabihf
|
targets: armv7-unknown-linux-gnueabihf
|
||||||
|
|
||||||
- name: Build ${{ matrix.target }} ${{ matrix.bin }} release binary
|
- name: Build ${{ matrix.target }} ${{ matrix.bin }} release binary
|
||||||
@ -69,7 +69,7 @@ jobs:
|
|||||||
run: target/${{ matrix.target }}/release/${{ matrix.bin }} --help
|
run: target/${{ matrix.target }}/release/${{ matrix.bin }} --help
|
||||||
|
|
||||||
# Remove once python 3 is the default
|
# Remove once python 3 is the default
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
|
|
||||||
|
58
.github/workflows/ci.yml
vendored
58
.github/workflows/ci.yml
vendored
@ -4,8 +4,6 @@ on:
|
|||||||
pull_request: # Need to run on pull-requests, otherwise PRs from forks don't run
|
pull_request: # Need to run on pull-requests, otherwise PRs from forks don't run
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "staging" # Bors uses this branch
|
|
||||||
- "trying" # Bors uses this branch
|
|
||||||
- "master" # Always build head of master for the badge in the README
|
- "master" # Always build head of master for the badge in the README
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@ -13,12 +11,19 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2.2.0
|
- uses: dtolnay/rust-toolchain@master
|
||||||
|
with:
|
||||||
|
toolchain: "1.70"
|
||||||
|
components: clippy,rustfmt
|
||||||
|
|
||||||
|
- uses: Swatinem/rust-cache@v2.7.3
|
||||||
|
|
||||||
- name: Check formatting
|
- 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
|
- name: Run clippy with default features
|
||||||
run: cargo clippy --workspace --all-targets -- -D warnings
|
run: cargo clippy --workspace --all-targets -- -D warnings
|
||||||
@ -30,9 +35,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2.0.2
|
- uses: Swatinem/rust-cache@v2.7.3
|
||||||
|
|
||||||
- name: Build swap
|
- name: Build swap
|
||||||
run: cargo build --bin swap
|
run: cargo build --bin swap
|
||||||
@ -44,12 +49,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2.0.2
|
- uses: Swatinem/rust-cache@v2.7.3
|
||||||
|
|
||||||
- name: Install sqlx-cli
|
- name: Install sqlx-cli
|
||||||
run: cargo install sqlx-cli
|
run: cargo install sqlx-cli --locked
|
||||||
|
|
||||||
- name: Run sqlite_dev_setup.sh script
|
- name: Run sqlite_dev_setup.sh script
|
||||||
run: |
|
run: |
|
||||||
@ -71,13 +76,13 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2.2.0
|
- uses: Swatinem/rust-cache@v2.7.3
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@master
|
- uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
toolchain: 1.63
|
toolchain: "1.70"
|
||||||
targets: armv7-unknown-linux-gnueabihf
|
targets: armv7-unknown-linux-gnueabihf
|
||||||
|
|
||||||
- name: Build binary
|
- name: Build binary
|
||||||
@ -93,13 +98,13 @@ jobs:
|
|||||||
run: cross build -p swap --target ${{ matrix.target }}
|
run: cross build -p swap --target ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Upload swap binary
|
- name: Upload swap binary
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: swap-${{ matrix.target }}
|
name: swap-${{ matrix.target }}
|
||||||
path: target/${{ matrix.target }}/debug/swap
|
path: target/${{ matrix.target }}/debug/swap
|
||||||
|
|
||||||
- name: Upload asb binary
|
- name: Upload asb binary
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: asb-${{ matrix.target }}
|
name: asb-${{ matrix.target }}
|
||||||
path: target/${{ matrix.target }}/debug/asb
|
path: target/${{ matrix.target }}/debug/asb
|
||||||
@ -110,10 +115,23 @@ jobs:
|
|||||||
os: [ubuntu-latest, macos-latest]
|
os: [ubuntu-latest, macos-latest]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: (Free disk space on Ubuntu)
|
||||||
uses: actions/checkout@v3.3.0
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
||||||
|
with:
|
||||||
|
# removing all of these takes ~10 mins, so just do as needed
|
||||||
|
android: true
|
||||||
|
dotnet: true
|
||||||
|
haskell: true
|
||||||
|
docker-images: false
|
||||||
|
large-packages: false
|
||||||
|
swap-storage: false
|
||||||
|
tool-cache: false
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2.2.0
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
|
- uses: Swatinem/rust-cache@v2.7.3
|
||||||
|
|
||||||
- name: Build tests
|
- name: Build tests
|
||||||
run: cargo build --tests --workspace --all-features
|
run: cargo build --tests --workspace --all-features
|
||||||
@ -148,9 +166,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2.2.0
|
- uses: Swatinem/rust-cache@v2.7.3
|
||||||
|
|
||||||
- name: Run test ${{ matrix.test_name }}
|
- name: Run test ${{ matrix.test_name }}
|
||||||
run: cargo test --package swap --all-features --test ${{ matrix.test_name }} -- --nocapture
|
run: cargo test --package swap --all-features --test ${{ matrix.test_name }} -- --nocapture
|
||||||
|
2
.github/workflows/create-release.yml
vendored
2
.github/workflows/create-release.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
|||||||
if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/')
|
if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3.3.0
|
- uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
- name: Extract version from branch name
|
- name: Extract version from branch name
|
||||||
id: extract-version
|
id: extract-version
|
||||||
|
12
.github/workflows/draft-new-release.yml
vendored
12
.github/workflows/draft-new-release.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
name: "Draft a new release"
|
name: "Draft a new release"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3.3.0
|
- uses: actions/checkout@v4.1.1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.BOTTY_GITHUB_TOKEN }}
|
token: ${{ secrets.BOTTY_GITHUB_TOKEN }}
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ jobs:
|
|||||||
run: git checkout -b release/${{ github.event.inputs.version }}
|
run: git checkout -b release/${{ github.event.inputs.version }}
|
||||||
|
|
||||||
- name: Update changelog
|
- name: Update changelog
|
||||||
uses: thomaseizinger/keep-a-changelog-new-release@1.3.0
|
uses: thomaseizinger/keep-a-changelog-new-release@2.0.0
|
||||||
with:
|
with:
|
||||||
version: ${{ github.event.inputs.version }}
|
version: ${{ github.event.inputs.version }}
|
||||||
changelogPath: CHANGELOG.md
|
changelogPath: CHANGELOG.md
|
||||||
@ -41,8 +41,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Commit changelog and manifest files
|
- name: Commit changelog and manifest files
|
||||||
id: make-commit
|
id: make-commit
|
||||||
|
env:
|
||||||
|
DPRINT_VERSION: 0.39.1
|
||||||
|
RUST_TOOLCHAIN: 1.70
|
||||||
run: |
|
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
|
/home/runner/.dprint/bin/dprint fmt
|
||||||
|
|
||||||
git add CHANGELOG.md Cargo.lock swap/Cargo.toml
|
git add CHANGELOG.md Cargo.lock swap/Cargo.toml
|
||||||
@ -54,7 +58,7 @@ jobs:
|
|||||||
run: git push origin release/${{ github.event.inputs.version }} --force
|
run: git push origin release/${{ github.event.inputs.version }} --force
|
||||||
|
|
||||||
- name: Create pull request
|
- name: Create pull request
|
||||||
uses: thomaseizinger/create-pull-request@1.3.0
|
uses: thomaseizinger/create-pull-request@1.3.1
|
||||||
with:
|
with:
|
||||||
GITHUB_TOKEN: ${{ secrets.BOTTY_GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.BOTTY_GITHUB_TOKEN }}
|
||||||
head: release/${{ github.event.inputs.version }}
|
head: release/${{ github.event.inputs.version }}
|
||||||
|
2
.github/workflows/preview-release.yml
vendored
2
.github/workflows/preview-release.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
|||||||
name: Create preview release
|
name: Create preview release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3.3.0
|
- uses: actions/checkout@v4.1.1
|
||||||
|
|
||||||
- name: Delete 'preview' release
|
- name: Delete 'preview' release
|
||||||
uses: larryjoelane/delete-release-action@v1.0.24
|
uses: larryjoelane/delete-release-action@v1.0.24
|
||||||
|
15
CHANGELOG.md
15
CHANGELOG.md
@ -7,9 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
- Minimum Supported Rust Version (MSRV) bumped to 1.70
|
||||||
|
|
||||||
|
## [0.12.3] - 2023-09-20
|
||||||
|
|
||||||
|
- Swap: If no Monero daemon is manually specified, we will automatically choose one from a list of public daemons by connecting to each and checking their availability.
|
||||||
|
|
||||||
|
## [0.12.2] - 2023-08-08
|
||||||
|
|
||||||
### Changed
|
### 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
|
## [0.12.1] - 2023-01-09
|
||||||
|
|
||||||
@ -342,7 +351,9 @@ 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.
|
- 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.
|
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.3...HEAD
|
||||||
|
[0.12.3]: https://github.com/comit-network/xmr-btc-swap/compare/0.12.2...0.12.3
|
||||||
|
[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.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.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
|
[0.11.0]: https://github.com/comit-network/xmr-btc-swap/compare/0.10.2...0.11.0
|
||||||
|
1561
Cargo.lock
generated
1561
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -44,13 +44,13 @@ It is not recommended to bump fees when swapping because it can have unpredictab
|
|||||||
|
|
||||||
## Contributing
|
## 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).
|
Please have a look at the [contribution guidelines](./CONTRIBUTING.md).
|
||||||
|
|
||||||
## Rust Version Support
|
## Rust Version Support
|
||||||
|
|
||||||
Please note that only the latest stable Rust toolchain is supported.
|
Please note that only the latest stable Rust toolchain is supported.
|
||||||
All stable toolchains since 1.63 _should_ work.
|
All stable toolchains since 1.70 _should_ work.
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
|
25
bors.toml
25
bors.toml
@ -1,25 +0,0 @@
|
|||||||
status = [
|
|
||||||
"static_analysis",
|
|
||||||
"bdk_test",
|
|
||||||
"sqlx_test",
|
|
||||||
"build (x86_64-unknown-linux-gnu, ubuntu-latest)",
|
|
||||||
"build (armv7-unknown-linux-gnueabihf, ubuntu-latest)",
|
|
||||||
"build (x86_64-apple-darwin, macos-latest)",
|
|
||||||
"build (x86_64-pc-windows-msvc, windows-latest)",
|
|
||||||
"test (ubuntu-latest)",
|
|
||||||
"test (macos-latest)",
|
|
||||||
"docker_tests (happy_path)",
|
|
||||||
"docker_tests (happy_path_restart_bob_after_xmr_locked)",
|
|
||||||
"docker_tests (happy_path_restart_alice_after_xmr_locked)",
|
|
||||||
"docker_tests (happy_path_restart_bob_before_xmr_locked)",
|
|
||||||
"docker_tests (alice_and_bob_refund_using_cancel_and_refund_command)",
|
|
||||||
"docker_tests (alice_and_bob_refund_using_cancel_then_refund_command)",
|
|
||||||
"docker_tests (alice_and_bob_refund_using_cancel_and_refund_command_timelock_not_expired)",
|
|
||||||
"docker_tests (punish)",
|
|
||||||
"docker_tests (alice_punishes_after_restart_bob_dead)",
|
|
||||||
"docker_tests (alice_manually_punishes_after_bob_dead)",
|
|
||||||
"docker_tests (alice_refunds_after_restart_bob_refunded)",
|
|
||||||
"docker_tests (ensure_same_swap_id)",
|
|
||||||
"docker_tests (concurrent_bobs_before_xmr_lock_proof_sent)",
|
|
||||||
"docker_tests (alice_manually_redeems_after_enc_sig_learned)"
|
|
||||||
]
|
|
@ -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).
|
The ASB daemon supports the libp2p [rendezvous-protocol](https://github.com/libp2p/specs/tree/master/rendezvous).
|
||||||
Usage of the rendezvous functionality is entirely optional.
|
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 the registration to be successful, you also need to configure the externally reachable addresses within the `[network]` section.
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[network]
|
[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"]
|
external_addresses = ["/dns4/example.com/tcp/9939"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
16
dprint.json
16
dprint.json
@ -3,22 +3,16 @@
|
|||||||
"projectType": "openSource",
|
"projectType": "openSource",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"markdown": {},
|
"markdown": {},
|
||||||
"rustfmt": {
|
"exec": {
|
||||||
"edition": 2021,
|
"associations": "**/*.{rs}",
|
||||||
"condense_wildcard_suffixes": true,
|
"rustfmt": "rustfmt --edition 2021",
|
||||||
"format_macro_matchers": true,
|
"rustfmt.associations": "**/*.rs"
|
||||||
"imports_granularity": "Module",
|
|
||||||
"use_field_init_shorthand": true,
|
|
||||||
"format_code_in_doc_comments": true,
|
|
||||||
"normalize_comments": true,
|
|
||||||
"wrap_comments": true,
|
|
||||||
"overflow_delimited_expr": true
|
|
||||||
},
|
},
|
||||||
"includes": ["**/*.{md}", "**/*.{toml}", "**/*.{rs}"],
|
"includes": ["**/*.{md}", "**/*.{toml}", "**/*.{rs}"],
|
||||||
"excludes": ["target/"],
|
"excludes": ["target/"],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"https://plugins.dprint.dev/markdown-0.13.1.wasm",
|
"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://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"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ futures = "0.3"
|
|||||||
monero-rpc = { path = "../monero-rpc" }
|
monero-rpc = { path = "../monero-rpc" }
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
spectral = "0.6"
|
spectral = "0.6"
|
||||||
testcontainers = "0.12"
|
testcontainers = "0.14"
|
||||||
tokio = { version = "1", default-features = false, features = [ "rt-multi-thread", "time", "macros" ] }
|
tokio = { version = "1", default-features = false, features = [ "rt-multi-thread", "time", "macros" ] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.2", default-features = false, features = [ "fmt", "ansi", "env-filter", "tracing-log" ] }
|
tracing-subscriber = { version = "0.2", default-features = false, features = [ "fmt", "ansi", "env-filter", "tracing-log" ] }
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
use std::collections::HashMap;
|
use testcontainers::{core::WaitFor, Image, ImageArgs};
|
||||||
use testcontainers::core::{Container, Docker, WaitForMessage};
|
|
||||||
use testcontainers::Image;
|
|
||||||
|
|
||||||
pub const MONEROD_DAEMON_CONTAINER_NAME: &str = "monerod";
|
pub const MONEROD_DAEMON_CONTAINER_NAME: &str = "monerod";
|
||||||
pub const MONEROD_DEFAULT_NETWORK: &str = "monero_network";
|
pub const MONEROD_DEFAULT_NETWORK: &str = "monero_network";
|
||||||
@ -13,43 +11,22 @@ pub const MONEROD_DEFAULT_NETWORK: &str = "monero_network";
|
|||||||
/// this doesn't matter.
|
/// this doesn't matter.
|
||||||
pub const RPC_PORT: u16 = 18081;
|
pub const RPC_PORT: u16 = 18081;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
pub struct Monerod {
|
pub struct Monerod;
|
||||||
args: MonerodArgs,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Image for Monerod {
|
impl Image for Monerod {
|
||||||
type Args = MonerodArgs;
|
type Args = MonerodArgs;
|
||||||
type EnvVars = HashMap<String, String>;
|
|
||||||
type Volumes = HashMap<String, String>;
|
|
||||||
type EntryPoint = str;
|
|
||||||
|
|
||||||
fn descriptor(&self) -> String {
|
fn name(&self) -> String {
|
||||||
"rinocommunity/monero:v0.18.1.2".to_owned()
|
"rinocommunity/monero".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait_until_ready<D: Docker>(&self, container: &Container<'_, D, Self>) {
|
fn tag(&self) -> String {
|
||||||
container
|
"v0.18.1.2".into()
|
||||||
.logs()
|
|
||||||
.stdout
|
|
||||||
.wait_for_message("RPC server started ok")
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn args(&self) -> <Self as Image>::Args {
|
fn ready_conditions(&self) -> Vec<WaitFor> {
|
||||||
self.args.clone()
|
vec![WaitFor::message_on_stdout("RPC server started ok")]
|
||||||
}
|
|
||||||
|
|
||||||
fn volumes(&self) -> Self::Volumes {
|
|
||||||
HashMap::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn env_vars(&self) -> Self::EnvVars {
|
|
||||||
HashMap::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_args(self, args: <Self as Image>::Args) -> Self {
|
|
||||||
Self { args }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entrypoint(&self) -> Option<String> {
|
fn entrypoint(&self) -> Option<String> {
|
||||||
@ -58,43 +35,22 @@ impl Image for Monerod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct MoneroWalletRpc {
|
pub struct MoneroWalletRpc;
|
||||||
args: MoneroWalletRpcArgs,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Image for MoneroWalletRpc {
|
impl Image for MoneroWalletRpc {
|
||||||
type Args = MoneroWalletRpcArgs;
|
type Args = MoneroWalletRpcArgs;
|
||||||
type EnvVars = HashMap<String, String>;
|
|
||||||
type Volumes = HashMap<String, String>;
|
|
||||||
type EntryPoint = str;
|
|
||||||
|
|
||||||
fn descriptor(&self) -> String {
|
fn name(&self) -> String {
|
||||||
"rinocommunity/monero:v0.18.1.2".to_owned()
|
"rinocommunity/monero".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait_until_ready<D: Docker>(&self, container: &Container<'_, D, Self>) {
|
fn tag(&self) -> String {
|
||||||
container
|
"v0.18.1.2".into()
|
||||||
.logs()
|
|
||||||
.stdout
|
|
||||||
.wait_for_message("Run server thread name: RPC")
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn args(&self) -> <Self as Image>::Args {
|
fn ready_conditions(&self) -> Vec<WaitFor> {
|
||||||
self.args.clone()
|
vec![WaitFor::message_on_stdout("Run server thread name: RPC")]
|
||||||
}
|
|
||||||
|
|
||||||
fn volumes(&self) -> Self::Volumes {
|
|
||||||
HashMap::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn env_vars(&self) -> Self::EnvVars {
|
|
||||||
HashMap::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_args(self, args: <Self as Image>::Args) -> Self {
|
|
||||||
Self { args }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entrypoint(&self) -> Option<String> {
|
fn entrypoint(&self) -> Option<String> {
|
||||||
@ -104,10 +60,9 @@ impl Image for MoneroWalletRpc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MoneroWalletRpc {
|
impl MoneroWalletRpc {
|
||||||
pub fn new(name: &str, daemon_address: String) -> Self {
|
pub fn new(name: &str, daemon_address: String) -> (Self, MoneroWalletRpcArgs) {
|
||||||
Self {
|
let args = MoneroWalletRpcArgs::new(name, daemon_address);
|
||||||
args: MoneroWalletRpcArgs::new(name, daemon_address),
|
(Self, args)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,6 +146,12 @@ impl IntoIterator for MonerodArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ImageArgs for MonerodArgs {
|
||||||
|
fn into_iterator(self) -> Box<dyn Iterator<Item = String>> {
|
||||||
|
Box::new(self.into_iter())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MoneroWalletRpcArgs {
|
pub struct MoneroWalletRpcArgs {
|
||||||
pub disable_rpc_login: bool,
|
pub disable_rpc_login: bool,
|
||||||
@ -200,12 +161,6 @@ pub struct MoneroWalletRpcArgs {
|
|||||||
pub daemon_address: String,
|
pub daemon_address: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MoneroWalletRpcArgs {
|
|
||||||
fn default() -> Self {
|
|
||||||
unimplemented!("A default instance for `MoneroWalletRpc` doesn't make sense because we always need to connect to a node.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MoneroWalletRpcArgs {
|
impl MoneroWalletRpcArgs {
|
||||||
pub fn new(wallet_name: &str, daemon_address: String) -> Self {
|
pub fn new(wallet_name: &str, daemon_address: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -247,3 +202,9 @@ impl IntoIterator for MoneroWalletRpcArgs {
|
|||||||
args.into_iter()
|
args.into_iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ImageArgs for MoneroWalletRpcArgs {
|
||||||
|
fn into_iterator(self) -> Box<dyn Iterator<Item = String>> {
|
||||||
|
Box::new(self.into_iter())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -20,17 +20,20 @@
|
|||||||
//! every BLOCK_TIME_SECS seconds.
|
//! every BLOCK_TIME_SECS seconds.
|
||||||
//!
|
//!
|
||||||
//! Also provides standalone JSON RPC clients for monerod and monero-wallet-rpc.
|
//! Also provides standalone JSON RPC clients for monerod and monero-wallet-rpc.
|
||||||
pub mod image;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::image::{MONEROD_DAEMON_CONTAINER_NAME, MONEROD_DEFAULT_NETWORK, RPC_PORT};
|
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
|
use testcontainers::clients::Cli;
|
||||||
|
use testcontainers::{Container, RunnableImage};
|
||||||
|
use tokio::time;
|
||||||
|
|
||||||
use monero_rpc::monerod;
|
use monero_rpc::monerod;
|
||||||
use monero_rpc::monerod::MonerodRpc as _;
|
use monero_rpc::monerod::MonerodRpc as _;
|
||||||
use monero_rpc::wallet::{self, GetAddress, MoneroWalletRpc as _, Refreshed, Transfer};
|
use monero_rpc::wallet::{self, GetAddress, MoneroWalletRpc as _, Refreshed, Transfer};
|
||||||
use std::time::Duration;
|
|
||||||
use testcontainers::clients::Cli;
|
use crate::image::{MONEROD_DAEMON_CONTAINER_NAME, MONEROD_DEFAULT_NETWORK, RPC_PORT};
|
||||||
use testcontainers::{Container, Docker, RunArgs};
|
|
||||||
use tokio::time;
|
pub mod image;
|
||||||
|
|
||||||
/// How often we mine a block.
|
/// How often we mine a block.
|
||||||
const BLOCK_TIME_SECS: u64 = 1;
|
const BLOCK_TIME_SECS: u64 = 1;
|
||||||
@ -56,8 +59,8 @@ impl<'c> Monero {
|
|||||||
additional_wallets: Vec<&'static str>,
|
additional_wallets: Vec<&'static str>,
|
||||||
) -> Result<(
|
) -> Result<(
|
||||||
Self,
|
Self,
|
||||||
Container<'c, Cli, image::Monerod>,
|
Container<'c, image::Monerod>,
|
||||||
Vec<Container<'c, Cli, image::MoneroWalletRpc>>,
|
Vec<Container<'c, image::MoneroWalletRpc>>,
|
||||||
)> {
|
)> {
|
||||||
let prefix = format!("{}_", random_prefix());
|
let prefix = format!("{}_", random_prefix());
|
||||||
let monerod_name = format!("{}{}", prefix, MONEROD_DAEMON_CONTAINER_NAME);
|
let monerod_name = format!("{}{}", prefix, MONEROD_DAEMON_CONTAINER_NAME);
|
||||||
@ -221,15 +224,14 @@ impl<'c> Monerod {
|
|||||||
cli: &'c Cli,
|
cli: &'c Cli,
|
||||||
name: String,
|
name: String,
|
||||||
network: String,
|
network: String,
|
||||||
) -> Result<(Self, Container<'c, Cli, image::Monerod>)> {
|
) -> Result<(Self, Container<'c, image::Monerod>)> {
|
||||||
let image = image::Monerod::default();
|
let image = image::Monerod::default();
|
||||||
let run_args = RunArgs::default()
|
let image: RunnableImage<image::Monerod> = RunnableImage::from(image)
|
||||||
.with_name(name.clone())
|
.with_container_name(name.clone())
|
||||||
.with_network(network.clone());
|
.with_network(network.clone());
|
||||||
let container = cli.run_with_args(image, run_args);
|
|
||||||
let monerod_rpc_port = container
|
let container = cli.run(image);
|
||||||
.get_host_port(RPC_PORT)
|
let monerod_rpc_port = container.get_host_port_ipv4(RPC_PORT);
|
||||||
.context("port not exposed")?;
|
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
Self {
|
Self {
|
||||||
@ -249,7 +251,7 @@ impl<'c> Monerod {
|
|||||||
/// address
|
/// address
|
||||||
pub async fn start_miner(&self, miner_wallet_address: &str) -> Result<()> {
|
pub async fn start_miner(&self, miner_wallet_address: &str) -> Result<()> {
|
||||||
let monerod = self.client().clone();
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -262,19 +264,15 @@ impl<'c> MoneroWalletRpc {
|
|||||||
name: &str,
|
name: &str,
|
||||||
monerod: &Monerod,
|
monerod: &Monerod,
|
||||||
prefix: String,
|
prefix: String,
|
||||||
) -> Result<(Self, Container<'c, Cli, image::MoneroWalletRpc>)> {
|
) -> Result<(Self, Container<'c, image::MoneroWalletRpc>)> {
|
||||||
let daemon_address = format!("{}:{}", monerod.name, RPC_PORT);
|
let daemon_address = format!("{}:{}", monerod.name, RPC_PORT);
|
||||||
let image = image::MoneroWalletRpc::new(name, daemon_address);
|
let (image, args) = image::MoneroWalletRpc::new(name, daemon_address);
|
||||||
|
let image = RunnableImage::from((image, args))
|
||||||
|
.with_container_name(format!("{}{}", prefix, name))
|
||||||
|
.with_network(monerod.network.clone());
|
||||||
|
|
||||||
let network = monerod.network.clone();
|
let container = cli.run(image);
|
||||||
let run_args = RunArgs::default()
|
let wallet_rpc_port = container.get_host_port_ipv4(RPC_PORT);
|
||||||
// prefix the container name so we can run multiple tests
|
|
||||||
.with_name(format!("{}{}", prefix, name))
|
|
||||||
.with_network(network.clone());
|
|
||||||
let container = cli.run_with_args(image, run_args);
|
|
||||||
let wallet_rpc_port = container
|
|
||||||
.get_host_port(RPC_PORT)
|
|
||||||
.context("port not exposed")?;
|
|
||||||
|
|
||||||
let client = wallet::Client::localhost(wallet_rpc_port)?;
|
let client = wallet::Client::localhost(wallet_rpc_port)?;
|
||||||
|
|
||||||
|
@ -19,5 +19,5 @@ serde_json = "1.0"
|
|||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex-literal = "0.3"
|
hex-literal = "0.4"
|
||||||
tokio = { version = "1", features = [ "full" ] }
|
tokio = { version = "1", features = [ "full" ] }
|
||||||
|
@ -47,9 +47,10 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_o_indexes(&self, txid: Hash) -> Result<GetOIndexesResponse> {
|
pub async fn get_o_indexes(&self, txid: Hash) -> Result<GetOIndexesResponse> {
|
||||||
self.binary_request(self.get_o_indexes_bin_url.clone(), GetOIndexesPayload {
|
self.binary_request(
|
||||||
txid,
|
self.get_o_indexes_bin_url.clone(),
|
||||||
})
|
GetOIndexesPayload { txid },
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,7 +195,7 @@ mod monero_serde_hex_block {
|
|||||||
{
|
{
|
||||||
let hex = String::deserialize(deserializer)?;
|
let hex = String::deserialize(deserializer)?;
|
||||||
|
|
||||||
let bytes = hex::decode(&hex).map_err(D::Error::custom)?;
|
let bytes = hex::decode(hex).map_err(D::Error::custom)?;
|
||||||
let mut cursor = Cursor::new(bytes);
|
let mut cursor = Cursor::new(bytes);
|
||||||
|
|
||||||
let block = monero::Block::consensus_decode(&mut cursor).map_err(D::Error::custom)?;
|
let block = monero::Block::consensus_decode(&mut cursor).map_err(D::Error::custom)?;
|
||||||
|
@ -14,6 +14,6 @@ rand = "0.7"
|
|||||||
curve25519-dalek = "3"
|
curve25519-dalek = "3"
|
||||||
monero-harness = { path = "../monero-harness" }
|
monero-harness = { path = "../monero-harness" }
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
testcontainers = "0.12"
|
testcontainers = "0.14"
|
||||||
tokio = { version = "1", features = [ "rt-multi-thread", "time", "macros", "sync", "process", "fs" ] }
|
tokio = { version = "1", features = [ "rt-multi-thread", "time", "macros", "sync", "process", "fs" ] }
|
||||||
tracing-subscriber = { version = "0.2", default-features = false, features = [ "fmt", "ansi", "env-filter", "chrono", "tracing-log" ] }
|
tracing-subscriber = { version = "0.2", default-features = false, features = [ "fmt", "ansi", "env-filter", "chrono", "tracing-log" ] }
|
||||||
|
@ -61,13 +61,12 @@ mod tests {
|
|||||||
use monero_harness::image::Monerod;
|
use monero_harness::image::Monerod;
|
||||||
use monero_rpc::monerod::{Client, GetOutputsOut};
|
use monero_rpc::monerod::{Client, GetOutputsOut};
|
||||||
use testcontainers::clients::Cli;
|
use testcontainers::clients::Cli;
|
||||||
use testcontainers::Docker;
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_outs_for_key_offsets() {
|
async fn get_outs_for_key_offsets() {
|
||||||
let cli = Cli::default();
|
let cli = Cli::default();
|
||||||
let container = cli.run(Monerod::default());
|
let container = cli.run(Monerod::default());
|
||||||
let rpc_client = Client::localhost(container.get_host_port(18081).unwrap()).unwrap();
|
let rpc_client = Client::localhost(container.get_host_port_ipv4(18081)).unwrap();
|
||||||
rpc_client.generateblocks(150, "498AVruCDWgP9Az9LjMm89VWjrBrSZ2W2K3HFBiyzzrRjUJWUcCVxvY1iitfuKoek2FdX6MKGAD9Qb1G1P8QgR5jPmmt3Vj".to_owned()).await.unwrap();
|
rpc_client.generateblocks(150, "498AVruCDWgP9Az9LjMm89VWjrBrSZ2W2K3HFBiyzzrRjUJWUcCVxvY1iitfuKoek2FdX6MKGAD9Qb1G1P8QgR5jPmmt3Vj".to_owned()).await.unwrap();
|
||||||
let wallet = Wallet {
|
let wallet = Wallet {
|
||||||
client: rpc_client.clone(),
|
client: rpc_client.clone(),
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.63" # also update this in the readme, changelog, and github actions
|
channel = "1.70" # also update this in the readme, changelog, and github actions
|
||||||
components = ["clippy"]
|
components = ["clippy"]
|
||||||
targets = ["armv7-unknown-linux-gnueabihf"]
|
targets = ["armv7-unknown-linux-gnueabihf"]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "swap"
|
name = "swap"
|
||||||
version = "0.12.1"
|
version = "0.12.3"
|
||||||
authors = [ "The COMIT guys <hello@comit.network>" ]
|
authors = [ "The COMIT guys <hello@comit.network>" ]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "XMR/BTC trustless atomic swaps."
|
description = "XMR/BTC trustless atomic swaps."
|
||||||
@ -14,29 +14,29 @@ async-compression = { version = "0.3", features = [ "bzip2", "tokio" ] }
|
|||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
atty = "0.2"
|
atty = "0.2"
|
||||||
backoff = { version = "0.4", features = [ "tokio" ] }
|
backoff = { version = "0.4", features = [ "tokio" ] }
|
||||||
base64 = "0.20"
|
base64 = "0.21"
|
||||||
bdk = "0.25"
|
bdk = "0.28"
|
||||||
big-bytes = "1"
|
big-bytes = "1"
|
||||||
bitcoin = { version = "0.29", features = [ "rand", "serde" ] }
|
bitcoin = { version = "0.29", features = [ "rand", "serde" ] }
|
||||||
bmrng = "0.5"
|
bmrng = "0.5"
|
||||||
comfy-table = "6.1"
|
comfy-table = "7.1"
|
||||||
config = { version = "0.13", default-features = false, features = [ "toml" ] }
|
config = { version = "0.14", default-features = false, features = [ "toml" ] }
|
||||||
conquer-once = "0.3"
|
conquer-once = "0.4"
|
||||||
curve25519-dalek = { package = "curve25519-dalek-ng", version = "4" }
|
curve25519-dalek = { package = "curve25519-dalek-ng", version = "4" }
|
||||||
data-encoding = "2.3"
|
data-encoding = "2.5"
|
||||||
dialoguer = "0.10"
|
dialoguer = "0.11"
|
||||||
directories-next = "2"
|
directories-next = "2"
|
||||||
ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "libsecp_compat", "serde", "adaptor" ] }
|
ecdsa_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "libsecp_compat", "serde", "adaptor" ] }
|
||||||
ed25519-dalek = "1"
|
ed25519-dalek = "1"
|
||||||
futures = { version = "0.3", default-features = false }
|
futures = { version = "0.3", default-features = false }
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
itertools = "0.10"
|
itertools = "0.12"
|
||||||
libp2p = { version = "0.42.2", default-features = false, features = [ "tcp-tokio", "yamux", "mplex", "dns-tokio", "noise", "request-response", "websocket", "ping", "rendezvous", "identify" ] }
|
libp2p = { version = "0.42.2", default-features = false, features = [ "tcp-tokio", "yamux", "mplex", "dns-tokio", "noise", "request-response", "websocket", "ping", "rendezvous", "identify" ] }
|
||||||
monero = { version = "0.12", features = [ "serde_support" ] }
|
monero = { version = "0.12", features = [ "serde_support" ] }
|
||||||
monero-rpc = { path = "../monero-rpc" }
|
monero-rpc = { path = "../monero-rpc" }
|
||||||
pem = "1.1"
|
pem = "3.0"
|
||||||
proptest = "1"
|
proptest = "1"
|
||||||
qrcode = "0.12"
|
qrcode = "0.13"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
rand_chacha = "0.3"
|
rand_chacha = "0.3"
|
||||||
reqwest = { version = "0.11", features = [ "rustls-tls", "stream", "socks" ], default-features = false }
|
reqwest = { version = "0.11", features = [ "rustls-tls", "stream", "socks" ], default-features = false }
|
||||||
@ -50,21 +50,21 @@ sha2 = "0.10"
|
|||||||
sigma_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "ed25519", "serde", "secp256k1", "alloc" ] }
|
sigma_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "ed25519", "serde", "secp256k1", "alloc" ] }
|
||||||
sqlx = { version = "0.6", features = [ "sqlite", "runtime-tokio-rustls", "offline" ] }
|
sqlx = { version = "0.6", features = [ "sqlite", "runtime-tokio-rustls", "offline" ] }
|
||||||
structopt = "0.3"
|
structopt = "0.3"
|
||||||
strum = { version = "0.24", features = [ "derive" ] }
|
strum = { version = "0.26", features = [ "derive" ] }
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
time = "0.3"
|
time = "0.3"
|
||||||
tokio = { version = "1", features = [ "rt-multi-thread", "time", "macros", "sync", "process", "fs", "net" ] }
|
tokio = { version = "1", features = [ "rt-multi-thread", "time", "macros", "sync", "process", "fs", "net" ] }
|
||||||
tokio-socks = "0.5"
|
tokio-socks = "0.5"
|
||||||
tokio-tungstenite = { version = "0.15", features = [ "rustls-tls" ] }
|
tokio-tungstenite = { version = "0.15", features = [ "rustls-tls" ] }
|
||||||
tokio-util = { version = "0.7", features = [ "io", "codec" ] }
|
tokio-util = { version = "0.7", features = [ "io", "codec" ] }
|
||||||
toml = "0.5"
|
toml = "0.8"
|
||||||
torut = { version = "0.2", default-features = false, features = [ "v3", "control" ] }
|
torut = { version = "0.2", default-features = false, features = [ "v3", "control" ] }
|
||||||
tracing = { version = "0.1", features = [ "attributes" ] }
|
tracing = { version = "0.1", features = [ "attributes" ] }
|
||||||
tracing-appender = "0.2"
|
tracing-appender = "0.2"
|
||||||
tracing-futures = { version = "0.2", features = [ "std-future", "futures-03" ] }
|
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" ] }
|
tracing-subscriber = { version = "0.3", default-features = false, features = [ "fmt", "ansi", "env-filter", "time", "tracing-log", "json" ] }
|
||||||
url = { version = "2", features = [ "serde" ] }
|
url = { version = "2", features = [ "serde" ] }
|
||||||
uuid = { version = "1.2", features = [ "serde", "v4" ] }
|
uuid = { version = "1.7", features = [ "serde", "v4" ] }
|
||||||
void = "1"
|
void = "1"
|
||||||
|
|
||||||
[target.'cfg(not(windows))'.dependencies]
|
[target.'cfg(not(windows))'.dependencies]
|
||||||
@ -76,16 +76,17 @@ zip = "0.5"
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
bitcoin-harness = "0.2.2"
|
bitcoin-harness = "0.2.2"
|
||||||
get-port = "3"
|
get-port = "3"
|
||||||
hyper = "0.14"
|
hyper = "1.2"
|
||||||
|
mockito = "1.3.0"
|
||||||
monero-harness = { path = "../monero-harness" }
|
monero-harness = { path = "../monero-harness" }
|
||||||
port_check = "0.1"
|
port_check = "0.1"
|
||||||
proptest = "1"
|
proptest = "1"
|
||||||
serde_cbor = "0.11"
|
serde_cbor = "0.11"
|
||||||
serial_test = "0.10"
|
serial_test = "3.0"
|
||||||
spectral = "0.6"
|
spectral = "0.6"
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
testcontainers = "0.12"
|
testcontainers = "0.14"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
vergen = { version = "7", default-features = false, features = [ "git", "build" ] }
|
vergen = { version = "8.3", default-features = false, features = [ "build", "git", "git2" ] }
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use vergen::{vergen, Config, SemverKind};
|
use vergen::EmitBuilder;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let mut config = Config::default();
|
EmitBuilder::builder()
|
||||||
*config.git_mut().semver_kind_mut() = SemverKind::Lightweight;
|
.git_describe(true, true, None)
|
||||||
|
.emit()?;
|
||||||
vergen(config)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ pub mod tracing;
|
|||||||
|
|
||||||
pub use event_loop::{EventLoop, EventLoopHandle, FixedRate, KrakenRate, LatestRate};
|
pub use event_loop::{EventLoop, EventLoopHandle, FixedRate, KrakenRate, LatestRate};
|
||||||
pub use network::behaviour::{Behaviour, OutEvent};
|
pub use network::behaviour::{Behaviour, OutEvent};
|
||||||
|
pub use network::rendezvous::RendezvousNode;
|
||||||
pub use network::transport;
|
pub use network::transport;
|
||||||
pub use rate::Rate;
|
pub use rate::Rate;
|
||||||
pub use recovery::cancel::cancel;
|
pub use recovery::cancel::cancel;
|
||||||
@ -18,4 +19,4 @@ pub use recovery::safely_abort::safely_abort;
|
|||||||
pub use recovery::{cancel, refund};
|
pub use recovery::{cancel, refund};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub use network::rendezous;
|
pub use network::rendezvous;
|
||||||
|
@ -226,7 +226,7 @@ pub enum Command {
|
|||||||
name = "asb",
|
name = "asb",
|
||||||
about = "Automated Swap Backend for swapping XMR for BTC",
|
about = "Automated Swap Backend for swapping XMR for BTC",
|
||||||
author,
|
author,
|
||||||
version = env!("VERGEN_GIT_SEMVER_LIGHTWEIGHT")
|
version = env!("VERGEN_GIT_DESCRIBE")
|
||||||
)]
|
)]
|
||||||
pub struct RawArguments {
|
pub struct RawArguments {
|
||||||
#[structopt(long, help = "Swap on testnet")]
|
#[structopt(long, help = "Swap on testnet")]
|
||||||
|
@ -134,8 +134,8 @@ pub struct Data {
|
|||||||
pub struct Network {
|
pub struct Network {
|
||||||
#[serde(deserialize_with = "addr_list::deserialize")]
|
#[serde(deserialize_with = "addr_list::deserialize")]
|
||||||
pub listen: Vec<Multiaddr>,
|
pub listen: Vec<Multiaddr>,
|
||||||
#[serde(default)]
|
#[serde(default, deserialize_with = "addr_list::deserialize")]
|
||||||
pub rendezvous_point: Option<Multiaddr>,
|
pub rendezvous_point: Vec<Multiaddr>,
|
||||||
#[serde(default, deserialize_with = "addr_list::deserialize")]
|
#[serde(default, deserialize_with = "addr_list::deserialize")]
|
||||||
pub external_addresses: Vec<Multiaddr>,
|
pub external_addresses: Vec<Multiaddr>,
|
||||||
}
|
}
|
||||||
@ -156,7 +156,7 @@ mod addr_list {
|
|||||||
let list: Result<Vec<_>, _> = s
|
let list: Result<Vec<_>, _> = s
|
||||||
.split(',')
|
.split(',')
|
||||||
.filter(|s| !s.is_empty())
|
.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();
|
.collect();
|
||||||
Ok(list?)
|
Ok(list?)
|
||||||
}
|
}
|
||||||
@ -165,7 +165,7 @@ mod addr_list {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
if let Value::String(s) = v {
|
if let Value::String(s) = v {
|
||||||
s.parse().map_err(de::Error::custom)
|
s.trim().parse().map_err(de::Error::custom)
|
||||||
} else {
|
} else {
|
||||||
Err(de::Error::custom("expected a string"))
|
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 ask_spread = Decimal::from_f64(ask_spread).context("Unable to parse spread")?;
|
||||||
|
|
||||||
let rendezvous_point = Input::<Multiaddr>::with_theme(&ColorfulTheme::default())
|
let mut number = 1;
|
||||||
.with_prompt("Do you want to advertise your ASB instance with a rendezvous node? Enter an empty string if not.")
|
let mut done = false;
|
||||||
.allow_empty(true)
|
let mut rendezvous_points = Vec::new();
|
||||||
.interact_text()?;
|
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!();
|
println!();
|
||||||
|
|
||||||
@ -358,11 +375,7 @@ pub fn query_user_for_initial_config(testnet: bool) -> Result<Config> {
|
|||||||
data: Data { dir: data_dir },
|
data: Data { dir: data_dir },
|
||||||
network: Network {
|
network: Network {
|
||||||
listen: listen_addresses,
|
listen: listen_addresses,
|
||||||
rendezvous_point: if rendezvous_point.is_empty() {
|
rendezvous_point: rendezvous_points, // keeping the singular key name for backcompat
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(rendezvous_point)
|
|
||||||
},
|
|
||||||
external_addresses: vec![],
|
external_addresses: vec![],
|
||||||
},
|
},
|
||||||
bitcoin: Bitcoin {
|
bitcoin: Bitcoin {
|
||||||
@ -417,7 +430,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
network: Network {
|
network: Network {
|
||||||
listen: vec![defaults.listen_address_tcp, defaults.listen_address_ws],
|
listen: vec![defaults.listen_address_tcp, defaults.listen_address_ws],
|
||||||
rendezvous_point: None,
|
rendezvous_point: vec![],
|
||||||
external_addresses: vec![],
|
external_addresses: vec![],
|
||||||
},
|
},
|
||||||
monero: Monero {
|
monero: Monero {
|
||||||
@ -461,7 +474,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
network: Network {
|
network: Network {
|
||||||
listen: vec![defaults.listen_address_tcp, defaults.listen_address_ws],
|
listen: vec![defaults.listen_address_tcp, defaults.listen_address_ws],
|
||||||
rendezvous_point: None,
|
rendezvous_point: vec![],
|
||||||
external_addresses: vec![],
|
external_addresses: vec![],
|
||||||
},
|
},
|
||||||
monero: Monero {
|
monero: Monero {
|
||||||
@ -515,7 +528,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
network: Network {
|
network: Network {
|
||||||
listen,
|
listen,
|
||||||
rendezvous_point: None,
|
rendezvous_point: vec![],
|
||||||
external_addresses,
|
external_addresses,
|
||||||
},
|
},
|
||||||
monero: Monero {
|
monero: Monero {
|
||||||
|
@ -253,8 +253,8 @@ where
|
|||||||
channel
|
channel
|
||||||
}.boxed());
|
}.boxed());
|
||||||
}
|
}
|
||||||
SwarmEvent::Behaviour(OutEvent::Rendezvous(libp2p::rendezvous::client::Event::Registered { .. })) => {
|
SwarmEvent::Behaviour(OutEvent::Rendezvous(libp2p::rendezvous::client::Event::Registered { rendezvous_node, ttl, namespace })) => {
|
||||||
tracing::info!("Successfully registered with rendezvous node");
|
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))) => {
|
SwarmEvent::Behaviour(OutEvent::Rendezvous(libp2p::rendezvous::client::Event::RegisterFailed(error))) => {
|
||||||
tracing::error!("Registration with rendezvous node failed: {:?}", error);
|
tracing::error!("Registration with rendezvous node failed: {:?}", error);
|
||||||
|
@ -44,7 +44,9 @@ pub mod transport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod behaviour {
|
pub mod behaviour {
|
||||||
use super::*;
|
use libp2p::swarm::behaviour::toggle::Toggle;
|
||||||
|
|
||||||
|
use super::{rendezvous::RendezvousNode, *};
|
||||||
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -108,7 +110,7 @@ pub mod behaviour {
|
|||||||
where
|
where
|
||||||
LR: LatestRate + Send + 'static,
|
LR: LatestRate + Send + 'static,
|
||||||
{
|
{
|
||||||
pub rendezvous: libp2p::swarm::behaviour::toggle::Toggle<rendezous::Behaviour>,
|
pub rendezvous: Toggle<rendezvous::Behaviour>,
|
||||||
pub quote: quote::Behaviour,
|
pub quote: quote::Behaviour,
|
||||||
pub swap_setup: alice::Behaviour<LR>,
|
pub swap_setup: alice::Behaviour<LR>,
|
||||||
pub transfer_proof: transfer_proof::Behaviour,
|
pub transfer_proof: transfer_proof::Behaviour,
|
||||||
@ -132,25 +134,22 @@ pub mod behaviour {
|
|||||||
resume_only: bool,
|
resume_only: bool,
|
||||||
env_config: env::Config,
|
env_config: env::Config,
|
||||||
identify_params: (identity::Keypair, XmrBtcNamespace),
|
identify_params: (identity::Keypair, XmrBtcNamespace),
|
||||||
rendezvous_params: Option<(identity::Keypair, PeerId, Multiaddr, XmrBtcNamespace)>,
|
rendezvous_nodes: Vec<RendezvousNode>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let agentVersion = format!("asb/{} ({})", env!("CARGO_PKG_VERSION"), identify_params.1);
|
let (identity, namespace) = identify_params;
|
||||||
let protocolVersion = "/comit/xmr/btc/1.0.0".to_string();
|
let agent_version = format!("asb/{} ({})", env!("CARGO_PKG_VERSION"), namespace);
|
||||||
let identifyConfig = IdentifyConfig::new(protocolVersion, identify_params.0.public())
|
let protocol_version = "/comit/xmr/btc/1.0.0".to_string();
|
||||||
.with_agent_version(agentVersion);
|
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 {
|
Self {
|
||||||
rendezvous: libp2p::swarm::behaviour::toggle::Toggle::from(rendezvous_params.map(
|
rendezvous: Toggle::from(behaviour),
|
||||||
|(identity, rendezvous_peer_id, rendezvous_address, namespace)| {
|
|
||||||
rendezous::Behaviour::new(
|
|
||||||
identity,
|
|
||||||
rendezvous_peer_id,
|
|
||||||
rendezvous_address,
|
|
||||||
namespace,
|
|
||||||
None, // use default ttl on rendezvous point
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
quote: quote::asb(),
|
quote: quote::asb(),
|
||||||
swap_setup: alice::Behaviour::new(
|
swap_setup: alice::Behaviour::new(
|
||||||
min_buy,
|
min_buy,
|
||||||
@ -186,13 +185,14 @@ pub mod behaviour {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod rendezous {
|
pub mod rendezvous {
|
||||||
use super::*;
|
use super::*;
|
||||||
use libp2p::swarm::dial_opts::DialOpts;
|
use libp2p::swarm::dial_opts::DialOpts;
|
||||||
use libp2p::swarm::DialError;
|
use libp2p::swarm::DialError;
|
||||||
|
use std::collections::VecDeque;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
enum ConnectionStatus {
|
enum ConnectionStatus {
|
||||||
Disconnected,
|
Disconnected,
|
||||||
Dialling,
|
Dialling,
|
||||||
@ -209,39 +209,59 @@ pub mod rendezous {
|
|||||||
|
|
||||||
pub struct Behaviour {
|
pub struct Behaviour {
|
||||||
inner: libp2p::rendezvous::client::Behaviour,
|
inner: libp2p::rendezvous::client::Behaviour,
|
||||||
rendezvous_point: Multiaddr,
|
rendezvous_nodes: Vec<RendezvousNode>,
|
||||||
rendezvous_peer_id: PeerId,
|
to_dial: VecDeque<PeerId>,
|
||||||
namespace: XmrBtcNamespace,
|
|
||||||
registration_status: RegistrationStatus,
|
|
||||||
connection_status: ConnectionStatus,
|
|
||||||
registration_ttl: Option<u64>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
pub fn new(
|
||||||
identity: identity::Keypair,
|
address: &Multiaddr,
|
||||||
rendezvous_peer_id: PeerId,
|
peer_id: PeerId,
|
||||||
rendezvous_address: Multiaddr,
|
|
||||||
namespace: XmrBtcNamespace,
|
namespace: XmrBtcNamespace,
|
||||||
registration_ttl: Option<u64>,
|
registration_ttl: Option<u64>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: libp2p::rendezvous::client::Behaviour::new(identity),
|
address: address.to_owned(),
|
||||||
rendezvous_point: rendezvous_address,
|
|
||||||
rendezvous_peer_id,
|
|
||||||
namespace,
|
|
||||||
registration_status: RegistrationStatus::RegisterOnNextConnection,
|
|
||||||
connection_status: ConnectionStatus::Disconnected,
|
connection_status: ConnectionStatus::Disconnected,
|
||||||
|
namespace,
|
||||||
|
peer_id,
|
||||||
|
registration_status: RegistrationStatus::RegisterOnNextConnection,
|
||||||
registration_ttl,
|
registration_ttl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register(&mut self) {
|
fn set_connection(&mut self, status: ConnectionStatus) {
|
||||||
self.inner.register(
|
self.connection_status = status;
|
||||||
self.namespace.into(),
|
}
|
||||||
self.rendezvous_peer_id,
|
|
||||||
self.registration_ttl,
|
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> {
|
fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec<Multiaddr> {
|
||||||
if peer_id == &self.rendezvous_peer_id {
|
for node in self.rendezvous_nodes.iter() {
|
||||||
return vec![self.rendezvous_point.clone()];
|
if peer_id == &node.peer_id {
|
||||||
|
return vec![node.address.clone()];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inject_connected(&mut self, peer_id: &PeerId) {
|
fn inject_connected(&mut self, peer_id: &PeerId) {
|
||||||
if peer_id == &self.rendezvous_peer_id {
|
for i in 0..self.rendezvous_nodes.len() {
|
||||||
self.connection_status = ConnectionStatus::Connected;
|
if peer_id == &self.rendezvous_nodes[i].peer_id {
|
||||||
|
self.rendezvous_nodes[i].set_connection(ConnectionStatus::Connected);
|
||||||
match &self.registration_status {
|
match &self.rendezvous_nodes[i].registration_status {
|
||||||
RegistrationStatus::RegisterOnNextConnection => {
|
RegistrationStatus::RegisterOnNextConnection => {
|
||||||
self.register();
|
self.register(i);
|
||||||
self.registration_status = RegistrationStatus::Pending;
|
self.rendezvous_nodes[i].set_registration(RegistrationStatus::Pending);
|
||||||
|
}
|
||||||
|
RegistrationStatus::Registered { .. } => {}
|
||||||
|
RegistrationStatus::Pending => {}
|
||||||
}
|
}
|
||||||
RegistrationStatus::Registered { .. } => {}
|
|
||||||
RegistrationStatus::Pending => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inject_disconnected(&mut self, peer_id: &PeerId) {
|
fn inject_disconnected(&mut self, peer_id: &PeerId) {
|
||||||
if peer_id == &self.rendezvous_peer_id {
|
for i in 0..self.rendezvous_nodes.len() {
|
||||||
self.connection_status = ConnectionStatus::Disconnected;
|
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,
|
_handler: Self::ProtocolsHandler,
|
||||||
_error: &DialError,
|
_error: &DialError,
|
||||||
) {
|
) {
|
||||||
if let Some(id) = peer_id {
|
for i in 0..self.rendezvous_nodes.len() {
|
||||||
if id == self.rendezvous_peer_id {
|
let mut node = &mut self.rendezvous_nodes[i];
|
||||||
self.connection_status = ConnectionStatus::Disconnected;
|
if let Some(id) = peer_id {
|
||||||
|
if id == node.peer_id {
|
||||||
|
node.connection_status = ConnectionStatus::Disconnected;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -311,62 +340,73 @@ pub mod rendezous {
|
|||||||
cx: &mut std::task::Context<'_>,
|
cx: &mut std::task::Context<'_>,
|
||||||
params: &mut impl PollParameters,
|
params: &mut impl PollParameters,
|
||||||
) -> Poll<NetworkBehaviourAction<Self::OutEvent, Self::ProtocolsHandler>> {
|
) -> Poll<NetworkBehaviourAction<Self::OutEvent, Self::ProtocolsHandler>> {
|
||||||
match &mut self.registration_status {
|
if let Some(peer_id) = self.to_dial.pop_front() {
|
||||||
RegistrationStatus::RegisterOnNextConnection => match self.connection_status {
|
return Poll::Ready(NetworkBehaviourAction::Dial {
|
||||||
ConnectionStatus::Disconnected => {
|
opts: DialOpts::peer_id(peer_id)
|
||||||
self.connection_status = ConnectionStatus::Dialling;
|
.condition(PeerCondition::Disconnected)
|
||||||
|
.build(),
|
||||||
|
|
||||||
return Poll::Ready(NetworkBehaviourAction::Dial {
|
handler: Self::ProtocolsHandler::new(Duration::from_secs(30)),
|
||||||
opts: DialOpts::peer_id(self.rendezvous_peer_id)
|
});
|
||||||
.condition(PeerCondition::Disconnected)
|
}
|
||||||
.build(),
|
// check the status of each rendezvous node
|
||||||
|
for i in 0..self.rendezvous_nodes.len() {
|
||||||
handler: Self::ProtocolsHandler::new(Duration::from_secs(30)),
|
let connection_status = self.rendezvous_nodes[i].connection_status.clone();
|
||||||
});
|
match &mut self.rendezvous_nodes[i].registration_status {
|
||||||
}
|
RegistrationStatus::RegisterOnNextConnection => match connection_status {
|
||||||
ConnectionStatus::Dialling => {}
|
ConnectionStatus::Disconnected => {
|
||||||
ConnectionStatus::Connected => {
|
self.rendezvous_nodes[i].set_connection(ConnectionStatus::Dialling);
|
||||||
self.registration_status = RegistrationStatus::Pending;
|
self.to_dial.push_back(self.rendezvous_nodes[i].peer_id);
|
||||||
self.register();
|
}
|
||||||
}
|
ConnectionStatus::Dialling => {}
|
||||||
},
|
ConnectionStatus::Connected => {
|
||||||
RegistrationStatus::Registered { re_register_in } => {
|
self.rendezvous_nodes[i].set_registration(RegistrationStatus::Pending);
|
||||||
if let Poll::Ready(()) = re_register_in.poll_unpin(cx) {
|
self.register(i);
|
||||||
match self.connection_status {
|
}
|
||||||
ConnectionStatus::Connected => {
|
},
|
||||||
self.registration_status = RegistrationStatus::Pending;
|
RegistrationStatus::Registered { re_register_in } => {
|
||||||
self.register();
|
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);
|
||||||
|
}
|
||||||
|
ConnectionStatus::Disconnected => {
|
||||||
|
self.rendezvous_nodes[i].set_registration(
|
||||||
|
RegistrationStatus::RegisterOnNextConnection,
|
||||||
|
);
|
||||||
|
self.to_dial.push_back(self.rendezvous_nodes[i].peer_id);
|
||||||
|
}
|
||||||
|
ConnectionStatus::Dialling => {}
|
||||||
}
|
}
|
||||||
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)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ConnectionStatus::Dialling => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RegistrationStatus::Pending => {}
|
||||||
}
|
}
|
||||||
RegistrationStatus::Pending => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let inner_poll = self.inner.poll(cx, params);
|
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(
|
if let Poll::Ready(NetworkBehaviourAction::GenerateEvent(
|
||||||
libp2p::rendezvous::client::Event::Registered { ttl, .. },
|
libp2p::rendezvous::client::Event::Registered {
|
||||||
|
ttl,
|
||||||
|
rendezvous_node,
|
||||||
|
..
|
||||||
|
},
|
||||||
)) = &inner_poll
|
)) = &inner_poll
|
||||||
{
|
{
|
||||||
let half_of_ttl = Duration::from_secs(*ttl) / 2;
|
if let Some(i) = self
|
||||||
|
.rendezvous_nodes
|
||||||
self.registration_status = RegistrationStatus::Registered {
|
.iter()
|
||||||
re_register_in: Box::pin(tokio::time::sleep(half_of_ttl)),
|
.position(|n| &n.peer_id == rendezvous_node)
|
||||||
};
|
{
|
||||||
|
let half_of_ttl = Duration::from_secs(*ttl) / 2;
|
||||||
|
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
|
inner_poll
|
||||||
@ -380,6 +420,7 @@ pub mod rendezous {
|
|||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use libp2p::rendezvous;
|
use libp2p::rendezvous;
|
||||||
use libp2p::swarm::SwarmEvent;
|
use libp2p::swarm::SwarmEvent;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn given_no_initial_connection_when_constructed_asb_connects_and_registers_with_rendezvous_node(
|
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(|_, _| {
|
let mut rendezvous_node = new_swarm(|_, _| {
|
||||||
rendezvous::server::Behaviour::new(rendezvous::server::Config::default())
|
rendezvous::server::Behaviour::new(rendezvous::server::Config::default())
|
||||||
});
|
});
|
||||||
let rendezvous_address = rendezvous_node.listen_on_random_memory_address().await;
|
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| {
|
let mut asb = new_swarm(|_, identity| {
|
||||||
rendezous::Behaviour::new(
|
super::rendezvous::Behaviour::new(identity, vec![rendezvous_point])
|
||||||
identity,
|
|
||||||
*rendezvous_node.local_peer_id(),
|
|
||||||
rendezvous_address,
|
|
||||||
XmrBtcNamespace::Testnet,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
asb.listen_on_random_memory_address().await; // this adds an external address
|
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),
|
rendezvous::server::Config::default().with_min_ttl(2),
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
let rendezvous_address = rendezvous_node.listen_on_random_memory_address().await;
|
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| {
|
let mut asb = new_swarm(|_, identity| {
|
||||||
rendezous::Behaviour::new(
|
super::rendezvous::Behaviour::new(identity, vec![rendezvous_point])
|
||||||
identity,
|
|
||||||
*rendezvous_node.local_peer_id(),
|
|
||||||
rendezvous_address,
|
|
||||||
XmrBtcNamespace::Testnet,
|
|
||||||
Some(5),
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
asb.listen_on_random_memory_address().await; // this adds an external address
|
asb.listen_on_random_memory_address().await; // this adds an external address
|
||||||
|
|
||||||
@ -467,5 +508,62 @@ pub mod rendezous {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,6 +102,19 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
match cmd {
|
match cmd {
|
||||||
Command::Start { resume_only } => {
|
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_wallet = init_monero_wallet(&config, env_config).await?;
|
||||||
let monero_address = monero_wallet.get_main_address();
|
let monero_address = monero_wallet.get_main_address();
|
||||||
tracing::info!(%monero_address, "Monero wallet address");
|
tracing::info!(%monero_address, "Monero wallet address");
|
||||||
@ -161,7 +174,7 @@ async fn main() -> Result<()> {
|
|||||||
resume_only,
|
resume_only,
|
||||||
env_config,
|
env_config,
|
||||||
namespace,
|
namespace,
|
||||||
config.network.rendezvous_point,
|
&rendezvous_addrs,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
for listen in config.network.listen.clone() {
|
for listen in config.network.listen.clone() {
|
||||||
|
@ -521,7 +521,7 @@ async fn init_bitcoin_wallet(
|
|||||||
|
|
||||||
async fn init_monero_wallet(
|
async fn init_monero_wallet(
|
||||||
data_dir: PathBuf,
|
data_dir: PathBuf,
|
||||||
monero_daemon_address: String,
|
monero_daemon_address: Option<String>,
|
||||||
env_config: Config,
|
env_config: Config,
|
||||||
) -> Result<(monero::Wallet, monero::WalletRpcProcess)> {
|
) -> Result<(monero::Wallet, monero::WalletRpcProcess)> {
|
||||||
let network = env_config.monero_network;
|
let network = env_config.monero_network;
|
||||||
@ -531,7 +531,7 @@ async fn init_monero_wallet(
|
|||||||
let monero_wallet_rpc = monero::WalletRpc::new(data_dir.join("monero")).await?;
|
let monero_wallet_rpc = monero::WalletRpc::new(data_dir.join("monero")).await?;
|
||||||
|
|
||||||
let monero_wallet_rpc_process = monero_wallet_rpc
|
let monero_wallet_rpc_process = monero_wallet_rpc
|
||||||
.run(network, monero_daemon_address.as_str())
|
.run(network, monero_daemon_address)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let monero_wallet = monero::Wallet::open_or_create(
|
let monero_wallet = monero::Wallet::open_or_create(
|
||||||
|
@ -210,14 +210,20 @@ impl TxCancel {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// The order in which these are inserted doesn't matter
|
// The order in which these are inserted doesn't matter
|
||||||
satisfier.insert(A, ::bitcoin::EcdsaSig {
|
satisfier.insert(
|
||||||
sig: sig_a.into(),
|
A,
|
||||||
hash_ty: EcdsaSighashType::All,
|
::bitcoin::EcdsaSig {
|
||||||
});
|
sig: sig_a.into(),
|
||||||
satisfier.insert(B, ::bitcoin::EcdsaSig {
|
hash_ty: EcdsaSighashType::All,
|
||||||
sig: sig_b.into(),
|
},
|
||||||
hash_ty: EcdsaSighashType::All,
|
);
|
||||||
});
|
satisfier.insert(
|
||||||
|
B,
|
||||||
|
::bitcoin::EcdsaSig {
|
||||||
|
sig: sig_b.into(),
|
||||||
|
hash_ty: EcdsaSighashType::All,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
satisfier
|
satisfier
|
||||||
};
|
};
|
||||||
|
@ -65,14 +65,20 @@ impl TxPunish {
|
|||||||
let B = B.try_into()?;
|
let B = B.try_into()?;
|
||||||
|
|
||||||
// The order in which these are inserted doesn't matter
|
// The order in which these are inserted doesn't matter
|
||||||
satisfier.insert(A, ::bitcoin::EcdsaSig {
|
satisfier.insert(
|
||||||
sig: sig_a.into(),
|
A,
|
||||||
hash_ty: EcdsaSighashType::All,
|
::bitcoin::EcdsaSig {
|
||||||
});
|
sig: sig_a.into(),
|
||||||
satisfier.insert(B, ::bitcoin::EcdsaSig {
|
hash_ty: EcdsaSighashType::All,
|
||||||
sig: sig_b.into(),
|
},
|
||||||
hash_ty: EcdsaSighashType::All,
|
);
|
||||||
});
|
satisfier.insert(
|
||||||
|
B,
|
||||||
|
::bitcoin::EcdsaSig {
|
||||||
|
sig: sig_b.into(),
|
||||||
|
hash_ty: EcdsaSighashType::All,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
satisfier
|
satisfier
|
||||||
};
|
};
|
||||||
|
@ -87,14 +87,20 @@ impl TxRedeem {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// The order in which these are inserted doesn't matter
|
// The order in which these are inserted doesn't matter
|
||||||
satisfier.insert(A, ::bitcoin::EcdsaSig {
|
satisfier.insert(
|
||||||
sig: sig_a.into(),
|
A,
|
||||||
hash_ty: EcdsaSighashType::All,
|
::bitcoin::EcdsaSig {
|
||||||
});
|
sig: sig_a.into(),
|
||||||
satisfier.insert(B, ::bitcoin::EcdsaSig {
|
hash_ty: EcdsaSighashType::All,
|
||||||
sig: sig_b.into(),
|
},
|
||||||
hash_ty: EcdsaSighashType::All,
|
);
|
||||||
});
|
satisfier.insert(
|
||||||
|
B,
|
||||||
|
::bitcoin::EcdsaSig {
|
||||||
|
sig: sig_b.into(),
|
||||||
|
hash_ty: EcdsaSighashType::All,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
satisfier
|
satisfier
|
||||||
};
|
};
|
||||||
|
@ -70,14 +70,20 @@ impl TxRefund {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// The order in which these are inserted doesn't matter
|
// The order in which these are inserted doesn't matter
|
||||||
satisfier.insert(A, ::bitcoin::EcdsaSig {
|
satisfier.insert(
|
||||||
sig: sig_a.into(),
|
A,
|
||||||
hash_ty: EcdsaSighashType::All,
|
::bitcoin::EcdsaSig {
|
||||||
});
|
sig: sig_a.into(),
|
||||||
satisfier.insert(B, ::bitcoin::EcdsaSig {
|
hash_ty: EcdsaSighashType::All,
|
||||||
sig: sig_b.into(),
|
},
|
||||||
hash_ty: EcdsaSighashType::All,
|
);
|
||||||
});
|
satisfier.insert(
|
||||||
|
B,
|
||||||
|
::bitcoin::EcdsaSig {
|
||||||
|
sig: sig_b.into(),
|
||||||
|
hash_ty: EcdsaSighashType::All,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
satisfier
|
satisfier
|
||||||
};
|
};
|
||||||
|
@ -54,7 +54,7 @@ impl Wallet {
|
|||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let data_dir = data_dir.as_ref();
|
let data_dir = data_dir.as_ref();
|
||||||
let wallet_dir = data_dir.join(WALLET);
|
let wallet_dir = data_dir.join(WALLET);
|
||||||
let database = bdk::sled::open(&wallet_dir)?.open_tree(SLED_TREE_NAME)?;
|
let database = bdk::sled::open(wallet_dir)?.open_tree(SLED_TREE_NAME)?;
|
||||||
let network = env_config.bitcoin_network;
|
let network = env_config.bitcoin_network;
|
||||||
|
|
||||||
let wallet = match bdk::Wallet::new(
|
let wallet = match bdk::Wallet::new(
|
||||||
@ -97,7 +97,7 @@ impl Wallet {
|
|||||||
std::fs::rename(from, to)?;
|
std::fs::rename(from, to)?;
|
||||||
|
|
||||||
let wallet_dir = data_dir.join(WALLET);
|
let wallet_dir = data_dir.join(WALLET);
|
||||||
let database = bdk::sled::open(&wallet_dir)?.open_tree(SLED_TREE_NAME)?;
|
let database = bdk::sled::open(wallet_dir)?.open_tree(SLED_TREE_NAME)?;
|
||||||
|
|
||||||
let wallet = bdk::Wallet::new(
|
let wallet = bdk::Wallet::new(
|
||||||
bdk::template::Bip84(xprivkey, KeychainKind::External),
|
bdk::template::Bip84(xprivkey, KeychainKind::External),
|
||||||
@ -738,12 +738,15 @@ impl Client {
|
|||||||
let client = bdk::electrum_client::Client::new(electrum_rpc_url.as_str())
|
let client = bdk::electrum_client::Client::new(electrum_rpc_url.as_str())
|
||||||
.context("Failed to initialize Electrum RPC client")?;
|
.context("Failed to initialize Electrum RPC client")?;
|
||||||
let blockchain = ElectrumBlockchain::from(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 {
|
Ok(Self {
|
||||||
electrum,
|
electrum,
|
||||||
blockchain,
|
blockchain,
|
||||||
latest_block_height: BlockHeight::try_from(latest_block)?,
|
latest_block_height: BlockHeight::try_from(latest_block)?,
|
||||||
last_sync: Instant::now() - interval,
|
last_sync,
|
||||||
sync_interval: interval,
|
sync_interval: interval,
|
||||||
script_history: Default::default(),
|
script_history: Default::default(),
|
||||||
subscriptions: Default::default(),
|
subscriptions: Default::default(),
|
||||||
@ -758,9 +761,10 @@ impl Client {
|
|||||||
self.blockchain.get_tx(txid)
|
self.blockchain.get_tx(txid)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_state(&mut self) -> Result<()> {
|
fn update_state(&mut self, force_sync: bool) -> Result<()> {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
if now < self.last_sync + self.sync_interval {
|
|
||||||
|
if !force_sync && now < self.last_sync + self.sync_interval {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -780,9 +784,14 @@ impl Client {
|
|||||||
|
|
||||||
if !self.script_history.contains_key(&script) {
|
if !self.script_history.contains_key(&script) {
|
||||||
self.script_history.insert(script.clone(), vec![]);
|
self.script_history.insert(script.clone(), vec![]);
|
||||||
}
|
|
||||||
|
|
||||||
self.update_state()?;
|
// When we first subscribe to a script we want to immediately fetch its status
|
||||||
|
// Otherwise we would have to wait for the next sync interval, which can take a minute
|
||||||
|
// This would result in potentially inaccurate status updates until that next sync interval is hit
|
||||||
|
self.update_state(true)?;
|
||||||
|
} else {
|
||||||
|
self.update_state(false)?;
|
||||||
|
}
|
||||||
|
|
||||||
let history = self.script_history.entry(script).or_default();
|
let history = self.script_history.entry(script).or_default();
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ pub use list_sellers::{list_sellers, Seller, Status as SellerStatus};
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::asb;
|
use crate::asb;
|
||||||
|
use crate::asb::rendezvous::RendezvousNode;
|
||||||
use crate::cli::list_sellers::{Seller, Status};
|
use crate::cli::list_sellers::{Seller, Status};
|
||||||
use crate::network::quote;
|
use crate::network::quote;
|
||||||
use crate::network::quote::BidQuote;
|
use crate::network::quote::BidQuote;
|
||||||
@ -33,10 +34,8 @@ mod tests {
|
|||||||
async fn list_sellers_should_report_all_registered_asbs_with_a_quote() {
|
async fn list_sellers_should_report_all_registered_asbs_with_a_quote() {
|
||||||
let namespace = XmrBtcNamespace::Mainnet;
|
let namespace = XmrBtcNamespace::Mainnet;
|
||||||
let (rendezvous_address, rendezvous_peer_id) = setup_rendezvous_point().await;
|
let (rendezvous_address, rendezvous_peer_id) = setup_rendezvous_point().await;
|
||||||
let expected_seller_1 =
|
let expected_seller_1 = setup_asb(rendezvous_peer_id, &rendezvous_address, namespace).await;
|
||||||
setup_asb(rendezvous_peer_id, rendezvous_address.clone(), namespace).await;
|
let expected_seller_2 = setup_asb(rendezvous_peer_id, &rendezvous_address, namespace).await;
|
||||||
let expected_seller_2 =
|
|
||||||
setup_asb(rendezvous_peer_id, rendezvous_address.clone(), namespace).await;
|
|
||||||
|
|
||||||
let list_sellers = list_sellers(
|
let list_sellers = list_sellers(
|
||||||
rendezvous_peer_id,
|
rendezvous_peer_id,
|
||||||
@ -72,7 +71,7 @@ mod tests {
|
|||||||
|
|
||||||
async fn setup_asb(
|
async fn setup_asb(
|
||||||
rendezvous_peer_id: PeerId,
|
rendezvous_peer_id: PeerId,
|
||||||
rendezvous_address: Multiaddr,
|
rendezvous_address: &Multiaddr,
|
||||||
namespace: XmrBtcNamespace,
|
namespace: XmrBtcNamespace,
|
||||||
) -> Seller {
|
) -> Seller {
|
||||||
let static_quote = BidQuote {
|
let static_quote = BidQuote {
|
||||||
@ -81,18 +80,18 @@ mod tests {
|
|||||||
max_quantity: bitcoin::Amount::from_sat(9001),
|
max_quantity: bitcoin::Amount::from_sat(9001),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut asb = new_swarm(|_, identity| StaticQuoteAsbBehaviour {
|
let mut asb = new_swarm(|_, identity| {
|
||||||
rendezvous: asb::rendezous::Behaviour::new(
|
let rendezvous_node =
|
||||||
identity,
|
RendezvousNode::new(rendezvous_address, rendezvous_peer_id, namespace, None);
|
||||||
rendezvous_peer_id,
|
let rendezvous = asb::rendezvous::Behaviour::new(identity, vec![rendezvous_node]);
|
||||||
rendezvous_address,
|
|
||||||
namespace,
|
StaticQuoteAsbBehaviour {
|
||||||
None,
|
rendezvous,
|
||||||
),
|
ping: Default::default(),
|
||||||
ping: Default::default(),
|
quote: quote::asb(),
|
||||||
quote: quote::asb(),
|
static_quote,
|
||||||
static_quote,
|
registered: false,
|
||||||
registered: false,
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let asb_address = asb.listen_on_tcp_localhost().await;
|
let asb_address = asb.listen_on_tcp_localhost().await;
|
||||||
@ -121,7 +120,7 @@ mod tests {
|
|||||||
#[derive(libp2p::NetworkBehaviour)]
|
#[derive(libp2p::NetworkBehaviour)]
|
||||||
#[behaviour(event_process = true)]
|
#[behaviour(event_process = true)]
|
||||||
struct StaticQuoteAsbBehaviour {
|
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.
|
// Support `Ping` as a workaround until https://github.com/libp2p/rust-libp2p/issues/2109 is fixed.
|
||||||
ping: libp2p::ping::Ping,
|
ping: libp2p::ping::Ping,
|
||||||
quote: quote::Behaviour,
|
quote: quote::Behaviour,
|
||||||
|
@ -14,10 +14,6 @@ use structopt::{clap, StructOpt};
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
// See: https://moneroworld.com/
|
|
||||||
pub const DEFAULT_MONERO_DAEMON_ADDRESS: &str = "node.community.rino.io:18081";
|
|
||||||
pub const DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET: &str = "stagenet.community.rino.io:38081";
|
|
||||||
|
|
||||||
// See: https://1209k.com/bitcoin-eye/ele.php?chain=btc
|
// See: https://1209k.com/bitcoin-eye/ele.php?chain=btc
|
||||||
const DEFAULT_ELECTRUM_RPC_URL: &str = "ssl://blockstream.info:700";
|
const DEFAULT_ELECTRUM_RPC_URL: &str = "ssl://blockstream.info:700";
|
||||||
// See: https://1209k.com/bitcoin-eye/ele.php?chain=tbtc
|
// See: https://1209k.com/bitcoin-eye/ele.php?chain=tbtc
|
||||||
@ -80,11 +76,11 @@ where
|
|||||||
} => {
|
} => {
|
||||||
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
|
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
|
||||||
bitcoin.apply_defaults(is_testnet)?;
|
bitcoin.apply_defaults(is_testnet)?;
|
||||||
let monero_daemon_address = monero.apply_defaults(is_testnet);
|
|
||||||
let monero_receive_address =
|
let monero_receive_address =
|
||||||
validate_monero_address(monero_receive_address, is_testnet)?;
|
validate_monero_address(monero_receive_address, is_testnet)?;
|
||||||
let bitcoin_change_address =
|
let bitcoin_change_address =
|
||||||
validate_bitcoin_address(bitcoin_change_address, is_testnet)?;
|
validate_bitcoin_address(bitcoin_change_address, is_testnet)?;
|
||||||
|
let monero_daemon_address = monero.monero_daemon_address;
|
||||||
|
|
||||||
Arguments {
|
Arguments {
|
||||||
env_config: env_config_from(is_testnet),
|
env_config: env_config_from(is_testnet),
|
||||||
@ -167,7 +163,7 @@ where
|
|||||||
} => {
|
} => {
|
||||||
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
|
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
|
||||||
bitcoin.apply_defaults(is_testnet)?;
|
bitcoin.apply_defaults(is_testnet)?;
|
||||||
let monero_daemon_address = monero.apply_defaults(is_testnet);
|
let monero_daemon_address = monero.monero_daemon_address;
|
||||||
|
|
||||||
Arguments {
|
Arguments {
|
||||||
env_config: env_config_from(is_testnet),
|
env_config: env_config_from(is_testnet),
|
||||||
@ -254,7 +250,7 @@ pub enum Command {
|
|||||||
bitcoin_target_block: usize,
|
bitcoin_target_block: usize,
|
||||||
bitcoin_change_address: bitcoin::Address,
|
bitcoin_change_address: bitcoin::Address,
|
||||||
monero_receive_address: monero::Address,
|
monero_receive_address: monero::Address,
|
||||||
monero_daemon_address: String,
|
monero_daemon_address: Option<String>,
|
||||||
tor_socks5_port: u16,
|
tor_socks5_port: u16,
|
||||||
namespace: XmrBtcNamespace,
|
namespace: XmrBtcNamespace,
|
||||||
},
|
},
|
||||||
@ -274,7 +270,7 @@ pub enum Command {
|
|||||||
swap_id: Uuid,
|
swap_id: Uuid,
|
||||||
bitcoin_electrum_rpc_url: Url,
|
bitcoin_electrum_rpc_url: Url,
|
||||||
bitcoin_target_block: usize,
|
bitcoin_target_block: usize,
|
||||||
monero_daemon_address: String,
|
monero_daemon_address: Option<String>,
|
||||||
tor_socks5_port: u16,
|
tor_socks5_port: u16,
|
||||||
namespace: XmrBtcNamespace,
|
namespace: XmrBtcNamespace,
|
||||||
},
|
},
|
||||||
@ -302,7 +298,7 @@ pub enum Command {
|
|||||||
name = "swap",
|
name = "swap",
|
||||||
about = "CLI for swapping BTC for XMR",
|
about = "CLI for swapping BTC for XMR",
|
||||||
author,
|
author,
|
||||||
version = env!("VERGEN_GIT_SEMVER_LIGHTWEIGHT")
|
version = env!("VERGEN_GIT_DESCRIBE")
|
||||||
)]
|
)]
|
||||||
struct RawArguments {
|
struct RawArguments {
|
||||||
// global is necessary to ensure that clap can match against testnet in subcommands
|
// global is necessary to ensure that clap can match against testnet in subcommands
|
||||||
@ -436,23 +432,11 @@ enum RawCommand {
|
|||||||
struct Monero {
|
struct Monero {
|
||||||
#[structopt(
|
#[structopt(
|
||||||
long = "monero-daemon-address",
|
long = "monero-daemon-address",
|
||||||
help = "Specify to connect to a monero daemon of your choice: <host>:<port>"
|
help = "Specify to connect to a monero daemon of your choice: <host>:<port>. If none is specified, we will connect to a public node."
|
||||||
)]
|
)]
|
||||||
monero_daemon_address: Option<String>,
|
monero_daemon_address: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Monero {
|
|
||||||
fn apply_defaults(self, testnet: bool) -> String {
|
|
||||||
if let Some(address) = self.monero_daemon_address {
|
|
||||||
address
|
|
||||||
} else if testnet {
|
|
||||||
DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET.to_string()
|
|
||||||
} else {
|
|
||||||
DEFAULT_MONERO_DAEMON_ADDRESS.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(structopt::StructOpt, Debug)]
|
#[derive(structopt::StructOpt, Debug)]
|
||||||
struct Bitcoin {
|
struct Bitcoin {
|
||||||
#[structopt(long = "electrum-rpc", help = "Provide the Bitcoin Electrum RPC URL")]
|
#[structopt(long = "electrum-rpc", help = "Provide the Bitcoin Electrum RPC URL")]
|
||||||
@ -1174,7 +1158,7 @@ mod tests {
|
|||||||
bitcoin_change_address: BITCOIN_TESTNET_ADDRESS.parse().unwrap(),
|
bitcoin_change_address: BITCOIN_TESTNET_ADDRESS.parse().unwrap(),
|
||||||
monero_receive_address: monero::Address::from_str(MONERO_STAGENET_ADDRESS)
|
monero_receive_address: monero::Address::from_str(MONERO_STAGENET_ADDRESS)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET.to_string(),
|
monero_daemon_address: None,
|
||||||
tor_socks5_port: DEFAULT_SOCKS5_PORT,
|
tor_socks5_port: DEFAULT_SOCKS5_PORT,
|
||||||
namespace: XmrBtcNamespace::Testnet,
|
namespace: XmrBtcNamespace::Testnet,
|
||||||
},
|
},
|
||||||
@ -1194,7 +1178,7 @@ mod tests {
|
|||||||
bitcoin_change_address: BITCOIN_MAINNET_ADDRESS.parse().unwrap(),
|
bitcoin_change_address: BITCOIN_MAINNET_ADDRESS.parse().unwrap(),
|
||||||
monero_receive_address: monero::Address::from_str(MONERO_MAINNET_ADDRESS)
|
monero_receive_address: monero::Address::from_str(MONERO_MAINNET_ADDRESS)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS.to_string(),
|
monero_daemon_address: None,
|
||||||
tor_socks5_port: DEFAULT_SOCKS5_PORT,
|
tor_socks5_port: DEFAULT_SOCKS5_PORT,
|
||||||
namespace: XmrBtcNamespace::Mainnet,
|
namespace: XmrBtcNamespace::Mainnet,
|
||||||
},
|
},
|
||||||
@ -1212,7 +1196,7 @@ mod tests {
|
|||||||
bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL_TESTNET)
|
bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL_TESTNET)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET,
|
bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET,
|
||||||
monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET.to_string(),
|
monero_daemon_address: None,
|
||||||
tor_socks5_port: DEFAULT_SOCKS5_PORT,
|
tor_socks5_port: DEFAULT_SOCKS5_PORT,
|
||||||
namespace: XmrBtcNamespace::Testnet,
|
namespace: XmrBtcNamespace::Testnet,
|
||||||
},
|
},
|
||||||
@ -1229,7 +1213,7 @@ mod tests {
|
|||||||
swap_id: Uuid::from_str(SWAP_ID).unwrap(),
|
swap_id: Uuid::from_str(SWAP_ID).unwrap(),
|
||||||
bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(),
|
bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(),
|
||||||
bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET,
|
bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET,
|
||||||
monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS.to_string(),
|
monero_daemon_address: None,
|
||||||
tor_socks5_port: DEFAULT_SOCKS5_PORT,
|
tor_socks5_port: DEFAULT_SOCKS5_PORT,
|
||||||
namespace: XmrBtcNamespace::Mainnet,
|
namespace: XmrBtcNamespace::Mainnet,
|
||||||
},
|
},
|
||||||
|
@ -350,23 +350,26 @@ mod tests {
|
|||||||
|
|
||||||
list.sort();
|
list.sort();
|
||||||
|
|
||||||
assert_eq!(list, vec![
|
assert_eq!(
|
||||||
Seller {
|
list,
|
||||||
multiaddr: "/ip4/127.0.0.1/tcp/5678".parse().unwrap(),
|
vec![
|
||||||
status: Status::Online(BidQuote {
|
Seller {
|
||||||
price: Default::default(),
|
multiaddr: "/ip4/127.0.0.1/tcp/5678".parse().unwrap(),
|
||||||
min_quantity: Default::default(),
|
status: Status::Online(BidQuote {
|
||||||
max_quantity: Default::default(),
|
price: Default::default(),
|
||||||
})
|
min_quantity: Default::default(),
|
||||||
},
|
max_quantity: Default::default(),
|
||||||
Seller {
|
})
|
||||||
multiaddr: Multiaddr::empty(),
|
},
|
||||||
status: Status::Unreachable
|
Seller {
|
||||||
},
|
multiaddr: Multiaddr::empty(),
|
||||||
Seller {
|
status: Status::Unreachable
|
||||||
multiaddr: "/ip4/127.0.0.1/tcp/1234".parse().unwrap(),
|
},
|
||||||
status: Status::Unreachable
|
Seller {
|
||||||
},
|
multiaddr: "/ip4/127.0.0.1/tcp/1234".parse().unwrap(),
|
||||||
])
|
status: Status::Unreachable
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ pub struct Regtest;
|
|||||||
impl GetConfig for Mainnet {
|
impl GetConfig for Mainnet {
|
||||||
fn get_config() -> Config {
|
fn get_config() -> Config {
|
||||||
Config {
|
Config {
|
||||||
bitcoin_lock_mempool_timeout: 3.std_minutes(),
|
bitcoin_lock_mempool_timeout: 10.std_minutes(),
|
||||||
bitcoin_lock_confirmed_timeout: 2.std_hours(),
|
bitcoin_lock_confirmed_timeout: 2.std_hours(),
|
||||||
bitcoin_finality_confirmations: 1,
|
bitcoin_finality_confirmations: 1,
|
||||||
bitcoin_avg_block_time: 10.std_minutes(),
|
bitcoin_avg_block_time: 10.std_minutes(),
|
||||||
@ -63,7 +63,7 @@ impl GetConfig for Mainnet {
|
|||||||
impl GetConfig for Testnet {
|
impl GetConfig for Testnet {
|
||||||
fn get_config() -> Config {
|
fn get_config() -> Config {
|
||||||
Config {
|
Config {
|
||||||
bitcoin_lock_mempool_timeout: 3.std_minutes(),
|
bitcoin_lock_mempool_timeout: 10.std_minutes(),
|
||||||
bitcoin_lock_confirmed_timeout: 1.std_hours(),
|
bitcoin_lock_confirmed_timeout: 1.std_hours(),
|
||||||
bitcoin_finality_confirmations: 1,
|
bitcoin_finality_confirmations: 1,
|
||||||
bitcoin_avg_block_time: 10.std_minutes(),
|
bitcoin_avg_block_time: 10.std_minutes(),
|
||||||
|
@ -174,11 +174,6 @@ impl Wallet {
|
|||||||
pub async fn transfer(&self, request: TransferRequest) -> Result<TransferProof> {
|
pub async fn transfer(&self, request: TransferRequest) -> Result<TransferProof> {
|
||||||
let inner = self.inner.lock().await;
|
let inner = self.inner.lock().await;
|
||||||
|
|
||||||
inner
|
|
||||||
.open_wallet(self.name.clone())
|
|
||||||
.await
|
|
||||||
.with_context(|| format!("Failed to open wallet {}", self.name))?;
|
|
||||||
|
|
||||||
let TransferRequest {
|
let TransferRequest {
|
||||||
public_spend_key,
|
public_spend_key,
|
||||||
public_view_key,
|
public_view_key,
|
||||||
|
@ -1,19 +1,45 @@
|
|||||||
use ::monero::Network;
|
use ::monero::Network;
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{bail, Context, Error, Result};
|
||||||
use big_bytes::BigByte;
|
use big_bytes::BigByte;
|
||||||
use futures::{StreamExt, TryStreamExt};
|
use futures::{StreamExt, TryStreamExt};
|
||||||
use monero_rpc::wallet::{Client, MoneroWalletRpc as _};
|
use monero_rpc::wallet::{Client, MoneroWalletRpc as _};
|
||||||
use reqwest::header::CONTENT_LENGTH;
|
use reqwest::header::CONTENT_LENGTH;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::fmt;
|
||||||
|
use std::fmt::{Debug, Display, Formatter};
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
|
use std::time::Duration;
|
||||||
use tokio::fs::{remove_file, OpenOptions};
|
use tokio::fs::{remove_file, OpenOptions};
|
||||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||||
use tokio::process::{Child, Command};
|
use tokio::process::{Child, Command};
|
||||||
use tokio_util::codec::{BytesCodec, FramedRead};
|
use tokio_util::codec::{BytesCodec, FramedRead};
|
||||||
use tokio_util::io::StreamReader;
|
use tokio_util::io::StreamReader;
|
||||||
|
|
||||||
|
// See: https://www.moneroworld.com/#nodes, https://monero.fail
|
||||||
|
// We don't need any testnet nodes because we don't support testnet at all
|
||||||
|
const MONERO_DAEMONS: [MoneroDaemon; 17] = [
|
||||||
|
MoneroDaemon::new("xmr-node.cakewallet.com", 18081, Network::Mainnet),
|
||||||
|
MoneroDaemon::new("nodex.monerujo.io", 18081, Network::Mainnet),
|
||||||
|
MoneroDaemon::new("node.moneroworld.com", 18089, Network::Mainnet),
|
||||||
|
MoneroDaemon::new("nodes.hashvault.pro", 18081, Network::Mainnet),
|
||||||
|
MoneroDaemon::new("p2pmd.xmrvsbeast.com", 18081, Network::Mainnet),
|
||||||
|
MoneroDaemon::new("node.monerodevs.org", 18089, Network::Mainnet),
|
||||||
|
MoneroDaemon::new("xmr-node-usa-east.cakewallet.com", 18081, Network::Mainnet),
|
||||||
|
MoneroDaemon::new("xmr-node-uk.cakewallet.com", 18081, Network::Mainnet),
|
||||||
|
MoneroDaemon::new("node.community.rino.io", 18081, Network::Mainnet),
|
||||||
|
MoneroDaemon::new("testingjohnross.com", 20031, Network::Mainnet),
|
||||||
|
MoneroDaemon::new("xmr.litepay.ch", 18081, Network::Mainnet),
|
||||||
|
MoneroDaemon::new("node.trocador.app", 18089, Network::Mainnet),
|
||||||
|
MoneroDaemon::new("stagenet.xmr-tw.org", 38081, Network::Stagenet),
|
||||||
|
MoneroDaemon::new("node.monerodevs.org", 38089, Network::Stagenet),
|
||||||
|
MoneroDaemon::new("singapore.node.xmr.pm", 38081, Network::Stagenet),
|
||||||
|
MoneroDaemon::new("xmr-lux.boldsuck.org", 38081, Network::Stagenet),
|
||||||
|
MoneroDaemon::new("stagenet.community.rino.io", 38081, Network::Stagenet),
|
||||||
|
];
|
||||||
|
|
||||||
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
|
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
|
||||||
compile_error!("unsupported operating system");
|
compile_error!("unsupported operating system");
|
||||||
|
|
||||||
@ -50,6 +76,91 @@ pub struct WalletRpcProcess {
|
|||||||
port: u16,
|
port: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct MoneroDaemon {
|
||||||
|
address: &'static str,
|
||||||
|
port: u16,
|
||||||
|
network: Network,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MoneroDaemon {
|
||||||
|
const fn new(address: &'static str, port: u16, network: Network) -> Self {
|
||||||
|
Self {
|
||||||
|
address,
|
||||||
|
port,
|
||||||
|
network,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the Monero daemon is available by sending a request to its `get_info` endpoint.
|
||||||
|
async fn is_available(&self, client: &reqwest::Client) -> Result<bool, Error> {
|
||||||
|
let url = format!("http://{}:{}/get_info", self.address, self.port);
|
||||||
|
let res = client
|
||||||
|
.get(url)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.context("Failed to send request to get_info endpoint")?;
|
||||||
|
|
||||||
|
let json: MoneroDaemonGetInfoResponse = res
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.context("Failed to deserialize daemon get_info response")?;
|
||||||
|
|
||||||
|
let is_status_ok = json.status == "OK";
|
||||||
|
let is_synchronized = json.synchronized;
|
||||||
|
let is_correct_network = match self.network {
|
||||||
|
Network::Mainnet => json.mainnet,
|
||||||
|
Network::Stagenet => json.stagenet,
|
||||||
|
Network::Testnet => json.testnet,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(is_status_ok && is_synchronized && is_correct_network)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for MoneroDaemon {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}:{}", self.address, self.port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct MoneroDaemonGetInfoResponse {
|
||||||
|
status: String,
|
||||||
|
synchronized: bool,
|
||||||
|
mainnet: bool,
|
||||||
|
stagenet: bool,
|
||||||
|
testnet: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Chooses an available Monero daemon based on the specified network.
|
||||||
|
async fn choose_monero_daemon(network: Network) -> Result<&'static MoneroDaemon, Error> {
|
||||||
|
let client = reqwest::Client::builder()
|
||||||
|
.timeout(Duration::from_secs(30))
|
||||||
|
.https_only(false)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
// We only want to check for daemons that match the specified network
|
||||||
|
let network_matching_daemons = MONERO_DAEMONS
|
||||||
|
.iter()
|
||||||
|
.filter(|daemon| daemon.network == network);
|
||||||
|
|
||||||
|
for daemon in network_matching_daemons {
|
||||||
|
match daemon.is_available(&client).await {
|
||||||
|
Ok(true) => {
|
||||||
|
tracing::debug!(%daemon, "Found available Monero daemon");
|
||||||
|
return Ok(daemon);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
tracing::debug!(%err, %daemon, "Failed to connect to Monero daemon");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Ok(false) => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bail!("No Monero daemon could be found. Please specify one manually or try again later.")
|
||||||
|
}
|
||||||
|
|
||||||
impl WalletRpcProcess {
|
impl WalletRpcProcess {
|
||||||
pub fn endpoint(&self) -> Url {
|
pub fn endpoint(&self) -> Url {
|
||||||
Url::parse(&format!("http://127.0.0.1:{}/json_rpc", self.port))
|
Url::parse(&format!("http://127.0.0.1:{}/json_rpc", self.port))
|
||||||
@ -153,13 +264,23 @@ impl WalletRpc {
|
|||||||
Ok(monero_wallet_rpc)
|
Ok(monero_wallet_rpc)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(&self, network: Network, daemon_address: &str) -> Result<WalletRpcProcess> {
|
pub async fn run(
|
||||||
|
&self,
|
||||||
|
network: Network,
|
||||||
|
daemon_address: Option<String>,
|
||||||
|
) -> Result<WalletRpcProcess> {
|
||||||
let port = tokio::net::TcpListener::bind("127.0.0.1:0")
|
let port = tokio::net::TcpListener::bind("127.0.0.1:0")
|
||||||
.await?
|
.await?
|
||||||
.local_addr()?
|
.local_addr()?
|
||||||
.port();
|
.port();
|
||||||
|
|
||||||
|
let daemon_address = match daemon_address {
|
||||||
|
Some(daemon_address) => daemon_address,
|
||||||
|
None => choose_monero_daemon(network).await?.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
|
%daemon_address,
|
||||||
%port,
|
%port,
|
||||||
"Starting monero-wallet-rpc"
|
"Starting monero-wallet-rpc"
|
||||||
);
|
);
|
||||||
@ -232,7 +353,6 @@ impl WalletRpc {
|
|||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
async fn extract_archive(monero_wallet_rpc: &Self) -> Result<()> {
|
async fn extract_archive(monero_wallet_rpc: &Self) -> Result<()> {
|
||||||
use anyhow::bail;
|
|
||||||
use tokio_tar::Archive;
|
use tokio_tar::Archive;
|
||||||
|
|
||||||
let mut options = OpenOptions::new();
|
let mut options = OpenOptions::new();
|
||||||
@ -297,3 +417,123 @@ impl WalletRpc {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn extract_host_and_port(address: String) -> (&'static str, u16) {
|
||||||
|
let parts: Vec<&str> = address.split(':').collect();
|
||||||
|
|
||||||
|
if parts.len() == 2 {
|
||||||
|
let host = parts[0].to_string();
|
||||||
|
let port = parts[1].parse::<u16>().unwrap();
|
||||||
|
let static_str_host: &'static str = Box::leak(host.into_boxed_str());
|
||||||
|
return (static_str_host, port);
|
||||||
|
}
|
||||||
|
panic!("Could not extract host and port from address: {}", address)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_is_daemon_available_success() {
|
||||||
|
let mut server = mockito::Server::new();
|
||||||
|
|
||||||
|
let _ = server
|
||||||
|
.mock("GET", "/get_info")
|
||||||
|
.with_status(200)
|
||||||
|
.with_body(
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
"status": "OK",
|
||||||
|
"synchronized": true,
|
||||||
|
"mainnet": true,
|
||||||
|
"stagenet": false,
|
||||||
|
"testnet": false
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.create();
|
||||||
|
|
||||||
|
let (host, port) = extract_host_and_port(server.host_with_port());
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let result = MoneroDaemon::new(host, port, Network::Mainnet)
|
||||||
|
.is_available(&client)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert!(result.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_is_daemon_available_wrong_network_failure() {
|
||||||
|
let mut server = mockito::Server::new();
|
||||||
|
|
||||||
|
let _ = server
|
||||||
|
.mock("GET", "/get_info")
|
||||||
|
.with_status(200)
|
||||||
|
.with_body(
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
"status": "OK",
|
||||||
|
"synchronized": true,
|
||||||
|
"mainnet": true,
|
||||||
|
"stagenet": false,
|
||||||
|
"testnet": false
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.create();
|
||||||
|
|
||||||
|
let (host, port) = extract_host_and_port(server.host_with_port());
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let result = MoneroDaemon::new(host, port, Network::Stagenet)
|
||||||
|
.is_available(&client)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert!(!result.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_is_daemon_available_not_synced_failure() {
|
||||||
|
let mut server = mockito::Server::new();
|
||||||
|
|
||||||
|
let _ = server
|
||||||
|
.mock("GET", "/get_info")
|
||||||
|
.with_status(200)
|
||||||
|
.with_body(
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
"status": "OK",
|
||||||
|
"synchronized": false,
|
||||||
|
"mainnet": true,
|
||||||
|
"stagenet": false,
|
||||||
|
"testnet": false
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.create();
|
||||||
|
|
||||||
|
let (host, port) = extract_host_and_port(server.host_with_port());
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let result = MoneroDaemon::new(host, port, Network::Mainnet)
|
||||||
|
.is_available(&client)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert!(!result.unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_is_daemon_available_network_error_failure() {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let result = MoneroDaemon::new("does.not.exist.com", 18081, Network::Mainnet)
|
||||||
|
.is_available(&client)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -155,13 +155,16 @@ impl ProtocolsHandler for Handler {
|
|||||||
let env_config = self.env_config;
|
let env_config = self.env_config;
|
||||||
|
|
||||||
let protocol = tokio::time::timeout(self.timeout, async move {
|
let protocol = tokio::time::timeout(self.timeout, async move {
|
||||||
write_cbor_message(&mut substream, SpotPriceRequest {
|
write_cbor_message(
|
||||||
btc: info.btc,
|
&mut substream,
|
||||||
blockchain_network: BlockchainNetwork {
|
SpotPriceRequest {
|
||||||
bitcoin: env_config.bitcoin_network,
|
btc: info.btc,
|
||||||
monero: env_config.monero_network,
|
blockchain_network: BlockchainNetwork {
|
||||||
|
bitcoin: env_config.bitcoin_network,
|
||||||
|
monero: env_config.monero_network,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let xmr = Result::from(read_cbor_message::<SpotPriceResponse>(&mut substream).await?)?;
|
let xmr = Result::from(read_cbor_message::<SpotPriceResponse>(&mut substream).await?)?;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use crate::asb::LatestRate;
|
use crate::asb::{LatestRate, RendezvousNode};
|
||||||
use crate::libp2p_ext::MultiAddrExt;
|
use crate::libp2p_ext::MultiAddrExt;
|
||||||
use crate::network::rendezvous::XmrBtcNamespace;
|
use crate::network::rendezvous::XmrBtcNamespace;
|
||||||
use crate::seed::Seed;
|
use crate::seed::Seed;
|
||||||
use crate::{asb, bitcoin, cli, env, tor};
|
use crate::{asb, bitcoin, cli, env, tor};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::Result;
|
||||||
use libp2p::swarm::{NetworkBehaviour, SwarmBuilder};
|
use libp2p::swarm::{NetworkBehaviour, SwarmBuilder};
|
||||||
use libp2p::{identity, Multiaddr, Swarm};
|
use libp2p::{identity, Multiaddr, Swarm};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
@ -17,22 +17,23 @@ pub fn asb<LR>(
|
|||||||
resume_only: bool,
|
resume_only: bool,
|
||||||
env_config: env::Config,
|
env_config: env::Config,
|
||||||
namespace: XmrBtcNamespace,
|
namespace: XmrBtcNamespace,
|
||||||
rendezvous_point: Option<Multiaddr>,
|
rendezvous_addrs: &[Multiaddr],
|
||||||
) -> Result<Swarm<asb::Behaviour<LR>>>
|
) -> Result<Swarm<asb::Behaviour<LR>>>
|
||||||
where
|
where
|
||||||
LR: LatestRate + Send + 'static + Debug + Clone,
|
LR: LatestRate + Send + 'static + Debug + Clone,
|
||||||
{
|
{
|
||||||
let identity = seed.derive_libp2p_identity();
|
let identity = seed.derive_libp2p_identity();
|
||||||
|
|
||||||
let rendezvous_params = if let Some(address) = rendezvous_point {
|
let rendezvous_nodes = rendezvous_addrs
|
||||||
let peer_id = address
|
.iter()
|
||||||
.extract_peer_id()
|
.map(|addr| {
|
||||||
.context("Rendezvous node address must contain peer ID")?;
|
let peer_id = addr
|
||||||
|
.extract_peer_id()
|
||||||
|
.expect("Rendezvous node address must contain peer ID");
|
||||||
|
|
||||||
Some((identity.clone(), peer_id, address, namespace))
|
RendezvousNode::new(addr, peer_id, namespace, None)
|
||||||
} else {
|
})
|
||||||
None
|
.collect();
|
||||||
};
|
|
||||||
|
|
||||||
let behaviour = asb::Behaviour::new(
|
let behaviour = asb::Behaviour::new(
|
||||||
min_buy,
|
min_buy,
|
||||||
@ -41,7 +42,7 @@ where
|
|||||||
resume_only,
|
resume_only,
|
||||||
env_config,
|
env_config,
|
||||||
(identity.clone(), namespace),
|
(identity.clone(), namespace),
|
||||||
rendezvous_params,
|
rendezvous_nodes,
|
||||||
);
|
);
|
||||||
|
|
||||||
let transport = asb::transport::new(&identity)?;
|
let transport = asb::transport::new(&identity)?;
|
||||||
|
@ -21,7 +21,7 @@ struct GlobalSpawnTokioExecutor;
|
|||||||
|
|
||||||
impl Executor for GlobalSpawnTokioExecutor {
|
impl Executor for GlobalSpawnTokioExecutor {
|
||||||
fn exec(&self, future: Pin<Box<dyn Future<Output = ()> + Send>>) {
|
fn exec(&self, future: Pin<Box<dyn Future<Output = ()> + Send>>) {
|
||||||
let _ = tokio::spawn(future);
|
tokio::spawn(future);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,29 +184,32 @@ impl State0 {
|
|||||||
|
|
||||||
let v = self.v_a + msg.v_b;
|
let v = self.v_a + msg.v_b;
|
||||||
|
|
||||||
Ok((msg.swap_id, State1 {
|
Ok((
|
||||||
a: self.a,
|
msg.swap_id,
|
||||||
B: msg.B,
|
State1 {
|
||||||
s_a: self.s_a,
|
a: self.a,
|
||||||
S_a_monero: self.S_a_monero,
|
B: msg.B,
|
||||||
S_a_bitcoin: self.S_a_bitcoin,
|
s_a: self.s_a,
|
||||||
S_b_monero: msg.S_b_monero,
|
S_a_monero: self.S_a_monero,
|
||||||
S_b_bitcoin: msg.S_b_bitcoin,
|
S_a_bitcoin: self.S_a_bitcoin,
|
||||||
v,
|
S_b_monero: msg.S_b_monero,
|
||||||
v_a: self.v_a,
|
S_b_bitcoin: msg.S_b_bitcoin,
|
||||||
dleq_proof_s_a: self.dleq_proof_s_a,
|
v,
|
||||||
btc: self.btc,
|
v_a: self.v_a,
|
||||||
xmr: self.xmr,
|
dleq_proof_s_a: self.dleq_proof_s_a,
|
||||||
cancel_timelock: self.cancel_timelock,
|
btc: self.btc,
|
||||||
punish_timelock: self.punish_timelock,
|
xmr: self.xmr,
|
||||||
refund_address: msg.refund_address,
|
cancel_timelock: self.cancel_timelock,
|
||||||
redeem_address: self.redeem_address,
|
punish_timelock: self.punish_timelock,
|
||||||
punish_address: self.punish_address,
|
refund_address: msg.refund_address,
|
||||||
tx_redeem_fee: self.tx_redeem_fee,
|
redeem_address: self.redeem_address,
|
||||||
tx_punish_fee: self.tx_punish_fee,
|
punish_address: self.punish_address,
|
||||||
tx_refund_fee: msg.tx_refund_fee,
|
tx_redeem_fee: self.tx_redeem_fee,
|
||||||
tx_cancel_fee: msg.tx_cancel_fee,
|
tx_punish_fee: self.tx_punish_fee,
|
||||||
}))
|
tx_refund_fee: msg.tx_refund_fee,
|
||||||
|
tx_cancel_fee: msg.tx_cancel_fee,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ impl Seed {
|
|||||||
let file_path = Path::new(&file_path_buf);
|
let file_path = Path::new(&file_path_buf);
|
||||||
|
|
||||||
if file_path.exists() {
|
if file_path.exists() {
|
||||||
return Self::from_file(&file_path);
|
return Self::from_file(file_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::debug!("No seed file found, creating at {}", file_path.display());
|
tracing::debug!("No seed file found, creating at {}", file_path.display());
|
||||||
@ -106,11 +106,12 @@ impl Seed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn from_pem(pem: pem::Pem) -> Result<Self, Error> {
|
fn from_pem(pem: pem::Pem) -> Result<Self, Error> {
|
||||||
if pem.contents.len() != SEED_LENGTH {
|
let contents = pem.contents();
|
||||||
Err(Error::IncorrectLength(pem.contents.len()))
|
if contents.len() != SEED_LENGTH {
|
||||||
|
Err(Error::IncorrectLength(contents.len()))
|
||||||
} else {
|
} else {
|
||||||
let mut array = [0; SEED_LENGTH];
|
let mut array = [0; SEED_LENGTH];
|
||||||
for (i, b) in pem.contents.iter().enumerate() {
|
for (i, b) in contents.iter().enumerate() {
|
||||||
array[i] = *b;
|
array[i] = *b;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,10 +123,7 @@ impl Seed {
|
|||||||
ensure_directory_exists(&seed_file)?;
|
ensure_directory_exists(&seed_file)?;
|
||||||
|
|
||||||
let data = self.bytes();
|
let data = self.bytes();
|
||||||
let pem = Pem {
|
let pem = Pem::new("SEED", data);
|
||||||
tag: String::from("SEED"),
|
|
||||||
contents: data.to_vec(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let pem_string = encode(&pem);
|
let pem_string = encode(&pem);
|
||||||
|
|
||||||
@ -187,6 +185,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn seed_from_pem_works() {
|
fn seed_from_pem_works() {
|
||||||
|
use base64::engine::general_purpose;
|
||||||
|
use base64::Engine;
|
||||||
|
|
||||||
let payload: &str = "syl9wSYaruvgxg9P5Q1qkZaq5YkM6GvXkxe+VYrL/XM=";
|
let payload: &str = "syl9wSYaruvgxg9P5Q1qkZaq5YkM6GvXkxe+VYrL/XM=";
|
||||||
|
|
||||||
// 32 bytes base64 encoded.
|
// 32 bytes base64 encoded.
|
||||||
@ -195,7 +196,7 @@ syl9wSYaruvgxg9P5Q1qkZaq5YkM6GvXkxe+VYrL/XM=
|
|||||||
-----END SEED-----
|
-----END SEED-----
|
||||||
";
|
";
|
||||||
|
|
||||||
let want = base64::decode(payload).unwrap();
|
let want = general_purpose::STANDARD.decode(payload).unwrap();
|
||||||
let pem = pem::parse(pem_string).unwrap();
|
let pem = pem::parse(pem_string).unwrap();
|
||||||
let got = Seed::from_pem(pem).unwrap();
|
let got = Seed::from_pem(pem).unwrap();
|
||||||
|
|
||||||
@ -221,19 +222,20 @@ VnZUNFZ4dlY=
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
|
||||||
fn seed_from_pem_fails_for_long_seed() {
|
fn seed_from_pem_fails_for_long_seed() {
|
||||||
let long = "-----BEGIN SEED-----
|
let long = "-----BEGIN SEED-----
|
||||||
mbKANv2qKGmNVg1qtquj6Hx1pFPelpqOfE2JaJJAMEg1FlFhNRNlFlE=
|
MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc
|
||||||
mbKANv2qKGmNVg1qtquj6Hx1pFPelpqOfE2JaJJAMEg1FlFhNRNlFlE=
|
dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO
|
||||||
-----END SEED-----
|
-----END SEED-----
|
||||||
";
|
";
|
||||||
let pem = pem::parse(long).unwrap();
|
let pem = pem::parse(long).unwrap();
|
||||||
|
assert_eq!(pem.contents().len(), 96);
|
||||||
|
|
||||||
match Seed::from_pem(pem) {
|
match Seed::from_pem(pem) {
|
||||||
Ok(_) => panic!("should fail for long payload"),
|
Ok(_) => panic!("should fail for long payload"),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
match e {
|
match e {
|
||||||
Error::IncorrectLength(_) => {} // pass
|
Error::IncorrectLength(len) => assert_eq!(len, 96), // pass
|
||||||
_ => panic!("should fail with IncorrectLength error"),
|
_ => panic!("should fail with IncorrectLength error"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ async fn ensure_same_swap_id_for_alice_and_bob() {
|
|||||||
harness::setup_test(SlowCancelConfig, |mut ctx| async move {
|
harness::setup_test(SlowCancelConfig, |mut ctx| async move {
|
||||||
let (bob_swap, _) = ctx.bob_swap().await;
|
let (bob_swap, _) = ctx.bob_swap().await;
|
||||||
let bob_swap_id = bob_swap.id;
|
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
|
// once Bob's swap is spawned we can retrieve Alice's swap and assert on the
|
||||||
// swap ID
|
// swap ID
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::BTreeMap;
|
||||||
use testcontainers::core::{Container, Docker, WaitForMessage};
|
use testcontainers::{core::WaitFor, Image, ImageArgs};
|
||||||
use testcontainers::Image;
|
|
||||||
|
|
||||||
pub const RPC_USER: &str = "admin";
|
pub const RPC_USER: &str = "admin";
|
||||||
pub const RPC_PASSWORD: &str = "123";
|
pub const RPC_PASSWORD: &str = "123";
|
||||||
@ -10,57 +9,27 @@ pub const DATADIR: &str = "/home/bdk";
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Bitcoind {
|
pub struct Bitcoind {
|
||||||
args: BitcoindArgs,
|
|
||||||
entrypoint: Option<String>,
|
entrypoint: Option<String>,
|
||||||
volume: Option<String>,
|
volumes: BTreeMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Image for Bitcoind {
|
impl Image for Bitcoind {
|
||||||
type Args = BitcoindArgs;
|
type Args = BitcoindArgs;
|
||||||
type EnvVars = HashMap<String, String>;
|
|
||||||
type Volumes = HashMap<String, String>;
|
|
||||||
type EntryPoint = str;
|
|
||||||
|
|
||||||
fn descriptor(&self) -> String {
|
fn name(&self) -> String {
|
||||||
"coblox/bitcoin-core:0.21.0".to_string()
|
"coblox/bitcoin-core".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait_until_ready<D: Docker>(&self, container: &Container<'_, D, Self>) {
|
fn tag(&self) -> String {
|
||||||
container
|
"0.21.0".into()
|
||||||
.logs()
|
|
||||||
.stdout
|
|
||||||
.wait_for_message("init message: Done loading")
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn args(&self) -> <Self as Image>::Args {
|
fn ready_conditions(&self) -> Vec<WaitFor> {
|
||||||
self.args.clone()
|
vec![WaitFor::message_on_stdout("init message: Done loading")]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn volumes(&self) -> Self::Volumes {
|
fn volumes(&self) -> Box<dyn Iterator<Item = (&String, &String)> + '_> {
|
||||||
let mut volumes = HashMap::new();
|
Box::new(self.volumes.iter())
|
||||||
match self.volume.clone() {
|
|
||||||
None => {}
|
|
||||||
Some(volume) => {
|
|
||||||
volumes.insert(volume, DATADIR.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
volumes
|
|
||||||
}
|
|
||||||
|
|
||||||
fn env_vars(&self) -> Self::EnvVars {
|
|
||||||
HashMap::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_args(self, args: <Self as Image>::Args) -> Self {
|
|
||||||
Bitcoind { args, ..self }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_entrypoint(self, entrypoint: &Self::EntryPoint) -> Self {
|
|
||||||
Self {
|
|
||||||
entrypoint: Some(entrypoint.to_string()),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entrypoint(&self) -> Option<String> {
|
fn entrypoint(&self) -> Option<String> {
|
||||||
@ -71,16 +40,15 @@ impl Image for Bitcoind {
|
|||||||
impl Default for Bitcoind {
|
impl Default for Bitcoind {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Bitcoind {
|
Bitcoind {
|
||||||
args: BitcoindArgs::default(),
|
|
||||||
entrypoint: Some("/usr/bin/bitcoind".into()),
|
entrypoint: Some("/usr/bin/bitcoind".into()),
|
||||||
volume: None,
|
volumes: BTreeMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bitcoind {
|
impl Bitcoind {
|
||||||
pub fn with_volume(mut self, volume: String) -> Self {
|
pub fn with_volume(mut self, volume: String) -> Self {
|
||||||
self.volume = Some(volume);
|
self.volumes.insert(volume, DATADIR.to_string());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,7 +77,6 @@ impl IntoIterator for BitcoindArgs {
|
|||||||
format!("-rpcuser={}", RPC_USER),
|
format!("-rpcuser={}", RPC_USER),
|
||||||
format!("-rpcpassword={}", RPC_PASSWORD),
|
format!("-rpcpassword={}", RPC_PASSWORD),
|
||||||
"-printtoconsole".to_string(),
|
"-printtoconsole".to_string(),
|
||||||
"-rest".to_string(),
|
|
||||||
"-fallbackfee=0.0002".to_string(),
|
"-fallbackfee=0.0002".to_string(),
|
||||||
format!("-datadir={}", DATADIR),
|
format!("-datadir={}", DATADIR),
|
||||||
format!("-rpcport={}", RPC_PORT),
|
format!("-rpcport={}", RPC_PORT),
|
||||||
@ -120,3 +87,9 @@ impl IntoIterator for BitcoindArgs {
|
|||||||
args.into_iter()
|
args.into_iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ImageArgs for BitcoindArgs {
|
||||||
|
fn into_iterator(self) -> Box<dyn Iterator<Item = String>> {
|
||||||
|
Box::new(self.into_iter())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use crate::harness::bitcoind;
|
use crate::harness::bitcoind;
|
||||||
use bitcoin::Network;
|
use bitcoin::Network;
|
||||||
use std::collections::HashMap;
|
use testcontainers::{core::WaitFor, Image, ImageArgs};
|
||||||
use testcontainers::core::{Container, Docker, WaitForMessage};
|
|
||||||
use testcontainers::Image;
|
|
||||||
|
|
||||||
pub const HTTP_PORT: u16 = 60401;
|
pub const HTTP_PORT: u16 = 60401;
|
||||||
pub const RPC_PORT: u16 = 3002;
|
pub const RPC_PORT: u16 = 3002;
|
||||||
@ -13,50 +13,25 @@ pub struct Electrs {
|
|||||||
args: ElectrsArgs,
|
args: ElectrsArgs,
|
||||||
entrypoint: Option<String>,
|
entrypoint: Option<String>,
|
||||||
wait_for_message: String,
|
wait_for_message: String,
|
||||||
volume: String,
|
volumes: BTreeMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Image for Electrs {
|
impl Image for Electrs {
|
||||||
type Args = ElectrsArgs;
|
type Args = ElectrsArgs;
|
||||||
type EnvVars = HashMap<String, String>;
|
fn name(&self) -> String {
|
||||||
type Volumes = HashMap<String, String>;
|
"vulpemventures/electrs".into()
|
||||||
type EntryPoint = str;
|
|
||||||
|
|
||||||
fn descriptor(&self) -> String {
|
|
||||||
format!("vulpemventures/electrs:{}", self.tag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait_until_ready<D: Docker>(&self, container: &Container<'_, D, Self>) {
|
fn tag(&self) -> String {
|
||||||
container
|
self.tag.clone()
|
||||||
.logs()
|
|
||||||
.stderr
|
|
||||||
.wait_for_message(&self.wait_for_message)
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn args(&self) -> <Self as Image>::Args {
|
fn ready_conditions(&self) -> Vec<WaitFor> {
|
||||||
self.args.clone()
|
vec![WaitFor::message_on_stderr(self.wait_for_message.clone())]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn volumes(&self) -> Self::Volumes {
|
fn volumes(&self) -> Box<dyn Iterator<Item = (&String, &String)> + '_> {
|
||||||
let mut volumes = HashMap::new();
|
Box::new(self.volumes.iter())
|
||||||
volumes.insert(self.volume.clone(), bitcoind::DATADIR.to_string());
|
|
||||||
volumes
|
|
||||||
}
|
|
||||||
|
|
||||||
fn env_vars(&self) -> Self::EnvVars {
|
|
||||||
HashMap::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_args(self, args: <Self as Image>::Args) -> Self {
|
|
||||||
Electrs { args, ..self }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_entrypoint(self, entrypoint: &Self::EntryPoint) -> Self {
|
|
||||||
Self {
|
|
||||||
entrypoint: Some(entrypoint.to_string()),
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entrypoint(&self) -> Option<String> {
|
fn entrypoint(&self) -> Option<String> {
|
||||||
@ -71,7 +46,7 @@ impl Default for Electrs {
|
|||||||
args: ElectrsArgs::default(),
|
args: ElectrsArgs::default(),
|
||||||
entrypoint: Some("/build/electrs".into()),
|
entrypoint: Some("/build/electrs".into()),
|
||||||
wait_for_message: "Running accept thread".to_string(),
|
wait_for_message: "Running accept thread".to_string(),
|
||||||
volume: uuid::Uuid::new_v4().to_string(),
|
volumes: BTreeMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -85,7 +60,7 @@ impl Electrs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_volume(mut self, volume: String) -> Self {
|
pub fn with_volume(mut self, volume: String) -> Self {
|
||||||
self.volume = volume;
|
self.volumes.insert(volume, bitcoind::DATADIR.to_string());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,6 +68,11 @@ impl Electrs {
|
|||||||
self.args.daemon_rpc_addr = name;
|
self.args.daemon_rpc_addr = name;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn self_and_args(self) -> (Self, ElectrsArgs) {
|
||||||
|
let args = self.args.clone();
|
||||||
|
(self, args)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -137,7 +117,7 @@ impl IntoIterator for ElectrsArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
args.push("-vvvvv".to_string());
|
args.push("-vvvvv".to_string());
|
||||||
args.push(format!("--daemon-dir=={}", self.daemon_dir.as_str()));
|
args.push(format!("--daemon-dir={}", self.daemon_dir.as_str()));
|
||||||
args.push(format!("--daemon-rpc-addr={}", self.daemon_rpc_addr));
|
args.push(format!("--daemon-rpc-addr={}", self.daemon_rpc_addr));
|
||||||
args.push(format!("--cookie={}", self.cookie));
|
args.push(format!("--cookie={}", self.cookie));
|
||||||
args.push(format!("--http-addr={}", self.http_addr));
|
args.push(format!("--http-addr={}", self.http_addr));
|
||||||
@ -147,3 +127,9 @@ impl IntoIterator for ElectrsArgs {
|
|||||||
args.into_iter()
|
args.into_iter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ImageArgs for ElectrsArgs {
|
||||||
|
fn into_iterator(self) -> Box<dyn Iterator<Item = String>> {
|
||||||
|
Box::new(self.into_iter())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -28,7 +28,7 @@ use swap::seed::Seed;
|
|||||||
use swap::{asb, bitcoin, cli, env, monero};
|
use swap::{asb, bitcoin, cli, env, monero};
|
||||||
use tempfile::{tempdir, NamedTempFile};
|
use tempfile::{tempdir, NamedTempFile};
|
||||||
use testcontainers::clients::Cli;
|
use testcontainers::clients::Cli;
|
||||||
use testcontainers::{Container, Docker, RunArgs};
|
use testcontainers::{Container, RunnableImage};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::sync::mpsc::Receiver;
|
use tokio::sync::mpsc::Receiver;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
@ -61,10 +61,7 @@ where
|
|||||||
let alice_starting_balances =
|
let alice_starting_balances =
|
||||||
StartingBalances::new(bitcoin::Amount::ZERO, xmr_amount, Some(10));
|
StartingBalances::new(bitcoin::Amount::ZERO, xmr_amount, Some(10));
|
||||||
|
|
||||||
let electrs_rpc_port = containers
|
let electrs_rpc_port = containers.electrs.get_host_port_ipv4(electrs::RPC_PORT);
|
||||||
.electrs
|
|
||||||
.get_host_port(electrs::RPC_PORT)
|
|
||||||
.expect("Could not map electrs rpc port");
|
|
||||||
|
|
||||||
let alice_seed = Seed::random().unwrap();
|
let alice_seed = Seed::random().unwrap();
|
||||||
let (alice_bitcoin_wallet, alice_monero_wallet) = init_test_wallets(
|
let (alice_bitcoin_wallet, alice_monero_wallet) = init_test_wallets(
|
||||||
@ -146,25 +143,28 @@ where
|
|||||||
async fn init_containers(cli: &Cli) -> (Monero, Containers<'_>) {
|
async fn init_containers(cli: &Cli) -> (Monero, Containers<'_>) {
|
||||||
let prefix = random_prefix();
|
let prefix = random_prefix();
|
||||||
let bitcoind_name = format!("{}_{}", prefix, "bitcoind");
|
let bitcoind_name = format!("{}_{}", prefix, "bitcoind");
|
||||||
let (bitcoind, bitcoind_url) =
|
let (_bitcoind, bitcoind_url, mapped_port) =
|
||||||
init_bitcoind_container(cli, prefix.clone(), bitcoind_name.clone(), prefix.clone())
|
init_bitcoind_container(cli, prefix.clone(), bitcoind_name.clone(), prefix.clone())
|
||||||
.await
|
.await
|
||||||
.expect("could not init bitcoind");
|
.expect("could not init bitcoind");
|
||||||
let electrs = init_electrs_container(cli, prefix.clone(), bitcoind_name, prefix)
|
let electrs = init_electrs_container(cli, prefix.clone(), bitcoind_name, prefix, mapped_port)
|
||||||
.await
|
.await
|
||||||
.expect("could not init electrs");
|
.expect("could not init electrs");
|
||||||
let (monero, monerod_container, monero_wallet_rpc_containers) =
|
let (monero, _monerod_container, _monero_wallet_rpc_containers) =
|
||||||
Monero::new(cli, vec![MONERO_WALLET_NAME_ALICE, MONERO_WALLET_NAME_BOB])
|
Monero::new(cli, vec![MONERO_WALLET_NAME_ALICE, MONERO_WALLET_NAME_BOB])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
(monero, Containers {
|
(
|
||||||
bitcoind_url,
|
monero,
|
||||||
bitcoind,
|
Containers {
|
||||||
monerod_container,
|
bitcoind_url,
|
||||||
monero_wallet_rpc_containers,
|
_bitcoind,
|
||||||
electrs,
|
_monerod_container,
|
||||||
})
|
_monero_wallet_rpc_containers,
|
||||||
|
electrs,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn init_bitcoind_container(
|
async fn init_bitcoind_container(
|
||||||
@ -172,29 +172,28 @@ async fn init_bitcoind_container(
|
|||||||
volume: String,
|
volume: String,
|
||||||
name: String,
|
name: String,
|
||||||
network: String,
|
network: String,
|
||||||
) -> Result<(Container<'_, Cli, bitcoind::Bitcoind>, Url)> {
|
) -> Result<(Container<'_, bitcoind::Bitcoind>, Url, u16)> {
|
||||||
let image = bitcoind::Bitcoind::default().with_volume(volume);
|
let image = bitcoind::Bitcoind::default().with_volume(volume);
|
||||||
|
let image = RunnableImage::from(image)
|
||||||
|
.with_container_name(name)
|
||||||
|
.with_network(network);
|
||||||
|
|
||||||
let run_args = RunArgs::default().with_name(name).with_network(network);
|
let docker = cli.run(image);
|
||||||
|
let port = docker.get_host_port_ipv4(bitcoind::RPC_PORT);
|
||||||
let docker = cli.run_with_args(image, run_args);
|
|
||||||
let a = docker
|
|
||||||
.get_host_port(bitcoind::RPC_PORT)
|
|
||||||
.context("Could not map bitcoind rpc port")?;
|
|
||||||
|
|
||||||
let bitcoind_url = {
|
let bitcoind_url = {
|
||||||
let input = format!(
|
let input = format!(
|
||||||
"http://{}:{}@localhost:{}",
|
"http://{}:{}@localhost:{}",
|
||||||
bitcoind::RPC_USER,
|
bitcoind::RPC_USER,
|
||||||
bitcoind::RPC_PASSWORD,
|
bitcoind::RPC_PASSWORD,
|
||||||
a
|
port
|
||||||
);
|
);
|
||||||
Url::parse(&input).unwrap()
|
Url::parse(&input).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
init_bitcoind(bitcoind_url.clone(), 5).await?;
|
init_bitcoind(bitcoind_url.clone(), 5).await?;
|
||||||
|
|
||||||
Ok((docker, bitcoind_url.clone()))
|
Ok((docker, bitcoind_url.clone(), bitcoind::RPC_PORT))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn init_electrs_container(
|
pub async fn init_electrs_container(
|
||||||
@ -202,16 +201,18 @@ pub async fn init_electrs_container(
|
|||||||
volume: String,
|
volume: String,
|
||||||
bitcoind_container_name: String,
|
bitcoind_container_name: String,
|
||||||
network: String,
|
network: String,
|
||||||
) -> Result<Container<'_, Cli, electrs::Electrs>> {
|
port: u16,
|
||||||
let bitcoind_rpc_addr = format!("{}:{}", bitcoind_container_name, bitcoind::RPC_PORT);
|
) -> Result<Container<'_, electrs::Electrs>> {
|
||||||
|
let bitcoind_rpc_addr = format!("{}:{}", bitcoind_container_name, port);
|
||||||
let image = electrs::Electrs::default()
|
let image = electrs::Electrs::default()
|
||||||
.with_volume(volume)
|
.with_volume(volume)
|
||||||
.with_daemon_rpc_addr(bitcoind_rpc_addr)
|
.with_daemon_rpc_addr(bitcoind_rpc_addr)
|
||||||
.with_tag("latest");
|
.with_tag("latest");
|
||||||
|
let image = RunnableImage::from(image.self_and_args())
|
||||||
|
.with_network(network.clone())
|
||||||
|
.with_container_name(format!("{}_electrs", network));
|
||||||
|
|
||||||
let run_args = RunArgs::default().with_network(network);
|
let docker = cli.run(image);
|
||||||
|
|
||||||
let docker = cli.run_with_args(image, run_args);
|
|
||||||
|
|
||||||
Ok(docker)
|
Ok(docker)
|
||||||
}
|
}
|
||||||
@ -245,7 +246,7 @@ async fn start_alice(
|
|||||||
resume_only,
|
resume_only,
|
||||||
env_config,
|
env_config,
|
||||||
XmrBtcNamespace::Testnet,
|
XmrBtcNamespace::Testnet,
|
||||||
None,
|
&[],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
swarm.listen_on(listen_address).unwrap();
|
swarm.listen_on(listen_address).unwrap();
|
||||||
@ -925,7 +926,7 @@ async fn init_bitcoind(node_url: Url, spendable_quantity: u32) -> Result<Client>
|
|||||||
bitcoind_client
|
bitcoind_client
|
||||||
.generatetoaddress(101 + spendable_quantity, reward_address.clone())
|
.generatetoaddress(101 + spendable_quantity, reward_address.clone())
|
||||||
.await?;
|
.await?;
|
||||||
let _ = tokio::spawn(mine(bitcoind_client.clone(), reward_address));
|
tokio::spawn(mine(bitcoind_client.clone(), reward_address));
|
||||||
Ok(bitcoind_client)
|
Ok(bitcoind_client)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -949,13 +950,12 @@ pub async fn mint(node_url: Url, address: bitcoin::Address, amount: bitcoin::Amo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This is just to keep the containers alive
|
// This is just to keep the containers alive
|
||||||
#[allow(dead_code)]
|
|
||||||
struct Containers<'a> {
|
struct Containers<'a> {
|
||||||
bitcoind_url: Url,
|
bitcoind_url: Url,
|
||||||
bitcoind: Container<'a, Cli, bitcoind::Bitcoind>,
|
_bitcoind: Container<'a, bitcoind::Bitcoind>,
|
||||||
monerod_container: Container<'a, Cli, image::Monerod>,
|
_monerod_container: Container<'a, image::Monerod>,
|
||||||
monero_wallet_rpc_containers: Vec<Container<'a, Cli, image::MoneroWalletRpc>>,
|
_monero_wallet_rpc_containers: Vec<Container<'a, image::MoneroWalletRpc>>,
|
||||||
electrs: Container<'a, Cli, electrs::Electrs>,
|
electrs: Container<'a, electrs::Electrs>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod alice_run_until {
|
pub mod alice_run_until {
|
||||||
|
Loading…
Reference in New Issue
Block a user