diff --git a/.circleci/config.yml b/.circleci/config.yml index dbe48e0710..9ef46f3769 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,19 +8,9 @@ workflows: branches: ignore: /^(staging.tmp|trying.tmp)$/ - release-build-test: - filters: # runs for "release/" branches and all tags. + filters: # runs for "release/" branches branches: - only: /^release.*$/ - tags: - only: /.*/ - - publish-github-release: - requires: - - release-build-test - filters: # runs for no branches and only for X.Y.Z. - branches: - ignore: /.*/ - tags: - only: /^\d+\.\d+\.\d+$/ + only: /^release\/.*$/ jobs: debug-build-test: @@ -44,6 +34,7 @@ jobs: - restore_caches - install_rust - install_node_devlibs + - install_lnd - print_current_versions - run: name: Debug build and test @@ -63,60 +54,13 @@ jobs: - restore_caches - install_rust - install_node_devlibs + - install_lnd - print_current_versions - run: name: Release build and test command: | export CND_BIN=~/comit/target/release/cnd make ci BUILD_ARGS='--release' - - store_artifacts: - path: api_tests/log - - run: - name: Consolidate release binaries - command: | - set -v - mkdir ~/artifacts - mv -v ~/comit/target/release/cnd ~/artifacts - - persist_to_workspace: - root: ~/artifacts - paths: - - cnd - - publish-github-release: - working_directory: ~/comit - machine: - image: ubuntu-1604:201903-01 - environment: - RUST_TEST_THREADS: "8" - steps: - - attach_workspace: - at: ~/artifacts - - checkout - - restore_caches - - install_rust - - install_node_devlibs - - print_current_versions - - setup_remote_docker - - run: - name: "Package & Publish Release on GitHub and Docker Hub" - command: | - set -v - VERSION=$(cargo pkgid -- cnd|cut -d# -f2) - - # Let's abort if the git tag does not match the version in Cargo.toml as it would be fishy - test "${VERSION}" = "${CIRCLE_TAG}" - - mkdir ~/package - cd ~/artifacts && tar czvf ~/package/comit-rs_${VERSION}_$(uname -s)_$(uname -m).tar.gz * - go get github.com/tcnksm/ghr - ls ~/package/ - ghr -t ${GITHUB_TOKEN_FOR_RELEASES} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -delete ${VERSION} ~/package/ - - echo $DOCKER_HUB_TOKEN | docker login -u thomaseizinger --password-stdin - - docker build . -t comitnetwork/cnd:${VERSION} -t comitnetwork/cnd:latest - docker push comitnetwork/cnd:${VERSION} - docker push comitnetwork/cnd:latest commands: install_node_devlibs: @@ -132,21 +76,35 @@ commands: curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - curl -sL https://deb.nodesource.com/gpgkey/nodesource.gpg.key | sudo apt-key add - echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list - echo "deb https://deb.nodesource.com/node_10.x/ trusty main" | sudo tee /etc/apt/sources.list.d/node_10.list + echo "deb https://deb.nodesource.com/node_12.x/ trusty main" | sudo tee /etc/apt/sources.list.d/node_12.list sudo apt-get update - sudo apt-get install -y nodejs=10.* yarn + sudo apt-get install -y nodejs=12.* yarn install_rust: steps: - run: name: "Install Rust" command: | curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $(< rust-toolchain) && source $HOME/.cargo/env - rustup install nightly-2019-07-31 - rustup component add rustfmt --toolchain nightly-2019-07-31 + rustup install nightly-2020-01-15 + rustup component add rustfmt --toolchain nightly-2020-01-15 # Define variables that need interpolation # As CircleCI starts a new shell for each `run` declaration, we need to export cargo home to $BASH_ENV echo 'export PATH=$HOME/.cargo/bin:$HOME/.local/bin:$PATH' >> $BASH_ENV + install_lnd: + steps: + - run: + name: "Install go 1.13 & lnd" + command: | + sudo rm -rf /usr/local/go + wget https://dl.google.com/go/go1.13.3.linux-amd64.tar.gz + sudo tar -xzvf go1.13.3.linux-amd64.tar.gz -C /usr/local/ + unset GOPATH + go get -d github.com/lightningnetwork/lnd + cd ~/go/src/github.com/lightningnetwork/lnd + git checkout v0.9.0-beta + make tags=invoicesrpc && make tags=invoicesrpc install + echo 'export PATH=$HOME/.cargo/bin:$HOME/.local/bin:$HOME/go/bin:$PATH' >> $BASH_ENV print_current_versions: steps: - run: @@ -164,7 +122,7 @@ commands: steps: - restore_cache: keys: - - rustup-{{ checksum "rust-toolchain" }}-nightly-2019-07-31 + - rustup-{{ checksum "rust-toolchain" }}-nightly-2020-01-15 # We don't want multiple toolchains to pile up in our cache, so only restore the ones we actually use. - restore_cache: keys: @@ -176,15 +134,13 @@ commands: # We don't want the target folder to blow up over time, hence we only download the cache if it exactly matches `Cargo.lock` - restore_cache: keys: - - yarn-packages-v1-{{ .Branch }}-{{ checksum "api_tests/yarn.lock" }} - - yarn-packages-v1-{{ .Branch }}- - - yarn-packages-v1- + - yarn-packages-v1-{{ checksum "api_tests/yarn.lock" }} save_caches: steps: - save_cache: paths: - ~/.rustup - key: rustup-{{ checksum "rust-toolchain" }}-nightly-2019-07-31 + key: rustup-{{ checksum "rust-toolchain" }}-nightly-2020-01-15 - save_cache: paths: - ~/.cargo @@ -197,4 +153,4 @@ commands: paths: - ~/.cache/yarn - api_tests/node_modules - key: yarn-packages-v1-{{ .Branch }}-{{ checksum "api_tests/yarn.lock" }} + key: yarn-packages-v1-{{ checksum "api_tests/yarn.lock" }} diff --git a/.dependabot/config.yml b/.dependabot/config.yml index b83aa5f702..43be7c79d8 100644 --- a/.dependabot/config.yml +++ b/.dependabot/config.yml @@ -10,26 +10,12 @@ update_configs: update_type: "all" dependency_type: "direct" ignored_updates: - # as per https://github.com/comit-network/comit-rs/issues/1316 - - match: - dependency_name: "primitive-types" - - match: - dependency_name: "rlp" - # we only depend on ethbloom to access certain types. our version needs to match whichever version web3 is transitively bringing in - - match: - dependency_name: "ethbloom" # we only depend on `libsqlite3-sys` directly to activate the "bundled" feature. the version we are using has to match the one that diesel is depending on, hence bumping it manually is pointless. - match: dependency_name: "libsqlite3-sys" - # this needs updating based on the libp2p version - - match: - dependency_name: "multistream-select" - # this needs updating based on the tokio_codec version + # this needs updating based on the futures_codec version - match: dependency_name: "bytes" - # until all our code is async/await ready, we cannot update tokio through dependabot - - match: - dependency_name: "tokio" - package_manager: "javascript" directory: "/api_tests" update_schedule: "daily" diff --git a/.github/workflows/auto-assign.yml b/.github/workflows/auto-assign.yml index 1fca8c1c53..66c037e392 100644 --- a/.github/workflows/auto-assign.yml +++ b/.github/workflows/auto-assign.yml @@ -1,6 +1,8 @@ name: Assign PR to creator -on: pull_request +on: + pull_request: + types: opened jobs: automation: @@ -8,6 +10,5 @@ jobs: steps: - name: Assign PR to creator uses: thomaseizinger/assign-pr-creator-action@v1.0.0 - if: github.event_name == 'pull_request' && github.event.action == 'opened' with: repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/cargo-audit.yml b/.github/workflows/cargo-audit.yml new file mode 100644 index 0000000000..8fd8458469 --- /dev/null +++ b/.github/workflows/cargo-audit.yml @@ -0,0 +1,12 @@ +name: Security audit +on: + schedule: + - cron: '0 0 * * *' +jobs: + audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/audit-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/draft-new-release.yml b/.github/workflows/draft-new-release.yml new file mode 100644 index 0000000000..52e235a8ae --- /dev/null +++ b/.github/workflows/draft-new-release.yml @@ -0,0 +1,77 @@ +name: "Draft new release" + +on: + issues: + types: [opened, labeled] + +jobs: + draft-new-release: + name: "Draft a new release" + runs-on: ubuntu-latest + # Only run for issues with a specific title and label. Not strictly required but makes finding the release issue again later easier. + # There is also a whitelist that you may want to use to restrict, who can trigger this workflow. + # Unfortunately, we cannot create an array on the fly, so the whitelist is just comma-separated. + if: startsWith(github.event.issue.title, 'Release version') && contains(github.event.issue.labels.*.name, 'release') && contains('thomaseizinger,bonomat,D4nte,da-kami,luckysori,tcharding,rishflab', github.event.issue.user.login) + steps: + - uses: actions/checkout@v2 + + - name: Extract version from issue title + run: | + TITLE="${{ github.event.issue.title }}" + VERSION=${TITLE#Release version } + + echo "::set-env name=RELEASE_VERSION::$VERSION" + + - name: Create release branch + run: git checkout -b release/${{ env.RELEASE_VERSION }} + + - name: Update changelog + uses: thomaseizinger/keep-a-changelog-new-release@1.1.0 + with: + version: ${{ env.RELEASE_VERSION }} + + - name: Initialize mandatory git config + run: | + git config user.name "GitHub actions" + git config user.email noreply@github.com + + - name: Bump version in Cargo.toml + uses: thomaseizinger/set-crate-version@1.0.0 + with: + version: ${{ env.RELEASE_VERSION }} + manifest: cnd/Cargo.toml + + - name: Update Cargo.lock + uses: actions-rs/cargo@v1 + with: + command: update + args: --package cnd + + - name: Commit changelog and manifest files + id: make-commit + run: | + git add CHANGELOG.md cnd/Cargo.toml Cargo.lock + git commit --message "Prepare release ${{ env.RELEASE_VERSION }}" + + echo "::set-output name=commit::$(git rev-parse HEAD)" + + - name: Push new branch + run: git push origin release/${{ env.RELEASE_VERSION }} --force + + - name: Create pull request + uses: thomaseizinger/create-pull-request@1.0.0 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + head: release/${{ env.RELEASE_VERSION }} + base: master + title: ${{ github.event.issue.title }} + reviewers: ${{ github.event.issue.user.login }} # By default, we request a review from the person who opened the issue. You can replace this with a static list of users. + # Write a nice message to the user. + # We are claiming things here based on the `publish-new-release.yml` workflow. + body: | + Hi @${{ github.event.issue.user.login }}! + + This PR was created in response to this release issue: #${{ github.event.issue.number }}. + I've updated the changelog and bumped the versions in the manifest files in this commit: ${{ steps.make-commit.outputs.commit }}. + + Merging this PR will create a GitHub release and upload any assets that are created as part of the release build. diff --git a/.github/workflows/publish-new-release.yml b/.github/workflows/publish-new-release.yml new file mode 100644 index 0000000000..089d4fb72b --- /dev/null +++ b/.github/workflows/publish-new-release.yml @@ -0,0 +1,191 @@ +name: "Publish new release" + +on: + pull_request: + branches: + - master + types: + - closed + +jobs: + build_binary: + if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/') # only merged release branches must trigger this + name: Build binary + strategy: + matrix: + os: [ubuntu, macos] + runs-on: ${{ matrix.os }}-latest + steps: + - name: Checkout merge commit + uses: actions/checkout@v2 + with: + ref: ${{ github.event.pull_request.merge_commit_sha }} + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + override: true + + - name: Build ${{ matrix.os }} release binary + id: build + run: make build BUILD_ARGS='--release' + + - name: Extract version from branch name + id: extract-version + run: | + BRANCH_NAME="${{ github.event.pull_request.head.ref }}" + VERSION=${BRANCH_NAME#release/} + + echo "::set-output name=version::$VERSION" + + - name: Create archive + id: create-archive + run: | + MACHINE=$(uname -m) + KERNEL=$(uname -s) + VERSION=${{ steps.extract-version.outputs.version }} + + ARCHIVE="cnd_${VERSION}_${KERNEL}_${MACHINE}.tar.gz" + + tar -C ./target/release --create --file=$ARCHIVE cnd + + echo "::set-output name=archive::$ARCHIVE" + + - name: Upload artifact + uses: actions/upload-artifact@v1 + with: + name: ${{ matrix.os }}-release-archive + path: ${{ steps.create-archive.outputs.archive }} + + create_docker_image: + if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/') # only merged release branches must trigger this + name: Create and publish Docker image + needs: build_binary + runs-on: ubuntu-latest + steps: + - name: Checkout merge commit + uses: actions/checkout@v2 + with: + ref: ${{ github.event.pull_request.merge_commit_sha }} + + - name: Download ubuntu release package + uses: actions/download-artifact@v1 + with: + name: ubuntu-release-archive + + - name: Move binary into correct place for Docker build + run: | + mkdir -p ./target/release + + tar --extract -f ./ubuntu-release-archive/*.tar.gz -C ./target/release/ + + - name: Login to docker + uses: azure/docker-login@v1 + with: + username: ${{ secrets.DOCKER_REGISTRY_USERNAME }} + password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }} + + - name: Extract version from branch name + id: extract-version + run: | + BRANCH_NAME="${{ github.event.pull_request.head.ref }}" + VERSION=${BRANCH_NAME#release/} + + echo "::set-output name=version::$VERSION" + + - name: Publish docker image as ${{ steps.extract-version.outputs.version }} and latest + run: | + VERSION="${{ steps.extract-version.outputs.version }}" + + docker build . -t comitnetwork/cnd:$VERSION -t comitnetwork/cnd:latest + docker push comitnetwork/cnd:$VERSION + docker push comitnetwork/cnd:latest + + + release: + if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/') # only merged release branches must trigger this + name: Create GitHub release + needs: build_binary + runs-on: ubuntu-latest + steps: + - name: Extract version from branch name + id: extract-version + run: | + BRANCH_NAME="${{ github.event.pull_request.head.ref }}" + VERSION=${BRANCH_NAME#release/} + + echo "::set-output name=version::$VERSION" + + - name: Download ubuntu release package + uses: actions/download-artifact@v1 + with: + name: ubuntu-release-archive + + - name: Download macos release package + uses: actions/download-artifact@v1 + with: + name: macos-release-archive + + - name: Detect archives to upload + id: detect-archives + run: | + echo "::set-output name=ubuntu-archive::$(cd ./ubuntu-release-archive/; echo *.tar.gz)" + echo "::set-output name=macos-archive::$(cd ./macos-release-archive/; echo *.tar.gz)" + + - name: Create Release + id: create-release + uses: thomaseizinger/create-release@1.0.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + target_commitish: ${{ github.event.pull_request.merge_commit_sha }} + tag_name: ${{ steps.extract-version.outputs.version }} + name: ${{ steps.extract-version.outputs.version }} + draft: false + prerelease: false + + - name: Upload ubuntu release binary + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create-release.outputs.upload_url }} + asset_path: ./ubuntu-release-archive/${{ steps.detect-archives.outputs.ubuntu-archive }} + asset_name: ${{ steps.detect-archives.outputs.ubuntu-archive }} + asset_content_type: application/gzip + + - name: Upload macos release binary + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create-release.outputs.upload_url }} + asset_path: ./macos-release-archive/${{ steps.detect-archives.outputs.macos-archive }} + asset_name: ${{ steps.detect-archives.outputs.macos-archive }} + asset_content_type: application/gzip + + merge_release_into_dev: + if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/') # only merged release branches must trigger this + name: Merge release-branch back into dev + runs-on: ubuntu-latest + steps: + - name: Extract version from branch name + id: extract-version + run: | + BRANCH_NAME="${{ github.event.pull_request.head.ref }}" + VERSION=${BRANCH_NAME#release/} + + echo "::set-output name=version::$VERSION" + + - name: Create pull request for merging release-branch back into dev + uses: thomaseizinger/create-pull-request@1.0.0 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + head: release/${{ steps.extract-version.outputs.version }} + base: dev + title: Merge release ${{ steps.extract-version.outputs.version }} into dev branch + body: | + This PR merges the release branch for ${{ steps.extract-version.outputs.version }} back into dev. + This happens to ensure that the updates that happend on the release branch, i.e. CHANGELOG and manifest updates are also present on the dev branch. + diff --git a/.mergify.yml b/.mergify.yml index a77ea73fcb..18be9772ee 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -1,4 +1,14 @@ pull_request_rules: + - name: comment on broken dependabot PRs, asking for help + conditions: + - "author=dependabot-preview[bot]" + - "status-failure=ci/circleci: debug-build-test" + actions: + comment: + message: "The build on this dependency update is broken and needs attention." + request_reviews: + teams: + - "@comit-network/rust-devs" - name: instruct bors to merge dependabot PRs with passing tests conditions: - "author=dependabot-preview[bot]" @@ -34,6 +44,7 @@ pull_request_rules: - name: Delete branch if the pull request is merged conditions: - merged + - head~=^(?!release.*).*$ actions: delete_head_branch: - name: nag if changelog is not updated diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f9a1145b9..0e8d6e1fbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Changelog + All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), @@ -6,76 +7,114 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## 0.6.0 - 2020-02-13 +## [0.7.0] - 2020-03-12 + +### Added + +- Added a step during initialisation to test whether the chain/network id's of the Ethereum/Bitcoin nodes that cnd has connected to matches the chain/network id's specified in the config. Cnd will abort if the config and the actual chain/network id does not match. Cnd will log a warning if it cannot make a request to the nodes. + +### Changed + +- **Breaking config changes**: cnd config has changed. Bitcoin and Ethereum has 2 optional fields specifically for the connector (i.e. bitcoind and parity). If provided, the network (for bitcoin) and chain_id (for ethereum) are mandatory. If the url was not provided, a default aiming at localhost will be derived. If no connectors were provided, defaults will be provided. For a full example config run: `cnd --dump-config`. + +## [0.6.0] - 2020-02-13 ### Fixed -- Ensure that failed Ethereum transactions are ignored during a swap. + +- Ensure that failed Ethereum transactions are ignored during a swap. ### Changed -- Upgrade `blockchain-contracts` crate to 0.3.1. Ether and Erc20 HTLCs now use `revert` to fail the Ethereum transaction if the redeem or refund are unsuccessful. + +- Upgrade `blockchain-contracts` crate to 0.3.1. Ether and Erc20 HTLCs now use `revert` to fail the Ethereum transaction if the redeem or refund are unsuccessful. ## [0.5.1] - 2020-01-21 ### Added -- Return Siren document containing peer ID, listen addresses and links to `/swaps` and `/swaps/rfc003` on `GET /` with the Accept request HTTP header set to `application/vnd.siren+json`. + +- Return Siren document containing peer ID, listen addresses and links to `/swaps` and `/swaps/rfc003` on `GET /` with the Accept request HTTP header set to `application/vnd.siren+json`. ### Changed -- Write all diagnostics and log messages to stderr. + +- Write all diagnostics and log messages to stderr. ## [0.5.0] - 2019-12-06 ### Added -- Added persistent storage to cnd, now we save swaps to an Sqlite database when they are requested and accepted. + +- Added persistent storage to cnd, now we save swaps to an Sqlite database when they are requested and accepted. +- Use stdlib `SocketAddr` as base for HTTP API config option. ## [0.4.0] - 2019-11-26 ### Changed -- **Breaking (HTTP+COMIT API):** Change the identity for the Bitcoin Ledger from a public key hash to a public key. This change impacts the HTTP and the COMIT API of cnd. -- **Breaking (COMIT API):** Replace Ethereum `network` with Ethereum `chain_id` -- cnd no longer automatically generates a config file, but instead simply defaults to what it would have written to the file on first startup. -- Make expiries optional when sending a swap request, with defaults: - - 24 hours later for alpha ledger. - - 12 hours later for beta ledger. -- **Breaking (Config file format):** Expand `http_api` section in config file to contain both the socket and CORS settings. + +- **Breaking (HTTP+COMIT API):** Change the identity for the Bitcoin Ledger from a public key hash to a public key. This change impacts the HTTP and the COMIT API of cnd. +- **Breaking (COMIT API):** Replace Ethereum `network` with Ethereum `chain_id` +- cnd no longer automatically generates a config file, but instead simply defaults to what it would have written to the file on first startup. +- Make expiries optional when sending a swap request, with defaults: + - 24 hours later for alpha ledger. + - 12 hours later for beta ledger. +- **Breaking (Config file format):** Expand `http_api` section in config file to contain both the socket and CORS settings. ### Added -- Return Ethereum `chain_id` on the HTTP API. -- Support Ethereum `chain_id` in the Swap Request (HTTP API). -- Ability to set CORS allowed origins through the configuration file. -- Added command line option `--dump-config` to print the running configuration to stdout. + +- Return Ethereum `chain_id` on the HTTP API. +- Support Ethereum `chain_id` in the Swap Request (HTTP API). +- Ability to set CORS allowed origins through the configuration file. +- Added command line option `--dump-config` to print the running configuration to stdout. ### Fixed -- Error responses now properly identify themselves as `application/problem+json`. They have been conforming to this format for a while already, we just never set the `Content-Type` header properly. From now on, applications can fully rely on the error format! + +- Error responses now properly identify themselves as `application/problem+json`. They have been conforming to this format for a while already, we just never set the `Content-Type` header properly. From now on, applications can fully rely on the error format! ## [0.3.0] - 2019-10-02 + ### Changed -- Embed btsieve as a library inside cnd: From now on, you'll only need to run cnd to use COMIT. + +- Embed btsieve as a library inside cnd: From now on, you'll only need to run cnd to use COMIT. ## [0.2.1] - 2019-09-24 + ### Changed -- Use the same Swap ID to identify a swap for both parties. + +- Use the same Swap ID to identify a swap for both parties. ## [0.2.0] - 2019-09-13 + ### Changed -- Statically link openssl in the release build to allow the binaries to be ran out-of-the-box on most Linux distros. -- Replace ZMQ by using bitcoind's HTTP API for retrieving bitcoin blocks. + +- Statically link openssl in the release build to allow the binaries to be ran out-of-the-box on most Linux distros. +- Replace ZMQ by using bitcoind's HTTP API for retrieving bitcoin blocks. ## [0.1.0] - 2019-09-05 + ### Added -- All the code since the dawn of comit-rs. -- Check if btsieve's version matches the expected version, on every request. -- Ping btsieve on cnd startup, checking for presence and version compatibility. + +- All the code since the dawn of comit-rs. +- Check if btsieve's version matches the expected version, on every request. +- Ping btsieve on cnd startup, checking for presence and version compatibility. ### Changed -- Move config files to standard location based on platform (OSX, Windows, Linux). -- Align implementation with RFC-002 to use the decision header instead of status codes. -[Unreleased]: https://github.com/comit-network/comit-rs/compare/0.6.0...HEAD +- Move config files to standard location based on platform (OSX, Windows, Linux). +- Align implementation with RFC-002 to use the decision header instead of status codes. + +[Unreleased]: https://github.com/comit-network/comit-rs/compare/0.7.0...HEAD + +[0.7.0]: https://github.com/comit-network/comit-rs/compare/0.6.0...0.7.0 + [0.6.0]: https://github.com/comit-network/comit-rs/compare/0.5.1...0.6.0 + [0.5.1]: https://github.com/comit-network/comit-rs/compare/0.5.0...0.5.1 + [0.5.0]: https://github.com/comit-network/comit-rs/compare/0.4.0...0.5.0 + [0.4.0]: https://github.com/comit-network/comit-rs/compare/0.3.0...0.4.0 + [0.3.0]: https://github.com/comit-network/comit-rs/compare/0.2.1...0.3.0 + [0.2.1]: https://github.com/comit-network/comit-rs/compare/0.2.0...0.2.1 + [0.2.0]: https://github.com/comit-network/comit-rs/compare/b2dd02a7f93dc82f5cc9fd4b6eaaf54de1459ff6...40116c3e8a9f57a213661917b8cc057e1db60755 + [0.1.0]: https://github.com/comit-network/comit-rs/compare/1625533e04119e8496b14d5e18786f150b4fce4d...b2dd02a7f93dc82f5cc9fd4b6eaaf54de1459ff6 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5667b22843..bedb1298ba 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,9 +41,9 @@ If you wish to directly contribute to the code, please make pull requests agains Refer to [GitHub documentation](https://help.github.com/articles/about-pull-requests/) on using the Pull Request feature. You can also find more details in the [Open Source Guides](https://opensource.guide/how-to-contribute/#opening-a-pull-request). -**Before** committing, always run `cargo make format` (or `cargo make js-format` for JavaScript) or your change will be rejected by the CI. +**Before** committing, always run `make format` or your change will be rejected by the CI. -To ensure you have not made any breaking changes, run `cargo make all`. +To ensure you have not made any breaking changes, run `make all`. When creating commits, please follow these commit guidelines: https://chris.beams.io/posts/git-commit/. diff --git a/Cargo.lock b/Cargo.lock index da181b9309..3f8693371a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,7 +25,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfd7e7ae3f9a1fb5c03b389fc6bb9a51400d0c13053f0dca698c832bfd893a0d" dependencies = [ "block-cipher-trait", - "byteorder 1.3.2", + "byteorder 1.3.4", "opaque-debug", ] @@ -51,9 +51,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" +checksum = "743ad5a418686aad3b87fd14c43badd828cf26e214a00f92a384291cf22e1811" dependencies = [ "memchr", ] @@ -66,7 +66,7 @@ checksum = "61874b33258f18ca7923047c12887078ccfe95c2811b03c1a09e309c19b7e50b" dependencies = [ "proc-macro2 1.0.8", "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", ] [[package]] @@ -84,6 +84,15 @@ version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" +[[package]] +name = "array-init" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23589ecb866b460d3a0f1278834750268c607e8e28a1b982c907219f3178cd72" +dependencies = [ + "nodrop", +] + [[package]] name = "arrayref" version = "0.3.6" @@ -121,7 +130,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d0864d84b8e07b145449be9a8537db86bf9de5ce03b913214694643b4743502" dependencies = [ "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", +] + +[[package]] +name = "async-std" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "538ecb01eb64eecd772087e5b6f7540cbc917f047727339a472dafed2185b267" +dependencies = [ + "async-task", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "futures-core", + "futures-io", + "futures-timer 2.0.2", + "kv-log-macro", + "log 0.4.8", + "memchr", + "mio", + "mio-uds", + "num_cpus", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "async-task" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ac2c016b079e771204030951c366db398864f5026f84a44dafb0ff20f02085d" +dependencies = [ + "libc", + "winapi 0.3.8", ] [[package]] @@ -132,7 +176,7 @@ checksum = "750b1c38a1dfadd108da0f01c08f4cdc7ff1bb39b325f9c82cc972361780a6e1" dependencies = [ "proc-macro2 1.0.8", "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", ] [[package]] @@ -158,52 +202,27 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -[[package]] -name = "backtrace" -version = "0.3.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b1549d804b6c73f4817df2ba073709e96e426f12987127c48e6745568c350b" -dependencies = [ - "backtrace-sys", - "cfg-if", - "libc", - "rustc-demangle", -] - -[[package]] -name = "backtrace-sys" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "base64" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" dependencies = [ - "byteorder 1.3.2", + "byteorder 1.3.4", "safemem", ] [[package]] name = "base64" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -dependencies = [ - "byteorder 1.3.2", -] +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" [[package]] name = "base64" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +checksum = "7d5ca2cd0adc3f48f9e9ea5a6bbdf9ccc0bfade884847e484d452414c7ccffb3" [[package]] name = "bech32" @@ -246,9 +265,9 @@ dependencies = [ [[package]] name = "bitcoincore-rpc" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c071fc80bd852462b2b23ed8e2b57b60fcc805c73599e278b3ef75db6bcc88df" +checksum = "0f41f59566728c0940ca4032d3b3c51089ddeed32f3b2071d80c285394861dc7" dependencies = [ "bitcoincore-rpc-json", "jsonrpc", @@ -259,9 +278,9 @@ dependencies = [ [[package]] name = "bitcoincore-rpc-json" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710e5d00583f67e9b0f81a2f922b11b98c952c1a508ae1bf27a43b2380c73b4b" +checksum = "42c8db224907013dc662cc5fecb35f853051dce42dd14d00dd82b42f4d82ff46" dependencies = [ "bitcoin", "hex 0.3.2", @@ -276,6 +295,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "bitvec" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993f74b4c99c1908d156b8d2e0fb6277736b0ecbd833982fd1241d39b2766a6" + [[package]] name = "blake2" version = "0.8.1" @@ -288,6 +313,16 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "blake2-rfc" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +dependencies = [ + "arrayvec 0.4.12", + "constant_time_eq", +] + [[package]] name = "blake2b_simd" version = "0.5.10" @@ -307,7 +342,7 @@ checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" dependencies = [ "block-padding", "byte-tools", - "byteorder 1.3.2", + "byteorder 1.3.4", "generic-array", ] @@ -336,8 +371,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1d96fd22884503a21dc47fdb102c37131973d12682960c9b3405b95b6625dc7" dependencies = [ "bitcoin", - "byteorder 1.3.2", - "hex 0.4.1", + "byteorder 1.3.4", + "hex 0.4.2", "hex-literal", "itertools", "regex", @@ -354,9 +389,15 @@ checksum = "b170cd256a3f9fa6b9edae3e44a7dfdfc77e8124dbc3e2612d75f9c3e2396dae" [[package]] name = "bumpalo" -version = "3.1.2" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f359dc14ff8911330a51ef78022d376f25ed00248912803b58f00cb1c27f742" + +[[package]] +name = "byte-slice-cast" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb8038c1ddc0a5f73787b130f4cc75151e96ed33e417fde765eb5a81e3532f4" +checksum = "b0a5e3906bcbf133e33c1d4d95afc664ad37fbdb9f6568d8043e7ea8c27d93d3" [[package]] name = "byte-tools" @@ -372,9 +413,9 @@ checksum = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" [[package]] name = "byteorder" -version = "1.3.2" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] name = "bytes" @@ -382,8 +423,7 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" dependencies = [ - "byteorder 1.3.2", - "either", + "byteorder 1.3.4", "iovec", ] @@ -420,11 +460,20 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +[[package]] +name = "chacha20-poly1305-aead" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77d2058ba29594f69c75e8a9018e0485e3914ca5084e3613cd64529042f5423b" +dependencies = [ + "constant_time_eq", +] + [[package]] name = "chrono" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" +checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" dependencies = [ "num-integer", "num-traits", @@ -467,29 +516,29 @@ dependencies = [ [[package]] name = "cnd" -version = "0.6.0" +version = "0.7.0" dependencies = [ "ambassador", "anyhow", "async-trait", - "base64 0.11.0", + "base64 0.12.0", "bigdecimal", "bitcoin", "bitcoincore-rpc", "blockchain_contracts", "chrono", "config", - "derivative", + "derivative 2.0.1", "diesel", "diesel_migrations", "directories", "ethbloom", "fern", - "futures 0.1.29", - "futures 0.3.1", + "futures 0.3.4", "genawaiter", - "hex 0.4.1", + "hex 0.4.2", "http-api-problem", + "impl-template", "lazy_static", "libp2p", "libp2p-comit", @@ -505,8 +554,8 @@ dependencies = [ "rand 0.7.3", "regex", "reqwest", - "rlp", "serde", + "serde-hex", "serde_json", "serde_urlencoded", "serdebug", @@ -520,25 +569,24 @@ dependencies = [ "testcontainers", "thiserror", "tiny-keccak 2.0.1", - "tokio 0.2.11", - "tokio-compat", + "tokio", "toml", "tracing", "tracing-core", + "tracing-futures", "tracing-log", "tracing-subscriber", "url 2.1.1", "uuid", "void", "warp", - "web3", ] [[package]] name = "colored" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8815e2ab78f3a59928fc32e141fbeece88320a240e43f47b2fd64ea3a88a5b3d" +checksum = "f4ffc801dacf156c5854b9df4f425a626539c3a6ef7893cc0c5084a23f0b6c59" dependencies = [ "atty", "lazy_static", @@ -609,55 +657,48 @@ dependencies = [ ] [[package]] -name = "crossbeam-deque" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils 0.7.0", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.8.0" +name = "crossbeam-channel" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac" +checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" dependencies = [ - "autocfg 0.1.7", - "cfg-if", - "crossbeam-utils 0.7.0", - "lazy_static", - "memoffset", - "scopeguard", + "crossbeam-utils", + "maybe-uninit", ] [[package]] -name = "crossbeam-queue" -version = "0.1.2" +name = "crossbeam-deque" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" +checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" dependencies = [ - "crossbeam-utils 0.6.6", + "crossbeam-epoch", + "crossbeam-utils", + "maybe-uninit", ] [[package]] -name = "crossbeam-utils" -version = "0.6.6" +name = "crossbeam-epoch" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" +checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" dependencies = [ + "autocfg 1.0.0", "cfg-if", + "crossbeam-utils", "lazy_static", + "maybe-uninit", + "memoffset", + "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ - "autocfg 0.1.7", + "autocfg 1.0.0", "cfg-if", "lazy_static", ] @@ -698,26 +739,13 @@ dependencies = [ "rand 0.3.23", ] -[[package]] -name = "curve25519-dalek" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7dcd30ba50cdf88b55b033456138b7c0ac4afdc436d82e1b79f370f24cc66d" -dependencies = [ - "byteorder 1.3.2", - "clear_on_drop", - "digest", - "rand_core 0.3.1", - "subtle 2.2.2", -] - [[package]] name = "curve25519-dalek" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26778518a7f6cffa1d25a44b602b62b979bd88adb9e99ffec546998cf3404839" dependencies = [ - "byteorder 1.3.2", + "byteorder 1.3.4", "digest", "rand_core 0.5.1", "subtle 2.2.2", @@ -726,15 +754,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f47ca1860a761136924ddd2422ba77b2ea54fe8cc75b9040804a0d9d32ad97" +checksum = "11c0346158a19b3627234e15596f5e465c360fcdb97d817bcb255e0510f5a788" [[package]] name = "derivative" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942ca430eef7a3806595a6737bc388bf51adb888d3fc0dd1b50f1c170167ee3a" +checksum = "3c6d883546668a3e2011b6a716a7330b82eabb0151b138217f632c8243e17135" dependencies = [ "proc-macro2 0.4.30", "quote 0.6.13", @@ -742,17 +770,14 @@ dependencies = [ ] [[package]] -name = "derive_more" -version = "0.15.0" +name = "derivative" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a141330240c921ec6d074a3e188a7c7ef95668bb95e7d44fa0e5778ec2a7afe" +checksum = "40c1f2732657d77ebc4e52a04dcf529ffb72e46e78d2e113b9016d5c01598649" dependencies = [ - "lazy_static", - "proc-macro2 0.4.30", - "quote 0.6.13", - "regex", - "rustc_version", - "syn 0.15.44", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.15", ] [[package]] @@ -761,7 +786,7 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d7cc03b910de9935007861dce440881f69102aaaedfd4bc5a6f40340ca5840c" dependencies = [ - "byteorder 1.3.2", + "byteorder 1.3.4", "chrono", "diesel_derives", "libsqlite3-sys", @@ -775,7 +800,7 @@ checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3" dependencies = [ "proc-macro2 1.0.8", "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", ] [[package]] @@ -825,15 +850,15 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4d33be9473d06f75f58220f71f7a9317aca647dc061dbd3c361b0bef505fbea" dependencies = [ - "byteorder 1.3.2", + "byteorder 1.3.4", "quick-error", ] [[package]] name = "dtoa" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" +checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" [[package]] name = "ed25519-dalek" @@ -842,7 +867,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978710b352437433c97b2bff193f2fb1dfd58a093f863dd95e225a19baa599a2" dependencies = [ "clear_on_drop", - "curve25519-dalek 2.0.0", + "curve25519-dalek", "rand 0.7.3", "sha2", ] @@ -872,79 +897,19 @@ dependencies = [ "regex", ] -[[package]] -name = "error-chain" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab49e9dcb602294bc42f9a7dfc9bc6e936fca4418ea300dbfb84fe16de0b7d9" -dependencies = [ - "version_check 0.1.5", -] - -[[package]] -name = "ethabi" -version = "8.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebdeeea85a6d217b9fcc862906d7e283c047e04114165c433756baf5dce00a6c" -dependencies = [ - "error-chain", - "ethereum-types", - "rustc-hex", - "serde", - "serde_derive", - "serde_json", - "tiny-keccak 1.5.0", -] - [[package]] name = "ethbloom" -version = "0.6.4" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3932e82d64d347a045208924002930dc105a138995ccdc1479d0f05f0359f17c" +checksum = "32cfe1c169414b709cf28aa30c74060bdb830a03a8ba473314d079ac79d80a5f" dependencies = [ "crunchy", "fixed-hash", "impl-rlp", - "impl-serde", + "impl-serde 0.2.3", "tiny-keccak 1.5.0", ] -[[package]] -name = "ethereum-types" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62d1bc682337e2c5ec98930853674dd2b4bd5d0d246933a9e98e5280f7c76c5f" -dependencies = [ - "ethbloom", - "fixed-hash", - "impl-rlp", - "impl-serde", - "primitive-types", - "uint 0.7.1", -] - -[[package]] -name = "failure" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" -dependencies = [ - "backtrace", - "failure_derive", -] - -[[package]] -name = "failure_derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" -dependencies = [ - "proc-macro2 1.0.8", - "quote 1.0.2", - "syn 1.0.14", - "synstructure", -] - [[package]] name = "fake-simd" version = "0.1.2" @@ -953,29 +918,33 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[package]] name = "fern" -version = "0.5.9" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e69ab0d5aca163e388c3a49d284fed6c3d0810700e77c5ae2756a50ec1a4daaa" +checksum = "8c9a4820f0ccc8a7afd67c39a0f1a0f4b07ca1725164271a64939d7aeb9af065" dependencies = [ - "chrono", "colored", "log 0.4.8", ] [[package]] name = "fixed-hash" -version = "0.3.2" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1a683d1234507e4f3bf2736eeddf0de1dc65996dc0164d57eba0a74bcf29489" +checksum = "3367952ceb191f4ab95dd5685dc163ac539e36202f9fcfd0cb22f9f9c542fefc" dependencies = [ - "byteorder 1.3.2", - "heapsize", + "byteorder 1.3.4", "libc", - "rand 0.5.6", + "rand 0.7.3", "rustc-hex", - "static_assertions 0.2.5", + "static_assertions 1.1.0", ] +[[package]] +name = "fixedbitset" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" + [[package]] name = "flate2" version = "1.0.13" @@ -984,11 +953,8 @@ checksum = "6bd6d6f4752952feb71363cffc9ebac9411b75b87c6ab6058c40c8900cf43c0f" dependencies = [ "cfg-if", "crc32fast", - "futures 0.1.29", "libc", - "libz-sys", "miniz_oxide", - "tokio-io", ] [[package]] @@ -1042,12 +1008,13 @@ checksum = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef" [[package]] name = "futures" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f16056ecbb57525ff698bb955162d0cd03bee84e6241c27ff75c08d8ca5987" +checksum = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -1056,9 +1023,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcae98ca17d102fd8a3603727b9259fcf7fa4239b603d2142926189bc8999b86" +checksum = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8" dependencies = [ "futures-core", "futures-sink", @@ -1066,55 +1033,69 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866" +checksum = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a" [[package]] -name = "futures-cpupool" -version = "0.1.8" +name = "futures-executor" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" +checksum = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba" dependencies = [ - "futures 0.1.29", + "futures-core", + "futures-task", + "futures-util", "num_cpus", ] [[package]] name = "futures-io" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e676577d229e70952ab25f3945795ba5b16d63ca794ca9d2c860e5595d20b5ff" +checksum = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6" [[package]] name = "futures-macro" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e7c56c15537adb4f76d0b7a76ad131cb4d2f4f32d3b0bcabcbe1c7c5e87764" +checksum = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7" dependencies = [ "proc-macro-hack", "proc-macro2 1.0.8", "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", ] [[package]] name = "futures-sink" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16" +checksum = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6" [[package]] name = "futures-task" -version = "0.3.1" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27" + +[[package]] +name = "futures-timer" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6" + +[[package]] +name = "futures-timer" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76" +checksum = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5" dependencies = [ "futures 0.1.29", "futures-channel", @@ -1127,7 +1108,32 @@ dependencies = [ "pin-utils", "proc-macro-hack", "proc-macro-nested", - "slab 0.4.2", + "slab", + "tokio-io", +] + +[[package]] +name = "futures_codec" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0a73299e4718f5452e45980fc1d6957a070abe308d3700b63b8673f47e1c2b3" +dependencies = [ + "bytes 0.5.4", + "futures 0.3.4", + "memchr", + "pin-project", +] + +[[package]] +name = "futures_codec" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe8859feb7140742ed1a2a85a07941100ad2b5f98a421b353931d718a34144d1" +dependencies = [ + "bytes 0.5.4", + "futures 0.3.4", + "memchr", + "pin-project", ] [[package]] @@ -1138,22 +1144,46 @@ checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" [[package]] name = "genawaiter" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1236259ce812baf9e1d1e316724e0fec341ce788f038aa543f813dfe78e12d32" - -[[package]] -name = "generic-array" -version = "0.12.3" +version = "0.99.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +checksum = "c86bd0361bcbde39b13475e6e36cb24c329964aa2611be285289d1e4b751c1a0" dependencies = [ - "typenum", + "genawaiter-macro", + "genawaiter-proc-macro", + "proc-macro-hack", ] [[package]] -name = "get_if_addrs" -version = "0.5.3" +name = "genawaiter-macro" +version = "0.99.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b32dfe1fdfc0bbde1f22a5da25355514b5e450c33a6af6770884c8750aedfbc" + +[[package]] +name = "genawaiter-proc-macro" +version = "0.99.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784f84eebc366e15251c4a8c3acee82a6a6f427949776ecb88377362a9621738" +dependencies = [ + "proc-macro-error", + "proc-macro-hack", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.15", +] + +[[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +dependencies = [ + "typenum", +] + +[[package]] +name = "get_if_addrs" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abddb55a898d32925f3148bd281174a68eeb68bbfd9a5938a57b18f506ee4ef7" dependencies = [ @@ -1186,38 +1216,20 @@ dependencies = [ [[package]] name = "h2" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462" -dependencies = [ - "byteorder 1.3.2", - "bytes 0.4.12", - "fnv", - "futures 0.1.29", - "http 0.1.21", - "indexmap", - "log 0.4.8", - "slab 0.4.2", - "string", - "tokio-io", -] - -[[package]] -name = "h2" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9433d71e471c1736fd5a61b671fc0b148d7a2992f666c958d03cd8feb3b88d1" +checksum = "9d5c295d1c0c68e4e42003d75f908f5e16a1edd1cbe0b0d02e4dc2006a384f47" dependencies = [ "bytes 0.5.4", "fnv", "futures-core", "futures-sink", "futures-util", - "http 0.2.0", + "http", "indexmap", "log 0.4.8", - "slab 0.4.2", - "tokio 0.2.11", + "slab", + "tokio", "tokio-util", ] @@ -1241,7 +1253,7 @@ dependencies = [ "bitflags", "bytes 0.5.4", "headers-core", - "http 0.2.0", + "http", "mime 0.3.16", "sha-1", "time", @@ -1253,16 +1265,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" dependencies = [ - "http 0.2.0", -] - -[[package]] -name = "heapsize" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1679e6ea370dee694f91f1dc469bf94cf8f52051d147aec3e1f9497c6fc22461" -dependencies = [ - "winapi 0.3.8", + "http", ] [[package]] @@ -1276,9 +1279,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772" +checksum = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8" dependencies = [ "libc", ] @@ -1291,9 +1294,9 @@ checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" [[package]] name = "hex" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cdda6bf525062a0c9e8f14ee2b37935c86b8efb6c8b69b3c83dfb518a914af" +checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" [[package]] name = "hex-literal" @@ -1335,17 +1338,6 @@ dependencies = [ "hmac", ] -[[package]] -name = "http" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0" -dependencies = [ - "bytes 0.4.12", - "fnv", - "itoa", -] - [[package]] name = "http" version = "0.2.0" @@ -1363,24 +1355,12 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea904a890b9a8803939988503ba27ba7c298bcaca9a65d0e311a827583d4621" dependencies = [ - "http 0.2.0", + "http", "serde", "serde_json", "warp", ] -[[package]] -name = "http-body" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", - "http 0.1.21", - "tokio-buf", -] - [[package]] name = "http-body" version = "0.3.1" @@ -1388,7 +1368,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" dependencies = [ "bytes 0.5.4", - "http 0.2.0", + "http", ] [[package]] @@ -1418,56 +1398,26 @@ dependencies = [ [[package]] name = "hyper" -version = "0.12.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dbe6ed1438e1f8ad955a4701e9a944938e9519f6888d12d8558b645e247d5f6" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", - "futures-cpupool", - "h2 0.1.26", - "http 0.1.21", - "http-body 0.1.0", - "httparse", - "iovec", - "itoa", - "log 0.4.8", - "net2", - "rustc_version", - "time", - "tokio 0.1.22", - "tokio-buf", - "tokio-executor", - "tokio-io", - "tokio-reactor", - "tokio-tcp", - "tokio-threadpool", - "tokio-timer 0.2.12", - "want 0.2.0", -] - -[[package]] -name = "hyper" -version = "0.13.1" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf49cfb32edee45d890537d9057d1b02ed55f53b7b6a30bae83a38c9231749e" +checksum = "e7b15203263d1faa615f9337d79c1d37959439dc46c2b4faab33286fadc2a1c5" dependencies = [ "bytes 0.5.4", "futures-channel", "futures-core", "futures-util", - "h2 0.2.1", - "http 0.2.0", - "http-body 0.3.1", + "h2", + "http", + "http-body", "httparse", "itoa", "log 0.4.8", "net2", "pin-project", "time", - "tokio 0.2.11", + "tokio", "tower-service", - "want 0.3.0", + "want", ] [[package]] @@ -1477,9 +1427,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3adcd308402b9553630734e9c36b77a7e48b3821251ca2493e8cd596763aafaa" dependencies = [ "bytes 0.5.4", - "hyper 0.13.1", + "hyper 0.13.3", "native-tls", - "tokio 0.2.11", + "tokio", "tokio-tls", ] @@ -1507,11 +1457,11 @@ dependencies = [ [[package]] name = "impl-codec" -version = "0.2.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2050d823639fbeae26b2b5ba09aca8907793117324858070ade0673c49f793b" +checksum = "1be51a921b067b0eaca2fad532d9400041561aa922221cc65f95a85641c6bf53" dependencies = [ - "parity-codec", + "parity-scale-codec", ] [[package]] @@ -1532,11 +1482,32 @@ dependencies = [ "serde", ] +[[package]] +name = "impl-serde" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bbe9ea9b182f0fb1cabbd61f4ff9b7b7b9197955e95a7e4c27de5055eb29ff8" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-template" +version = "1.0.0-alpha" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e20558cac9252437815e7231074926454f1d59d9797fff4840d135a30c930a49" +dependencies = [ + "itertools", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.15", +] + [[package]] name = "indexmap" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b54058f0a6ff80b6803da8faf8997cde53872b38f4023728f6830b06cd3c0dc" +checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" dependencies = [ "autocfg 1.0.0", ] @@ -1552,9 +1523,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f4b06b21db0228860c8dfd17d2106c49c7c6bd07477a4036985347d84def04" +checksum = "a859057dc563d1388c1e816f98a1892629075fc046ed06e845b883bb8b2916fb" [[package]] name = "itertools" @@ -1592,19 +1563,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "jsonrpc-core" -version = "11.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b83fdc5e0218128d0d270f2f2e7a5ea716f3240c8518a58bc89e6716ba8581" -dependencies = [ - "futures 0.1.29", - "log 0.4.8", - "serde", - "serde_derive", - "serde_json", -] - [[package]] name = "keccak" version = "0.1.0" @@ -1621,6 +1579,15 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "kv-log-macro" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c54d9f465d530a752e6ebdc217e081a7a614b48cb200f6f0aee21ba6bc9aabb" +dependencies = [ + "log 0.4.8", +] + [[package]] name = "language-tags" version = "0.2.2" @@ -1648,24 +1615,25 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.66" +version = "0.2.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" +checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" [[package]] name = "libp2p" -version = "0.13.2" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4674c6738fdd8b1cf7104dd046abcef78dc932fe25f8eb40f3a8e71341717d" +checksum = "bba17ee9cac4bb89de5812159877d9b4f0a993bf41697a5a875940cd1eb71f24" dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", + "bytes 0.5.4", + "futures 0.3.4", "lazy_static", "libp2p-core", "libp2p-core-derive", "libp2p-deflate", "libp2p-dns", "libp2p-floodsub", + "libp2p-gossipsub", "libp2p-identify", "libp2p-kad", "libp2p-mdns", @@ -1673,20 +1641,18 @@ dependencies = [ "libp2p-noise", "libp2p-ping", "libp2p-plaintext", + "libp2p-pnet", "libp2p-secio", "libp2p-swarm", "libp2p-tcp", "libp2p-uds", "libp2p-wasm-ext", - "libp2p-websocket", "libp2p-yamux", "parity-multiaddr", "parity-multihash", - "parking_lot 0.9.0", - "smallvec 0.6.13", - "tokio-codec", - "tokio-executor", - "tokio-io", + "parking_lot 0.10.0", + "pin-project", + "smallvec 1.2.0", "wasm-timer", ] @@ -1694,381 +1660,378 @@ dependencies = [ name = "libp2p-comit" version = "0.1.0" dependencies = [ - "bytes 0.4.12", - "derivative", - "futures 0.1.29", - "libp2p-core", - "libp2p-swarm", - "log 0.4.8", - "matches", - "multistream-select", + "bytes 0.5.4", + "derivative 2.0.1", + "futures 0.3.4", + "futures_codec 0.4.0", + "libp2p", "serde", "serde_json", "spectral", "strum_macros", "thiserror", - "tokio 0.1.22", - "tokio-codec", "tracing", ] [[package]] name = "libp2p-core" -version = "0.13.2" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01efc769c392d0d8863a7160d266f9b9f794968554f87490c8af4aa34ccaa94f" +checksum = "3b874594c4b29de1a29f27871feba8e6cd13aa54a8a1e8f8c7cf3dfac5ca287c" dependencies = [ "asn1_der", "bs58", - "bytes 0.4.12", "ed25519-dalek", - "failure", "fnv", - "futures 0.1.29", + "futures 0.3.4", + "futures-timer 3.0.2", "lazy_static", "libsecp256k1", "log 0.4.8", "multistream-select", "parity-multiaddr", "parity-multihash", - "parking_lot 0.9.0", - "protobuf", - "quick-error", + "parking_lot 0.10.0", + "pin-project", + "prost", + "prost-build", "rand 0.7.3", "ring", "rw-stream-sink", "sha2", - "smallvec 0.6.13", - "tokio-executor", - "tokio-io", - "unsigned-varint 0.2.3", - "untrusted", + "smallvec 1.2.0", + "thiserror", + "unsigned-varint", "void", - "wasm-timer", "zeroize", ] [[package]] name = "libp2p-core-derive" -version = "0.13.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eeb2704ac14c60f31967e351ed928b848526a5fc6db4104520020665012826f" +checksum = "96d472e9d522f588805c77801de10b957be84e10f019ca5f869fa1825b15ea9b" dependencies = [ - "quote 0.6.13", - "syn 0.15.44", + "quote 1.0.2", + "syn 1.0.15", ] [[package]] name = "libp2p-deflate" -version = "0.5.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2b0bf5d37692ac90e2bffa436bec26c0b0def6c0cab7ea85ff67a353d58aaa" +checksum = "2e25004d4d9837b44b22c5f1a69be1724a5168fef6cff1716b5176a972c3aa62" dependencies = [ "flate2", - "futures 0.1.29", + "futures 0.3.4", "libp2p-core", - "tokio-io", ] [[package]] name = "libp2p-dns" -version = "0.13.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3175fb0fc9016c95c8517a297bbdb5fb6bfbd5665bacd2eb23495d1cbdeb033" +checksum = "b99e552f9939b606eb4b59f7f64d9b01e3f96752f47e350fc3c5fc646ed3f649" dependencies = [ - "futures 0.1.29", + "futures 0.3.4", "libp2p-core", "log 0.4.8", - "tokio-dns-unofficial", ] [[package]] name = "libp2p-floodsub" -version = "0.13.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b360bbaad2560d6b8a905bd63528273d933fe54475a44def47f31e23108b3683" +checksum = "1d3234f12e44f9a50351a9807b97fe7de11eb9ae4482370392ba10da6dc90722" dependencies = [ - "bs58", - "bytes 0.4.12", "cuckoofilter", "fnv", - "futures 0.1.29", + "futures 0.3.4", "libp2p-core", "libp2p-swarm", - "protobuf", - "rand 0.6.5", - "smallvec 0.6.13", - "tokio-io", + "prost", + "prost-build", + "rand 0.7.3", + "smallvec 1.2.0", +] + +[[package]] +name = "libp2p-gossipsub" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d46cb3e0841bd951cbf4feae56cdc081e6347836a644fb260c3ec554149b4006" +dependencies = [ + "base64 0.11.0", + "byteorder 1.3.4", + "bytes 0.5.4", + "fnv", + "futures 0.3.4", + "futures_codec 0.3.4", + "libp2p-core", + "libp2p-swarm", + "log 0.4.8", + "lru", + "prost", + "prost-build", + "rand 0.7.3", + "sha2", + "smallvec 1.2.0", + "unsigned-varint", + "wasm-timer", ] [[package]] name = "libp2p-identify" -version = "0.13.2" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c087bcd044a6f67a994573a92a109487a902a31555e4e63bcc4ae144c45594fe" +checksum = "bfeb935a9bd41263e4f3a24b988e9f4a044f3ae89ac284e83c17fe2f84e0d66b" dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", + "futures 0.3.4", "libp2p-core", "libp2p-swarm", "log 0.4.8", - "parity-multiaddr", - "protobuf", - "smallvec 0.6.13", - "tokio-codec", - "tokio-io", - "unsigned-varint 0.2.3", + "prost", + "prost-build", + "smallvec 1.2.0", "wasm-timer", ] [[package]] name = "libp2p-kad" -version = "0.13.2" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaf76a5b33b6c0203e85d450ae1855cae6860dc82eb0174ac1fee8bf68f7af5" +checksum = "464dc8412978d40f0286be72ed9ab5e0e1386a4a06e7f174526739b5c3c1f041" dependencies = [ "arrayvec 0.5.1", - "bytes 0.4.12", + "bytes 0.5.4", "either", "fnv", - "futures 0.1.29", + "futures 0.3.4", + "futures_codec 0.3.4", "libp2p-core", "libp2p-swarm", "log 0.4.8", - "parity-multiaddr", "parity-multihash", - "protobuf", + "prost", + "prost-build", "rand 0.7.3", "sha2", - "smallvec 0.6.13", - "tokio-codec", - "tokio-io", - "uint 0.8.2", - "unsigned-varint 0.2.3", + "smallvec 1.2.0", + "uint", + "unsigned-varint", "void", "wasm-timer", ] [[package]] name = "libp2p-mdns" -version = "0.13.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4c2e225a7dfc571c3ad77a0a5ecccc9537afe42d72289ac9f19768567cd677d" +checksum = "881fcfb360c2822db9f0e6bb6f89529621556ed9a8b038313414eda5107334de" dependencies = [ + "async-std", "data-encoding", "dns-parser", - "futures 0.1.29", + "either", + "futures 0.3.4", + "lazy_static", "libp2p-core", "libp2p-swarm", "log 0.4.8", "net2", - "parity-multiaddr", - "rand 0.6.5", - "smallvec 0.6.13", - "tokio-io", - "tokio-reactor", - "tokio-udp", + "rand 0.7.3", + "smallvec 1.2.0", "void", "wasm-timer", ] [[package]] name = "libp2p-mplex" -version = "0.13.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2fe584816d993dc0f893396521a3c93191d78a6f28a892b150baa714a12c3e5" +checksum = "d8507b37ad0eed275efcde67a023c3d85af6c80768b193845b9288e848e1af95" dependencies = [ - "bytes 0.4.12", + "bytes 0.5.4", "fnv", - "futures 0.1.29", + "futures 0.3.4", + "futures_codec 0.3.4", "libp2p-core", "log 0.4.8", - "parking_lot 0.8.0", - "tokio-codec", - "tokio-io", - "unsigned-varint 0.2.3", + "parking_lot 0.10.0", + "unsigned-varint", ] [[package]] name = "libp2p-noise" -version = "0.11.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50494fcba7cdab08390d72b3cb9d2c72fcf178e6a0c1043855ab259d818b972" +checksum = "b15a8a3d71f898beb6f854c8aae27aa1d198e0d1f2e49412261c2d90ef39675a" dependencies = [ - "bytes 0.4.12", - "curve25519-dalek 1.2.3", - "futures 0.1.29", + "curve25519-dalek", + "futures 0.3.4", "lazy_static", "libp2p-core", "log 0.4.8", - "protobuf", + "prost", + "prost-build", "rand 0.7.3", - "ring", + "sha2", "snow", - "tokio-io", + "static_assertions 1.1.0", "x25519-dalek", "zeroize", ] [[package]] name = "libp2p-ping" -version = "0.13.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b975ad345eb9bb29ddc64670664a50a8ab3e66e28357abb0f83cfc0a9ca2d78" +checksum = "33d22f2f228b3a828dca1cb8aa9fa331e0bc9c36510cb2c1916956e20dc85e8c" dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", + "futures 0.3.4", "libp2p-core", "libp2p-swarm", "log 0.4.8", - "parity-multiaddr", "rand 0.7.3", - "tokio-io", "void", "wasm-timer", ] [[package]] name = "libp2p-plaintext" -version = "0.13.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f07be6983e1c00e8f6a5676da54ed3a8cae7fb50f1fb6ea163414613ca656cc" +checksum = "56126a204d7b3382bac163143ff4125a14570b3ba76ba979103d1ae1abed1923" dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", + "bytes 0.5.4", + "futures 0.3.4", + "futures_codec 0.3.4", "libp2p-core", "log 0.4.8", - "protobuf", + "prost", + "prost-build", "rw-stream-sink", - "tokio-io", + "unsigned-varint", "void", ] +[[package]] +name = "libp2p-pnet" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b916938a8868f75180aeeffcc6a516a922d165e8fa2a90b57bad989d1ccbb57a" +dependencies = [ + "futures 0.3.4", + "log 0.4.8", + "pin-project", + "rand 0.7.3", + "salsa20", + "sha3", +] + [[package]] name = "libp2p-secio" -version = "0.13.1" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04aa6d67a5fb2b36241a1ba54037a13deb2594cf141e43b597ce379521d530a8" +checksum = "1219e9ecb4945d7331a05f5ffe96a1f6e28051bfa1223d4c60353c251de0354e" dependencies = [ "aes-ctr", - "bytes 0.4.12", "ctr", - "futures 0.1.29", + "futures 0.3.4", "hmac", "js-sys", "lazy_static", "libp2p-core", "log 0.4.8", "parity-send-wrapper", - "protobuf", - "rand 0.6.5", + "pin-project", + "prost", + "prost-build", + "quicksink", + "rand 0.7.3", "ring", "rw-stream-sink", "sha2", - "tokio-codec", - "tokio-io", + "static_assertions 1.1.0", "twofish", - "untrusted", "wasm-bindgen", - "wasm-bindgen-futures 0.3.27", + "wasm-bindgen-futures", "web-sys", ] [[package]] name = "libp2p-swarm" -version = "0.3.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd55bc9f5f9eac2bb1ff24ca3c8a655810a566ac38c7a6ee1f30aced5a62905b" +checksum = "275471e7c0e88ae004660866cd54f603bd8bd1f4caef541a27f50dd8640c4d4c" dependencies = [ - "futures 0.1.29", + "futures 0.3.4", "libp2p-core", - "smallvec 0.6.13", - "tokio-io", + "log 0.4.8", + "smallvec 1.2.0", "void", "wasm-timer", ] [[package]] name = "libp2p-tcp" -version = "0.13.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234a7093d05651ab5630db926a4a42ca8978a65bab8c27c2ce2b66b200c76989" +checksum = "f9e80ad4e3535345f3d666554ce347d3100453775611c05c60786bf9a1747a10" dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", + "async-std", + "futures 0.3.4", + "futures-timer 3.0.2", "get_if_addrs", "ipnet", "libp2p-core", "log 0.4.8", - "tokio-io", - "tokio-tcp", - "tokio-timer 0.2.12", ] [[package]] name = "libp2p-uds" -version = "0.13.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2fe0648967da3e56e4a55055c857c8c48326b66be0047d0e04c8ca60d34630" +checksum = "76d329564a43da9d0e055a5b938633c4a8ceab1f59cec133fbc4647917c07341" dependencies = [ - "futures 0.1.29", + "async-std", + "futures 0.3.4", "libp2p-core", "log 0.4.8", - "tokio-uds", ] [[package]] name = "libp2p-wasm-ext" -version = "0.6.0" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f7b8f2bd81fb356e81352d4513856bc21215ecf91502aa1f55b6449642a9acf" +checksum = "923581c055bc4b8c5f42d4ce5ef43e52fe5216f1ea4bc26476cb8a966ce6220b" dependencies = [ - "futures 0.1.29", + "futures 0.3.4", "js-sys", "libp2p-core", "parity-send-wrapper", - "tokio-io", "wasm-bindgen", - "wasm-bindgen-futures 0.3.27", + "wasm-bindgen-futures", ] [[package]] -name = "libp2p-websocket" -version = "0.13.0" +name = "libp2p-yamux" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d74d4fc229ad7e8d1a973178786bdcd5dadbdd7b9822c4477c8687df6f82f66" +checksum = "9dac30de24ccde0e67f363d71a125c587bbe6589503f664947e9b084b68a34f1" dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", + "futures 0.3.4", "libp2p-core", - "log 0.4.8", - "rw-stream-sink", - "soketto", - "tokio-codec", - "tokio-io", - "tokio-rustls", - "url 2.1.1", - "webpki-roots", -] - -[[package]] -name = "libp2p-yamux" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1913eb7dd6eb5515957b6f1770296f6921968db87bc9b985f0e974b6657e1003" -dependencies = [ - "futures 0.1.29", - "libp2p-core", - "log 0.4.8", - "tokio-io", + "parking_lot 0.10.0", + "thiserror", "yamux", ] [[package]] name = "libsecp256k1" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df6edf84fd62aad1c93932b39324eaeda3912c1d26bc18dfaee6293848e49a50" +checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962" dependencies = [ "arrayref", "crunchy", @@ -2091,27 +2054,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "libz-sys" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "lock_api" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed946d4529956a20f2d63ebe1b69996d5a2137c91913fe3ebbeff957f5bca7ff" -dependencies = [ - "scopeguard", -] - [[package]] name = "lock_api" version = "0.3.3" @@ -2172,9 +2114,9 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "memchr" -version = "2.3.0" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" [[package]] name = "memoffset" @@ -2203,7 +2145,7 @@ dependencies = [ "migrations_internals", "proc-macro2 1.0.8", "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", ] [[package]] @@ -2233,9 +2175,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f3f74f726ae935c3f514300cc6773a0c9492abc5e972d42ba0c0ebb88757625" +checksum = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5" dependencies = [ "adler32", ] @@ -2255,7 +2197,7 @@ dependencies = [ "log 0.4.8", "miow", "net2", - "slab 0.4.2", + "slab", "winapi 0.2.8", ] @@ -2282,18 +2224,24 @@ dependencies = [ "ws2_32-sys", ] +[[package]] +name = "multimap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97fbd5d00e0e37bfb10f433af8f5aaf631e739368dc9fc28286ca81ca4948dc" + [[package]] name = "multistream-select" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc3ef54aab1b2e37e911bcb99e376dbe4c1e0710afcdb8428608e4f993b39c47" +checksum = "f938ffe420493e77c8b6cbcc3f282283f68fc889c5dcbc8e51668d5f3a01ad94" dependencies = [ - "bytes 0.4.12", + "bytes 0.5.4", "futures 0.1.29", "log 0.4.8", - "smallvec 0.6.13", + "smallvec 1.2.0", "tokio-io", - "unsigned-varint 0.2.3", + "unsigned-varint", ] [[package]] @@ -2333,9 +2281,9 @@ checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" [[package]] name = "nohash-hasher" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721a2bf1c26159ebf17e0a980bc4ce61f4b2fec5ec3b42d42fddd7a84a9e538f" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] name = "nom" @@ -2494,6 +2442,12 @@ dependencies = [ "libc", ] +[[package]] +name = "once_cell" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" + [[package]] name = "opaque-debug" version = "0.2.3" @@ -2502,9 +2456,9 @@ checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] name = "openssl" -version = "0.10.26" +version = "0.10.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3cc5799d98e1088141b8e01ff760112bbd9f19d850c124500566ca6901a585" +checksum = "973293749822d7dd6370d6da1e523b0d1db19f06c459134c658b2a4261378b52" dependencies = [ "bitflags", "cfg-if", @@ -2522,50 +2476,40 @@ checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" [[package]] name = "openssl-sys" -version = "0.9.53" +version = "0.9.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465d16ae7fc0e313318f7de5cecf57b2fbe7511fd213978b457e1c96ff46736f" +checksum = "1024c0a59774200a555087a6da3f253a9095a5f344e353b212ac4c8b8e450986" dependencies = [ - "autocfg 0.1.7", + "autocfg 1.0.0", "cc", "libc", "pkg-config", "vcpkg", ] -[[package]] -name = "parity-codec" -version = "3.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b9df1283109f542d8852cd6b30e9341acc2137481eb6157d2e62af68b0afec9" -dependencies = [ - "arrayvec 0.4.12", - "serde", -] - [[package]] name = "parity-multiaddr" -version = "0.6.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82afcb7461eae5d122543d8be1c57d306ed89af2d6ff7f8b0f5a3cc8f7e511bc" +checksum = "26df883298bc3f4e92528b4c5cc9f806b791955b136da3e5e939ed9de0fd958b" dependencies = [ "arrayref", "bs58", - "byteorder 1.3.2", - "bytes 0.4.12", + "byteorder 1.3.4", "data-encoding", "parity-multihash", "percent-encoding 2.1.0", "serde", - "unsigned-varint 0.2.3", + "static_assertions 1.1.0", + "unsigned-varint", "url 2.1.1", ] [[package]] name = "parity-multihash" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a4d7b05e51bff5ae2c29c7b8c3d889985bbd8f4e15b3542fcc1f6f9666d292" +checksum = "7a1cd2ba02391b81367bec529fb209019d718684fdc8ad6a712c2b536e46f775" dependencies = [ "blake2", "bytes 0.5.4", @@ -2573,7 +2517,19 @@ dependencies = [ "sha-1", "sha2", "sha3", - "unsigned-varint 0.3.0", + "unsigned-varint", +] + +[[package]] +name = "parity-scale-codec" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f509c5e67ca0605ee17dcd3f91ef41cadd685c75a298fb6261b781a5acb3f910" +dependencies = [ + "arrayvec 0.5.1", + "bitvec", + "byte-slice-cast", + "serde", ] [[package]] @@ -2584,36 +2540,34 @@ checksum = "aa9777aa91b8ad9dd5aaa04a9b6bcb02c7f1deb952fca5a66034d5e63afc5c6f" [[package]] name = "parking_lot" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7767817701cce701d5585b9c4db3cdd02086398322c1d7e8bf5094a96a2ce7" +checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" dependencies = [ - "lock_api 0.2.0", - "parking_lot_core 0.5.0", + "lock_api", + "parking_lot_core 0.6.2", "rustc_version", ] [[package]] name = "parking_lot" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" +checksum = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc" dependencies = [ - "lock_api 0.3.3", - "parking_lot_core 0.6.2", - "rustc_version", + "lock_api", + "parking_lot_core 0.7.0", ] [[package]] name = "parking_lot_core" -version = "0.5.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb88cb1cb3790baa6776844f968fea3be44956cf184fa1be5a03341f5491278c" +checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" dependencies = [ "cfg-if", "cloudabi", "libc", - "rand 0.6.5", "redox_syscall", "rustc_version", "smallvec 0.6.13", @@ -2622,24 +2576,23 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" +checksum = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1" dependencies = [ "cfg-if", "cloudabi", "libc", "redox_syscall", - "rustc_version", - "smallvec 0.6.13", + "smallvec 1.2.0", "winapi 0.3.8", ] [[package]] name = "paste" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "423a519e1c6e828f1e73b720f9d9ed2fa643dce8a7737fb43235ce0b41eeaa49" +checksum = "63e1afe738d71b1ebab5f1207c055054015427dbfc7bbe9ee1266894156ec046" dependencies = [ "paste-impl", "proc-macro-hack", @@ -2647,14 +2600,14 @@ dependencies = [ [[package]] name = "paste-impl" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4214c9e912ef61bf42b81ba9a47e8aad1b2ffaf739ab162bf96d1e011f54e6c5" +checksum = "6d4dc4a7f6f743211c5aab239640a65091535d97d43d92a52bca435a640892bb" dependencies = [ "proc-macro-hack", "proc-macro2 1.0.8", "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", ] [[package]] @@ -2680,6 +2633,16 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "petgraph" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c127eea4a29ec6c85d153c59dc1213f33ec74cead30fe4730aecc88cc1fd92" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project" version = "0.4.8" @@ -2697,7 +2660,7 @@ checksum = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f" dependencies = [ "proc-macro2 1.0.8", "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", ] [[package]] @@ -2726,40 +2689,39 @@ checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" [[package]] name = "primitive-types" -version = "0.3.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2288eb2a39386c4bc817974cc413afe173010dc80e470fcb1e9a35580869f024" +checksum = "e4336f4f5d5524fa60bcbd6fe626f9223d8142a50e7053e979acdf0da41ab975" dependencies = [ "fixed-hash", "impl-codec", - "impl-rlp", - "impl-serde", - "uint 0.7.1", + "impl-serde 0.3.0", + "uint", ] [[package]] name = "proc-macro-error" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875077759af22fa20b610ad4471d8155b321c89c3f2785526c9839b099be4e0a" +checksum = "052b3c9af39c7e5e94245f820530487d19eb285faedcb40e0c3275132293f242" dependencies = [ "proc-macro-error-attr", "proc-macro2 1.0.8", "quote 1.0.2", "rustversion", - "syn 1.0.14", + "syn 1.0.15", ] [[package]] name = "proc-macro-error-attr" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5717d9fa2664351a01ed73ba5ef6df09c01a521cb42cb65a061432a826f3c7a" +checksum = "d175bef481c7902e63e3165627123fff3502f06ac043d3ef42d08c1246da9253" dependencies = [ "proc-macro2 1.0.8", "quote 1.0.2", "rustversion", - "syn 1.0.14", + "syn 1.0.15", "syn-mid", ] @@ -2771,7 +2733,7 @@ checksum = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" dependencies = [ "proc-macro2 1.0.8", "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", ] [[package]] @@ -2799,10 +2761,55 @@ dependencies = [ ] [[package]] -name = "protobuf" -version = "2.8.1" +name = "prost" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce49aefe0a6144a45de32927c77bd2859a5f7677b55f220ae5b744e87389c212" +dependencies = [ + "bytes 0.5.4", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b10678c913ecbd69350e8535c3aef91a8676c0773fc1d7b95cdd196d7f2f26" +dependencies = [ + "bytes 0.5.4", + "heck", + "itertools", + "log 0.4.8", + "multimap", + "petgraph", + "prost", + "prost-types", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537aa19b95acde10a12fec4301466386f757403de4cd4e5b4fa78fb5ecb18f72" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.15", +] + +[[package]] +name = "prost-types" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40361836defdd5871ff7e84096c6f6444af7fc157f8ef1789f54f147687caa20" +checksum = "1834f67c0697c001304b75be76f67add9c89742eda3a085ad8ee0bb38c3417aa" +dependencies = [ + "bytes 0.5.4", + "prost", +] [[package]] name = "quick-error" @@ -2822,6 +2829,17 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "quicksink" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8461ef7445f61fd72d8dcd0629ce724b9131b3c2eb36e83a5d3d4161c127530" +dependencies = [ + "futures-core", + "futures-sink", + "pin-project-lite", +] + [[package]] name = "quote" version = "0.6.13" @@ -2863,38 +2881,6 @@ dependencies = [ "winapi 0.3.8", ] -[[package]] -name = "rand" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" -dependencies = [ - "cloudabi", - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "winapi 0.3.8", -] - -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -dependencies = [ - "autocfg 0.1.7", - "libc", - "rand_chacha 0.1.1", - "rand_core 0.4.2", - "rand_hc 0.1.0", - "rand_isaac", - "rand_jitter", - "rand_os", - "rand_pcg", - "rand_xorshift", - "winapi 0.3.8", -] - [[package]] name = "rand" version = "0.7.3" @@ -2903,19 +2889,9 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom", "libc", - "rand_chacha 0.2.1", + "rand_chacha", "rand_core 0.5.1", - "rand_hc 0.2.0", -] - -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -dependencies = [ - "autocfg 0.1.7", - "rand_core 0.3.1", + "rand_hc", ] [[package]] @@ -2952,15 +2928,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "rand_hc" version = "0.2.0" @@ -2970,59 +2937,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -dependencies = [ - "libc", - "rand_core 0.4.2", - "winapi 0.3.8", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -dependencies = [ - "cloudabi", - "fuchsia-cprng", - "libc", - "rand_core 0.4.2", - "rdrand", - "winapi 0.3.8", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -dependencies = [ - "autocfg 0.1.7", - "rand_core 0.4.2", -] - -[[package]] -name = "rand_xorshift" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "rdrand" version = "0.4.0" @@ -3040,7 +2954,7 @@ checksum = "c9fd3125016eb3257fe8038dcafaa5fbecc081bcd46defe9ccb98ceae0175219" dependencies = [ "proc-macro2 1.0.8", "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", ] [[package]] @@ -3078,7 +2992,7 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92b73c2a1770c255c240eaa4ee600df1704a38dc3feaa6e949e7fcd4f8dc09f9" dependencies = [ - "byteorder 1.3.2", + "byteorder 1.3.4", "regex-syntax", "utf8-ranges", ] @@ -3100,18 +3014,18 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.10.1" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0e798e19e258bf6c30a304622e3e9ac820e483b06a1857a026e1f109b113fe4" +checksum = "02b81e49ddec5109a9dcfc5f2a317ff53377c915e9ae9d4f2fb50914b85614e2" dependencies = [ "base64 0.11.0", "bytes 0.5.4", "encoding_rs", "futures-core", "futures-util", - "http 0.2.0", - "http-body 0.3.1", - "hyper 0.13.1", + "http", + "http-body", + "hyper 0.13.3", "hyper-tls", "js-sys", "lazy_static", @@ -3125,20 +3039,20 @@ dependencies = [ "serde_json", "serde_urlencoded", "time", - "tokio 0.2.11", + "tokio", "tokio-tls", "url 2.1.1", "wasm-bindgen", - "wasm-bindgen-futures 0.4.8", + "wasm-bindgen-futures", "web-sys", "winreg", ] [[package]] name = "ring" -version = "0.16.9" +version = "0.16.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6747f8da1f2b1fabbee1aaa4eb8a11abf9adef0bf58a41cee45db5d59cecdfac" +checksum = "741ba1704ae21999c00942f9f5944f801e977f54302af346b596287599ad1862" dependencies = [ "cc", "lazy_static", @@ -3167,15 +3081,9 @@ dependencies = [ "base64 0.11.0", "blake2b_simd", "constant_time_eq", - "crossbeam-utils 0.7.0", + "crossbeam-utils", ] -[[package]] -name = "rustc-demangle" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" - [[package]] name = "rustc-hex" version = "2.1.0" @@ -3197,19 +3105,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustls" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b25a18b1bf7387f0145e7f8324e700805aade3842dd3db2e74e4cdeb4677c09e" -dependencies = [ - "base64 0.10.1", - "log 0.4.8", - "ring", - "sct", - "webpki", -] - [[package]] name = "rustversion" version = "1.0.2" @@ -3218,18 +3113,18 @@ checksum = "b3bba175698996010c4f6dce5e7f173b6eb781fce25d2cfc45e27091ce0b79f6" dependencies = [ "proc-macro2 1.0.8", "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", ] [[package]] name = "rw-stream-sink" -version = "0.1.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f9cbe61c20455d3015b2bb7be39e1872310283b8e5a52f5b242b0ac7581fe78" +checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020" dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", - "tokio-io", + "futures 0.3.4", + "pin-project", + "static_assertions 1.1.0", ] [[package]] @@ -3245,42 +3140,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" [[package]] -name = "schannel" -version = "0.1.16" +name = "salsa20" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f550b06b6cba9c8b8be3ee73f391990116bf527450d2556e9b9ce263b9a021" +checksum = "2324b0e8c3bb9a586a571fdb3136f70e7e2c748de00a78043f86e0cff91f91fe" dependencies = [ - "lazy_static", - "winapi 0.3.8", + "byteorder 1.3.4", + "salsa20-core", + "stream-cipher", ] [[package]] -name = "scoped-tls" -version = "0.1.2" +name = "salsa20-core" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28" +checksum = "2fe6cc1b9f5a5867853ade63099de70f042f7679e408d1ffe52821c9248e6e69" +dependencies = [ + "stream-cipher", +] [[package]] -name = "scoped-tls" -version = "1.0.0" +name = "schannel" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +checksum = "507a9e6e8ffe0a4e0ebb9a10293e62fdf7657c06f1b8bb07a8fcf697d2abf295" +dependencies = [ + "lazy_static", + "winapi 0.3.8", +] [[package]] -name = "scopeguard" +name = "scoped-tls" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" [[package]] -name = "sct" -version = "0.6.0" +name = "scopeguard" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" -dependencies = [ - "ring", - "untrusted", -] +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "secp256k1" @@ -3352,6 +3251,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-hex" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca37e3e4d1b39afd7ff11ee4e947efae85adfddf4841787bfa47c470e96dc26d" +dependencies = [ + "array-init", + "serde", + "smallvec 0.6.13", +] + [[package]] name = "serde_derive" version = "1.0.104" @@ -3360,14 +3270,14 @@ checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" dependencies = [ "proc-macro2 1.0.8", "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", ] [[package]] name = "serde_json" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15913895b61e0be854afd32fd4163fcd2a3df34142cf2cb961b310ce694cbf90" +checksum = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25" dependencies = [ "itoa", "ryu", @@ -3419,12 +3329,6 @@ dependencies = [ "opaque-debug", ] -[[package]] -name = "sha1" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" - [[package]] name = "sha2" version = "0.8.1" @@ -3465,19 +3369,13 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2795d361da761822a8681fd70bde6bdd7ad4955413ffb8ad883520ee5684c870" dependencies = [ - "derivative", - "http 0.2.0", + "derivative 1.0.4", + "http", "readonly", "serde", "serde_json", ] -[[package]] -name = "slab" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" - [[package]] name = "slab" version = "0.4.2" @@ -3495,9 +3393,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4" +checksum = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" [[package]] name = "snow" @@ -3506,30 +3404,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afb767eee7d257ba202f0b9b08673bc13b22281632ef45267b19f13100accd2f" dependencies = [ "arrayref", + "blake2-rfc", + "chacha20-poly1305-aead", + "rand 0.7.3", "rand_core 0.5.1", "ring", "rustc_version", + "sha2", "subtle 2.2.2", -] - -[[package]] -name = "soketto" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bceb1a3a15232d013d9a3b7cac9e5ce8e2313f348f01d4bc1097e5e53aa07095" -dependencies = [ - "base64 0.10.1", - "bytes 0.4.12", - "flate2", - "futures 0.1.29", - "http 0.1.21", - "httparse", - "log 0.4.8", - "rand 0.6.5", - "sha1", - "smallvec 0.6.13", - "tokio-codec", - "tokio-io", + "x25519-dalek", ] [[package]] @@ -3553,12 +3436,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "static_assertions" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19be23126415861cb3a23e501d34a708f7f9b2183c5252d690941c2e69199d5" - [[package]] name = "static_assertions" version = "0.3.4" @@ -3580,15 +3457,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "string" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d" -dependencies = [ - "bytes 0.4.12", -] - [[package]] name = "strsim" version = "0.8.0" @@ -3597,9 +3465,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "structopt" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1bcbed7d48956fcbb5d80c6b95aedb553513de0a1b451ea92679d999c010e98" +checksum = "3fe43617218c0805c6eb37160119dc3c548110a67786da7218d1c6555212f073" dependencies = [ "clap", "lazy_static", @@ -3608,33 +3476,33 @@ dependencies = [ [[package]] name = "structopt-derive" -version = "0.4.2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "095064aa1f5b94d14e635d0a5684cf140c43ae40a0fd990708d38f5d669e5f64" +checksum = "c6e79c80e0f4efd86ca960218d4e056249be189ff1c42824dcd9a7f51a56f0bd" dependencies = [ "heck", "proc-macro-error", "proc-macro2 1.0.8", "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", ] [[package]] name = "strum" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "530efb820d53b712f4e347916c5e7ed20deb76a4f0457943b3182fb889b06d2c" +checksum = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b" [[package]] name = "strum_macros" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6e163a520367c465f59e0a61a23cfae3b10b6546d78b6f672a382be79f7110" +checksum = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c" dependencies = [ "heck", "proc-macro2 1.0.8", "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", ] [[package]] @@ -3673,9 +3541,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5" +checksum = "7a0294dc449adc58bb6592fff1a23d3e5e6e235afc6a0ffca2657d19e7bbffe5" dependencies = [ "proc-macro2 1.0.8", "quote 1.0.2", @@ -3690,7 +3558,7 @@ checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" dependencies = [ "proc-macro2 1.0.8", "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", ] [[package]] @@ -3701,7 +3569,7 @@ checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" dependencies = [ "proc-macro2 1.0.8", "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", "unicode-xid 0.2.0", ] @@ -3725,8 +3593,8 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aacbbd75f3df9b76dcb5ef222594e94780d5da271d2e533e2e00d4b001877c5" dependencies = [ - "derivative", - "hex 0.4.1", + "derivative 1.0.4", + "hex 0.4.2", "hmac", "log 0.4.8", "rand 0.7.3", @@ -3746,22 +3614,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "205684fd018ca14432b12cce6ea3d46763311a571c3d294e71ba3f01adcf1aad" +checksum = "ee14bf8e6767ab4c687c9e8bc003879e042a96fd67a3ba5934eadb6536bef4db" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e4d2e50ca050ed44fb58309bdce3efa79948f84f9993ad1978de5eebdce5a7" +checksum = "a7b51e1fbc44b5a0840be594fbc0f960be09050f2617e61e6aa43bef97cd3ef4" dependencies = [ "proc-macro2 1.0.8", "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", ] [[package]] @@ -3804,33 +3672,9 @@ dependencies = [ [[package]] name = "tokio" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", - "mio", - "num_cpus", - "tokio-codec", - "tokio-current-thread", - "tokio-executor", - "tokio-fs", - "tokio-io", - "tokio-reactor", - "tokio-sync", - "tokio-tcp", - "tokio-threadpool", - "tokio-timer 0.2.12", - "tokio-udp", - "tokio-uds", -] - -[[package]] -name = "tokio" -version = "0.2.11" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fdd17989496f49cdc57978c96f0c9fe5e4a58a8bddc6813c449a4624f6a030b" +checksum = "0fa5e81d6bc4e67fe889d5783bd2a128ab2e0cfa487e0be16b6a8d177b101616" dependencies = [ "bytes 0.5.4", "fnv", @@ -3841,116 +3685,15 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", - "slab 0.4.2", + "slab", "tokio-macros", ] -[[package]] -name = "tokio-buf" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46" -dependencies = [ - "bytes 0.4.12", - "either", - "futures 0.1.29", -] - -[[package]] -name = "tokio-codec" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", - "tokio-io", -] - -[[package]] -name = "tokio-compat" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4000e3c984d0e58ace4926f1eae4d830a90a76c386dccf5b82aeca4cbee6df" -dependencies = [ - "futures 0.1.29", - "futures-core", - "futures-util", - "pin-project-lite", - "tokio 0.2.11", - "tokio-current-thread", - "tokio-executor", - "tokio-reactor", - "tokio-timer 0.2.12", -] - -[[package]] -name = "tokio-core" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeeffbbb94209023feaef3c196a41cbcdafa06b4a6f893f68779bb5e53796f71" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", - "iovec", - "log 0.4.8", - "mio", - "scoped-tls 0.1.2", - "tokio 0.1.22", - "tokio-executor", - "tokio-io", - "tokio-reactor", - "tokio-timer 0.2.12", -] - -[[package]] -name = "tokio-current-thread" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443" -dependencies = [ - "futures 0.1.29", - "tokio-executor", -] - -[[package]] -name = "tokio-dns-unofficial" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c65483db54eb91b4ef3a9389a3364558590faf30ce473141707c0e16fda975" -dependencies = [ - "futures 0.1.29", - "futures-cpupool", - "lazy_static", - "tokio 0.1.22", -] - -[[package]] -name = "tokio-executor" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6df436c42b0c3330a82d855d2ef017cd793090ad550a6bc2184f4b933532ab" -dependencies = [ - "crossbeam-utils 0.6.6", - "futures 0.1.29", -] - -[[package]] -name = "tokio-fs" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fe6dc22b08d6993916647d108a1a7d15b9cd29c4f4496c62b92c45b5041b7af" -dependencies = [ - "futures 0.1.29", - "tokio-io", - "tokio-threadpool", -] - [[package]] name = "tokio-io" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926" +checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" dependencies = [ "bytes 0.4.12", "futures 0.1.29", @@ -3965,103 +3708,7 @@ checksum = "f4b1e7ed7d5d4c2af3d999904b0eebe76544897cdbfb2b9684bed2174ab20f7c" dependencies = [ "proc-macro2 1.0.8", "quote 1.0.2", - "syn 1.0.14", -] - -[[package]] -name = "tokio-reactor" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6732fe6b53c8d11178dcb77ac6d9682af27fc6d4cb87789449152e5377377146" -dependencies = [ - "crossbeam-utils 0.6.6", - "futures 0.1.29", - "lazy_static", - "log 0.4.8", - "mio", - "num_cpus", - "parking_lot 0.9.0", - "slab 0.4.2", - "tokio-executor", - "tokio-io", - "tokio-sync", -] - -[[package]] -name = "tokio-rustls" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d7cf08f990090abd6c6a73cab46fed62f85e8aef8b99e4b918a9f4a637f0676" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", - "iovec", - "rustls", - "tokio-io", - "webpki", -] - -[[package]] -name = "tokio-sync" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06554cce1ae4a50f42fba8023918afa931413aded705b560e29600ccf7c6d76" -dependencies = [ - "fnv", - "futures 0.1.29", -] - -[[package]] -name = "tokio-tcp" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", - "iovec", - "mio", - "tokio-io", - "tokio-reactor", -] - -[[package]] -name = "tokio-threadpool" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c32ffea4827978e9aa392d2f743d973c1dfa3730a2ed3f22ce1e6984da848c" -dependencies = [ - "crossbeam-deque", - "crossbeam-queue", - "crossbeam-utils 0.6.6", - "futures 0.1.29", - "lazy_static", - "log 0.4.8", - "num_cpus", - "slab 0.4.2", - "tokio-executor", -] - -[[package]] -name = "tokio-timer" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6131e780037787ff1b3f8aad9da83bca02438b72277850dd6ad0d455e0e20efc" -dependencies = [ - "futures 0.1.29", - "slab 0.3.0", -] - -[[package]] -name = "tokio-timer" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1739638e364e558128461fc1ad84d997702c8e31c2e6b18fb99842268199e827" -dependencies = [ - "crossbeam-utils 0.6.6", - "futures 0.1.29", - "slab 0.4.2", - "tokio-executor", + "syn 1.0.15", ] [[package]] @@ -4071,40 +3718,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bde02a3a5291395f59b06ec6945a3077602fac2b07eeeaf0dee2122f3619828" dependencies = [ "native-tls", - "tokio 0.2.11", -] - -[[package]] -name = "tokio-udp" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02298505547f73e60f568359ef0d016d5acd6e830ab9bc7c4a5b3403440121b" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", - "log 0.4.8", - "mio", - "tokio-codec", - "tokio-io", - "tokio-reactor", -] - -[[package]] -name = "tokio-uds" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037ffc3ba0e12a0ab4aca92e5234e0dedeb48fddf6ccd260f1f150a36a9f2445" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", - "iovec", - "libc", - "log 0.4.8", - "mio", - "mio-uds", - "tokio-codec", - "tokio-io", - "tokio-reactor", + "tokio", ] [[package]] @@ -4118,7 +3732,7 @@ dependencies = [ "futures-sink", "log 0.4.8", "pin-project-lite", - "tokio 0.2.11", + "tokio", ] [[package]] @@ -4138,9 +3752,9 @@ checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" [[package]] name = "tracing" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e213bd24252abeb86a0b7060e02df677d367ce6cb772cef17e9214b8390a8d3" +checksum = "1721cc8cf7d770cc4257872507180f35a4797272f5962f24c806af9e7faf52ab" dependencies = [ "cfg-if", "tracing-attributes", @@ -4149,23 +3763,33 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cfd395def5a60236e187e1ff905cb55668a59f29928dec05e6e1b1fd2ac1f3" +checksum = "7fbad39da2f9af1cae3016339ad7f2c7a9e870f12e8fd04c4fd7ef35b30c0d2b" dependencies = [ "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", ] [[package]] name = "tracing-core" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13a46f11e372b8bd4b4398ea54353412fdd7fd42a8370c7e543e218cf7661978" +checksum = "0aa83a9a47081cd522c09c81b31aec2c9273424976f922ad61c053b58350b715" dependencies = [ "lazy_static", ] +[[package]] +name = "tracing-futures" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58b0b7fd92dc7b71f29623cc6836dd7200f32161a2313dd78be233a8405694f6" +dependencies = [ + "pin-project", + "tracing", +] + [[package]] name = "tracing-log" version = "0.1.1" @@ -4179,9 +3803,9 @@ dependencies = [ [[package]] name = "tracing-serde" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65dea8255e378ab7db9db2077a90cb3e051515e18eaa819a405c4eb129b9beb" +checksum = "b6ccba2f8f16e0ed268fc765d9b7ff22e965e7185d32f8f1ec8294fe17d86e79" dependencies = [ "serde", "tracing-core", @@ -4189,9 +3813,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.2.0" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dc36e47794112347ccb9ae8a05b5ec4fab45f01545e4929323cdb1a543a8f4" +checksum = "dedebcf5813b02261d6bab3a12c6a8ae702580c0405a2e8ec16c3713caf14c20" dependencies = [ "ansi_term", "chrono", @@ -4201,7 +3825,7 @@ dependencies = [ "serde", "serde_json", "sharded-slab", - "smallvec 1.1.0", + "smallvec 1.2.0", "tracing-core", "tracing-log", "tracing-serde", @@ -4226,7 +3850,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712d261e83e727c8e2dbb75dacac67c36e35db36a958ee504f2164fc052434e1" dependencies = [ "block-cipher-trait", - "byteorder 1.3.2", + "byteorder 1.3.4", "opaque-debug", ] @@ -4242,25 +3866,13 @@ version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" -[[package]] -name = "uint" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2143cded94692b156c356508d92888acc824db5bffc0b4089732264c6fcf86d4" -dependencies = [ - "byteorder 1.3.2", - "crunchy", - "heapsize", - "rustc-hex", -] - [[package]] name = "uint" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e75a4cdd7b87b28840dba13c483b9a88ee6bbf16ba5c951ee1ecfcf723078e0d" dependencies = [ - "byteorder 1.3.2", + "byteorder 1.3.4", "crunchy", "rustc-hex", "static_assertions 1.1.0", @@ -4299,7 +3911,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" dependencies = [ - "smallvec 1.1.0", + "smallvec 1.2.0", ] [[package]] @@ -4328,20 +3940,14 @@ checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" [[package]] name = "unsigned-varint" -version = "0.2.3" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f0023a96687fe169081e8adce3f65e3874426b7886e9234d490af2dc077959" +checksum = "3b7ffb36714206d2f5f05d61a2bc350415c642f2c54433f0ebf829afbe41d570" dependencies = [ - "bytes 0.4.12", - "tokio-codec", + "bytes 0.5.4", + "futures_codec 0.3.4", ] -[[package]] -name = "unsigned-varint" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c689459fbaeb50e56c6749275f084decfd02194ac5852e6617d95d0d3cf02eaf" - [[package]] name = "untrusted" version = "0.7.0" @@ -4423,17 +4029,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -[[package]] -name = "want" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230" -dependencies = [ - "futures 0.1.29", - "log 0.4.8", - "try-lock", -] - [[package]] name = "want" version = "0.3.0" @@ -4446,24 +4041,24 @@ dependencies = [ [[package]] name = "warp" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce153bc4ad61ed81c255cad4f1bf2474a1d284b482b20eecaefb152d0675fb1b" +checksum = "54cd1e2b3eb3539284d88b76a9afcf5e20f2ef2fab74db5b21a1c30d7d945e82" dependencies = [ "bytes 0.5.4", - "futures 0.3.1", + "futures 0.3.4", "headers", - "http 0.2.0", - "hyper 0.13.1", + "http", + "hyper 0.13.3", "log 0.4.8", "mime 0.3.16", "mime_guess", "pin-project", - "scoped-tls 1.0.0", + "scoped-tls", "serde", "serde_json", "serde_urlencoded", - "tokio 0.2.11", + "tokio", "tower-service", "urlencoding", ] @@ -4497,23 +4092,10 @@ dependencies = [ "log 0.4.8", "proc-macro2 1.0.8", "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83420b37346c311b9ed822af41ec2e82839bfe99867ec6c54e2da43b7538771c" -dependencies = [ - "cfg-if", - "futures 0.1.29", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-futures" version = "0.4.8" @@ -4544,7 +4126,7 @@ checksum = "e85031354f25eaebe78bb7db1c3d86140312a911a106b2e29f9cc440ce3e7668" dependencies = [ "proc-macro2 1.0.8", "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -4566,22 +4148,24 @@ dependencies = [ "log 0.4.8", "proc-macro2 1.0.8", "quote 1.0.2", - "syn 1.0.14", + "syn 1.0.15", "wasm-bindgen-backend", "weedle", ] [[package]] name = "wasm-timer" -version = "0.1.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa3e01d234bb71760e685cfafa5e2c96f8ad877c161a721646356651069e26ac" +checksum = "324c5e65a08699c9c4334ba136597ab22b85dccd4b65dd1e36ccf8f723a95b54" dependencies = [ - "futures 0.1.29", + "futures 0.3.4", "js-sys", + "parking_lot 0.9.0", + "pin-utils", "send_wrapper", - "tokio-timer 0.2.12", "wasm-bindgen", + "wasm-bindgen-futures", "web-sys", ] @@ -4599,55 +4183,21 @@ dependencies = [ ] [[package]] -name = "web3" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076f34ed252d74a8521e3b013254b1a39f94a98f23aae7cfc85cda6e7b395664" -dependencies = [ - "arrayvec 0.4.12", - "base64 0.10.1", - "derive_more", - "ethabi", - "ethereum-types", - "futures 0.1.29", - "hyper 0.12.35", - "jsonrpc-core", - "log 0.4.8", - "parking_lot 0.8.0", - "rustc-hex", - "serde", - "serde_json", - "tokio-core", - "tokio-timer 0.1.2", - "url 1.7.2", -] - -[[package]] -name = "webpki" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7e664e770ac0110e2384769bcc59ed19e329d81f555916a6e072714957b81b4" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.18.0" +name = "weedle" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cd5736df7f12a964a5067a12c62fa38e1bd8080aff1f80bc29be7c80d19ab4" +checksum = "3bb43f70885151e629e2a19ce9e50bd730fd436cfd4b666894c9ce4de9141164" dependencies = [ - "webpki", + "nom 4.2.3", ] [[package]] -name = "weedle" -version = "0.10.0" +name = "which" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bb43f70885151e629e2a19ce9e50bd730fd436cfd4b666894c9ce4de9141164" +checksum = "5475d47078209a02e60614f7ba5e645ef3ed60f771920ac1906d7c1cc65024c8" dependencies = [ - "nom 4.2.3", + "libc", ] [[package]] @@ -4705,30 +4255,28 @@ dependencies = [ [[package]] name = "x25519-dalek" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee1585dc1484373cbc1cee7aafda26634665cf449436fd6e24bfd1fad230538" +checksum = "637ff90c9540fa3073bb577e65033069e4bae7c79d49d74aa3ffdf5342a53217" dependencies = [ - "clear_on_drop", - "curve25519-dalek 1.2.3", - "rand_core 0.3.1", + "curve25519-dalek", + "rand_core 0.5.1", + "zeroize", ] [[package]] name = "yamux" -version = "0.2.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2758f29014c1cb7a6e74c1b1160ac8c8203be342d35b73462fc6a13cc6385423" +checksum = "d73295bc9d9acf89dd9336b3b5f5b57731ee72b587857dd4312721a0196b48e5" dependencies = [ - "bytes 0.4.12", - "futures 0.1.29", + "bytes 0.5.4", + "futures 0.3.4", "log 0.4.8", "nohash-hasher", - "parking_lot 0.9.0", - "quick-error", + "parking_lot 0.10.0", "rand 0.7.3", - "tokio-codec", - "tokio-io", + "thiserror", ] [[package]] @@ -4736,3 +4284,18 @@ name = "zeroize" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" +dependencies = [ + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn 1.0.15", + "synstructure", +] diff --git a/README.md b/README.md index d483f8173d..4118533f1d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ - +COMIT logo --- @@ -47,9 +47,10 @@ Please see `cnd --help` for help with command line options. ## Setup testing/dev environment -1. Install `docker` -2. Install `node` (check the version required in api_tests/package.json) & `yarn` -3. Run `make` in the root folder of the repository, this will install various crates & tools such as clippy +1. Install `docker`, +2. Install `node` (check the version required in api_tests/package.json) & `yarn`, +3. Install `lnd` v0.9.0-beta (optional) using `make tags=invoicesrpc && make tags=invoicesrpc install`, this is only needed to run lnd e2e tests, +4. Run `make` in the root folder of the repository, this will install various crates & tools such as clippy. ## Testing diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000000..3b7516530a --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,20 @@ +# Releasing + +Releases of comit-rs are mostly automated based on the GitFlow branching model. + +To release a new version, create an issue with the title "Release version x.y.z" and label it with the "release" label. + +From here, [the automation](./.github/workflows/draft-new-release.yml) takes over and: + +1. Creates a new branch `release/x.y.z` +1. Updates the changelog to the new version +1. Bumps the version of the cnd/Cargo.toml manifest +1. Commits and pushes the changes +1. Creates a pull request for merging the release branch into master. + +Merging this pull request will trigger another [workflow](./.github/workflows/publish-new-release.yml) that: + +1. Builds the release artifacts for Linux and MacOS. +1. Pushes a new tag of the [comitnetwork/cnd](https://hub.docker.com/repository/docker/comitnetwork/cnd) docker image. +1. Tags the merge commit and creates a release on GitHub with the binaries attached. +1. Opens a PR to merge back to _dev_ branch: **Make sure to merge this one in a timely fashion**. diff --git a/api_tests/.gitignore b/api_tests/.gitignore index 3d7264d653..9cb9f76f23 100644 --- a/api_tests/.gitignore +++ b/api_tests/.gitignore @@ -3,3 +3,4 @@ log *.js gen/ yarn-error.log +dist/ diff --git a/api_tests/.prettierignore b/api_tests/.prettierignore index e8e450bed8..1d493a9707 100644 --- a/api_tests/.prettierignore +++ b/api_tests/.prettierignore @@ -1 +1,2 @@ gen/ +dist/ diff --git a/api_tests/dry/config.toml b/api_tests/dry/config.toml deleted file mode 100644 index 64b175f08c..0000000000 --- a/api_tests/dry/config.toml +++ /dev/null @@ -1 +0,0 @@ -actors = ["alice", "bob", "charlie"] diff --git a/api_tests/dry/multiple_peers.ts b/api_tests/dry/multiple_peers.ts deleted file mode 100644 index 835c458e08..0000000000 --- a/api_tests/dry/multiple_peers.ts +++ /dev/null @@ -1,85 +0,0 @@ -// These are stateless tests -- they don't require any state of the cnd and they don't change it -// They are mostly about checking invalid request responses -// These test do not use the sdk so that we can test edge cases -import { threeActorTest } from "../lib_sdk/actor_test"; -import { expect } from "chai"; -import "chai/register-should"; -import "../lib/setup_chai"; -import { SwapDetails } from "comit-sdk/dist/src/cnd"; -import { createDefaultSwapRequest } from "../lib_sdk/utils"; - -interface MatchInterface { - id: string; - status: string; - state: string; -} - -function toMatch(swapDetail: SwapDetails): MatchInterface { - return { - id: swapDetail.properties.id, - status: swapDetail.properties.status, - state: swapDetail.properties.state.communication.status, - }; -} - -setTimeout(async function() { - describe("SWAP requests to multiple peers", () => { - threeActorTest( - "[Alice] Should be able to send a swap request to Bob and Charlie", - async function({ alice, bob, charlie }) { - // Alice send swap request to Bob - const aliceToBobSwapUrl = await alice.cnd.postSwap( - await createDefaultSwapRequest(bob) - ); - - // Alice send swap request to Charlie - const aliceToCharlieSwapUrl = await alice.cnd.postSwap( - await createDefaultSwapRequest(charlie) - ); - - // fetch swap details - const aliceToBobSwapDetails = await alice.pollSwapDetails( - aliceToBobSwapUrl - ); - - const aliceToCharlieSwapDetails = await alice.pollSwapDetails( - aliceToCharlieSwapUrl - ); - - // Bob get swap details - const bobSwapDetails = await bob.pollSwapDetails( - aliceToBobSwapUrl - ); - - // Charlie get swap details - const charlieSwapDetails = await charlie.pollSwapDetails( - aliceToCharlieSwapUrl - ); - - expect( - bobSwapDetails.properties, - "[Bob] should have same id as Alice" - ).to.have.property("id", aliceToBobSwapDetails.properties.id); - expect( - charlieSwapDetails.properties, - "[Charlie] should have same id as Alice" - ).to.have.property( - "id", - aliceToCharlieSwapDetails.properties.id - ); - - expect( - [ - aliceToBobSwapDetails, - aliceToCharlieSwapDetails, - ].map(swapDetail => toMatch(swapDetail)) - ).to.have.deep.members([ - toMatch(bobSwapDetails), - toMatch(charlieSwapDetails), - ]); - } - ); - }); - - run(); -}, 0); diff --git a/api_tests/dry/peers_using_ip.ts b/api_tests/dry/peers_using_ip.ts deleted file mode 100644 index 84a93d1c84..0000000000 --- a/api_tests/dry/peers_using_ip.ts +++ /dev/null @@ -1,118 +0,0 @@ -// These are stateless tests -- they don't require any state of the cnd and they don't change it -// They are mostly about checking invalid request responses -// These test do not use the sdk so that we can test edge cases -import { threeActorTest, twoActorTest } from "../lib_sdk/actor_test"; -import "chai/register-should"; -import "../lib/setup_chai"; -import { expect, request } from "chai"; -import { Actor } from "../lib_sdk/actors/actor"; -import { sleep } from "../lib/util"; -import { createDefaultSwapRequest } from "../lib_sdk/utils"; - -async function assertNoPeersAvailable(actor: Actor, message: string) { - const peersResponse = await request(actor.cndHttpApiUrl()).get("/peers"); - - expect(peersResponse.status).to.equal(200); - expect(peersResponse.body.peers, message).to.be.empty; -} - -async function assertPeersAvailable(alice: Actor, bob: Actor, message: string) { - const peersResponse = await request(alice.cndHttpApiUrl()).get("/peers"); - - expect(peersResponse.status).to.equal(200); - expect(peersResponse.body.peers, message).to.containSubset([ - { - id: await bob.cnd.getPeerId(), - }, - ]); -} - -setTimeout(async function() { - describe("SWAP request with ip address", () => { - twoActorTest( - "[Alice] Should not yet see Bob's peer id in her list of peers", - async function({ alice }) { - const res = await request(alice.cndHttpApiUrl()).get("/peers"); - - expect(res.status).to.equal(200); - expect(res.body.peers).to.be.empty; - } - ); - - threeActorTest( - "[Alice] Should be able to make a swap request via HTTP api using a random peer id and Bob's ip address", - async function({ alice, bob, charlie }) { - await assertNoPeersAvailable( - alice, - "[Alice] Should not yet see Bob's nor Charlie's peer id in her list of peers" - ); - - // Alice send swap request to Bob - const swapRequest = await createDefaultSwapRequest(bob); - await alice.cnd.postSwap({ - ...swapRequest, - peer: { - peer_id: - "QmXfGiwNESAFWUvDVJ4NLaKYYVopYdV5HbpDSgz5TSypkb", // Random peer id on purpose to see if Bob still appears in GET /swaps using the multiaddress - address_hint: await bob.cnd - .getPeerListenAddresses() - .then(addresses => addresses[0]), - }, - }); - - await sleep(1000); - - await assertNoPeersAvailable( - alice, - "[Alice] Should not see any peers because the address did not resolve to the given PeerID" - ); - - await assertNoPeersAvailable( - bob, - "[Bob] Should not see Alice's PeerID because she dialed to a different PeerID" - ); - - await assertNoPeersAvailable( - charlie, - "[Charlie] Should not see Alice's PeerID because there was no communication so far" - ); - } - ); - - threeActorTest( - "[Alice] Should be able to make a swap request via HTTP api to Charlie using his peer ID and his ip address", - async function({ alice, bob, charlie }) { - await assertNoPeersAvailable( - alice, - "[Alice] Should not yet see Bob's nor Charlie's peer id in her list of peers" - ); - - // Alice send swap request to Bob - await alice.cnd.postSwap( - await createDefaultSwapRequest(charlie) - ); - - await sleep(1000); - - await assertNoPeersAvailable( - bob, - "[Bob] Should not see any peer ids in his list of peers" - ); - - await assertPeersAvailable( - alice, - charlie, - "[Alice] Should see Charlie's peer id in her list of peers after sending a swap request to him using his ip address" - ); - - await assertPeersAvailable( - charlie, - alice, - "[Charlie] Should see Alice's peer ID in his list of peers after receiving a swap request from Alice" - ); - } - ); - }); - - run(); -}, 0); diff --git a/api_tests/dry/rfc003_schema.ts b/api_tests/dry/rfc003_schema.ts deleted file mode 100644 index ab2c5152cf..0000000000 --- a/api_tests/dry/rfc003_schema.ts +++ /dev/null @@ -1,105 +0,0 @@ -// These are stateless tests -- they don't require any state of the cnd and they don't change it -// They are mostly about checking invalid request responses -// These test do not use the sdk so that we can test edge cases -import { twoActorTest } from "../lib_sdk/actor_test"; -import { expect, request } from "chai"; -import "chai/register-should"; -import "../lib/setup_chai"; -import { Actor } from "../lib_sdk/actors/actor"; -import { EmbeddedRepresentationSubEntity, Entity, Link } from "../gen/siren"; -import * as sirenJsonSchema from "../siren.schema.json"; -import * as swapPropertiesJsonSchema from "../swap.schema.json"; -import { createDefaultSwapRequest } from "../lib_sdk/utils"; - -async function assertValidSirenDocument( - swapsEntity: Entity, - alice: Actor, - message: string -) { - const selfLink = swapsEntity.links.find((link: Link) => - link.rel.includes("self") - ).href; - - const swapResponse = await request(alice.cndHttpApiUrl()).get(selfLink); - const swapEntity = swapResponse.body as Entity; - - expect(swapEntity, message).to.be.jsonSchema(sirenJsonSchema); - expect(swapEntity.properties, message).to.be.jsonSchema( - swapPropertiesJsonSchema - ); -} - -setTimeout(async function() { - describe("Response shape", () => { - twoActorTest( - "[Alice] Response for GET /swaps is a valid siren document", - async function({ alice }) { - const res = await request(alice.cndHttpApiUrl()).get("/swaps"); - - expect(res.body).to.be.jsonSchema(sirenJsonSchema); - } - ); - - twoActorTest( - "Response for GET /swaps/rfc003/{} is a valid siren document and properties match the json schema", - async function({ alice, bob }) { - // Alice send swap request to Bob - await alice.cnd.postSwap(await createDefaultSwapRequest(bob)); - - const aliceSwapEntity = await alice - .pollCndUntil("/swaps", body => body.entities.length > 0) - .then( - body => - body.entities[0] as EmbeddedRepresentationSubEntity - ); - - await assertValidSirenDocument( - aliceSwapEntity, - alice, - "[Alice] Response for GET /swaps/rfc003/{} is a valid siren document and properties match the json schema" - ); - - const bobsSwapEntity = await bob - .pollCndUntil("/swaps", body => body.entities.length > 0) - .then( - body => - body.entities[0] as EmbeddedRepresentationSubEntity - ); - await assertValidSirenDocument( - bobsSwapEntity, - bob, - "[Bob] Response for GET /swaps/rfc003/{} is a valid siren document and properties match the json schema" - ); - } - ); - - twoActorTest( - "[Alice] Response for GET /swaps/rfc003/{} contains a link to the protocol spec", - async function({ alice, bob }) { - // Alice send swap request to Bob - await alice.cnd.postSwap(await createDefaultSwapRequest(bob)); - - const aliceSwapEntity = await alice - .pollCndUntil("/swaps", body => body.entities.length > 0) - .then( - body => - body.entities[0] as EmbeddedRepresentationSubEntity - ); - - const protocolLink = aliceSwapEntity.links.find((link: Link) => - link.rel.includes("describedBy") - ); - - expect(protocolLink).to.be.deep.equal({ - rel: ["describedBy"], - class: ["protocol-spec"], - type: "text/html", - href: - "https://github.com/comit-network/RFCs/blob/master/RFC-003-SWAP-Basic.adoc", - }); - } - ); - }); - - run(); -}, 0); diff --git a/api_tests/dry/rfc003_swap_reject.ts b/api_tests/dry/rfc003_swap_reject.ts deleted file mode 100644 index d55750e70e..0000000000 --- a/api_tests/dry/rfc003_swap_reject.ts +++ /dev/null @@ -1,131 +0,0 @@ -// These are stateless tests -- they don't require any state of the cnd and they don't change it -// They are mostly about checking invalid request responses -// These test do not use the sdk so that we can test edge cases -import { twoActorTest } from "../lib_sdk/actor_test"; -import { expect, request } from "chai"; -import "chai/register-should"; -import "../lib/setup_chai"; -import { EmbeddedRepresentationSubEntity } from "../gen/siren"; -import * as swapPropertiesJsonSchema from "../swap.schema.json"; -import { Actor } from "../lib_sdk/actors/actor"; -import { createDefaultSwapRequest, DEFAULT_ALPHA } from "../lib_sdk/utils"; - -async function assertSwapsInProgress(actor: Actor, message: string) { - const res = await request(actor.cndHttpApiUrl()).get("/swaps"); - - const swapEntities = res.body.entities as EmbeddedRepresentationSubEntity[]; - - expect(swapEntities.map(entity => entity.properties, message)) - .to.each.have.property("status") - .that.is.equal("IN_PROGRESS"); -} - -setTimeout(async function() { - describe("SWAP request DECLINED", () => { - twoActorTest( - "[Alice] Should be able to make first swap request via HTTP api", - async function({ alice, bob }) { - // setup - - // Alice should be able to send two swap requests to Bob - await alice.cnd.postSwap({ - ...(await createDefaultSwapRequest(bob)), - alpha_asset: { - name: DEFAULT_ALPHA.asset.name, - quantity: DEFAULT_ALPHA.asset.quantity.reasonable, - }, - }); - await alice.cnd.postSwap({ - ...(await createDefaultSwapRequest(bob)), - alpha_asset: { - name: DEFAULT_ALPHA.asset.name, - quantity: DEFAULT_ALPHA.asset.quantity.stingy, - }, - }); - - await assertSwapsInProgress( - alice, - "[Alice] Shows the swaps as IN_PROGRESS in GET /swaps" - ); - await assertSwapsInProgress( - bob, - "[Bob] Shows the swaps as IN_PROGRESS in /swaps" - ); - } - ); - - twoActorTest("[Bob] Decline one swap", async function({ alice, bob }) { - // Alice should be able to send two swap requests to Bob - const aliceReasonableSwap = await alice.cnd.postSwap({ - ...(await createDefaultSwapRequest(bob)), - alpha_asset: { - name: DEFAULT_ALPHA.asset.name, - quantity: DEFAULT_ALPHA.asset.quantity.reasonable, - }, - }); - - const aliceStingySwap = await alice.cnd.postSwap({ - ...(await createDefaultSwapRequest(bob)), - alpha_asset: { - name: DEFAULT_ALPHA.asset.name, - quantity: DEFAULT_ALPHA.asset.quantity.stingy, - }, - }); - - const bobSwapDetails = await bob.pollSwapDetails(aliceStingySwap); - - expect( - bobSwapDetails.properties, - "[Bob] Has the RFC-003 parameters when GETing the swap" - ).jsonSchema(swapPropertiesJsonSchema); - expect( - bobSwapDetails.actions, - "[Bob] Has the accept and decline actions when GETing the swap" - ).containSubset([ - { - name: "accept", - }, - { - name: "decline", - }, - ]); - - /// Decline the swap - const decline = bobSwapDetails.actions.find( - action => action.name === "decline" - ); - const declineRes = await bob.cnd.executeAction(decline); - - declineRes.should.have.status(200); - expect( - await bob.pollCndUntil( - aliceStingySwap, - entity => - entity.properties.state.communication.status === - "DECLINED" - ), - "[Bob] Should be in the Declined State after declining a swap request providing a reason" - ).to.exist; - - const aliceReasonableSwapDetails = await alice.pollSwapDetails( - aliceReasonableSwap - ); - const aliceStingySwapDetails = await alice.pollSwapDetails( - aliceStingySwap - ); - - expect( - aliceStingySwapDetails.properties.state.communication.status, - "[Alice] Should be in the Declined State after Bob declines a swap" - ).to.eq("DECLINED"); - - expect( - aliceReasonableSwapDetails.properties.state.communication - .status, - "[Alice] Should be in the SENT State for the other swap request" - ).to.eq("SENT"); - }); - }); - - run(); -}, 0); diff --git a/api_tests/dry/sanity.ts b/api_tests/dry/sanity.ts deleted file mode 100644 index 4d642bdfd1..0000000000 --- a/api_tests/dry/sanity.ts +++ /dev/null @@ -1,181 +0,0 @@ -// These are stateless tests -- they don't require any state of the cnd and they don't change it -// They are mostly about checking invalid request responses -import "chai/register-should"; -import "../lib/setup_chai"; -import { oneActorTest } from "../lib_sdk/actor_test"; -import { expect, request } from "chai"; -import { Entity, Link } from "../gen/siren"; -import * as sirenJsonSchema from "../siren.schema.json"; - -setTimeout(async function() { - describe("Sanity tests", () => { - oneActorTest( - "[Alice] Returns 404 when you try and GET a non-existent swap", - async function({ alice }) { - const res = await request(alice.cndHttpApiUrl()).get( - "/swaps/rfc003/deadbeef-dead-beef-dead-deadbeefdead" - ); - - expect(res).to.have.status(404); - expect(res).to.have.header( - "content-type", - "application/problem+json" - ); - } - ); - oneActorTest( - "Returns an empty list when calling GET /swaps when there are no swaps", - async function({ alice }) { - const res = await request(alice.cndHttpApiUrl()).get("/swaps"); - - const body = res.body as Entity; - - expect(body.entities).to.have.lengthOf(0); - } - ); - - oneActorTest( - "[Alice] Returns 400 invalid body for an unsupported combination of parameters", - async function({ alice }) { - const res = await request(alice.cndHttpApiUrl()) - .post("/swaps/rfc003") - .send({ - alpha_ledger: { - name: "Thomas' wallet", - }, - beta_ledger: { - name: "Higher-Dimension", // This is the coffee place downstairs - }, - alpha_asset: { - name: "AUD", - quantity: "3.5", - }, - beta_asset: { - name: "Espresso", - "double-shot": true, - }, - alpha_ledger_refund_identity: "", - beta_ledger_redeem_identity: "", - alpha_expiry: 123456789, - beta_expiry: 123456789, - peer: "QmPRNaiDUcJmnuJWUyoADoqvFotwaMRFKV2RyZ7ZVr1fqd", - }); - - expect(res).to.have.status(400); - expect(res).to.have.header( - "content-type", - "application/problem+json" - ); - expect(res.body.title).to.equal("Invalid body."); - } - ); - - oneActorTest( - "[Alice] Returns 400 invalid body for malformed requests", - async function({ alice }) { - const res = await request(alice.cndHttpApiUrl()) - .post("/swaps/rfc003") - .send({ - garbage: true, - }); - - expect(res).to.have.status(400); - expect(res).to.have.header( - "content-type", - "application/problem+json" - ); - expect(res.body.title).to.equal("Invalid body."); - } - ); - - oneActorTest( - "[Alice] Should have no peers before making a swap request", - async function({ alice }) { - const res = await request(alice.cndHttpApiUrl()).get("/peers"); - - expect(res).to.have.status(200); - expect(res.body.peers).to.have.length(0); - } - ); - - oneActorTest( - "[Alice] Returns its peer ID and the addresses it listens on when you GET /", - async function({ alice }) { - const res = await request(alice.cndHttpApiUrl()).get("/"); - - expect(res.body.id).to.be.a("string"); - expect(res.body.listen_addresses).to.be.an("array"); - // At least 2 ipv4 addresses, lookup and external interface - expect(res.body.listen_addresses.length).to.be.greaterThan(1); - } - ); - - oneActorTest( - "[Alice] Response for GET / with accept header set as application/vnd.siren+json is a valid siren document", - async function({ alice }) { - const res = await request(alice.cndHttpApiUrl()).get("/"); - - expect(res).to.have.status(200); - expect(res.body).to.be.jsonSchema(sirenJsonSchema); - } - ); - - oneActorTest( - "[Alice] Returns its peer ID and the addresses it listens on when you GET / with accept header set as application/vnd.siren+json", - async function({ alice }) { - const res = await request(alice.cndHttpApiUrl()) - .get("/") - .set("accept", "application/vnd.siren+json"); - - expect(res.body.properties.id).to.be.a("string"); - expect(res.body.properties.listen_addresses).to.be.an("array"); - // At least 2 ipv4 addresses, lookup and external interface - expect( - res.body.properties.listen_addresses.length - ).to.be.greaterThan(1); - } - ); - - oneActorTest( - "[Alice] Returns the links for /swaps and /swaps/rfc003 when you GET / with accept header set as application/vnd.siren+json", - async function({ alice }) { - const res = await request(alice.cndHttpApiUrl()) - .get("/") - .set("accept", "application/vnd.siren+json"); - const links = res.body.links; - - const swapsLink = links.find( - (link: Link) => - link.rel.length === 1 && - link.rel.includes("collection") && - link.class.length === 1 && - link.class.includes("swaps") - ); - - expect(swapsLink).to.be.deep.equal({ - rel: ["collection"], - class: ["swaps"], - href: "/swaps", - }); - - const rfc003SwapsLink = links.find( - (link: Link) => - link.rel.length === 2 && - link.rel.includes("collection") && - link.rel.includes("edit") && - link.class.length === 2 && - link.class.includes("swaps") && - link.class.includes("rfc003") - ); - - expect(rfc003SwapsLink).to.be.deep.equal({ - rel: ["collection", "edit"], - class: ["swaps", "rfc003"], - href: "/swaps/rfc003", - }); - } - ); - }); - - run(); -}, 0); diff --git a/api_tests/e2e/rfc003/btc_eth-erc20/config.toml b/api_tests/e2e/rfc003/btc_eth-erc20/config.toml deleted file mode 100644 index 297c6a9182..0000000000 --- a/api_tests/e2e/rfc003/btc_eth-erc20/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -ledgers = ["bitcoin", "ethereum"] -actors = ["alice", "bob"] diff --git a/api_tests/e2e/rfc003/btc_eth-erc20/happy.ts b/api_tests/e2e/rfc003/btc_eth-erc20/happy.ts deleted file mode 100644 index 870c4c6f69..0000000000 --- a/api_tests/e2e/rfc003/btc_eth-erc20/happy.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { twoActorTest } from "../../../lib_sdk/actor_test"; -import { AssetKind } from "../../../lib_sdk/asset"; - -setTimeout(function() { - twoActorTest( - "rfc003-btc-eth-erc20-alice-redeems-bob-redeems", - async function({ alice, bob }) { - await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Erc20); - await bob.accept(); - - await alice.fund(); - await bob.deploy(); - await bob.fund(); - - await alice.redeem(); - await bob.redeem(); - - await alice.assertSwapped(); - await bob.assertSwapped(); - } - ); - - run(); -}, 0); diff --git a/api_tests/e2e/rfc003/btc_eth-erc20/refund.ts b/api_tests/e2e/rfc003/btc_eth-erc20/refund.ts deleted file mode 100644 index eacc66ece3..0000000000 --- a/api_tests/e2e/rfc003/btc_eth-erc20/refund.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { twoActorTest } from "../../../lib_sdk/actor_test"; -import { AssetKind } from "../../../lib_sdk/asset"; - -setTimeout(function() { - twoActorTest( - "rfc003-btc-eth-erc20-bob-refunds-alice-refunds", - async function({ alice, bob }) { - await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Erc20); - await bob.accept(); - - await alice.fund(); - await bob.deploy(); - await bob.fund(); - - await alice.refund(); - await bob.refund(); - - await alice.assertRefunded(); - await bob.assertRefunded(); - } - ); - - run(); -}, 0); diff --git a/api_tests/e2e/rfc003/btc_eth/config.toml b/api_tests/e2e/rfc003/btc_eth/config.toml deleted file mode 100644 index 297c6a9182..0000000000 --- a/api_tests/e2e/rfc003/btc_eth/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -ledgers = ["bitcoin", "ethereum"] -actors = ["alice", "bob"] diff --git a/api_tests/e2e/rfc003/btc_eth/happy.ts b/api_tests/e2e/rfc003/btc_eth/happy.ts deleted file mode 100644 index 34dd105531..0000000000 --- a/api_tests/e2e/rfc003/btc_eth/happy.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { twoActorTest } from "../../../lib_sdk/actor_test"; -import { AssetKind } from "../../../lib_sdk/asset"; - -setTimeout(function() { - twoActorTest("rfc003-btc-eth-alice-redeems-bob-redeems", async function({ - alice, - bob, - }) { - await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); - await bob.accept(); - - await alice.fund(); - await bob.fund(); - - await alice.redeem(); - await bob.redeem(); - - await alice.assertSwapped(); - await bob.assertSwapped(); - }); - - run(); -}, 0); diff --git a/api_tests/e2e/rfc003/btc_eth/overfunded_htlc.ts b/api_tests/e2e/rfc003/btc_eth/overfunded_htlc.ts deleted file mode 100644 index 631b71ce3f..0000000000 --- a/api_tests/e2e/rfc003/btc_eth/overfunded_htlc.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { twoActorTest } from "../../../lib_sdk/actor_test"; -import { AssetKind } from "../../../lib_sdk/asset"; - -setTimeout(function() { - twoActorTest("rfc003-btc-eth-alice-overfunds-bob-aborts", async function({ - alice, - bob, - }) { - await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); - await bob.accept(); - - await alice.overfund(); - - await bob.assertAlphaIncorrectlyFunded(); - await bob.assertBetaNotDeployed(); - await alice.assertAlphaIncorrectlyFunded(); - await alice.assertBetaNotDeployed(); - - await alice.refund(); - await alice.assertRefunded(); - - await bob.assertBetaNotDeployed(); - }); - - twoActorTest("rfc003-btc-eth-bob-overfunds-both-refund", async function({ - alice, - bob, - }) { - await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); - await bob.accept(); - await alice.fund(); - - await bob.assertAlphaFunded(); - await alice.assertAlphaFunded(); - - await bob.overfund(); - - await alice.assertBetaIncorrectlyFunded(); - await bob.assertBetaIncorrectlyFunded(); - - await bob.refund(); - await bob.assertRefunded(); - await alice.refund(); - await alice.assertRefunded(); - }); - - run(); -}, 0); diff --git a/api_tests/e2e/rfc003/btc_eth/refund.ts b/api_tests/e2e/rfc003/btc_eth/refund.ts deleted file mode 100644 index 8f05a0273e..0000000000 --- a/api_tests/e2e/rfc003/btc_eth/refund.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { twoActorTest } from "../../../lib_sdk/actor_test"; -import { AssetKind } from "../../../lib_sdk/asset"; - -setTimeout(function() { - twoActorTest("rfc003-btc-eth-bob-refunds-alice-refunds", async function({ - alice, - bob, - }) { - await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); - await bob.accept(); - - await alice.fund(); - await bob.fund(); - - await bob.refund(); - await alice.refund(); - - await bob.assertRefunded(); - await alice.assertRefunded(); - }); - - twoActorTest("rfc003-btc-eth-alice-refunds-bob-refunds", async function({ - alice, - bob, - }) { - await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); - await bob.accept(); - - await alice.fund(); - await bob.fund(); - - await alice.refund(); - await bob.refund(); - - await alice.assertRefunded(); - await bob.assertRefunded(); - }); - - run(); -}, 0); diff --git a/api_tests/e2e/rfc003/btc_eth/restart.ts b/api_tests/e2e/rfc003/btc_eth/restart.ts deleted file mode 100644 index 0762bfeb36..0000000000 --- a/api_tests/e2e/rfc003/btc_eth/restart.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { twoActorTest } from "../../../lib_sdk/actor_test"; -import { AssetKind } from "../../../lib_sdk/asset"; - -setTimeout(function() { - twoActorTest("rfc003-btc-eth-cnd-can-be-restarted", async function({ - alice, - bob, - }) { - await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); - await bob.accept(); - - await alice.currentSwapIsAccepted(); - await bob.currentSwapIsAccepted(); - - await alice.restart(); - await bob.restart(); - - await alice.currentSwapIsAccepted(); - await bob.currentSwapIsAccepted(); - }); - - run(); -}, 0); diff --git a/api_tests/e2e/rfc003/btc_eth/resume.ts b/api_tests/e2e/rfc003/btc_eth/resume.ts deleted file mode 100644 index 569ac02abd..0000000000 --- a/api_tests/e2e/rfc003/btc_eth/resume.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { sleep } from "../../../lib/util"; -import { twoActorTest } from "../../../lib_sdk/actor_test"; -import { AssetKind } from "../../../lib_sdk/asset"; - -setTimeout(function() { - twoActorTest("rfc003-btc-eth-resume-alice-down-bob-funds", async function({ - alice, - bob, - }) { - await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); - await bob.accept(); - - await alice.fund(); - alice.stop(); - - // Action happens while alice is down. - await bob.fund(); - - // Blocks are geneated every second here, wait to ensure - // we look into the past for the transaction. - await sleep(2000); - await alice.start(); - - await alice.redeem(); - await bob.redeem(); - - await alice.assertSwapped(); - await bob.assertSwapped(); - }); - - twoActorTest( - "rfc003-btc-eth-resume-alice-down-bob-redeems", - async function({ alice, bob }) { - await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); - await bob.accept(); - - await alice.fund(); - await bob.fund(); - - await alice.redeem(); - alice.stop(); - - // Action happens while alice is down. - await bob.redeem(); - - // Blocks are geneated every second here, wait to ensure - // we look into the past for the transaction. - await sleep(2000); - await alice.start(); - - await alice.assertSwapped(); - await bob.assertSwapped(); - } - ); - - twoActorTest("rfc003-btc-eth-resume-bob-down-alice-funds", async function({ - alice, - bob, - }) { - await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); - await bob.accept(); - - // Wait for Alice to receive the accept message before stopping Bob's cnd. - await alice.currentSwapIsAccepted(); - - bob.stop(); - - // Action happens while bob is down. - await alice.fund(); - - // Blocks are geneated every second here, wait to ensure - // we look into the past for the transaction. - await sleep(2000); - await bob.start(); - - await bob.fund(); - - await alice.redeem(); - await bob.redeem(); - - await alice.assertSwapped(); - await bob.assertSwapped(); - }); - - twoActorTest( - "rfc003-btc-eth-resume-bob-down-alice-redeems", - async function({ alice, bob }) { - await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); - await bob.accept(); - - await alice.fund(); - await bob.fund(); - - bob.stop(); - - // Action happens while bob is down. - await alice.redeem(); - - // Blocks are geneated every second here, wait to ensure - // we look into the past for the transaction. - await sleep(2000); - await bob.start(); - - await bob.redeem(); - - await alice.assertSwapped(); - await bob.assertSwapped(); - } - ); - - run(); -}, 0); diff --git a/api_tests/e2e/rfc003/btc_eth/underfunded_htlc.ts b/api_tests/e2e/rfc003/btc_eth/underfunded_htlc.ts deleted file mode 100644 index 482bdb22b4..0000000000 --- a/api_tests/e2e/rfc003/btc_eth/underfunded_htlc.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { twoActorTest } from "../../../lib_sdk/actor_test"; -import { AssetKind } from "../../../lib_sdk/asset"; - -setTimeout(function() { - twoActorTest("rfc003-btc-eth-alice-underfunds-bob-aborts", async function({ - alice, - bob, - }) { - await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); - await bob.accept(); - - await alice.underfund(); - - await bob.assertAlphaIncorrectlyFunded(); - await bob.assertBetaNotDeployed(); - await alice.assertAlphaIncorrectlyFunded(); - await alice.assertBetaNotDeployed(); - - await alice.refund(); - await alice.assertRefunded(); - - await bob.assertBetaNotDeployed(); - }); - - twoActorTest("rfc003-btc-eth-bob-underfunds-both-refund", async function({ - alice, - bob, - }) { - await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); - await bob.accept(); - await alice.fund(); - - await bob.assertAlphaFunded(); - await alice.assertAlphaFunded(); - - await bob.underfund(); - - await alice.assertBetaIncorrectlyFunded(); - await bob.assertBetaIncorrectlyFunded(); - - await bob.refund(); - await bob.assertRefunded(); - await alice.refund(); - await alice.assertRefunded(); - }); - - run(); -}, 0); diff --git a/api_tests/e2e/rfc003/eth-erc20_btc/config.toml b/api_tests/e2e/rfc003/eth-erc20_btc/config.toml deleted file mode 100644 index 297c6a9182..0000000000 --- a/api_tests/e2e/rfc003/eth-erc20_btc/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -ledgers = ["bitcoin", "ethereum"] -actors = ["alice", "bob"] diff --git a/api_tests/e2e/rfc003/eth-erc20_btc/happy.ts b/api_tests/e2e/rfc003/eth-erc20_btc/happy.ts deleted file mode 100644 index 24aeae0ddb..0000000000 --- a/api_tests/e2e/rfc003/eth-erc20_btc/happy.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { twoActorTest } from "../../../lib_sdk/actor_test"; -import { AssetKind } from "../../../lib_sdk/asset"; - -setTimeout(function() { - twoActorTest( - "rfc003-eth-erc20_btc-alice-redeems-bob-redeems", - async function({ alice, bob }) { - await alice.sendRequest(AssetKind.Erc20, AssetKind.Bitcoin); - await bob.accept(); - - await alice.deploy(); - await alice.fund(); - await bob.fund(); - - await alice.redeem(); - await bob.redeem(); - - await alice.assertSwapped(); - await bob.assertSwapped(); - } - ); - - run(); -}, 0); diff --git a/api_tests/e2e/rfc003/eth-erc20_btc/refund.ts b/api_tests/e2e/rfc003/eth-erc20_btc/refund.ts deleted file mode 100644 index 89ee391fbb..0000000000 --- a/api_tests/e2e/rfc003/eth-erc20_btc/refund.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { twoActorTest } from "../../../lib_sdk/actor_test"; -import { AssetKind } from "../../../lib_sdk/asset"; - -setTimeout(function() { - twoActorTest( - "rfc003-eth-erc20_btc-bob-refunds-alice-refunds", - async function({ alice, bob }) { - await alice.sendRequest(AssetKind.Erc20, AssetKind.Bitcoin); - await bob.accept(); - - await alice.deploy(); - await alice.fund(); - await bob.fund(); - - await alice.refund(); - await bob.refund(); - - await alice.assertRefunded(); - await bob.assertRefunded(); - } - ); - - run(); -}, 0); diff --git a/api_tests/e2e/rfc003/eth_btc/bitcoin_high_fee.ts b/api_tests/e2e/rfc003/eth_btc/bitcoin_high_fee.ts deleted file mode 100644 index ff611ea4dc..0000000000 --- a/api_tests/e2e/rfc003/eth_btc/bitcoin_high_fee.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { expect } from "chai"; -import "chai/register-should"; -import { ethers } from "ethers"; -import { Actor } from "../../../lib/actor"; -import * as bitcoin from "../../../lib/bitcoin"; -import { ActionKind, SwapRequest } from "../../../lib/comit"; -import "../../../lib/setup_chai"; -import { createTests, Step } from "../../../lib/test_creator"; -import { HarnessGlobal } from "../../../lib/util"; - -declare var global: HarnessGlobal; - -(async function() { - const alice = new Actor( - "alice", - { - ledgerConfig: global.ledgerConfigs, - addressForIncomingBitcoinPayments: - "bcrt1qs2aderg3whgu0m8uadn6dwxjf7j3wx97kk2qqtrum89pmfcxknhsf89pj0", - }, - null, - { - bitcoinFeePerWU: 100000000, - } - ); - const bob = new Actor( - "bob", - { - ledgerConfig: global.ledgerConfigs, - addressForIncomingBitcoinPayments: - "bcrt1qs2aderg3whgu0m8uadn6dwxjf7j3wx97kk2qqtrum89pmfcxknhsf89pj0", - }, - null, - { - bitcoinFeePerWU: 100000000, - } - ); - - const alphaAssetQuantity = ethers.utils.parseEther("10"); - const betaAssetQuantity = 100000000; - - const alphaExpiry = new Date("2080-06-11T23:00:00Z").getTime() / 1000; - const betaExpiry = new Date("2080-06-11T13:00:00Z").getTime() / 1000; - - await bitcoin.ensureFunding(); - await alice.wallet.eth().fund("11"); - await alice.wallet.btc().fund(0.1); - await bob.wallet.eth().fund("0.1"); - await bob.wallet.btc().fund(10); - - const swapRequest: SwapRequest = { - alpha_ledger: { - name: "ethereum", - chain_id: 17, - }, - beta_ledger: { - name: "bitcoin", - network: "regtest", - }, - alpha_asset: { - name: "ether", - quantity: alphaAssetQuantity.toString(), - }, - beta_asset: { - name: "bitcoin", - quantity: betaAssetQuantity.toString(), - }, - alpha_ledger_refund_identity: alice.wallet.eth().address(), - alpha_expiry: alphaExpiry, - beta_expiry: betaExpiry, - peer: await bob.peerId(), - }; - - const steps: Step[] = [ - { - actor: bob, - action: ActionKind.Accept, - waitUntil: state => state.communication.status === "ACCEPTED", - }, - { - actor: alice, - action: ActionKind.Fund, - waitUntil: state => state.alpha_ledger.status === "FUNDED", - }, - { - actor: bob, - action: ActionKind.Fund, - waitUntil: state => state.beta_ledger.status === "FUNDED", - }, - { - actor: alice, - action: { - kind: ActionKind.Redeem, - test: response => { - expect(response).to.have.status(400); - expect(response.body.title).to.equal("Fee is too high."); - }, - }, - }, - { - actor: bob, - action: { - kind: ActionKind.Refund, - test: response => { - expect(response).to.have.status(400); - expect(response.body.title).to.equal("Fee is too high."); - }, - }, - }, - ]; - - describe("RFC003: Ether for Bitcoin - Redeem/Refund Bitcoin with high fee", () => { - createTests(alice, bob, steps, "/swaps/rfc003", "/swaps", swapRequest); - }); - run(); -})(); diff --git a/api_tests/e2e/rfc003/eth_btc/config.toml b/api_tests/e2e/rfc003/eth_btc/config.toml deleted file mode 100644 index 297c6a9182..0000000000 --- a/api_tests/e2e/rfc003/eth_btc/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -ledgers = ["bitcoin", "ethereum"] -actors = ["alice", "bob"] diff --git a/api_tests/e2e/rfc003/eth_btc/happy.ts b/api_tests/e2e/rfc003/eth_btc/happy.ts deleted file mode 100644 index 45aee47961..0000000000 --- a/api_tests/e2e/rfc003/eth_btc/happy.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { twoActorTest } from "../../../lib_sdk/actor_test"; -import { AssetKind } from "../../../lib_sdk/asset"; - -setTimeout(function() { - twoActorTest("rfc003-eth-btc-alice-redeems-bob-redeems", async function({ - alice, - bob, - }) { - await alice.sendRequest(AssetKind.Ether, AssetKind.Bitcoin); - await bob.accept(); - - await alice.fund(); - await bob.fund(); - - await alice.redeem(); - await bob.redeem(); - - await alice.assertSwapped(); - await bob.assertSwapped(); - }); - - run(); -}, 0); diff --git a/api_tests/e2e/rfc003/eth_btc/ignore_fail_eth_transactions.ts b/api_tests/e2e/rfc003/eth_btc/ignore_fail_eth_transactions.ts deleted file mode 100644 index 0458cb1561..0000000000 --- a/api_tests/e2e/rfc003/eth_btc/ignore_fail_eth_transactions.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { twoActorTest } from "../../../lib_sdk/actor_test"; -import { AssetKind } from "../../../lib_sdk/asset"; - -setTimeout(function() { - twoActorTest("rfc003-eth-btc-alpha-deploy-fails", async function({ - alice, - bob, - }) { - await alice.sendRequest(AssetKind.Ether, AssetKind.Bitcoin); - await bob.accept(); - - await alice.fundLowGas("0x1b000"); - - await alice.assertAlphaNotDeployed(); - await bob.assertAlphaNotDeployed(); - await bob.assertBetaNotDeployed(); - await alice.assertBetaNotDeployed(); - }); - - run(); -}, 0); diff --git a/api_tests/e2e/rfc003/eth_btc/refund.ts b/api_tests/e2e/rfc003/eth_btc/refund.ts deleted file mode 100644 index 6c373586a2..0000000000 --- a/api_tests/e2e/rfc003/eth_btc/refund.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { twoActorTest } from "../../../lib_sdk/actor_test"; -import { AssetKind } from "../../../lib_sdk/asset"; - -setTimeout(function() { - twoActorTest("rfc003-eth-btc-bob-refunds-alice-refunds", async function({ - alice, - bob, - }) { - await alice.sendRequest(AssetKind.Ether, AssetKind.Bitcoin); - await bob.accept(); - - await alice.fund(); - await bob.fund(); - - await bob.refund(); - await alice.refund(); - - await bob.assertRefunded(); - await alice.assertRefunded(); - }); - - run(); -}, 0); diff --git a/api_tests/harness.ts b/api_tests/harness.ts deleted file mode 100644 index ee50e62c57..0000000000 --- a/api_tests/harness.ts +++ /dev/null @@ -1,210 +0,0 @@ -/// - -import { parse } from "@iarna/toml"; -import { execSync } from "child_process"; -import commander from "commander"; -import * as fs from "fs"; -import glob from "glob"; -import Mocha from "mocha"; -import path from "path"; -import readline from "readline-promise"; -import rimraf from "rimraf"; -import { CndRunner } from "./lib/cnd_runner"; -import { LedgerRunner } from "./lib/ledger_runner"; -import { HarnessGlobal } from "./lib/util"; - -const rlp = readline.createInterface({ - input: process.stdin, - output: process.stdout, - terminal: true, -}); - -const IS_CI = process.env.CI && Boolean(JSON.parse(process.env.CI)); - -commander - .option("--dump-logs", "Dump logs to stdout on failure") - .option("--verbose", "Verbose output") - .option( - "--wait-on-failure", - "Wait for user interaction before exiting the test suite in case a test failed", - false - ) - .parse(process.argv); - -if (IS_CI) { - commander.verbose = true; -} - -// ************************ // -// Setting global variables // -// ************************ // - -const projectRoot: string = execSync("git rev-parse --show-toplevel", { - encoding: "utf8", -}).trim(); -const testRoot = projectRoot + "/api_tests"; -const logDir = projectRoot + "/api_tests/log"; - -declare const global: HarnessGlobal; - -global.projectRoot = projectRoot; -global.testRoot = testRoot; -global.logRoot = logDir; -global.ledgerConfigs = {}; -global.verbose = false; -if (commander.verbose) { - global.verbose = true; -} - -rimraf.sync(logDir); -fs.mkdirSync(logDir); - -// ********************** // -// Start services helpers // -// ********************** // - -export interface E2ETestConfig { - actors: string[]; - ledgers: string[]; -} - -async function runTests(testFiles: string[]) { - const ledgerRunner = new LedgerRunner(projectRoot, logDir); - - const nodeRunner = new CndRunner(projectRoot, logDir); - - async function cleanupAll() { - try { - nodeRunner.stopCnds(); - await ledgerRunner.stopLedgers(); - } catch (e) { - console.error("Failed to clean up resources", e); - } - } - - process.on("SIGINT", async () => { - if (global.verbose) { - console.log("SIGINT RECEIVED"); - } - - await cleanupAll(); - - process.exit(0); - }); - - process.on("unhandledRejection", async reason => { - console.error(reason); - - await cleanupAll(); - - process.exit(1); - }); - - for (const testFile of testFiles) { - const testDir = path.dirname(testFile); - const config = (parse( - fs.readFileSync(testDir + "/config.toml", "utf8") - ) as unknown) as E2ETestConfig; - - if (config.ledgers) { - await ledgerRunner.ensureLedgersRunning(config.ledgers); - global.ledgerConfigs = await ledgerRunner.getLedgerConfig(); - } - - if (config.actors) { - await nodeRunner.ensureCndsRunning( - config.actors, - global.ledgerConfigs - ); - } - - const runTests = new Promise(res => { - new Mocha({ bail: true, ui: "bdd", delay: true }) - .addFile(testFile) - .run((failures: number) => res(failures)); - }); - - if (global.verbose) { - console.log(`Running test ${testFile}`); - } - - const failures = await runTests; - - if (global.verbose) { - console.log( - `Test ${testFile} ${failures ? "failed" : "succeeded"}` - ); - } - - if (failures) { - if (commander.dumpLogs || process.env.CARGO_MAKE_CI === "TRUE") { - execSync(`/bin/sh -c 'tail -n +1 ${testRoot}/log/*.log'`, { - stdio: "inherit", - }); - } - - if (commander.waitOnFailure) { - await rlp.questionAsync( - "A test failed, press any key to cleanup the environment." - ); - } - - await cleanupAll(); - - process.exit(1); - } - - nodeRunner.stopCnds(); - } - - await cleanupAll(); - process.exit(0); -} - -function validTestFile(path: string): boolean { - return ( - validTestPath(path) && - /^.*.ts$/.test(path) && - !/^.*harness.ts$/.test(path) - ); -} - -function validTestPath(path: string): boolean { - return ( - !/^.*lib\/.*$/.test(path) && - !/^.*lib_sdk\/.*$/.test(path) && - !/^.*node_modules\/.*$/.test(path) && - !/^.*gen\/.*$/.test(path) && - !/^.*log\/.*$/.test(path) && - !/^.*regtest\/.*$/.test(path) && - !/^.*types\/.*$/.test(path) - ); -} - -function expandGlob(paths: string[]): string[] { - if (!paths.length) { - return expandGlob(["**/*.ts"]); - } - - let result: string[] = []; - for (const path of paths) { - if (glob.hasMagic(path)) { - const expandedPaths: string[] = glob.sync(path); - for (const expandedPath of expandedPaths) { - if (validTestFile(expandedPath)) { - result.push(expandedPath); - } - } - } else if (fs.lstatSync(path).isDirectory()) { - result = result.concat(expandGlob([path + "/**/*.ts"])); - } else if (validTestFile(path)) { - result.push(path); - } - } - - return result; -} - -const args = commander.args; -const testFiles = expandGlob(args); -runTests(testFiles); diff --git a/api_tests/jest.config-dry.js b/api_tests/jest.config-dry.js new file mode 100644 index 0000000000..34b19b0130 --- /dev/null +++ b/api_tests/jest.config-dry.js @@ -0,0 +1,11 @@ +module.exports = { + preset: "ts-jest", + roots: ["/tests/dry"], + testRegex: "\\.ts$", + transform: { + "^.+\\.(t|j)s$": "ts-jest", + }, + moduleFileExtensions: ["ts", "js", "json", "node"], + testEnvironment: "/dist/src/dry_test_environment", + testTimeout: 63000, +}; diff --git a/api_tests/jest.config-e2e.js b/api_tests/jest.config-e2e.js new file mode 100644 index 0000000000..a3d38b5da2 --- /dev/null +++ b/api_tests/jest.config-e2e.js @@ -0,0 +1,11 @@ +module.exports = { + preset: "ts-jest", + roots: ["/tests/e2e"], + testRegex: "\\.ts$", + transform: { + "^.+\\.(t|j)s$": "ts-jest", + }, + moduleFileExtensions: ["ts", "js", "json", "node"], + testEnvironment: "/dist/src/e2e_test_environment", + testTimeout: 63000, +}; diff --git a/api_tests/lib/actor.ts b/api_tests/lib/actor.ts deleted file mode 100644 index 06cb05af63..0000000000 --- a/api_tests/lib/actor.ts +++ /dev/null @@ -1,272 +0,0 @@ -import { expect, request, use } from "chai"; -import chaiHttp = require("chai-http"); -// @ts-ignore -import { Response } from "superagent"; -import URI from "urijs"; -import { Action, Entity } from "../gen/siren"; -import * as bitcoin from "./bitcoin"; -import { LedgerAction } from "./comit"; -import { CND_CONFIGS, E2ETestActorConfig } from "./config"; -import { seconds_until, sleep } from "./util"; -import { HarnessGlobal } from "./util"; -import { Wallet, WalletConfig } from "./wallet"; - -declare var global: HarnessGlobal; - -use(chaiHttp); - -/// If declined, what is the reason? -interface DeclineConfig { - reason: string; -} - -export interface SirenActionAutofillParams { - bitcoinFeePerWU: number; -} - -const MOVE_CURSOR_UP_ONE_LINE = "\x1b[1A"; - -export class Actor { - public name: string; - public wallet: Wallet; - private cndConfig: E2ETestActorConfig; - private readonly declineConfig?: DeclineConfig; - private readonly sirenActionAutofillParams?: SirenActionAutofillParams; - - constructor( - name: "alice" | "bob" | "charlie", - walletConfig?: WalletConfig, - declineConfig?: DeclineConfig, - sirenActionAutofillParams?: SirenActionAutofillParams - ) { - this.name = name; - this.declineConfig = declineConfig; - this.sirenActionAutofillParams = sirenActionAutofillParams; - this.cndConfig = CND_CONFIGS[name]; - - if (walletConfig) { - this.wallet = new Wallet(name, walletConfig); - } - } - - public cndHttpApiUrl() { - return `http://127.0.0.1:${this.cndConfig.httpApiPort}`; - } - - public cndNetworkListenAddress() { - return `/ip4/127.0.0.1/tcp/${this.cndConfig.comitPort}`; - } - - public async peerId(): Promise { - const response = await request(this.cndHttpApiUrl()).get("/"); - - return response.body.id; - } - - public async pollCndUntil( - location: string, - predicate: (body: Entity) => boolean - ): Promise { - const response = await request(this.cndHttpApiUrl()).get(location); - - expect(response).to.have.status(200); - - if (predicate(response.body)) { - return response.body; - } else { - await sleep(500); - - return this.pollCndUntil(location, predicate); - } - } - - public doComitAction(action: Action): Promise { - const { url, body, method } = this.buildRequestFromAction(action); - - const agent = request(this.cndHttpApiUrl()); - - // let's ditch this stupid HTTP library ASAP to avoid this ... - switch (method) { - case "GET": { - return agent.get(url).send(body); - } - case "POST": { - return agent.post(url).send(body); - } - } - } - - public buildRequestFromAction(action: Action) { - const data: any = {}; - - for (const field of action.fields || []) { - if ( - field.class.some((e: string) => e === "ethereum") && - field.class.some((e: string) => e === "address") - ) { - data[field.name] = this.wallet.eth().address(); - } - - if ( - field.class.some((e: string) => e === "bitcoin") && - field.class.some((e: string) => e === "feePerWU") - ) { - expect(field.class).contains( - "feePerByte", - "API should be backwards compatible" - ); - - data[field.name] = this.sirenActionAutofillParams - ? this.sirenActionAutofillParams.bitcoinFeePerWU - : 20; - } - - if ( - field.class.some((e: string) => e === "bitcoin") && - field.class.some((e: string) => e === "address") - ) { - data[field.name] = this.wallet.btc().getNewAddress(); - } - } - - if (action.name === "decline" && this.declineConfig) { - data[action.fields[0].name] = this.declineConfig.reason; - } - - const method = action.method || "GET"; - if (method === "GET") { - return { - method, - url: new URI(action.href).query(data).toString(), - body: {}, - }; - } else { - if (action.type !== "application/json") { - throw new Error( - "Only application/json is supported for non-GET requests." - ); - } - - return { - method, - url: action.href, - body: data, - }; - } - } - - public async doLedgerAction(action: LedgerAction) { - // wait 1 second to make sure that both parties have created a btsieve - // query to watch for the action that is about to be performed. Should - // be removed with https://github.com/comit-network/comit-rs/issues/1289 - await sleep(1000); - - switch (action.type) { - case "bitcoin-send-amount-to-address": { - action.payload.should.include.all.keys("to", "amount"); - const { to, amount } = action.payload; - - return this.wallet - .btc() - .sendToAddress(to, parseInt(amount, 10)); - } - case "bitcoin-broadcast-signed-transaction": { - action.payload.should.include.all.keys("hex"); - - const fetchMedianTime = async () => { - const blockchainInfo = await bitcoin.getBlockchainInfo(); - return blockchainInfo.mediantime; - }; - - const { hex, min_median_block_time } = action.payload; - - if (min_median_block_time) { - let currentMedianBlockTime = await fetchMedianTime(); - let diff = min_median_block_time - currentMedianBlockTime; - - if (diff > 0) { - if (global.verbose) { - console.log( - `Waiting for median time to pass %d`, - min_median_block_time - ); - } - - let escapeSequence = ""; - - while (diff > 0) { - await sleep(1000); - - currentMedianBlockTime = await fetchMedianTime(); - diff = - min_median_block_time - currentMedianBlockTime; - - if (global.verbose) { - console.log( - `${escapeSequence}Current median time: %d`, - currentMedianBlockTime - ); - } - escapeSequence = MOVE_CURSOR_UP_ONE_LINE; - } - } - } - - return bitcoin.sendRawTransaction(hex); - } - case "ethereum-deploy-contract": { - action.payload.should.include.all.keys("data", "amount"); - const { data, amount, chain_id } = action.payload; - - return this.wallet - .eth() - .deploy_contract(data, amount, chain_id); - } - case "ethereum-call-contract": { - action.payload.should.include.all.keys( - "contract_address", - "gas_limit" - ); - - const { - contract_address, - data, - gas_limit, - min_block_timestamp, - chain_id, - } = action.payload; - - if ( - min_block_timestamp && - seconds_until(min_block_timestamp) > 0 - ) { - // Ethereum needs a buffer, otherwise the contract code is run but doesn't transfer any funds, - // see https://github.com/comit-network/RFCs/issues/62 - const buffer = 2; - const delay = seconds_until(min_block_timestamp) + buffer; - - if (global.verbose) { - console.log( - `Waiting for %d seconds before action can be executed to reach %d.`, - delay, - min_block_timestamp - ); - } - await sleep(delay * 1000); - } - - return this.wallet - .eth() - .sendEthTransactionTo( - contract_address, - data, - 0, - gas_limit, - chain_id - ); - } - default: - throw Error(`Action ${action} is not unsupported`); - } - } -} diff --git a/api_tests/lib/actor_test.ts b/api_tests/lib/actor_test.ts new file mode 100644 index 0000000000..42e2514616 --- /dev/null +++ b/api_tests/lib/actor_test.ts @@ -0,0 +1,60 @@ +import { Actors } from "./actors"; +import { createActors } from "./create_actors"; +import JasmineSmacker from "smack-my-jasmine-up"; +import { timeout } from "./utils"; + +/* + * This test function will take care of instantiating the actors and tearing them down again + * after the test, regardless if the test succeeded or failed. + */ +async function nActorTest( + actorNames: ["alice", "bob", "charlie"] | ["alice", "bob"] | ["alice"], + testFn: (actors: Actors) => Promise +) { + const name = JasmineSmacker.getCurrentTestName(); + if (!name.match(/[A-z0-9\-]+/)) { + // We use the test name as a file name for the log and hence need to restrict it. + throw new Error( + `Testname '${name}' is invalid. Only A-z, 0-9 and dashes are allowed.` + ); + } + + const actors = await createActors(name, actorNames); + + try { + await timeout(60_000, testFn(actors)); + } catch (e) { + for (const actorName of actorNames) { + await actors.getActorByName(actorName).dumpState(); + } + throw e; + } finally { + for (const actorName of actorNames) { + actors.getActorByName(actorName).stop(); + } + } +} + +/* + * Instantiates a new e2e test based on three actors + * + */ +export async function threeActorTest( + testFn: (actors: Actors) => Promise +) { + await nActorTest(["alice", "bob", "charlie"], testFn); +} + +/* + * Instantiates a new e2e test based on two actors + */ +export async function twoActorTest(testFn: (actors: Actors) => Promise) { + await nActorTest(["alice", "bob"], testFn); +} + +/* + * Instantiates a new e2e test based on one actor + */ +export async function oneActorTest(testFn: (actors: Actors) => Promise) { + await nActorTest(["alice"], testFn); +} diff --git a/api_tests/lib_sdk/actors/actor.ts b/api_tests/lib/actors/actor.ts similarity index 57% rename from api_tests/lib_sdk/actors/actor.ts rename to api_tests/lib/actors/actor.ts index ed3a93b48d..f00965f565 100644 --- a/api_tests/lib_sdk/actors/actor.ts +++ b/api_tests/lib/actors/actor.ts @@ -1,20 +1,28 @@ import { expect } from "chai"; -import { BigNumber, Cnd, ComitClient, Swap } from "comit-sdk"; +import { + BigNumber, + Cnd, + ComitClient, + LedgerAction, + Swap, + SwapDetails, +} from "comit-sdk"; import { parseEther } from "ethers/utils"; import getPort from "get-port"; import { Logger } from "log4js"; -import { E2ETestActorConfig } from "../../lib/config"; -import { LedgerConfig } from "../../lib/ledger_runner"; -import "../../lib/setup_chai"; -import { Asset, AssetKind } from "../asset"; -import { CndInstance } from "../cnd_instance"; -import { Ledger, LedgerKind } from "../ledger"; -import { sleep } from "../utils"; +import { E2ETestActorConfig } from "../config"; +import { LedgerConfig } from "../ledgers/ledger_runner"; +import "../setup_chai"; +import { Asset, AssetKind, toKey, toKind } from "../asset"; +import { CndInstance } from "../cnd/cnd_instance"; +import { Ledger, LedgerKind } from "../ledgers/ledger"; +import { HarnessGlobal, sleep } from "../utils"; import { Wallet, Wallets } from "../wallets"; import { Actors } from "./index"; -import { HarnessGlobal } from "../../lib/util"; import { Entity } from "../../gen/siren"; -import { SwapDetails } from "comit-sdk/dist/src/cnd"; +import { LndInstance } from "../ledgers/lnd_instance"; +import { sha256 } from "js-sha256"; +import { InvoiceState } from "@radar/lnrpc"; declare var global: HarnessGlobal; @@ -34,7 +42,9 @@ export class Actor { const actorConfig = new E2ETestActorConfig( await getPort(), await getPort(), - name + name, + await getPort(), + await getPort() ); const cndInstance = new CndInstance( @@ -54,7 +64,7 @@ export class Actor { JSON.stringify(actorConfig.generateCndConfigFile(ledgerConfig)) ); - return new Actor(logger, cndInstance); + return new Actor(logger, cndInstance, logRoot, actorConfig, name); } public actors: Actors; @@ -70,90 +80,164 @@ export class Actor { private betaLedger: Ledger; private betaAsset: Asset; - private readonly startingBalances: Map; - private readonly expectedBalanceChanges: Map; + private readonly startingBalances: Map; + private readonly expectedBalanceChanges: Map; + + public lndInstance: LndInstance; - private constructor( + constructor( private readonly logger: Logger, - private readonly cndInstance: CndInstance + private readonly cndInstance: CndInstance, + private readonly logRoot: string, + private readonly config: E2ETestActorConfig, + private name: string ) { this.wallets = new Wallets({}); - const { address, port } = cndInstance.getConfigFile().http_api.socket; - this.cnd = new Cnd(`http://${address}:${port}`); + const socket = cndInstance.getConfigFile().http_api.socket; + this.cnd = new Cnd(`http://${socket}`); this.startingBalances = new Map(); this.expectedBalanceChanges = new Map(); } public async sendRequest( - maybeAlphaAssetKind?: AssetKind, - maybeBetaAssetKind?: AssetKind + maybeAlpha?: AssetKind | { ledger: LedgerKind; asset: AssetKind }, + maybeBeta?: AssetKind | { ledger: LedgerKind; asset: AssetKind } ) { - const alphaAssetKind = maybeAlphaAssetKind - ? maybeAlphaAssetKind - : this.defaultAlphaAssetKind(); - const betaAssetKind = maybeBetaAssetKind - ? maybeBetaAssetKind - : this.defaultBetaAssetKind(); + this.logger.info("Sending swap request"); // By default, we will send the swap request to bob const to = this.actors.bob; - this.logger.info("Sending swap request"); + let alphaAssetKind: AssetKind; + let alphaLedgerKind: LedgerKind; + if (!maybeAlpha) { + alphaAssetKind = this.defaultAlphaAssetKind(); + alphaLedgerKind = this.defaultAlphaLedgerKind(); + } else if (typeof maybeAlpha === "string") { + alphaAssetKind = maybeAlpha; + alphaLedgerKind = defaultLedgerKindForAsset(alphaAssetKind); + } else { + alphaAssetKind = maybeAlpha.asset; + alphaLedgerKind = maybeAlpha.ledger; + } - this.alphaLedger = defaultLedgerDescriptionForAsset(alphaAssetKind); - this.alphaAsset = defaultAssetDescriptionForAsset(alphaAssetKind); + this.alphaLedger = defaultLedgerDescriptionForLedger(alphaLedgerKind); + this.alphaAsset = defaultAssetDescription( + alphaAssetKind, + alphaLedgerKind + ); to.alphaLedger = this.alphaLedger; to.alphaAsset = this.alphaAsset; this.logger.debug( - "Derived %o from asset %s", + "Derived Alpha Ledger %o from %s", this.alphaLedger, - alphaAssetKind + alphaLedgerKind ); this.logger.debug( - "Derived %o from asset %s", + "Derived Alpha Asset %o from %s", this.alphaAsset, alphaAssetKind ); - this.betaLedger = defaultLedgerDescriptionForAsset(betaAssetKind); - this.betaAsset = defaultAssetDescriptionForAsset(betaAssetKind); + let betaAssetKind; + let betaLedgerKind; + if (!maybeBeta) { + betaAssetKind = this.defaultBetaAssetKind(); + betaLedgerKind = this.defaultBetaLedgerKind(); + } else if (typeof maybeBeta === "string") { + betaAssetKind = maybeBeta; + betaLedgerKind = defaultLedgerKindForAsset(betaAssetKind); + } else { + betaAssetKind = maybeBeta.asset; + betaLedgerKind = maybeBeta.ledger; + } + + this.betaLedger = defaultLedgerDescriptionForLedger(betaLedgerKind); + this.betaAsset = defaultAssetDescription(betaAssetKind, betaLedgerKind); to.betaLedger = this.betaLedger; to.betaAsset = this.betaAsset; this.logger.debug( - "Derived %o from asset %s", + "Derived Beta Ledger %o from %s", this.betaLedger, - betaAssetKind + betaLedgerKind ); this.logger.debug( - "Derived %o from asset %s", + "Derived Beta Asset %o from %s", this.betaAsset, betaAssetKind ); - await this.initializeDependencies(); - await to.initializeDependencies(); + const listPromises: Promise[] = [ + this.initializeDependencies(), + to.initializeDependencies(), + ]; + await Promise.all(listPromises); await this.setStartingBalance([ this.alphaAsset, - { name: this.betaAsset.name, quantity: "0" }, + { + name: this.betaAsset.name, + ledger: this.betaLedger.name, + quantity: "0", + }, ]); await to.setStartingBalance([ - { name: to.alphaAsset.name, quantity: "0" }, + { + name: to.alphaAsset.name, + ledger: this.alphaLedger.name, + quantity: "0", + }, to.betaAsset, ]); + const isLightning = + this.alphaLedger.name === "lightning" || + this.betaLedger.name === "lightning"; + + if (isLightning) { + this.logger.debug(`Initialising lightning for ${this.config.name}`); + const thisLightningWallet = this.wallets.getWalletForLedger( + "lightning" + ); + const toLightningWallet = to.wallets.getWalletForLedger( + "lightning" + ); + + await thisLightningWallet.connectPeer(toLightningWallet); + + if (this.alphaLedger.name === "lightning") { + // Alpha Ledger is lightning so Alice will be sending assets over lightning + const quantity = parseInt(this.alphaAsset.quantity, 10); + await thisLightningWallet.openChannel( + toLightningWallet, + quantity * 1.5 // Similarly to minting, we open a channel with a bit more than what is needed for the swap + ); + } else { + // Beta Ledger is lightning so Bob will be sending assets over lightning + const quantity = parseInt(this.betaAsset.quantity, 10); + await toLightningWallet.openChannel( + thisLightningWallet, + quantity * 1.5 // Similarly to minting, we open a channel with a bit more than what is needed for the swap + ); + } + } + this.expectedBalanceChanges.set( - betaAssetKind, + toKey(this.betaAsset), new BigNumber(this.betaAsset.quantity) ); to.expectedBalanceChanges.set( - alphaAssetKind, + toKey(this.alphaAsset), new BigNumber(to.alphaAsset.quantity) ); + if (isLightning) { + this.logger.debug("Using lightning routes on cnd REST API"); + return; + } const comitClient: ComitClient = this.getComitClient(); const payload = { @@ -172,12 +256,10 @@ export class Actor { }; this.swap = await comitClient.sendSwap(payload); - to.swap = new Swap( - to.wallets.bitcoin.inner, - to.wallets.ethereum.inner, - to.cnd, - this.swap.self - ); + to.swap = new Swap(to.cnd, this.swap.self, { + bitcoin: to.wallets.bitcoin.inner, + ethereum: to.wallets.ethereum.inner, + }); this.logger.debug("Created new swap at %s", this.swap.self); return this.swap; @@ -246,10 +328,13 @@ export class Actor { } public async fundLowGas(hexGasLimit: string) { - const response = await this.swap.tryExecuteAction("fund", { - maxTimeoutSecs: 10, - tryIntervalSecs: 1, - }); + const response = await this.swap.tryExecuteSirenAction( + "fund", + { + maxTimeoutSecs: 10, + tryIntervalSecs: 1, + } + ); response.data.payload.gas_limit = hexGasLimit; const txid = await this.swap.doLedgerAction(response.data); this.logger.debug( @@ -265,27 +350,49 @@ export class Actor { } public async overfund() { - const response = await this.swap.tryExecuteAction("fund", { - maxTimeoutSecs: 10, - tryIntervalSecs: 1, - }); + const response = await this.swap.tryExecuteSirenAction( + "fund", + { + maxTimeoutSecs: 10, + tryIntervalSecs: 1, + } + ); const amount = response.data.payload.amount; - response.data.payload.amount = amount * 1.01; + const overfundAmount = amount * 1.01; + + response.data.payload.amount = overfundAmount; const txid = await this.swap.doLedgerAction(response.data); - this.logger.debug("Funded swap %s in %s", this.swap.self, txid); + this.logger.debug( + "Overfunded swap %s in %s with %d instead of %d", + this.swap.self, + txid, + overfundAmount, + amount + ); } public async underfund() { - const response = await this.swap.tryExecuteAction("fund", { - maxTimeoutSecs: 10, - tryIntervalSecs: 1, - }); + const response = await this.swap.tryExecuteSirenAction( + "fund", + { + maxTimeoutSecs: 10, + tryIntervalSecs: 1, + } + ); const amount = response.data.payload.amount; - response.data.payload.amount = amount * 0.01; + const underfundAmount = amount * 0.01; + + response.data.payload.amount = underfundAmount; const txid = await this.swap.doLedgerAction(response.data); - this.logger.debug("Funded swap %s in %s", this.swap.self, txid); + this.logger.debug( + "Underfunded swap %s in %s with %d instead of %d", + this.swap.self, + txid, + underfundAmount, + amount + ); } public async refund() { @@ -347,6 +454,16 @@ export class Actor { } } + public async redeemWithHighFee() { + // Hack the bitcoin fee per WU returned by the wallet + this.wallets.bitcoin.inner.getFee = () => "100000000"; + + return this.swap.tryExecuteSirenAction("redeem", { + maxTimeoutSecs: 10, + tryIntervalSecs: 1, + }); + } + public async currentSwapIsAccepted() { let swapEntity; @@ -371,27 +488,27 @@ export class Actor { } for (const [ - assetKind, + assetKey, expectedBalanceChange, ] of this.expectedBalanceChanges.entries()) { this.logger.debug( "Checking that %s balance changed by %d", - assetKind, + assetKey, expectedBalanceChange ); - const wallet = this.wallets[ - defaultLedgerDescriptionForAsset(assetKind).name - ]; + const { asset, ledger } = toKind(assetKey); + + const wallet = this.wallets[ledger]; const expectedBalance = new BigNumber( - this.startingBalances.get(assetKind) + this.startingBalances.get(assetKey) ).plus(expectedBalanceChange); const maximumFee = wallet.MaximumFee; const balanceInclFees = expectedBalance.minus(maximumFee); const currentWalletBalance = await wallet.getBalanceByAsset( - defaultAssetDescriptionForAsset(assetKind) + defaultAssetDescription(asset, ledger) ); expect(currentWalletBalance).to.be.bignumber.gte(balanceInclFees); @@ -406,22 +523,22 @@ export class Actor { public async assertRefunded() { this.logger.debug("Checking if swap @ %s was refunded", this.swap.self); - for (const [assetKind] of this.startingBalances.entries()) { - const wallet = this.wallets[ - defaultLedgerDescriptionForAsset(assetKind).name - ]; + for (const [assetKey] of this.startingBalances.entries()) { + const { asset, ledger } = toKind(assetKey); + + const wallet = this.wallets[ledger]; const maximumFee = wallet.MaximumFee; this.logger.debug( "Checking that %s balance changed by max %d (MaximumFee)", - assetKind, + assetKey, maximumFee ); const expectedBalance = new BigNumber( - this.startingBalances.get(assetKind) + this.startingBalances.get(assetKey) ); const currentWalletBalance = await wallet.getBalanceByAsset( - defaultAssetDescriptionForAsset(assetKind) + defaultAssetDescription(asset, ledger) ); const balanceInclFees = expectedBalance.minus(maximumFee); expect(currentWalletBalance).to.be.bignumber.gte(balanceInclFees); @@ -483,7 +600,11 @@ export class Actor { } public stop() { + this.logger.debug("Stopping actor"); this.cndInstance.stop(); + if (this.lndInstance && this.lndInstance.isRunning()) { + this.lndInstance.stop(); + } } public async restart() { @@ -518,6 +639,10 @@ export class Actor { return entity.properties.role; } + public getName() { + return this.name; + } + private async waitForAlphaExpiry() { const swapDetails = await this.swap.fetchDetails(); @@ -616,18 +741,48 @@ export class Actor { } private async initializeDependencies() { + const lightningNeeded = + this.alphaLedger.name === "lightning" || + this.betaLedger.name === "lightning"; + + if (lightningNeeded) { + this.lndInstance = new LndInstance( + this.logger, + this.logRoot, + this.config, + global.ledgerConfigs.bitcoin.dataDir + ); + await this.lndInstance.start(); + } + + const walletPromises: Promise[] = []; for (const ledgerName of [ this.alphaLedger.name, this.betaLedger.name, ]) { - await this.wallets.initializeForLedger(ledgerName); + let lnd; + if (this.lndInstance) { + lnd = { + lnd: this.lndInstance.lnd, + lndP2pSocket: this.lndInstance.getLightningSocket(), + }; + } + walletPromises.push( + this.wallets.initializeForLedger(ledgerName, this.logger, lnd) + ); } - this.comitClient = new ComitClient( - this.wallets.getWalletForLedger("bitcoin").inner, - this.wallets.getWalletForLedger("ethereum").inner, - this.cnd - ); + await Promise.all(walletPromises); + + if (!lightningNeeded) { + this.comitClient = new ComitClient(this.cnd) + .withBitcoinWallet( + this.wallets.getWalletForLedger("bitcoin").inner + ) + .withEthereumWallet( + this.wallets.getWalletForLedger("ethereum").inner + ); + } } private getComitClient(): ComitClient { @@ -641,11 +796,11 @@ export class Actor { private async setStartingBalance(assets: Asset[]) { for (const asset of assets) { if (parseFloat(asset.quantity) === 0) { - this.startingBalances.set(asset.name, new BigNumber(0)); + this.startingBalances.set(toKey(asset), new BigNumber(0)); continue; } - const ledger = defaultLedgerDescriptionForAsset(asset.name); + const ledger = defaultLedgerDescriptionForLedger(asset.ledger); const ledgerName = ledger.name; this.logger.debug("Minting %s on %s", asset.name, ledgerName); @@ -660,33 +815,53 @@ export class Actor { asset.name, balance.toString() ); - this.startingBalances.set(asset.name, balance); + this.startingBalances.set(toKey(asset), balance); } } private defaultAlphaAssetKind() { const defaultAlphaAssetKind = AssetKind.Bitcoin; this.logger.info( - "AssetKind for alpha ledger not specified, defaulting to %s", + "AssetKind for alpha asset not specified, defaulting to %s", defaultAlphaAssetKind ); return defaultAlphaAssetKind; } + private defaultAlphaLedgerKind() { + const defaultAlphaLedgerKind = LedgerKind.Bitcoin; + this.logger.info( + "LedgerKind for alpha ledger not specified, defaulting to %s", + defaultAlphaLedgerKind + ); + + return defaultAlphaLedgerKind; + } + private defaultBetaAssetKind() { const defaultBetaAssetKind = AssetKind.Ether; this.logger.info( - "AssetKind for beta ledger not specified, defaulting to %s", + "AssetKind for beta asset not specified, defaulting to %s", defaultBetaAssetKind ); return defaultBetaAssetKind; } + private defaultBetaLedgerKind() { + const defaultBetaLedgerKind = LedgerKind.Ethereum; + this.logger.info( + "LedgerKind for beta ledger not specified, defaulting to %s", + defaultBetaLedgerKind + ); + + return defaultBetaLedgerKind; + } + public cndHttpApiUrl() { - const cndSocket = this.cndInstance.getConfigFile().http_api.socket; - return `http://${cndSocket.address}:${cndSocket.port}`; + const socket = this.cndInstance.getConfigFile().http_api.socket; + return `http://${socket}`; } public async pollCndUntil( @@ -719,26 +894,146 @@ export class Actor { return (await this.cnd.fetch(swapUrl)).data; } catch (error) { await sleep(1000); - return await this.pollSwapDetails(swapUrl, iteration); + return this.pollSwapDetails(swapUrl, iteration); + } + } + + /// This is to be removed once cnd supports lightning + public lnCreateSha256Secret(): { secret: string; secretHash: string } { + const secretBuf = Buffer.alloc(32); + for (let i = 0; i < secretBuf.length; i++) { + secretBuf[i] = Math.floor(Math.random() * 255); + } + + const secretHash = sha256(secretBuf); + const secret = secretBuf.toString("hex"); + this.logger.debug(`LN: secret: ${secret}, secretHash: ${secretHash}`); + return { secret, secretHash }; + } + + public async lnCreateHoldInvoice( + sats: string, + secretHash: string, + expiry: number, + cltvExpiry: number + ): Promise { + this.logger.debug("LN: Create Hold Invoice", sats, secretHash, expiry); + const resp = await this.wallets.lightning.inner.addHoldInvoice( + sats, + secretHash, + expiry, + cltvExpiry + ); + this.logger.debug("LN: Create Hold Response:", resp); + } + + public async lnSendPayment( + to: Actor, + satAmount: string, + secretHash: string, + finalCltvDelta: number + ) { + const toPubkey = await to.wallets.lightning.inner.getPubkey(); + this.logger.debug( + "LN: Send Payment -", + "to:", + toPubkey, + "; amt:", + satAmount, + "; hash:", + secretHash, + "; finalCltvDelta: ", + finalCltvDelta + ); + const resp = await this.wallets.lightning.inner.sendPayment( + toPubkey, + satAmount, + secretHash, + finalCltvDelta + ); + this.logger.debug("LN: Send Payment Response:", resp); + return resp; + } + + /** Settles the invoice once it is `accepted`. + * + * When the other party sends the payment, the invoice status changes + * from `open` to `accepted`. Hence, we check first if the invoice is accepted + * with `lnAssertInvoiceAccepted`. If it throws, then we sleep 100ms and recursively + * call `lnSettleInvoice` (this function). + * If `lnAssertInvoiceAccepted` does not throw then it means the payment has been received + * and we proceed with the settlement. + */ + public async lnSettleInvoice(secret: string, secretHash: string) { + try { + await this.lnAssertInvoiceAccepted(secretHash); + this.logger.debug("LN: Settle Invoice", secret, secretHash); + await this.wallets.lightning.inner.settleInvoice(secret); + } catch { + await sleep(100); + await this.lnSettleInvoice(secret, secretHash); + } + } + + public async lnCreateInvoice(sats: string) { + this.logger.debug(`Creating invoice for ${sats} sats`); + return this.wallets.lightning.addInvoice(sats); + } + + public async lnPayInvoiceWithRequest(request: string): Promise { + this.logger.debug(`Paying invoice with request ${request}`); + await this.wallets.lightning.pay(request); + } + + public async lnAssertInvoiceSettled(secretHash: string) { + const resp = await this.wallets.lightning.lookupInvoice(secretHash); + if (resp.state !== InvoiceState.SETTLED) { + throw new Error( + `Invoice ${secretHash} is not settled, status is ${resp.state}` + ); + } + } + + public async lnAssertInvoiceAccepted(secretHash: string) { + const resp = await this.wallets.lightning.lookupInvoice(secretHash); + if (resp.state !== InvoiceState.ACCEPTED) { + throw new Error( + `Invoice ${secretHash} is not accepted, status is ${resp.state}` + ); } } } -function defaultLedgerDescriptionForAsset(asset: AssetKind): Ledger { +function defaultLedgerKindForAsset(asset: AssetKind): LedgerKind { switch (asset) { - case AssetKind.Bitcoin: { + case AssetKind.Bitcoin: + return LedgerKind.Bitcoin; + case AssetKind.Ether: + return LedgerKind.Ethereum; + case AssetKind.Erc20: + return LedgerKind.Ethereum; + } +} + +/** + * WIP as the cnd REST API routes for lightning are not yet defined. + * @param ledger + * @returns The ledger formatted as needed for the request body to cnd HTTP API on the lightning route. + */ +function defaultLedgerDescriptionForLedger(ledger: LedgerKind): Ledger { + switch (ledger) { + case LedgerKind.Lightning: { return { - name: LedgerKind.Bitcoin, - network: "regtest", + name: LedgerKind.Lightning, }; } - case AssetKind.Ether: { + case LedgerKind.Bitcoin: { return { - name: LedgerKind.Ethereum, - chain_id: 17, + name: LedgerKind.Bitcoin, + network: "regtest", }; } - case AssetKind.Erc20: { + case LedgerKind.Ethereum: { return { name: LedgerKind.Ethereum, chain_id: 17, @@ -747,23 +1042,26 @@ function defaultLedgerDescriptionForAsset(asset: AssetKind): Ledger { } } -function defaultAssetDescriptionForAsset(asset: AssetKind): Asset { +function defaultAssetDescription(asset: AssetKind, ledger: LedgerKind): Asset { switch (asset) { case AssetKind.Bitcoin: { return { name: AssetKind.Bitcoin, - quantity: "100000000", + ledger, + quantity: "10000000", }; } case AssetKind.Ether: { return { name: AssetKind.Ether, + ledger, quantity: parseEther("10").toString(), }; } case AssetKind.Erc20: { return { name: AssetKind.Erc20, + ledger, quantity: parseEther("100").toString(), token_contract: global.tokenContract, }; diff --git a/api_tests/lib_sdk/actors/index.ts b/api_tests/lib/actors/index.ts similarity index 100% rename from api_tests/lib_sdk/actors/index.ts rename to api_tests/lib/actors/index.ts diff --git a/api_tests/lib/asset.ts b/api_tests/lib/asset.ts new file mode 100644 index 0000000000..9d13426b14 --- /dev/null +++ b/api_tests/lib/asset.ts @@ -0,0 +1,31 @@ +import { Asset as AssetSdk } from "comit-sdk"; +import { LedgerKind } from "./ledgers/ledger"; + +export interface Asset extends AssetSdk { + name: AssetKind; + ledger: LedgerKind; + [k: string]: any; +} + +export enum AssetKind { + Bitcoin = "bitcoin", + Ether = "ether", + Erc20 = "erc20", +} + +export function toKey(asset: Asset): string { + return `${asset.name}-on-${asset.ledger}`; +} + +export function toKind(key: string): { asset: AssetKind; ledger: LedgerKind } { + switch (key) { + case "bitcoin-on-bitcoin": + return { asset: AssetKind.Bitcoin, ledger: LedgerKind.Bitcoin }; + case "bitcoin-on-lightning": + return { asset: AssetKind.Bitcoin, ledger: LedgerKind.Lightning }; + case "ether-on-ethereum": + return { asset: AssetKind.Ether, ledger: LedgerKind.Ethereum }; + case "erc20-on-ethereum": + return { asset: AssetKind.Erc20, ledger: LedgerKind.Ethereum }; + } +} diff --git a/api_tests/lib/bitcoin.ts b/api_tests/lib/bitcoin.ts deleted file mode 100644 index 003fe9fe98..0000000000 --- a/api_tests/lib/bitcoin.ts +++ /dev/null @@ -1,176 +0,0 @@ -import BitcoinRpcClient, { - HexRawTransactionResponse, - VerboseRawTransactionResponse, -} from "bitcoin-core"; -import { - address, - ECPair, - ECPairInterface, - networks, - Payment, - payments, - Transaction, - TransactionBuilder, -} from "bitcoinjs-lib"; -import sb from "satoshi-bitcoin"; -import { test_rng } from "./util"; - -export interface BitcoinNodeConfig { - network: string; - username: string; - password: string; - host: string; - rpcPort: number; - p2pPort: number; -} - -interface Utxo { - txId: string; - value: number; - vout: number; -} - -let bitcoinRpcClient: BitcoinRpcClient; -let bitcoinConfig: BitcoinNodeConfig; - -export function init(btcConfig: BitcoinNodeConfig) { - createBitcoinRpcClient(btcConfig); -} - -function createBitcoinRpcClient(btcConfig?: BitcoinNodeConfig) { - if (!btcConfig && !bitcoinConfig) { - throw new Error("bitcoin configuration is needed"); - } - - if (!bitcoinRpcClient || btcConfig !== bitcoinConfig) { - bitcoinRpcClient = new BitcoinRpcClient({ - network: btcConfig.network, - port: btcConfig.rpcPort, - host: btcConfig.host, - username: btcConfig.username, - password: btcConfig.password, - }); - bitcoinConfig = btcConfig; - } - return bitcoinRpcClient; -} - -export async function generate(num: number = 1) { - const client = createBitcoinRpcClient(bitcoinConfig); - - return client.generateToAddress(num, await client.getNewAddress()); -} - -export async function getBlockchainInfo() { - return createBitcoinRpcClient(bitcoinConfig).getBlockchainInfo(); -} - -export async function ensureFunding() { - const blockHeight = await createBitcoinRpcClient( - bitcoinConfig - ).getBlockCount(); - if (blockHeight < 101) { - const client = createBitcoinRpcClient(bitcoinConfig); - - await client.generateToAddress( - 101 - blockHeight, - await client.getNewAddress() - ); - } -} - -export async function sendRawTransaction(hexString: string) { - return createBitcoinRpcClient(bitcoinConfig).sendRawTransaction(hexString); -} - -export class BitcoinWallet { - private readonly identity: Payment; - private readonly keypair: ECPairInterface; - private readonly bitcoinUtxos: Utxo[]; - private readonly addressForIncomingPayments: string; - - constructor( - btcConfig: BitcoinNodeConfig, - addressForIncomingPayments: string - ) { - this.addressForIncomingPayments = addressForIncomingPayments; - this.keypair = ECPair.makeRandom({ rng: test_rng }); - this.bitcoinUtxos = []; - this.identity = payments.p2wpkh({ - pubkey: this.keypair.publicKey, - network: networks.regtest, - }); - - createBitcoinRpcClient(btcConfig); - } - - public getNewAddress() { - return this.addressForIncomingPayments; - } - - public satoshiReceivedInTx(redeemTxId: string) { - return getFirstUtxoValueTransferredTo( - redeemTxId, - this.addressForIncomingPayments - ); - } - - public async fund(bitcoin: number) { - const txId = await bitcoinRpcClient.sendToAddress( - this.identity.address, - bitcoin - ); - const rawTransaction = (await bitcoinRpcClient.getRawTransaction( - txId - )) as HexRawTransactionResponse; - const transaction = Transaction.fromHex(rawTransaction); - - const entries = transaction.outs; - this.bitcoinUtxos.push( - ...transaction.outs - .filter(entry => entry.script.equals(this.identity.output)) - .filter(entry => "value" in entry && entry.value > 0) - .map(entry => { - return { - txId, - vout: entries.indexOf(entry), - // @ts-ignore: we filtered out all outputs that don't have a value - value: entry.value, - }; - }) - ); - } - - public async sendToAddress(to: string, value: number) { - const txb = new TransactionBuilder(); - const utxo = this.bitcoinUtxos.shift(); - const inputAmount = utxo.value; - const keyPair = this.keypair; - const fee = 2500; - const change = inputAmount - value - fee; - txb.addInput(utxo.txId, utxo.vout, null, this.identity.output); - txb.addOutput(this.identity.output, change); - txb.addOutput(address.toOutputScript(to, networks.regtest), value); - txb.sign(0, keyPair, null, null, inputAmount); - - return bitcoinRpcClient.sendRawTransaction(txb.build().toHex()); - } -} - -async function getFirstUtxoValueTransferredTo(txId: string, address: string) { - let satoshi = 0; - const tx = (await bitcoinRpcClient.getRawTransaction( - txId, - true - )) as VerboseRawTransactionResponse; - const vout = tx.vout[0]; - - if ( - vout.scriptPubKey.addresses.length === 1 && - vout.scriptPubKey.addresses[0] === address - ) { - satoshi = sb.toSatoshi(vout.value); - } - - return satoshi; -} diff --git a/api_tests/lib/bitcoind_instance.ts b/api_tests/lib/bitcoind_instance.ts deleted file mode 100644 index 2aa22046ce..0000000000 --- a/api_tests/lib/bitcoind_instance.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { ChildProcess, spawn } from "child_process"; -import * as fs from "fs"; -import tmp from "tmp"; -import { promisify } from "util"; -import { LedgerInstance } from "./ledger_runner"; -import { LogReader } from "./log_reader"; - -const openAsync = promisify(fs.open); - -export class BitcoindInstance implements LedgerInstance { - private process: ChildProcess; - private dbDir: any; - private username: string; - private password: string; - - constructor( - private readonly projectRoot: string, - private readonly logDir: string, - public readonly p2pPort: number, - public readonly rpcPort: number - ) {} - - public async start() { - const bin = process.env.BITCOIND_BIN - ? process.env.BITCOIND_BIN - : this.projectRoot + - "/blockchain_nodes/bitcoin/bitcoin-0.17.0/bin/bitcoind"; - - this.dbDir = tmp.dirSync(); - - this.process = spawn( - bin, - [ - `-datadir=${this.dbDir.name}`, - "-regtest", - "-server", - "-printtoconsole", - `-bind=0.0.0.0:${this.p2pPort}`, - `-rpcbind=0.0.0.0:${this.rpcPort}`, - "-rpcallowip=0.0.0.0/0", - "-nodebug", - "-acceptnonstdtxn=0", - "-rest", - ], - { - cwd: this.projectRoot, - stdio: [ - "ignore", // stdin - await openAsync(this.logDir + "/bitcoind.log", "w"), // stdout - await openAsync(this.logDir + "/bitcoind.log", "w"), // stderr - ], - } - ); - - this.process.on("exit", (code: number, signal: number) => { - console.log(`bitcoind exited with ${code || "signal " + signal}`); - }); - - const logReader = new LogReader(this.logDir + "/bitcoind.log"); - await logReader.waitForLogMessage("Wallet completed loading"); - - const result = fs.readFileSync( - `${this.dbDir.name}/regtest/.cookie`, - "utf8" - ); - const [username, password] = result.split(":"); - - this.username = username; - this.password = password; - - return this; - } - - public stop() { - this.process.kill("SIGINT"); - } - - public getUsernamePassword() { - return { username: this.username, password: this.password }; - } -} diff --git a/api_tests/lib_sdk/cnd_instance.ts b/api_tests/lib/cnd/cnd_instance.ts similarity index 66% rename from api_tests/lib_sdk/cnd_instance.ts rename to api_tests/lib/cnd/cnd_instance.ts index 31f0571183..ea6384cdfb 100644 --- a/api_tests/lib_sdk/cnd_instance.ts +++ b/api_tests/lib/cnd/cnd_instance.ts @@ -3,10 +3,11 @@ import { ChildProcess, spawn } from "child_process"; import * as fs from "fs"; import tempWrite from "temp-write"; import { promisify } from "util"; -import { CndConfigFile, E2ETestActorConfig } from "../lib/config"; -import { LedgerConfig } from "../lib/ledger_runner"; -import { sleep } from "../lib/util"; -import { HarnessGlobal } from "../lib/util"; +import { CndConfigFile, E2ETestActorConfig } from "../config"; +import { LedgerConfig } from "../ledgers/ledger_runner"; +import { HarnessGlobal, sleep } from "../utils"; +import path from "path"; +import { LogReader } from "../ledgers/log_reader"; declare var global: HarnessGlobal; @@ -45,18 +46,17 @@ export class CndInstance { "config.toml" ); + const logFile = path.join( + this.logDir, + `cnd-${this.actorConfig.name}.log` + ); + this.process = spawn(bin, ["--config", configFile], { cwd: this.projectRoot, stdio: [ "ignore", // stdin - await openAsync( - this.logDir + "/cnd-" + this.actorConfig.name + ".log", - "w" - ), // stdout - await openAsync( - this.logDir + "/cnd-" + this.actorConfig.name + ".log", - "w" - ), // stderr + await openAsync(logFile, "w"), // stdout + await openAsync(logFile, "w"), // stderr ], }); @@ -66,19 +66,17 @@ export class CndInstance { ); } - this.process.on("exit", (code: number, signal: number) => { - if (global.verbose) { - console.log( - `cnd ${this.actorConfig.name} exited with ${code || - "signal " + signal}` - ); - } - }); + const logReader = new LogReader(logFile); + await logReader.waitForLogMessage("Starting HTTP server on"); - await sleep(1000); // allow the nodes to start up + // we emit the log _before_ we start the http server, let's make sure it actually starts up + await sleep(1000); } public stop() { + if (global.verbose) { + console.log(`terminating cnd ${this.actorConfig.name}`); + } this.process.kill("SIGINT"); this.process = null; } diff --git a/api_tests/lib/cnd_runner.ts b/api_tests/lib/cnd_runner.ts deleted file mode 100644 index 81d14f3b5f..0000000000 --- a/api_tests/lib/cnd_runner.ts +++ /dev/null @@ -1,90 +0,0 @@ -import * as fs from "fs"; -import * as path from "path"; -import { promisify } from "util"; -import { CndInstance } from "../lib_sdk/cnd_instance"; -import { CND_CONFIGS } from "./config"; -import { LedgerConfig } from "./ledger_runner"; -import { HarnessGlobal } from "./util"; - -declare var global: HarnessGlobal; - -const unlinkAsync = promisify(fs.unlink); -const existsAsync = promisify(fs.exists); - -export class CndRunner { - private runningNodes: { [key: string]: CndInstance }; - - constructor( - private readonly projectRoot: string, - private readonly logDir: string - ) { - this.runningNodes = {}; - } - - public async ensureCndsRunning( - actors: string[], - ledgerConfig: LedgerConfig - ) { - const actorsToBeStarted = actors.filter( - actor => !Object.keys(this.runningNodes).includes(actor) - ); - - if (global.verbose) { - console.log("Starting cnd for " + actorsToBeStarted.join(", ")); - } - - const promises = actorsToBeStarted.map(async name => { - const cndconfig = CND_CONFIGS[name]; - - if (!cndconfig) { - throw new Error( - `Please define a cnd configuration for ${name}` - ); - } - - const process = new CndInstance( - this.projectRoot, - this.logDir, - cndconfig, - ledgerConfig - ); - - const db = path.join(cndconfig.data, "cnd.sqlite"); - - if (await existsAsync(db)) { - await unlinkAsync(db); // delete the old database for the new test - } - - await process.start(); - - return { - name, - process, - }; - }); - - const startedNodes = await Promise.all(promises); - - for (const { name, process } of startedNodes) { - this.runningNodes[name] = process; - } - - if (global.verbose) { - console.log("All nodes successfully started"); - } - } - - public stopCnds() { - const names = Object.keys(this.runningNodes); - - if (names.length > 0) { - if (global.verbose) { - console.log("Stopping cnds: " + names.join(", ")); - } - for (const process of Object.values(this.runningNodes)) { - process.stop(); - } - this.runningNodes = {}; - } - } -} diff --git a/api_tests/lib/comit.ts b/api_tests/lib/comit.ts deleted file mode 100644 index 4b04899375..0000000000 --- a/api_tests/lib/comit.ts +++ /dev/null @@ -1,73 +0,0 @@ -/// HTTP API - -export type LedgerAction = - | { - type: "bitcoin-send-amount-to-address"; - payload: { to: string; amount: string; network: string }; - } - | { - type: "bitcoin-broadcast-signed-transaction"; - payload: { - hex: string; - network: string; - min_median_block_time?: number; - }; - } - | { - type: "ethereum-deploy-contract"; - payload: { - data: string; - amount: string; - gas_limit: string; - chain_id: number; - }; - } - | { - type: "ethereum-call-contract"; - payload: { - contract_address: string; - data: string; - gas_limit: string; - chain_id: number; - min_block_timestamp?: number; - }; - }; - -export interface Asset { - name: string; - quantity: string; - token_contract?: string; -} - -export interface BitcoinLedger { - name: string; - network: string; -} - -export interface EthereumLedger { - name: string; - chain_id: number; -} - -export type Ledger = BitcoinLedger | EthereumLedger; - -export interface SwapRequest { - alpha_ledger: Ledger; - beta_ledger: Ledger; - alpha_asset: Asset; - beta_asset: Asset; - beta_ledger_redeem_identity?: string; - alpha_ledger_refund_identity?: string; - alpha_expiry: number; - beta_expiry: number; - peer: string; -} - -export enum ActionKind { - Accept = "accept", - Decline = "decline", - Deploy = "deploy", - Fund = "fund", - Redeem = "redeem", - Refund = "refund", -} diff --git a/api_tests/lib/config.ts b/api_tests/lib/config.ts index 5169773470..9aacb85c8e 100644 --- a/api_tests/lib/config.ts +++ b/api_tests/lib/config.ts @@ -1,7 +1,7 @@ import * as tmp from "tmp"; -import { BitcoinNodeConfig } from "./bitcoin"; -import { EthereumNodeConfig } from "./ethereum"; -import { LedgerConfig } from "./ledger_runner"; +import { BitcoinNodeConfig } from "./ledgers/bitcoin"; +import { LedgerConfig } from "./ledgers/ledger_runner"; +import { EthereumNodeConfig } from "./ledgers/ethereum"; export interface CndConfigFile { http_api: HttpApi; @@ -11,7 +11,7 @@ export interface CndConfigFile { } export interface HttpApi { - socket: { address: string; port: number }; + socket: string; } export class E2ETestActorConfig { @@ -20,7 +20,9 @@ export class E2ETestActorConfig { constructor( public readonly httpApiPort: number, public readonly comitPort: number, - public readonly name: string + public readonly name: string, + public readonly lndP2pPort: number, + public readonly lndRpcPort: number ) { this.httpApiPort = httpApiPort; this.comitPort = comitPort; @@ -34,10 +36,7 @@ export class E2ETestActorConfig { public generateCndConfigFile(ledgerConfig: LedgerConfig): CndConfigFile { return { http_api: { - socket: { - address: "0.0.0.0", - port: this.httpApiPort, - }, + socket: `0.0.0.0:${this.httpApiPort}`, }, data: { dir: this.data, @@ -58,19 +57,24 @@ interface LedgerConnectors { ethereum?: EthereumConnector; } +interface Parity { + node_url: string; +} + interface EthereumConnector { + chain_id: number; + parity: Parity; +} + +interface Bitcoind { node_url: string; } interface BitcoinConnector { - node_url: string; network: string; + bitcoind: Bitcoind; } -export const ALICE_CONFIG = new E2ETestActorConfig(8000, 9938, "alice"); -export const BOB_CONFIG = new E2ETestActorConfig(8010, 9939, "bob"); -export const CHARLIE_CONFIG = new E2ETestActorConfig(8020, 8021, "charlie"); - function createLedgerConnectors(ledgerConfig: LedgerConfig): LedgerConnectors { const config: LedgerConnectors = {}; @@ -87,21 +91,18 @@ function createLedgerConnectors(ledgerConfig: LedgerConfig): LedgerConnectors { function bitcoinConnector(nodeConfig: BitcoinNodeConfig): BitcoinConnector { return { - node_url: `http://${nodeConfig.host}:${nodeConfig.rpcPort}`, + bitcoind: { + node_url: `http://${nodeConfig.host}:${nodeConfig.rpcPort}`, + }, network: nodeConfig.network, }; } function ethereumConnector(nodeConfig: EthereumNodeConfig): EthereumConnector { return { - node_url: nodeConfig.rpc_url, + chain_id: 17, + parity: { + node_url: nodeConfig.rpc_url, + }, }; } - -export const CND_CONFIGS: { - [actor: string]: E2ETestActorConfig | undefined; -} = { - alice: ALICE_CONFIG, - bob: BOB_CONFIG, - charlie: CHARLIE_CONFIG, -}; diff --git a/api_tests/lib_sdk/create_actor.ts b/api_tests/lib/create_actor.ts similarity index 72% rename from api_tests/lib_sdk/create_actor.ts rename to api_tests/lib/create_actor.ts index 44d05c97eb..ff9da6d758 100644 --- a/api_tests/lib_sdk/create_actor.ts +++ b/api_tests/lib/create_actor.ts @@ -1,11 +1,12 @@ import { configure } from "log4js"; -import { HarnessGlobal } from "../lib/util"; import { Actor } from "./actors/actor"; +import { HarnessGlobal } from "./utils"; +import path from "path"; declare var global: HarnessGlobal; export async function createActor( - logFileName: string, + testFolderName: string, name: string ): Promise { const loggerFactory = (whoAmI: string) => @@ -13,7 +14,7 @@ export async function createActor( appenders: { file: { type: "file", - filename: "log/tests/" + logFileName.replace(/\//g, "_"), + filename: path.join(testFolderName, "test.log"), }, }, categories: { @@ -21,13 +22,11 @@ export async function createActor( }, }).getLogger(whoAmI); - const actor = await Actor.newInstance( + return Actor.newInstance( loggerFactory, name, global.ledgerConfigs, global.projectRoot, - global.logRoot + testFolderName ); - - return actor; } diff --git a/api_tests/lib/create_actors.ts b/api_tests/lib/create_actors.ts new file mode 100644 index 0000000000..661e2564ed --- /dev/null +++ b/api_tests/lib/create_actors.ts @@ -0,0 +1,39 @@ +import { Actors } from "./actors"; +import { Actor } from "./actors/actor"; +import { createActor } from "./create_actor"; +import { HarnessGlobal, mkdirAsync, rimrafAsync } from "./utils"; +import path from "path"; + +declare var global: HarnessGlobal; + +export async function createActors( + testName: string, + actorNames: string[] +): Promise { + const actorsMap = new Map(); + const testFolderName = path.join(global.logRoot, "tests", testName); + + await resetLogs(testFolderName); + + const listPromises: Promise[] = []; + for (const name of actorNames) { + listPromises.push(createActor(testFolderName, name)); + } + const createdActors = await Promise.all(listPromises); + for (const actor of createdActors) { + actorsMap.set(actor.getName(), actor); + } + + const actors = new Actors(actorsMap); + + for (const name of actorNames) { + actorsMap.get(name).actors = actors; + } + + return actors; +} + +async function resetLogs(logDir: string) { + await rimrafAsync(logDir); + await mkdirAsync(logDir, { recursive: true }); +} diff --git a/api_tests/lib/ethereum.ts b/api_tests/lib/ethereum.ts deleted file mode 100644 index b760b1e185..0000000000 --- a/api_tests/lib/ethereum.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { ECPair, ECPairInterface } from "bitcoinjs-lib"; -import { ethers } from "ethers"; -import { JsonRpcProvider, TransactionRequest } from "ethers/providers"; -import { BigNumber } from "ethers/utils"; -import * as fs from "fs"; -import * as util from "./util"; - -let ethersClient: JsonRpcProvider; - -export interface EthereumNodeConfig { - rpc_url: string; -} - -function createEthereumClient(ethConfig: EthereumNodeConfig) { - if (!ethConfig && ethersClient) { - throw new Error("ethereum configuration is needed"); - } - if (!ethersClient || ethConfig !== ethConfig) { - ethersClient = new ethers.providers.JsonRpcProvider(ethConfig.rpc_url); - } - - return ethersClient; -} - -async function ethBalance(address: string) { - return ethersClient.getBalance(address); -} - -async function erc20Balance( - tokenHolderAddress: string, - contractAddress: string -) { - const functionIdentifier = "70a08231"; - - const paddedAddress = tokenHolderAddress - .replace(/^0x/, "") - .padStart(64, "0"); - const payload = "0x" + functionIdentifier + paddedAddress; - - const tx = { - from: tokenHolderAddress, - to: contractAddress, - data: payload, - }; - - const transactionReceipt = await ethersClient.call(tx); - return ethers.utils.bigNumberify(transactionReceipt); -} - -async function mintErc20Tokens( - ownerWallet: EthereumWallet, - contractAddress: string, - toAddress: string, - amount: BigNumber | string | number -) { - const functionIdentifier = "40c10f19"; - - toAddress = toAddress.replace(/^0x/, "").padStart(64, "0"); - - const bigNumber = ethers.utils.bigNumberify(amount); - const hexAmount = bigNumber - .toHexString() - .replace(/^0x/, "") - .padStart(64, "0"); - const payload = "0x" + functionIdentifier + toAddress + hexAmount; - - const transactionResponse = await ownerWallet.sendEthTransactionTo( - contractAddress, - payload, - "0x0" - ); - return transactionResponse.wait(1); -} - -export class EthereumWallet { - private readonly keypair: ECPairInterface; - private readonly account: string; - - constructor(ethConfig: EthereumNodeConfig) { - this.keypair = ECPair.makeRandom({ rng: util.test_rng }); - this.account = new ethers.Wallet(this.keypair.privateKey).address; - createEthereumClient(ethConfig); - } - - public address() { - return this.account; - } - - public ethBalance() { - return ethBalance(this.account); - } - - public erc20Balance(contractAddress: string) { - return erc20Balance(this.account, contractAddress); - } - - public async fund(ether: string, confirmation: number = 1) { - const parityPrivateKey = - "0x4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7"; - - const weiAmount = ethers.utils.parseEther(ether); - const chainId = await ethersClient.getNetwork(); - const tx: TransactionRequest = { - to: this.address(), - value: weiAmount.toHexString(), - gasLimit: 21000, - chainId: chainId.chainId, - }; - - const wallet = new ethers.Wallet(parityPrivateKey, ethersClient); - const transactionResponse = await wallet.sendTransaction(tx); - return transactionResponse.wait(confirmation); - } - - public async mintErc20To( - toAddress: string, - amount: BigNumber | string | number, - contractAddress: string - ) { - const receipt = await mintErc20Tokens( - this, - contractAddress, - toAddress, - amount - ); - - if (!receipt.status) { - throw new Error( - `Minting ${amount} tokens to address ${toAddress} failed` - ); - } - - return receipt; - } - - public async sendEthTransactionTo( - to: string, - data: string, - value: BigNumber | string | number = 0, - gasLimit: string = "0x100000", - chainId: number = 17 - ) { - if (!to) { - throw new Error("`to` cannot be null"); - } - - value = ethers.utils.bigNumberify(value); - - const tx: TransactionRequest = { - gasPrice: "0x0", - gasLimit, - to, - data, - value: value.toHexString(), - chainId, - }; - return this.signAndSend(tx); - } - - public async deployErc20TokenContract( - projectRoot: string - ): Promise { - const tokenContractDeploy = - "0x" + - fs - .readFileSync( - projectRoot + "/api_tests/erc20_token_contract.asm.hex", - "utf8" - ) - .trim(); - const transactionResponse = await this.deploy_contract( - tokenContractDeploy - ); - const transactionReceipt = await transactionResponse.wait(1); - return transactionReceipt.contractAddress; - } - - public async deploy_contract( - data: string = "0x0", - value: BigNumber | number | string = "0", - chainId: number = 17, - gasLimit = "0x3D0900" - ) { - const nonce = await ethersClient.getTransactionCount(this.address()); - - value = ethers.utils.bigNumberify(value); - - const tx: TransactionRequest = { - nonce: "0x" + nonce.toString(16), - gasPrice: "0x0", - gasLimit, - data, - value: value.toHexString(), - chainId, - }; - - return this.signAndSend(tx); - } - - public async signAndSend(tx: TransactionRequest) { - const wallet = new ethers.Wallet(this.keypair.privateKey, ethersClient); - tx.nonce = await wallet.getTransactionCount("latest"); - const signedTx = await wallet.sign(tx); - return ethersClient.sendTransaction(signedTx); - } -} diff --git a/api_tests/lib/ledgers/bitcoin.ts b/api_tests/lib/ledgers/bitcoin.ts new file mode 100644 index 0000000000..b48d76bc43 --- /dev/null +++ b/api_tests/lib/ledgers/bitcoin.ts @@ -0,0 +1,56 @@ +import BitcoinRpcClient from "bitcoin-core"; + +export interface BitcoinNodeConfig { + network: string; + username: string; + password: string; + host: string; + rpcPort: number; + p2pPort: number; + dataDir: string; +} + +let bitcoinRpcClient: BitcoinRpcClient; +let bitcoinConfig: BitcoinNodeConfig; + +export function init(btcConfig: BitcoinNodeConfig) { + createBitcoinRpcClient(btcConfig); +} + +function createBitcoinRpcClient(btcConfig?: BitcoinNodeConfig) { + if (!btcConfig && !bitcoinConfig) { + throw new Error("bitcoin configuration is needed"); + } + + if (!bitcoinRpcClient || btcConfig !== bitcoinConfig) { + bitcoinRpcClient = new BitcoinRpcClient({ + network: btcConfig.network, + port: btcConfig.rpcPort, + host: btcConfig.host, + username: btcConfig.username, + password: btcConfig.password, + }); + bitcoinConfig = btcConfig; + } + return bitcoinRpcClient; +} + +export async function generate(num: number = 1) { + const client = createBitcoinRpcClient(bitcoinConfig); + + return client.generateToAddress(num, await client.getNewAddress()); +} + +export async function ensureFunding() { + const blockHeight = await createBitcoinRpcClient( + bitcoinConfig + ).getBlockCount(); + if (blockHeight < 101) { + const client = createBitcoinRpcClient(bitcoinConfig); + + await client.generateToAddress( + 101 - blockHeight, + await client.getNewAddress() + ); + } +} diff --git a/api_tests/lib/ledgers/bitcoind_instance.ts b/api_tests/lib/ledgers/bitcoind_instance.ts new file mode 100644 index 0000000000..e7e5eb3a65 --- /dev/null +++ b/api_tests/lib/ledgers/bitcoind_instance.ts @@ -0,0 +1,100 @@ +import { ChildProcess, spawn } from "child_process"; +import * as fs from "fs"; +import { LedgerInstance } from "./ledger_runner"; +import { LogReader } from "./log_reader"; +import * as path from "path"; +import { openAsync, mkdirAsync, writeFileAsync } from "../utils"; + +export class BitcoindInstance implements LedgerInstance { + private process: ChildProcess; + private dataDir: string; + private username: string; + private password: string; + + constructor( + private readonly projectRoot: string, + private readonly logDir: string, + public readonly p2pPort: number, + public readonly rpcPort: number, + public readonly zmqPubRawBlockPort: number, + public readonly zmqPubRawTxPort: number + ) {} + + public async start() { + const bin = process.env.BITCOIND_BIN + ? process.env.BITCOIND_BIN + : path.join( + this.projectRoot, + "blockchain_nodes", + "bitcoin", + "bitcoin-0.17.0", + "bin", + "bitcoind" + ); + + this.dataDir = path.join(this.logDir, "bitcoind"); + await mkdirAsync(this.dataDir, "755"); + await this.createConfigFile(this.dataDir); + + const log = this.logPath(); + this.process = spawn(bin, [`-datadir=${this.dataDir}`], { + cwd: this.projectRoot, + stdio: [ + "ignore", // stdin + await openAsync(log, "w"), // stdout + await openAsync(log, "w"), // stderr + ], + }); + + this.process.on("exit", (code: number, signal: number) => { + console.log(`bitcoind exited with ${code || `signal ${signal}`}`); + }); + + const logReader = new LogReader(this.logPath()); + await logReader.waitForLogMessage("Wallet completed loading"); + + const result = fs.readFileSync( + path.join(this.dataDir, "regtest", ".cookie"), + "utf8" + ); + const [username, password] = result.split(":"); + + this.username = username; + this.password = password; + + return this; + } + + public stop() { + this.process.kill("SIGINT"); + } + + private logPath() { + return path.join(this.dataDir, "bitcoind.log"); + } + + public getDataDir() { + return this.dataDir; + } + + public getUsernamePassword() { + return { username: this.username, password: this.password }; + } + + private async createConfigFile(dataDir: string) { + const output = `regtest=1 +server=1 +printtoconsole=1 +bind=0.0.0.0:${this.p2pPort} +rpcbind=0.0.0.0:${this.rpcPort} +rpcallowip=0.0.0.0/0 +nodebug=1 +rest=1 +acceptnonstdtxn=0 +zmqpubrawblock=tcp://127.0.0.1:${this.zmqPubRawBlockPort} +zmqpubrawtx=tcp://127.0.0.1:${this.zmqPubRawTxPort} +`; + const config = path.join(dataDir, "bitcoin.conf"); + await writeFileAsync(config, output); + } +} diff --git a/api_tests/lib/ledgers/ethereum.ts b/api_tests/lib/ledgers/ethereum.ts new file mode 100644 index 0000000000..3390989309 --- /dev/null +++ b/api_tests/lib/ledgers/ethereum.ts @@ -0,0 +1,4 @@ +export interface EthereumNodeConfig { + rpc_url: string; + tokenContract: string; +} diff --git a/api_tests/lib_sdk/ledger.ts b/api_tests/lib/ledgers/ledger.ts similarity index 88% rename from api_tests/lib_sdk/ledger.ts rename to api_tests/lib/ledgers/ledger.ts index d9e64ad2a4..3a0c746825 100644 --- a/api_tests/lib_sdk/ledger.ts +++ b/api_tests/lib/ledgers/ledger.ts @@ -8,4 +8,5 @@ export interface Ledger extends LedgerSdk { export enum LedgerKind { Bitcoin = "bitcoin", Ethereum = "ethereum", + Lightning = "lightning", } diff --git a/api_tests/lib/ledger_runner.ts b/api_tests/lib/ledgers/ledger_runner.ts similarity index 68% rename from api_tests/lib/ledger_runner.ts rename to api_tests/lib/ledgers/ledger_runner.ts index 58d72a33f9..731286dd9e 100644 --- a/api_tests/lib/ledger_runner.ts +++ b/api_tests/lib/ledgers/ledger_runner.ts @@ -2,12 +2,10 @@ import getPort from "get-port"; import * as bitcoin from "./bitcoin"; import { BitcoinNodeConfig } from "./bitcoin"; import { BitcoindInstance } from "./bitcoind_instance"; -import { EthereumNodeConfig } from "./ethereum"; import { ParityInstance } from "./parity_instance"; -import { HarnessGlobal } from "./util"; -import { EthereumWallet } from "../lib_sdk/wallets/ethereum"; - -declare var global: HarnessGlobal; +import { EthereumWallet } from "../wallets/ethereum"; +import { HarnessGlobal } from "../utils"; +import { EthereumNodeConfig } from "./ethereum"; export interface LedgerConfig { bitcoin?: BitcoinNodeConfig; @@ -25,15 +23,19 @@ export class LedgerRunner { constructor( private readonly projectRoot: string, - private readonly logDir: string + private readonly logDir: string, + private harnessGlobal: HarnessGlobal ) { this.runningLedgers = {}; this.blockTimers = {}; } - public async ensureLedgersRunning(ledgers: string[]) { + public async ensureLedgersRunning( + ledgers: string[] + ): Promise { const toBeStarted = ledgers.filter(name => !this.runningLedgers[name]); + const returnValue: LedgerConfig = {}; const promises = toBeStarted.map(async ledger => { console.log(`Starting ledger ${ledger}`); @@ -43,7 +45,9 @@ export class LedgerRunner { this.projectRoot, this.logDir, await getPort({ port: 18444 }), - await getPort({ port: 18443 }) + await getPort({ port: 18443 }), + await getPort({ port: 28332 }), + await getPort({ port: 28333 }) ); return { ledger, @@ -73,53 +77,56 @@ export class LedgerRunner { this.runningLedgers[ledger] = instance; if (ledger === "bitcoin") { - if (global.verbose) { + if (this.harnessGlobal.verbose) { console.log( "Bitcoin: initialization after ledger is running." ); } bitcoin.init(await this.getBitcoinClientConfig()); await bitcoin.ensureFunding(); - this.blockTimers.bitcoin = global.setInterval(async () => { - await bitcoin.generate(); - }, 1000); + this.blockTimers.bitcoin = this.harnessGlobal.setInterval( + async () => { + await bitcoin.generate(); + }, + 1000 + ); + returnValue.bitcoin = await this.getBitcoinClientConfig().catch( + () => undefined + ); } if (ledger === "ethereum") { - const ethereumConfig = await this.getEthereumNodeConfig(); - const erc20Wallet = new EthereumWallet(ethereumConfig); - global.tokenContract = await erc20Wallet.deployErc20TokenContract( - global.projectRoot + const ethereumNodeUrl = await this.getEthereumNodeUrl().catch( + () => undefined ); - if (global.verbose) { + const erc20Wallet = new EthereumWallet(ethereumNodeUrl); + returnValue.ethereum = { + rpc_url: ethereumNodeUrl, + tokenContract: await erc20Wallet.deployErc20TokenContract( + this.projectRoot + ), + }; + if (this.harnessGlobal.verbose) { console.log( "Ethereum: deployed Erc20 contract at %s", - global.tokenContract + returnValue.ethereum.tokenContract ); } } } + + return returnValue; } - public async stopLedgers() { + public stopLedgers() { const ledgers = Object.entries(this.runningLedgers); - const promises = ledgers.map(async ([ledger, container]) => { + ledgers.map(([ledger, ledgerInstance]) => { console.log(`Stopping ledger ${ledger}`); - clearInterval(this.blockTimers[ledger]); - await container.stop(); + ledgerInstance.stop(); delete this.runningLedgers[ledger]; }); - - await Promise.all(promises); - } - - public async getLedgerConfig(): Promise { - return { - bitcoin: await this.getBitcoinClientConfig().catch(() => undefined), - ethereum: await this.getEthereumNodeConfig().catch(() => undefined), - }; } private async getBitcoinClientConfig(): Promise { @@ -135,19 +142,18 @@ export class LedgerRunner { p2pPort: instance.p2pPort, username, password, + dataDir: instance.getDataDir(), }; } else { return Promise.reject("bitcoin not yet started"); } } - private async getEthereumNodeConfig(): Promise { + private async getEthereumNodeUrl(): Promise { const instance = this.runningLedgers.ethereum as ParityInstance; if (instance) { - return { - rpc_url: `http://localhost:${instance.rpcPort}`, - }; + return `http://localhost:${instance.rpcPort}`; } else { return Promise.reject("ethereum not yet started"); } diff --git a/api_tests/lib/ledgers/lnd_instance.ts b/api_tests/lib/ledgers/lnd_instance.ts new file mode 100644 index 0000000000..11ef55103d --- /dev/null +++ b/api_tests/lib/ledgers/lnd_instance.ts @@ -0,0 +1,197 @@ +import { ChildProcess, spawn } from "child_process"; +import { E2ETestActorConfig } from "../config"; +import { mkdirAsync, waitUntilFileExists, writeFileAsync } from "../utils"; +import * as path from "path"; +import { Logger } from "log4js"; +import getPort from "get-port"; +import { LogReader } from "./log_reader"; +import { Lnd } from "comit-sdk"; + +export class LndInstance { + private process: ChildProcess; + private lndDir: string; + public lnd: Lnd; + private publicKey?: string; + + constructor( + private readonly logger: Logger, + private readonly testLogDir: string, + private readonly actorConfig: E2ETestActorConfig, + private readonly bitcoindDataDir: string + ) {} + + public async start() { + this.lndDir = path.join( + this.testLogDir, + "lnd-" + this.actorConfig.name + ); + await mkdirAsync(this.lndDir, "755"); + await this.createConfigFile(); + + this.execBinary(); + + this.logger.debug("Waiting for lnd log file to exist:", this.logPath()); + await waitUntilFileExists(this.logPath()); + + this.logger.debug("Waiting for lnd password RPC server"); + await this.logReader().waitForLogMessage( + "RPCS: password RPC server listening" + ); + + await this.initWallet(); + + this.logger.debug("Waiting for lnd unlocked RPC server"); + await this.logReader().waitForLogMessage("RPCS: RPC server listening"); + + this.logger.debug( + "Waiting for admin macaroon file to exist:", + this.adminMacaroonPath() + ); + await waitUntilFileExists(this.adminMacaroonPath()); + + this.logger.debug("Waiting for lnd to catch up with blocks"); + await this.logReader().waitForLogMessage( + "LNWL: Done catching up block hashes" + ); + + await this.initAuthenticatedLndConnection(); + + this.publicKey = (await this.lnd.lnrpc.getInfo()).identityPubkey; + this.logger.info("lnd is ready:", this.publicKey); + } + + private execBinary() { + const bin = process.env.LND_BIN ? process.env.LND_BIN : "lnd"; + this.logger.debug(`Using binary ${bin}`); + this.process = spawn(bin, ["--lnddir", this.lndDir], { + stdio: ["ignore", "ignore", "ignore"], // stdin, stdout, stderr. These are all logged already. + }); + this.logger.debug(`Process spawned LND with PID ${this.process.pid}`); + + this.process.on("exit", (code: number, signal: number) => { + this.logger.debug(`lnd exited with ${code || `signal ${signal}`}`); + }); + } + + private async initWallet() { + const config = { + server: this.getGrpcSocket(), + tls: this.tlsCertPath(), + }; + this.logger.debug("Instantiating lnd connection:", config); + const lnd = await Lnd.init(config); + + this.logger.debug("Calling genSeed"); + const { cipherSeedMnemonic } = await lnd.lnrpc.genSeed({}); + const walletPassword = Buffer.from("password", "utf8"); + this.logger.debug( + "Initialize wallet", + cipherSeedMnemonic, + walletPassword + ); + await lnd.lnrpc.initWallet({ cipherSeedMnemonic, walletPassword }); + this.logger.debug("Wallet initialized!"); + } + + private async initAuthenticatedLndConnection() { + const config = { + server: this.getGrpcSocket(), + tls: this.tlsCertPath(), + macaroonPath: this.adminMacaroonPath(), + }; + this.logger.debug("Instantiating lnd connection:", config); + this.lnd = await Lnd.init(config); + } + + public stop() { + this.logger.debug("Stopping lnd instance"); + this.process.kill("SIGTERM"); + this.process = null; + } + + public isRunning() { + return this.process != null; + } + + public logPath() { + return path.join(this.lndDir, "logs", "bitcoin", "regtest", "lnd.log"); + } + + public tlsCertPath() { + return path.join(this.lndDir, "tls.cert"); + } + + public adminMacaroonPath() { + return path.join( + this.lndDir, + "data", + "chain", + "bitcoin", + "regtest", + "admin.macaroon" + ); + } + + public getGrpcSocket() { + return `${this.getGrpcHost()}:${this.getGrpcPort()}`; + } + + public getGrpcHost() { + return "127.0.0.1"; + } + + public getGrpcPort() { + return this.actorConfig.lndRpcPort; + } + + public getLightningSocket() { + return `${this.getLightningHost()}:${this.getLightningPort()}`; + } + + public getLightningHost() { + return "127.0.0.1"; + } + + public getLightningPort() { + return this.actorConfig.lndP2pPort; + } + + private async createConfigFile() { + // We don't use REST but want a random port so we don't get used port errors. + const restPort = await getPort(); + const output = `[Application Options] +debuglevel=trace + +; peer to peer port +listen=127.0.0.1:${this.actorConfig.lndP2pPort} + +; gRPC +rpclisten=127.0.0.1:${this.actorConfig.lndRpcPort} + +; REST interface +restlisten=127.0.0.1:${restPort} + +; Do not seek out peers on the network +nobootstrap=true + +; Only wait 1 confirmation to open a channel +bitcoin.defaultchanconfs=1 + +[Bitcoin] + +bitcoin.active=true +bitcoin.regtest=true +bitcoin.node=bitcoind + +[Bitcoind] + +bitcoind.dir=${this.bitcoindDataDir} +`; + const config = path.join(this.lndDir, "lnd.conf"); + await writeFileAsync(config, output); + } + + private logReader() { + return new LogReader(this.logPath()); + } +} diff --git a/api_tests/lib/log_reader.ts b/api_tests/lib/ledgers/log_reader.ts similarity index 100% rename from api_tests/lib/log_reader.ts rename to api_tests/lib/ledgers/log_reader.ts diff --git a/api_tests/lib/parity_instance.ts b/api_tests/lib/ledgers/parity_instance.ts similarity index 95% rename from api_tests/lib/parity_instance.ts rename to api_tests/lib/ledgers/parity_instance.ts index a5e9827d3d..1c497bd95e 100644 --- a/api_tests/lib/parity_instance.ts +++ b/api_tests/lib/ledgers/parity_instance.ts @@ -1,10 +1,10 @@ import { ChildProcess, spawn } from "child_process"; import * as fs from "fs"; import tmp from "tmp"; -import { promisify } from "util"; import { LedgerInstance } from "./ledger_runner"; import { LogReader } from "./log_reader"; -import { sleep } from "./util"; +import { promisify } from "util"; +import { sleep } from "../utils"; const openAsync = promisify(fs.open); @@ -46,7 +46,7 @@ export class ParityInstance implements LedgerInstance { ); this.process.on("exit", (code: number, signal: number) => { - console.log(`parity exited with ${code || "signal " + signal}`); + console.log(`parity exited with ${code || `signal ${signal}`}`); }); const logReader = new LogReader(this.logDir + "/parity.log"); await logReader.waitForLogMessage("Public node URL:"); diff --git a/api_tests/lib/test_creator.ts b/api_tests/lib/test_creator.ts deleted file mode 100644 index 59a2b8bd0d..0000000000 --- a/api_tests/lib/test_creator.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { expect, request } from "chai"; -import { Response } from "superagent"; -import { Action, EmbeddedRepresentationSubEntity, Entity } from "../gen/siren"; -import { Actor } from "./actor"; -import { ActionKind, LedgerAction } from "./comit"; -import "./setup_chai"; - -export interface Test { - description: string; - callback: (swapEntity: Entity) => Promise; -} - -interface ArbitraryStepExecution { - description: string; - exec: (actor: Actor, swapHref: string) => Promise; -} -interface StandardStepExecution { - kind: ActionKind; - test: (response: Response) => void; -} -type StepExecution = - | ArbitraryStepExecution - | StandardStepExecution - | ActionKind; - -function isStandardStepExecutionWithDefaultTest( - action: StepExecution -): action is ActionKind { - return typeof action === "string"; -} - -function isStandardStepExecutionWithCustomTest( - action: StandardStepExecution | ArbitraryStepExecution -): action is StandardStepExecution { - return "kind" in action && "test" in action; -} - -function isArbitraryStepExecution( - action: StandardStepExecution | ArbitraryStepExecution -): action is ArbitraryStepExecution { - return "description" in action && "exec" in action; -} - -export interface Step { - actor: Actor; - action?: StepExecution; - waitUntil?: (state: any) => boolean; - test?: Test; -} - -interface SwapLocations { - [key: string]: string; -} - -export function createTests( - alice: Actor, - bob: Actor, - steps: Step[], - initialUrl: string, - listUrl: string, - initialRequest: object -) { - const swapLocations: SwapLocations = {}; - - it( - "[alice] Should be able to make a request via HTTP api to " + - initialUrl, - async () => { - const res: ChaiHttp.Response = await request(alice.cndHttpApiUrl()) - .post(initialUrl) - .send(initialRequest); - expect(res).to.have.status(201); - const swapLocation: string = res.header.location; - expect(swapLocation).to.not.be.empty; - swapLocations.alice = swapLocation; - } - ); - - it("[bob] Shows the Swap as IN_PROGRESS in " + listUrl, async () => { - const swapEntity = await bob - .pollCndUntil(listUrl, body => body.entities.length > 0) - .then(body => body.entities[0] as EmbeddedRepresentationSubEntity); - - expect(swapEntity.properties).to.have.property("protocol", "rfc003"); - expect(swapEntity.properties).to.have.property("status", "IN_PROGRESS"); - - const selfLink = swapEntity.links.find(link => - link.rel.includes("self") - ); - - expect(selfLink).to.not.be.undefined; - - swapLocations.bob = selfLink.href; - }); - - while (steps.length !== 0) { - const { action, actor, waitUntil, test } = steps.shift(); - - if (action) { - if (isStandardStepExecutionWithDefaultTest(action)) { - const defaultTest = (response: Response) => - expect(response).to.have.status(200); - - standardActionSteps(actor, swapLocations, action, defaultTest); - } else if (isStandardStepExecutionWithCustomTest(action)) { - standardActionSteps( - actor, - swapLocations, - action.kind, - action.test - ); - } else if (isArbitraryStepExecution(action)) { - it(`[${actor.name}] ${action.description}`, async function() { - this.timeout(10000); - await action.exec(actor, swapLocations[actor.name]); - }); - } - } - - if (waitUntil) { - it(`[${actor.name}] transitions to correct state`, async function() { - this.timeout(10000); - await actor.pollCndUntil(swapLocations[actor.name], body => - waitUntil(body.properties.state) - ); - }); - } - - if (test) { - it(`[${actor.name}] ${test.description}`, async function() { - this.timeout(10000); - - const body = await actor.pollCndUntil( - swapLocations[actor.name], - () => true - ); - - return test.callback(body); - }); - } - } - - return swapLocations; -} - -export function hasAction(actionKind: ActionKind) { - return (body: Entity) => - body.actions.findIndex(candidate => candidate.name === actionKind) !== - -1; -} - -export function mapToAction(actionKind: ActionKind): (body: Entity) => Action { - return (body: Entity) => - body.actions.find(candidate => candidate.name === actionKind); -} - -function standardActionSteps( - actor: Actor, - swapLocations: SwapLocations, - actionKind: ActionKind, - actionTest: (response: Response) => void -) { - let sirenAction: Action; - - it(`[${actor.name}] has the ${actionKind} action`, async function() { - this.timeout(5000); - - sirenAction = await actor - .pollCndUntil(swapLocations[actor.name], hasAction(actionKind)) - .then(mapToAction(actionKind)); - }); - - it(`[${actor.name}] Can execute the ${actionKind} action`, async function() { - if (actionKind === ActionKind.Refund) { - this.timeout(30000); - } else { - this.timeout(5000); - } - - const response = await actor.doComitAction(sirenAction); - - actionTest(response); - - // We should check against our own content type here to describe "LedgerActions" - // Don't take it literally but something like `application/vnd.comit-ledger-action+json` - // For now, checking for `application/json` + the fields should do the job as well because accept & decline don't return a body - if ( - response.type === "application/json" && - response.body && - response.body.type && - response.body.payload - ) { - const body = response.body as LedgerAction; - - await actor.doLedgerAction(body); - } - }); -} diff --git a/api_tests/lib/util.ts b/api_tests/lib/util.ts deleted file mode 100644 index f162a8b0cf..0000000000 --- a/api_tests/lib/util.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { LedgerConfig } from "./ledger_runner"; - -let testRngCounter = 0; - -export function test_rng() { - testRngCounter++; - return Buffer.from(("" + testRngCounter).padStart(32, "0")); -} - -export async function sleep(time: number) { - return new Promise(res => { - setTimeout(res, time); - }); -} - -export function seconds_until(time: number): number { - const diff = time - Math.floor(Date.now() / 1000); - - if (diff > 0) { - return diff; - } else { - return 0; - } -} - -/// This is needed to use the global variable in TypeScript -import Global = NodeJS.Global; - -export interface HarnessGlobal extends Global { - ledgerConfigs: LedgerConfig; - testRoot: string; - projectRoot: string; - logRoot: string; - verbose: boolean; - tokenContract: string; -} diff --git a/api_tests/lib_sdk/utils.ts b/api_tests/lib/utils.ts similarity index 68% rename from api_tests/lib_sdk/utils.ts rename to api_tests/lib/utils.ts index 691de90e0d..0d3a133522 100644 --- a/api_tests/lib_sdk/utils.ts +++ b/api_tests/lib/utils.ts @@ -1,6 +1,31 @@ import { ethers } from "ethers"; import { Actor } from "./actors/actor"; import { SwapRequest } from "comit-sdk"; +import * as fs from "fs"; +import { promisify } from "util"; +import { Global } from "@jest/types"; +import { LedgerConfig } from "./ledgers/ledger_runner"; +import rimraf from "rimraf"; +import { Mutex } from "async-mutex"; +import { exec } from "child_process"; + +export interface HarnessGlobal extends Global.Global { + ledgerConfigs: LedgerConfig; + testRoot: string; + projectRoot: string; + logRoot: string; + verbose: boolean; + tokenContract: string; + parityAccountMutex: Mutex; +} + +export const unlinkAsync = promisify(fs.unlink); +export const existsAsync = promisify(fs.exists); +export const openAsync = promisify(fs.open); +export const mkdirAsync = promisify(fs.mkdir); +export const writeFileAsync = promisify(fs.writeFile); +export const rimrafAsync = promisify(rimraf); +export const execAsync = promisify(exec); export async function sleep(time: number) { return new Promise(res => { @@ -13,7 +38,7 @@ export async function timeout(ms: number, promise: Promise): Promise { const timeout = new Promise((_, reject) => { const id = setTimeout(() => { clearTimeout(id); - reject(new Error("timed out after " + ms + "ms")); + reject(new Error(`timed out after ${ms}ms`)); }, ms); }); @@ -83,3 +108,11 @@ export async function createDefaultSwapRequest(counterParty: Actor) { }; return swapRequest; } + +export async function waitUntilFileExists(filepath: string) { + let logFileExists = false; + do { + await sleep(500); + logFileExists = await existsAsync(filepath); + } while (!logFileExists); +} diff --git a/api_tests/lib/wallet.ts b/api_tests/lib/wallet.ts deleted file mode 100644 index e8ee687756..0000000000 --- a/api_tests/lib/wallet.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { BitcoinWallet } from "./bitcoin"; -import { EthereumWallet } from "./ethereum"; -import { LedgerConfig } from "./ledger_runner"; - -export interface WalletConfig { - ledgerConfig: LedgerConfig; - addressForIncomingBitcoinPayments?: string; -} - -export class Wallet { - public owner: string; - private readonly ethWallet: EthereumWallet; - private readonly btcWallet: BitcoinWallet; - - constructor(owner: string, config: WalletConfig) { - this.owner = owner; - - if (config.ledgerConfig.ethereum) { - this.ethWallet = new EthereumWallet(config.ledgerConfig.ethereum); - } - - if (config.ledgerConfig.bitcoin) { - this.btcWallet = new BitcoinWallet( - config.ledgerConfig.bitcoin, - config.addressForIncomingBitcoinPayments - ); - } - } - - public eth() { - return this.ethWallet; - } - - public btc() { - return this.btcWallet; - } -} diff --git a/api_tests/lib_sdk/wallets/bitcoin.ts b/api_tests/lib/wallets/bitcoin.ts similarity index 89% rename from api_tests/lib_sdk/wallets/bitcoin.ts rename to api_tests/lib/wallets/bitcoin.ts index 9b79d057cc..e07f549640 100644 --- a/api_tests/lib_sdk/wallets/bitcoin.ts +++ b/api_tests/lib/wallets/bitcoin.ts @@ -6,7 +6,7 @@ import { InMemoryBitcoinWallet as BitcoinWalletSdk, } from "comit-sdk"; import { toBitcoin, toSatoshi } from "satoshi-bitcoin"; -import { BitcoinNodeConfig } from "../../lib/bitcoin"; +import { BitcoinNodeConfig } from "../ledgers/bitcoin"; import { pollUntilMinted, Wallet } from "./index"; export class BitcoinWallet implements Wallet { @@ -37,6 +37,23 @@ export class BitcoinWallet implements Wallet { private readonly bitcoinRpcClient: BitcoinRpcClient ) {} + public async mintToAddress( + minimumExpectedBalance: BigNumber, + toAddress: string + ): Promise { + const blockHeight = await this.bitcoinRpcClient.getBlockCount(); + if (blockHeight < 101) { + throw new Error( + "unable to mint bitcoin, coinbase transactions are not yet spendable" + ); + } + + await this.bitcoinRpcClient.sendToAddress( + toAddress, + toBitcoin(minimumExpectedBalance.times(2).toString()) // make sure we have at least twice as much + ); + } + public async mint(asset: Asset): Promise { if (asset.name !== "bitcoin") { throw new Error( @@ -50,17 +67,7 @@ export class BitcoinWallet implements Wallet { const minimumExpectedBalance = new BigNumber(asset.quantity); - const blockHeight = await this.bitcoinRpcClient.getBlockCount(); - if (blockHeight < 101) { - throw new Error( - "unable to mint bitcoin, coinbase transactions are not yet spendable" - ); - } - - await this.bitcoinRpcClient.sendToAddress( - await this.address(), - toBitcoin(minimumExpectedBalance.times(2).toString()) // make sure we have at least twice as much - ); + await this.mintToAddress(minimumExpectedBalance, await this.address()); await pollUntilMinted( this, @@ -69,7 +76,7 @@ export class BitcoinWallet implements Wallet { ); } - public address(): Promise { + public async address(): Promise { return this.inner.getAddress(); } diff --git a/api_tests/lib_sdk/wallets/ethereum.ts b/api_tests/lib/wallets/ethereum.ts similarity index 91% rename from api_tests/lib_sdk/wallets/ethereum.ts rename to api_tests/lib/wallets/ethereum.ts index fc6ab850c5..7ff66a61ee 100644 --- a/api_tests/lib_sdk/wallets/ethereum.ts +++ b/api_tests/lib/wallets/ethereum.ts @@ -2,12 +2,11 @@ import { BigNumber, EthereumWallet as EthereumWalletSdk } from "comit-sdk"; import { Asset } from "comit-sdk"; import { ethers } from "ethers"; import { BigNumber as BigNumberEthers } from "ethers/utils"; -import { EthereumNodeConfig } from "../../lib/ethereum"; import { pollUntilMinted, Wallet } from "./index"; -import { HarnessGlobal } from "../../lib/util"; import { TransactionRequest } from "ethers/providers"; import * as fs from "fs"; -import { sleep } from "../utils"; +import { HarnessGlobal, sleep } from "../utils"; +import { EthereumNodeConfig } from "../ledgers/ethereum"; declare var global: HarnessGlobal; @@ -61,7 +60,7 @@ export class EthereumWallet implements Wallet { value: "0x0", data, }; - const transactionResponse = await this.parity.sendTransaction(tx); + const transactionResponse = await this.sendTransaction(tx); const transactionReceipt = await transactionResponse.wait(1); if (!transactionReceipt.status) { @@ -77,14 +76,22 @@ export class EthereumWallet implements Wallet { } } + private async sendTransaction(tx: TransactionRequest) { + const release = await global.parityAccountMutex.acquire(); + try { + return await this.parity.sendTransaction(tx); + } finally { + release(); + } + } + private async mintEther(asset: Asset): Promise { const startingBalance = await this.getBalanceByAsset(asset); - const minimumExpectedBalance = asset.quantity; // make sure we have at least twice as much const value = new BigNumberEthers(minimumExpectedBalance).mul(2); - await this.parity.sendTransaction({ + await this.sendTransaction({ to: this.account(), value, gasLimit: 21000, diff --git a/api_tests/lib_sdk/wallets/index.ts b/api_tests/lib/wallets/index.ts similarity index 64% rename from api_tests/lib_sdk/wallets/index.ts rename to api_tests/lib/wallets/index.ts index 52d2aaf2fb..639c540431 100644 --- a/api_tests/lib_sdk/wallets/index.ts +++ b/api_tests/lib/wallets/index.ts @@ -1,15 +1,16 @@ -import { BigNumber } from "comit-sdk"; -import { HarnessGlobal } from "../../lib/util"; -import { Asset } from "comit-sdk"; -import { sleep } from "../utils"; +import { Asset, BigNumber, Lnd } from "comit-sdk"; +import { HarnessGlobal, sleep } from "../utils"; import { BitcoinWallet } from "./bitcoin"; import { EthereumWallet } from "./ethereum"; +import { LightningWallet } from "./lightning"; +import { Logger } from "log4js"; declare var global: HarnessGlobal; interface AllWallets { bitcoin?: BitcoinWallet; ethereum?: EthereumWallet; + lightning?: LightningWallet; } export interface Wallet { @@ -30,6 +31,10 @@ export class Wallets { return this.getWalletForLedger("ethereum"); } + get lightning(): LightningWallet { + return this.getWalletForLedger("lightning"); + } + public getWalletForLedger( name: K ): AllWallets[K] { @@ -42,7 +47,11 @@ export class Wallets { return wallet; } - public async initializeForLedger(name: K) { + public async initializeForLedger( + name: K, + logger: Logger, + lnd: { lnd: Lnd; lndP2pSocket: string } | undefined + ) { switch (name) { case "ethereum": this.wallets.ethereum = new EthereumWallet( @@ -54,6 +63,21 @@ export class Wallets { global.ledgerConfigs.bitcoin ); break; + case "lightning": + if (!lnd) { + throw new Error( + "Lnd is needed to instantiate lightning wallet." + ); + } + this.wallets.lightning = await LightningWallet.newInstance( + await BitcoinWallet.newInstance( + global.ledgerConfigs.bitcoin + ), + logger, + lnd.lnd, + lnd.lndP2pSocket + ); + break; } } } diff --git a/api_tests/lib/wallets/lightning.ts b/api_tests/lib/wallets/lightning.ts new file mode 100644 index 0000000000..d1a0e9dfac --- /dev/null +++ b/api_tests/lib/wallets/lightning.ts @@ -0,0 +1,175 @@ +import { pollUntilMinted, Wallet } from "./index"; +import { Asset } from "../asset"; +import BigNumber from "bignumber.js"; +import { Logger } from "log4js"; +import { BitcoinWallet } from "./bitcoin"; +import { sleep } from "../utils"; +import { + LightningWallet as LightningWalletSdk, + Lnd, + Outpoint, +} from "comit-sdk"; +import { AddressType } from "@radar/lnrpc"; + +export class LightningWallet implements Wallet { + public static async newInstance( + bitcoinWallet: BitcoinWallet, + logger: Logger, + lnd: Lnd, + lndp2pSocket: string + ) { + const inner = await LightningWalletSdk.newInstance( + lnd.config.tls, + lnd.config.macaroonPath, + lnd.config.server, + lndp2pSocket + ); + + logger.debug("lnd getinfo:", await inner.lnd.lnrpc.getInfo()); + + return new LightningWallet(inner, logger, bitcoinWallet); + } + + public MaximumFee = 0; + + private constructor( + public readonly inner: LightningWalletSdk, + private readonly logger: Logger, + private readonly bitcoinWallet: BitcoinWallet + ) {} + + public async mint(asset: Asset): Promise { + if (asset.name !== "bitcoin") { + throw new Error( + `Cannot mint asset ${asset.name} with LightningWallet` + ); + } + + const startingBalance = new BigNumber( + await this.getBalanceByAsset(asset) + ); + this.logger.debug("starting: ", startingBalance.toString()); + + const minimumExpectedBalance = new BigNumber(asset.quantity); + this.logger.debug("min expected: ", minimumExpectedBalance.toString()); + + await this.bitcoinWallet.mintToAddress( + minimumExpectedBalance, + await this.address() + ); + + await pollUntilMinted( + this, + startingBalance.plus(minimumExpectedBalance), + asset + ); + } + + public async address(): Promise { + return this.inner.newAddress(AddressType.NESTED_PUBKEY_HASH); + } + + public async getBalanceByAsset(asset: Asset): Promise { + if (asset.name !== "bitcoin") { + throw new Error( + `Cannot read balance for asset ${asset.name} with LightningdWallet` + ); + } + + const walletBalance = await this.inner.confirmedWalletBalance(); + const channelBalance = await this.inner.confirmedChannelBalance(); + return new BigNumber(walletBalance ? walletBalance : 0).plus( + channelBalance ? channelBalance : 0 + ); + } + + // This function does not have its place on a Wallet + public async getBlockchainTime(): Promise { + throw new Error( + "getBlockchainTime should not be called for LightningWallet" + ); + } + + public async connectPeer(toWallet: LightningWallet) { + const pubkey = await toWallet.inner.getPubkey(); + const host = toWallet.inner.p2pSocket; + return this.inner.lnd.lnrpc.connectPeer({ addr: { pubkey, host } }); + } + + public async listPeers() { + return this.inner.lnd.lnrpc.listPeers(); + } + + public async getChannels() { + const listChannelsResponse = await this.inner.lnd.lnrpc.listChannels(); + return listChannelsResponse.channels; + } + + // @ts-ignore + public async openChannel(toWallet: LightningWallet, quantity: number) { + // First, need to check everyone is sync'd to the chain + + let thisIsSynced = (await this.inner.getInfo()).syncedToChain; + let toIsSynced = (await toWallet.inner.getInfo()).syncedToChain; + + while (!thisIsSynced || !toIsSynced) { + this.logger.info( + `One of the lnd node is not yet synced, waiting. this: ${thisIsSynced}, to: ${toIsSynced}` + ); + await sleep(500); + + thisIsSynced = (await this.inner.getInfo()).syncedToChain; + toIsSynced = (await toWallet.inner.getInfo()).syncedToChain; + } + + const outpoint = await this.inner.openChannel( + await toWallet.inner.getPubkey(), + quantity + ); + this.logger.debug("Channel opened, waiting for confirmations"); + + await this.pollUntilChannelIsOpen(outpoint); + } + + /** + * Adds an invoice. + * @param sats + */ + public async addInvoice( + sats: string + ): Promise<{ + rHash: string; + paymentRequest: string; + }> { + return this.inner.addInvoice(sats); + } + + /** + * Pay a payment-request + * + * @param request A BOLT11-encoded payment request + */ + public async pay(request: string) { + return this.inner.sendPaymentWithRequest(request); + } + + public async lookupInvoice(secretHash: string) { + return this.inner.lookupInvoice(secretHash); + } + + private async pollUntilChannelIsOpen(outpoint: Outpoint): Promise { + const { txId, vout } = outpoint; + const channels = await this.getChannels(); + if (channels) { + for (const channel of channels) { + this.logger.debug(`Looking for channel ${txId}:${vout}`); + if (channel.channelPoint === `${txId}:${vout}`) { + this.logger.debug("Found a channel:", channel); + return; + } + } + } + await sleep(500); + return this.pollUntilChannelIsOpen(outpoint); + } +} diff --git a/api_tests/lib_sdk/actor_test.ts b/api_tests/lib_sdk/actor_test.ts deleted file mode 100644 index c55345bb01..0000000000 --- a/api_tests/lib_sdk/actor_test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { Actors } from "./actors"; -import { createActors } from "./create_actors"; -import { timeout } from "./utils"; - -/* - * This test function will take care of instantiating the actors and tearing them down again - * after the test, regardless if the test succeeded or failed. - */ -function nActorTest( - name: string, - actorNames: ["alice", "bob", "charlie"] | ["alice", "bob"] | ["alice"], - testFn: (actors: Actors) => Promise -) { - it(name, async function() { - this.timeout(100_000); // absurd timeout. we have our own one further down - const actors = await createActors(`${name}.log`, actorNames); - - try { - await timeout(60000, testFn(actors)); - } catch (e) { - for (const actorName of actorNames) { - await actors.getActorByName(actorName).dumpState(); - } - throw e; - } finally { - for (const actorName of actorNames) { - await actors.getActorByName(actorName).stop(); - } - } - }); -} - -/* - * Instantiates a new e2e test based on three actors - * - */ -export function threeActorTest( - name: string, - testFn: (actors: Actors) => Promise -) { - nActorTest(name, ["alice", "bob", "charlie"], testFn); -} - -/* - * Instantiates a new e2e test based on two actors - */ -export function twoActorTest( - name: string, - testFn: (actors: Actors) => Promise -) { - nActorTest(name, ["alice", "bob"], testFn); -} - -/* - * Instantiates a new e2e test based on one actor - */ -export function oneActorTest( - name: string, - testFn: (actors: Actors) => Promise -) { - nActorTest(name, ["alice"], testFn); -} diff --git a/api_tests/lib_sdk/asset.ts b/api_tests/lib_sdk/asset.ts deleted file mode 100644 index 1109107132..0000000000 --- a/api_tests/lib_sdk/asset.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Asset as AssetSdk } from "comit-sdk"; - -export interface Asset extends AssetSdk { - name: AssetKind; - [k: string]: any; -} - -export enum AssetKind { - Bitcoin = "bitcoin", - Ether = "ether", - Erc20 = "erc20", -} diff --git a/api_tests/lib_sdk/create_actors.ts b/api_tests/lib_sdk/create_actors.ts deleted file mode 100644 index 3727f74b82..0000000000 --- a/api_tests/lib_sdk/create_actors.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Actors } from "./actors"; -import { Actor } from "./actors/actor"; -import { createActor } from "./create_actor"; - -export async function createActors( - logFileName: string, - actorNames: string[] -): Promise { - const actorsMap = new Map(); - for (const name of actorNames) { - actorsMap.set(name, await createActor(logFileName, name)); - } - - const actors = new Actors(actorsMap); - - for (const name of actorNames) { - actorsMap.get(name).actors = actors; - } - - return Promise.resolve(actors); -} diff --git a/api_tests/package.json b/api_tests/package.json index c01b974151..34f783141f 100644 --- a/api_tests/package.json +++ b/api_tests/package.json @@ -6,32 +6,37 @@ "scripts": { "check": "tsc && prettier --check '**/*.{ts,json,yml}' && tslint --project .", "postinstall": "mkdir -p ./gen && json2ts -i ./siren.schema.json -o ./gen/siren.d.ts", - "pretest": "cargo build --bin cnd", - "test": "ts-node ./harness.ts", + "pretest": "cargo build --bin cnd && tsc", + "dry": "jest --config jest.config-dry.js --maxWorkers=4", + "e2e": "yarn jest --config jest.config-e2e.js --runInBand --forceExit --bail", + "test": "yarn dry && yarn e2e", "fix": "tslint --project . --fix && prettier --write '**/*.{ts,js,json,yml}'" }, "engines": { - "node": "^10.14" + "node": "^12" }, "author": "CoBloX Team", "license": "ISC", "dependencies": { "@iarna/toml": "^2.2.3", + "@radar/lnrpc": "^0.9.0-beta", "@types/bitcoinjs-lib": "^5.0.0", - "@types/chai": "^4.2.9", + "@types/chai": "^4.2.10", "@types/chai-as-promised": "^7.1.2", "@types/chai-json-schema": "^1.4.5", "@types/chai-subset": "^1.3.3", - "@types/dockerode": "^2.5.22", + "@types/dockerode": "^2.5.24", "@types/glob": "^7.1.1", + "@types/jest": "^25.1.4", + "@types/jsdom": "^16.1.0", "@types/log4js": "^2.3.5", - "@types/mocha": "^7.0.1", - "@types/node": "^10.14", + "@types/node": "^13.9", "@types/rimraf": "^2.0.3", "@types/scrypt": "^6.0.0", "@types/tail": "^2.0.0", "@types/tempfile": "^3.0.0", - "@types/urijs": "^1.19.5", + "@types/urijs": "^1.19.6", + "async-mutex": "^0.1.4", "bcoin": "https://github.com/bcoin-org/bcoin#2496acc7a98a43f00a7b5728eb256877c1bbf001", "bignumber.js": "^9.0.0", "bitcoin-core": "^3.0.0", @@ -44,29 +49,33 @@ "chai-json-schema": "^1.5.0", "chai-string": "^1.5.0", "chai-subset": "^1.6.0", - "comit-sdk": "^0.10.1", + "comit-sdk": "^0.14.0", "commander": "^4.1.1", - "ethers": "^4.0.44", + "ethers": "^4.0.45", "get-port": "^5.1.1", "glob": "^7.1.6", - "json-schema-to-typescript": "^8.0.1", + "jest": "^25.1.0", + "js-sha256": "^0.9.0", + "json-schema-to-typescript": "^8.1.0", + "ln-service": "^47.15.2", "log4js": "^6.1.2", - "mocha": "^7.0.1", - "multiaddr": "^7.3.0", + "multiaddr": "^7.4.0", "pem-ts": "^2.0.0", "prettier": "^1.19.1", "readline-promise": "^1.0.4", "rimraf": "^3.0.2", "satoshi-bitcoin": "^1.0.4", + "smack-my-jasmine-up": "^0.0.3", "tail": "^2.0.3", "temp-write": "^4.0.0", - "testcontainers": "^2.3.2", + "testcontainers": "^2.6.0", "tmp": "^0.1.0", + "ts-jest": "^25.2.1", "ts-node": "^8.6.2", "tslint": "^6.0.0", "tslint-config-prettier": "^1.18.0", "tslint-no-unused-expression-chai": "^0.1.4", - "typescript": "^3.7.5", + "typescript": "^3.8.3", "urijs": "^1.19.2" }, "prettier": { diff --git a/api_tests/src/dry_test_environment.ts b/api_tests/src/dry_test_environment.ts new file mode 100644 index 0000000000..3b74cb71d2 --- /dev/null +++ b/api_tests/src/dry_test_environment.ts @@ -0,0 +1,104 @@ +import { Config } from "@jest/types"; +import { LedgerRunner } from "../lib/ledgers/ledger_runner"; +import { + execAsync, + HarnessGlobal, + mkdirAsync, + rimrafAsync, +} from "../lib/utils"; +import NodeEnvironment from "jest-environment-node"; +import { Mutex } from "async-mutex"; +import path from "path"; + +// ************************ // +// Setting global variables // +// ************************ // + +export default class E2ETestEnvironment extends NodeEnvironment { + private docblockPragmas: Record; + private projectRoot: string; + private testRoot: string; + private logDir: string; + private ledgerRunner: LedgerRunner; + public global: HarnessGlobal; + + constructor(config: Config.ProjectConfig, context: any) { + super(config); + + this.docblockPragmas = context.docblockPragmas; + } + + async setup() { + await super.setup(); + + // retrieve project root by using git + const { stdout } = await execAsync("git rev-parse --show-toplevel", { + encoding: "utf8", + }); + this.projectRoot = stdout.trim(); + this.testRoot = path.join(this.projectRoot, "api_tests"); + + // setup global variables + this.global.projectRoot = this.projectRoot; + this.global.testRoot = this.testRoot; + this.global.ledgerConfigs = {}; + this.global.verbose = + this.global.process.argv.find(item => item.includes("verbose")) !== + undefined; + + this.global.parityAccountMutex = new Mutex(); + + if (this.global.verbose) { + console.log(`Starting up test environment`); + } + + const { logDir } = this.extractDocblockPragmas(this.docblockPragmas); + + this.logDir = path.join(this.projectRoot, "api_tests", "log", logDir); + await E2ETestEnvironment.cleanLogDir(this.logDir); + + this.global.logRoot = this.logDir; + } + + private static async cleanLogDir(logDir: string) { + await rimrafAsync(logDir); + await mkdirAsync(logDir, { recursive: true }); + } + + async teardown() { + await super.teardown(); + if (this.global.verbose) { + console.log(`Tearing down test environment.`); + } + this.cleanupAll(); + if (this.global.verbose) { + console.log(`All teared down.`); + } + } + + cleanupAll() { + try { + if (this.ledgerRunner) { + this.ledgerRunner.stopLedgers(); + } + } catch (e) { + console.error("Failed to clean up resources", e); + } + } + + private extractDocblockPragmas( + docblockPragmas: Record + ): { logDir: string; ledgers: string[] } { + const docblockLedgers = docblockPragmas.ledgers!; + const ledgers = docblockLedgers ? docblockLedgers.split(",") : []; + + const logDir = this.docblockPragmas.logDir!; + if (!logDir) { + throw new Error( + "Test file did not specify a log directory. Did you miss adding @logDir" + ); + } + + return { ledgers, logDir }; + } +} diff --git a/api_tests/src/e2e_test_environment.ts b/api_tests/src/e2e_test_environment.ts new file mode 100644 index 0000000000..074f456671 --- /dev/null +++ b/api_tests/src/e2e_test_environment.ts @@ -0,0 +1,126 @@ +import { Config } from "@jest/types"; +import { LedgerRunner } from "../lib/ledgers/ledger_runner"; +import { + execAsync, + HarnessGlobal, + mkdirAsync, + rimrafAsync, +} from "../lib/utils"; +import NodeEnvironment from "jest-environment-node"; +import { Mutex } from "async-mutex"; +import path from "path"; + +// ************************ // +// Setting global variables // +// ************************ // + +export default class E2ETestEnvironment extends NodeEnvironment { + private docblockPragmas: Record; + private projectRoot: string; + private testRoot: string; + private logDir: string; + private ledgerRunner: LedgerRunner; + public global: HarnessGlobal; + + constructor(config: Config.ProjectConfig, context: any) { + super(config); + + this.docblockPragmas = context.docblockPragmas; + } + + async setup() { + await super.setup(); + + // retrieve project root by using git + const { stdout } = await execAsync("git rev-parse --show-toplevel", { + encoding: "utf8", + }); + this.projectRoot = stdout.trim(); + this.testRoot = path.join(this.projectRoot, "api_tests"); + + // setup global variables + this.global.projectRoot = this.projectRoot; + this.global.testRoot = this.testRoot; + this.global.ledgerConfigs = {}; + this.global.verbose = + this.global.process.argv.find(item => item.includes("verbose")) !== + undefined; + + this.global.parityAccountMutex = new Mutex(); + + if (this.global.verbose) { + console.log(`Starting up test environment`); + } + + const { ledgers, logDir } = this.extractDocblockPragmas( + this.docblockPragmas + ); + + this.logDir = path.join(this.projectRoot, "api_tests", "log", logDir); + await E2ETestEnvironment.cleanLogDir(this.logDir); + + if (ledgers.length > 0) { + // setup ledgers + this.ledgerRunner = new LedgerRunner( + this.projectRoot, + this.logDir, + this.global + ); + + if (this.global.verbose) { + console.log(`Initializing ledgers : ${ledgers}`); + } + const ledgerConfig = await this.ledgerRunner.ensureLedgersRunning( + ledgers + ); + this.global.tokenContract = ledgerConfig.ethereum.tokenContract; + this.global.ledgerConfigs = { + bitcoin: ledgerConfig.bitcoin, + ethereum: ledgerConfig.ethereum, + }; + } + this.global.logRoot = this.logDir; + } + + private static async cleanLogDir(logDir: string) { + await rimrafAsync(logDir); + await mkdirAsync(logDir, { recursive: true }); + } + + async teardown() { + await super.teardown(); + if (this.global.verbose) { + console.log(`Tearing down test environment.`); + } + this.cleanupAll(); + if (this.global.verbose) { + console.log(`All teared down.`); + } + } + + cleanupAll() { + try { + if (this.ledgerRunner) { + this.ledgerRunner.stopLedgers(); + } + } catch (e) { + console.error("Failed to clean up resources", e); + } + } + + private extractDocblockPragmas( + docblockPragmas: Record + ): { logDir: string; ledgers: string[] } { + const docblockLedgers = docblockPragmas.ledgers!; + const ledgers = docblockLedgers ? docblockLedgers.split(",") : []; + + const logDir = this.docblockPragmas.logDir!; + if (!logDir) { + throw new Error( + "Test file did not specify a log directory. Did you miss adding @logDir" + ); + } + + return { ledgers, logDir }; + } +} diff --git a/api_tests/tests/dry/lightning_routes.ts b/api_tests/tests/dry/lightning_routes.ts new file mode 100644 index 0000000000..94e704dfdc --- /dev/null +++ b/api_tests/tests/dry/lightning_routes.ts @@ -0,0 +1,59 @@ +/** + * @logDir lightning_routes + */ + +import { expect } from "chai"; +import { oneActorTest } from "../../lib/actor_test"; + +// ******************************************** // +// Lightning routes // +// ******************************************** // +describe("Lightning routes tests", () => { + it("lightning-routes-post-eth-lnbtc-return-400", async function() { + await oneActorTest(async function({ alice }) { + const promise = alice.cnd.createHanEthereumEtherHalightLightningBitcoin(); + return expect(promise).to.eventually.be.rejected.then(error => { + expect(error).to.have.property( + "message", + "Request failed with status code 400" + ); + }); + }); + }); + + it("lightning-routes-post-erc20-lnbtc-return-400", async function() { + await oneActorTest(async function({ alice }) { + const promise = alice.cnd.createHerc20EthereumErc20HalightLightningBitcoin(); + return expect(promise).to.eventually.be.rejected.then(error => { + expect(error).to.have.property( + "message", + "Request failed with status code 400" + ); + }); + }); + }); + + it("lightning-routes-post-lnbtc-eth-return-400", async function() { + await oneActorTest(async function({ alice }) { + const promise = alice.cnd.createHalightLightningBitcoinHanEthereumEther(); + return expect(promise).to.eventually.be.rejected.then(error => { + expect(error).to.have.property( + "message", + "Request failed with status code 400" + ); + }); + }); + }); + + it("lightning-routes-post-lnbtc-erc20-return-400", async function() { + await oneActorTest(async function({ alice }) { + const promise = alice.cnd.createHalightLightningBitcoinHerc20EthereumErc20(); + return expect(promise).to.eventually.be.rejected.then(error => { + expect(error).to.have.property( + "message", + "Request failed with status code 400" + ); + }); + }); + }); +}); diff --git a/api_tests/tests/dry/multiple_peers.ts b/api_tests/tests/dry/multiple_peers.ts new file mode 100644 index 0000000000..decda7fcbe --- /dev/null +++ b/api_tests/tests/dry/multiple_peers.ts @@ -0,0 +1,77 @@ +/** + * @logDir multiple_peers + */ + +import { threeActorTest } from "../../lib/actor_test"; +import { createDefaultSwapRequest } from "../../lib/utils"; +import { expect } from "chai"; +import { SwapDetails } from "comit-sdk"; + +interface MatchInterface { + id: string; + status: string; + state: string; +} + +function toMatch(swapDetail: SwapDetails): MatchInterface { + return { + id: swapDetail.properties.id, + status: swapDetail.properties.status, + state: swapDetail.properties.state.communication.status, + }; +} + +// ******************************************** // +// Multiple peers // +// ******************************************** // +describe("Multiple peers tests", () => { + it("alice-sends-swap-request-to-bob-and-charlie", async function() { + await threeActorTest(async function({ alice, bob, charlie }) { + // Alice send swap request to Bob + const aliceToBobSwapUrl = await alice.cnd.postSwap( + await createDefaultSwapRequest(bob) + ); + + // Alice send swap request to Charlie + const aliceToCharlieSwapUrl = await alice.cnd.postSwap( + await createDefaultSwapRequest(charlie) + ); + + // fetch swap details + const aliceToBobSwapDetails = await alice.pollSwapDetails( + aliceToBobSwapUrl + ); + + const aliceToCharlieSwapDetails = await alice.pollSwapDetails( + aliceToCharlieSwapUrl + ); + + // Bob get swap details + const bobSwapDetails = await bob.pollSwapDetails(aliceToBobSwapUrl); + + // Charlie get swap details + const charlieSwapDetails = await charlie.pollSwapDetails( + aliceToCharlieSwapUrl + ); + + expect( + bobSwapDetails.properties, + "[Bob] should have same id as Alice" + ).to.have.property("id", aliceToBobSwapDetails.properties.id); + expect( + charlieSwapDetails.properties, + "[Charlie] should have same id as Alice" + ).to.have.property("id", aliceToCharlieSwapDetails.properties.id); + + expect( + [ + aliceToBobSwapDetails, + aliceToCharlieSwapDetails, + ].map(swapDetail => toMatch(swapDetail)) + ).to.have.deep.members([ + toMatch(bobSwapDetails), + toMatch(charlieSwapDetails), + ]); + }); + }); +}); diff --git a/api_tests/tests/dry/peers_using_ip.ts b/api_tests/tests/dry/peers_using_ip.ts new file mode 100644 index 0000000000..d0f0c27097 --- /dev/null +++ b/api_tests/tests/dry/peers_using_ip.ts @@ -0,0 +1,110 @@ +/** + * @logDir peers_ip + */ + +import { threeActorTest, twoActorTest } from "../../lib/actor_test"; +import { createDefaultSwapRequest, sleep } from "../../lib/utils"; +import { expect, request } from "chai"; +import { Actor } from "../../lib/actors/actor"; + +// ******************************************** // +// Peers using ips // +// ******************************************** // + +async function assertNoPeersAvailable(actor: Actor, message: string) { + const peersResponse = await request(actor.cndHttpApiUrl()).get("/peers"); + + expect(peersResponse.status).to.equal(200); + expect(peersResponse.body.peers, message).to.be.empty; +} + +async function assertPeersAvailable(alice: Actor, bob: Actor, message: string) { + const peersResponse = await request(alice.cndHttpApiUrl()).get("/peers"); + + expect(peersResponse.status).to.equal(200); + expect(peersResponse.body.peers, message).to.containSubset([ + { + id: await bob.cnd.getPeerId(), + }, + ]); +} + +describe("Peers using IP tests", () => { + it("alice-empty-peer-list", async function() { + await twoActorTest(async function({ alice }) { + const res = await request(alice.cndHttpApiUrl()).get("/peers"); + + expect(res.status).to.equal(200); + expect(res.body.peers).to.be.empty; + }); + }); + + it("alice-send-request-wrong-peer-id", async function() { + await threeActorTest(async function({ alice, bob, charlie }) { + await assertNoPeersAvailable( + alice, + "[Alice] Should not yet see Bob's nor Charlie's peer id in her list of peers" + ); + + // Alice send swap request to Bob + const swapRequest = await createDefaultSwapRequest(bob); + await alice.cnd.postSwap({ + ...swapRequest, + peer: { + peer_id: "QmXfGiwNESAFWUvDVJ4NLaKYYVopYdV5HbpDSgz5TSypkb", // Random peer id on purpose to see if Bob still appears in GET /swaps using the multiaddress + address_hint: await bob.cnd + .getPeerListenAddresses() + .then(addresses => addresses[0]), + }, + }); + + await sleep(1000); + + await assertNoPeersAvailable( + alice, + "[Alice] Should not see any peers because the address did not resolve to the given PeerID" + ); + + await assertNoPeersAvailable( + bob, + "[Bob] Should not see Alice's PeerID because she dialed to a different PeerID" + ); + + await assertNoPeersAvailable( + charlie, + "[Charlie] Should not see Alice's PeerID because there was no communication so far" + ); + }); + }); + + it("alice-send-swap-request-to-charlie", async function() { + await threeActorTest(async function({ alice, bob, charlie }) { + await assertNoPeersAvailable( + alice, + "[Alice] Should not yet see Bob's nor Charlie's peer id in her list of peers" + ); + + // Alice send swap request to Bob + await alice.cnd.postSwap(await createDefaultSwapRequest(charlie)); + + await sleep(1000); + + await assertNoPeersAvailable( + bob, + "[Bob] Should not see any peer ids in his list of peers" + ); + + await assertPeersAvailable( + alice, + charlie, + "[Alice] Should see Charlie's peer id in her list of peers after sending a swap request to him using his ip address" + ); + + await assertPeersAvailable( + charlie, + alice, + "[Charlie] Should see Alice's peer ID in his list of peers after receiving a swap request from Alice" + ); + }); + }); +}); diff --git a/api_tests/tests/dry/rfc003_schema.ts b/api_tests/tests/dry/rfc003_schema.ts new file mode 100644 index 0000000000..eb471533bc --- /dev/null +++ b/api_tests/tests/dry/rfc003_schema.ts @@ -0,0 +1,226 @@ +/** + * @logDir rfc003 + */ + +import { EmbeddedRepresentationSubEntity, Entity, Link } from "../../gen/siren"; +import { Actor } from "../../lib/actors/actor"; +import { expect, request } from "chai"; +import "chai/register-should"; +import "../../lib/setup_chai"; +import * as sirenJsonSchema from "../../siren.schema.json"; +import * as swapPropertiesJsonSchema from "../../swap.schema.json"; +import { twoActorTest } from "../../lib/actor_test"; +import { createDefaultSwapRequest, DEFAULT_ALPHA } from "../../lib/utils"; +import { Action } from "comit-sdk"; + +// ******************************************** // +// RFC003 schema tests // +// ******************************************** // + +async function assertValidSirenDocument( + swapsEntity: Entity, + alice: Actor, + message: string +) { + const selfLink = swapsEntity.links.find((link: Link) => + link.rel.includes("self") + ).href; + + const swapResponse = await request(alice.cndHttpApiUrl()).get(selfLink); + const swapEntity = swapResponse.body as Entity; + + expect(swapEntity, message).to.be.jsonSchema(sirenJsonSchema); + expect(swapEntity.properties, message).to.be.jsonSchema( + swapPropertiesJsonSchema + ); +} + +describe("Rfc003 schema tests", () => { + it("get-all-swaps-is-valid-siren", async function() { + await twoActorTest(async function({ alice }) { + const res = await request(alice.cndHttpApiUrl()).get("/swaps"); + + expect(res.body).to.be.jsonSchema(sirenJsonSchema); + }); + }); + it("get-single-swap-is-valid-siren", async function() { + await twoActorTest(async function({ alice, bob }) { + // Alice send swap request to Bob + await alice.cnd.postSwap(await createDefaultSwapRequest(bob)); + + const aliceSwapEntity = await alice + .pollCndUntil("/swaps", body => body.entities.length > 0) + .then( + body => body.entities[0] as EmbeddedRepresentationSubEntity + ); + + await assertValidSirenDocument( + aliceSwapEntity, + alice, + "[Alice] Response for GET /swaps/rfc003/{} is a valid siren document and properties match the json schema" + ); + + const bobsSwapEntity = await bob + .pollCndUntil("/swaps", body => body.entities.length > 0) + .then( + body => body.entities[0] as EmbeddedRepresentationSubEntity + ); + await assertValidSirenDocument( + bobsSwapEntity, + bob, + "[Bob] Response for GET /swaps/rfc003/{} is a valid siren document and properties match the json schema" + ); + }); + }); + + it("get-single-swap-contains-link-to-rfc", async function() { + await twoActorTest(async function({ alice, bob }) { + // Alice send swap request to Bob + await alice.cnd.postSwap(await createDefaultSwapRequest(bob)); + + const aliceSwapEntity = await alice + .pollCndUntil("/swaps", body => body.entities.length > 0) + .then( + body => body.entities[0] as EmbeddedRepresentationSubEntity + ); + + const protocolLink = aliceSwapEntity.links.find((link: Link) => + link.rel.includes("describedBy") + ); + + expect(protocolLink).to.be.deep.equal({ + rel: ["describedBy"], + class: ["protocol-spec"], + type: "text/html", + href: + "https://github.com/comit-network/RFCs/blob/master/RFC-003-SWAP-Basic.adoc", + }); + }); + }); +}); + +// ******************************************** // +// RFC003 Swap Reject // +// ******************************************** // + +async function assertSwapsInProgress(actor: Actor, message: string) { + const res = await request(actor.cndHttpApiUrl()).get("/swaps"); + + const swapEntities = res.body.entities as EmbeddedRepresentationSubEntity[]; + + expect(swapEntities.map(entity => entity.properties, message)) + .to.each.have.property("status") + .that.is.equal("IN_PROGRESS"); +} + +describe("Rfc003 schema swap reject tests", () => { + it("alice-can-make-default-swap-request", async function() { + await twoActorTest(async function({ alice, bob }) { + // Alice should be able to send two swap requests to Bob + const url1 = await alice.cnd.postSwap({ + ...(await createDefaultSwapRequest(bob)), + alpha_asset: { + name: DEFAULT_ALPHA.asset.name, + quantity: DEFAULT_ALPHA.asset.quantity.reasonable, + }, + }); + + const url2 = await alice.cnd.postSwap({ + ...(await createDefaultSwapRequest(bob)), + alpha_asset: { + name: DEFAULT_ALPHA.asset.name, + quantity: DEFAULT_ALPHA.asset.quantity.stingy, + }, + }); + + await assertSwapsInProgress( + alice, + "[Alice] Shows the swaps as IN_PROGRESS in GET /swaps" + ); + + // make sure bob processed the swaps fully + await bob.pollSwapDetails(url1); + await bob.pollSwapDetails(url2); + + await assertSwapsInProgress( + bob, + "[Bob] Shows the swaps as IN_PROGRESS in /swaps" + ); + }); + }); + + it("bob-can-decline-swap", async function() { + await twoActorTest(async function({ alice, bob }) { + // Alice should be able to send two swap requests to Bob + const aliceReasonableSwap = await alice.cnd.postSwap({ + ...(await createDefaultSwapRequest(bob)), + alpha_asset: { + name: DEFAULT_ALPHA.asset.name, + quantity: DEFAULT_ALPHA.asset.quantity.reasonable, + }, + }); + + const aliceStingySwap = await alice.cnd.postSwap({ + ...(await createDefaultSwapRequest(bob)), + alpha_asset: { + name: DEFAULT_ALPHA.asset.name, + quantity: DEFAULT_ALPHA.asset.quantity.stingy, + }, + }); + + const bobSwapDetails = await bob.pollSwapDetails(aliceStingySwap); + + expect( + bobSwapDetails.properties, + "[Bob] Has the RFC-003 parameters when GETing the swap" + ).jsonSchema(swapPropertiesJsonSchema); + expect( + bobSwapDetails.actions, + "[Bob] Has the accept and decline actions when GETing the swap" + ).containSubset([ + { + name: "accept", + }, + { + name: "decline", + }, + ]); + + /// Decline the swap + const decline = bobSwapDetails.actions.find( + (action: Action) => action.name === "decline" + ); + const declineRes = await bob.cnd.executeSirenAction(decline); + + declineRes.should.have.status(200); + + expect( + await bob.pollCndUntil( + aliceStingySwap, + entity => + entity.properties.state.communication.status === + "DECLINED" + ), + "[Bob] Should be in the Declined State after declining a swap request providing a reason" + ).to.exist; + + const aliceReasonableSwapDetails = await alice.pollSwapDetails( + aliceReasonableSwap + ); + const aliceStingySwapDetails = await alice.pollSwapDetails( + aliceStingySwap + ); + + expect( + aliceStingySwapDetails.properties.state.communication.status, + "[Alice] Should be in the Declined State after Bob declines a swap" + ).to.eq("DECLINED"); + + expect( + aliceReasonableSwapDetails.properties.state.communication + .status, + "[Alice] Should be in the SENT State for the other swap request" + ).to.eq("SENT"); + }); + }); +}); diff --git a/api_tests/tests/dry/sanity.ts b/api_tests/tests/dry/sanity.ts new file mode 100644 index 0000000000..80604d1b80 --- /dev/null +++ b/api_tests/tests/dry/sanity.ts @@ -0,0 +1,167 @@ +/** + * @logDir sanity + */ + +import { oneActorTest } from "../../lib/actor_test"; +import { expect, request } from "chai"; +import { Entity, Link } from "../../gen/siren"; +import * as sirenJsonSchema from "../../siren.schema.json"; + +// ******************************************** // +// Sanity tests // +// ******************************************** // + +describe("Sanity - peers using IP", () => { + it("invalid-swap-yields-404", async function() { + await oneActorTest(async function({ alice }) { + const res = await request(alice.cndHttpApiUrl()).get( + "/swaps/rfc003/deadbeef-dead-beef-dead-deadbeefdead" + ); + + expect(res).to.have.status(404); + expect(res).to.have.header( + "content-type", + "application/problem+json" + ); + }); + }); + + it("empty-swap-list-after-startup", async function() { + await oneActorTest(async function({ alice }) { + const res = await request(alice.cndHttpApiUrl()).get("/swaps"); + + const body = res.body as Entity; + + expect(body.entities).to.have.lengthOf(0); + }); + }); + + it("bad-request-for-invalid-swap-combination", async function() { + await oneActorTest(async function({ alice }) { + const res = await request(alice.cndHttpApiUrl()) + .post("/swaps/rfc003") + .send({ + alpha_ledger: { + name: "Thomas' wallet", + }, + beta_ledger: { + name: "Higher-Dimension", // This is the coffee place downstairs + }, + alpha_asset: { + name: "AUD", + quantity: "3.5", + }, + beta_asset: { + name: "Espresso", + "double-shot": true, + }, + alpha_ledger_refund_identity: "", + beta_ledger_redeem_identity: "", + alpha_expiry: 123456789, + beta_expiry: 123456789, + peer: "QmPRNaiDUcJmnuJWUyoADoqvFotwaMRFKV2RyZ7ZVr1fqd", + }); + + expect(res).to.have.status(400); + expect(res).to.have.header( + "content-type", + "application/problem+json" + ); + expect(res.body.title).to.equal("Invalid body."); + }); + }); + it("returns-invalid-body-for-bad-json", async function() { + await oneActorTest(async function({ alice }) { + const res = await request(alice.cndHttpApiUrl()) + .post("/swaps/rfc003") + .send({ + garbage: true, + }); + + expect(res).to.have.status(400); + expect(res).to.have.header( + "content-type", + "application/problem+json" + ); + expect(res.body.title).to.equal("Invalid body."); + }); + }); + it("alice-has-empty-peer-list", async function() { + await oneActorTest(async function({ alice }) { + const res = await request(alice.cndHttpApiUrl()).get("/peers"); + + expect(res).to.have.status(200); + expect(res.body.peers).to.have.length(0); + }); + }); + it("returns-listen-addresses-on-root-document", async function() { + await oneActorTest(async function({ alice }) { + const res = await request(alice.cndHttpApiUrl()).get("/"); + + expect(res.body.id).to.be.a("string"); + expect(res.body.listen_addresses).to.be.an("array"); + // At least 2 ipv4 addresses, lookup and external interface + expect(res.body.listen_addresses.length).to.be.greaterThan(1); + }); + }); + it("can-fetch-root-document-as-siren", async function() { + await oneActorTest(async function({ alice }) { + const res = await request(alice.cndHttpApiUrl()).get("/"); + + expect(res).to.have.status(200); + expect(res.body).to.be.jsonSchema(sirenJsonSchema); + }); + }); + it("returns-listen-addresses-on-root-document-as-siren", async function() { + await oneActorTest(async function({ alice }) { + const res = await request(alice.cndHttpApiUrl()) + .get("/") + .set("accept", "application/vnd.siren+json"); + + expect(res.body.properties.id).to.be.a("string"); + expect(res.body.properties.listen_addresses).to.be.an("array"); + // At least 2 ipv4 addresses, lookup and external interface + expect( + res.body.properties.listen_addresses.length + ).to.be.greaterThan(1); + }); + }); + it("returns-links-to-create-swap-endpoints-on-root-document-as-siren", async function() { + await oneActorTest(async function({ alice }) { + const res = await request(alice.cndHttpApiUrl()) + .get("/") + .set("accept", "application/vnd.siren+json"); + const links = res.body.links; + + const swapsLink = links.find( + (link: Link) => + link.rel.length === 1 && + link.rel.includes("collection") && + link.class.length === 1 && + link.class.includes("swaps") + ); + + expect(swapsLink).to.be.deep.equal({ + rel: ["collection"], + class: ["swaps"], + href: "/swaps", + }); + + const rfc003SwapsLink = links.find( + (link: Link) => + link.rel.length === 2 && + link.rel.includes("collection") && + link.rel.includes("edit") && + link.class.length === 2 && + link.class.includes("swaps") && + link.class.includes("rfc003") + ); + + expect(rfc003SwapsLink).to.be.deep.equal({ + rel: ["collection", "edit"], + class: ["swaps", "rfc003"], + href: "/swaps/rfc003", + }); + }); + }); +}); diff --git a/api_tests/tests/e2e/bitcoin_ethereum.ts b/api_tests/tests/e2e/bitcoin_ethereum.ts new file mode 100644 index 0000000000..51724daca1 --- /dev/null +++ b/api_tests/tests/e2e/bitcoin_ethereum.ts @@ -0,0 +1,491 @@ +/** + * @ledgers ethereum,bitcoin + * @logDir e2e + */ + +import { twoActorTest } from "../../lib/actor_test"; +import { AssetKind } from "../../lib/asset"; +import { sleep } from "../../lib/utils"; +import { expect } from "chai"; +import { LedgerKind } from "../../lib/ledgers/ledger"; + +// ******************************************** // +// Lightning Sanity Test // +// ******************************************** // +describe("E2E: Sanity - LND Alice pays Bob", () => { + it.skip("sanity-lnd-alice-pays-bob", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest( + { ledger: LedgerKind.Lightning, asset: AssetKind.Bitcoin }, + { ledger: LedgerKind.Bitcoin, asset: AssetKind.Bitcoin } + ); + const { rHash, paymentRequest } = await bob.lnCreateInvoice( + "20000" + ); + await alice.lnPayInvoiceWithRequest(paymentRequest); + await bob.lnAssertInvoiceSettled(rHash); + }); + }); + + it.skip("sanity-lnd-alice-pays-bob-using-hold-invoice", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest( + { ledger: LedgerKind.Lightning, asset: AssetKind.Bitcoin }, + { ledger: LedgerKind.Bitcoin, asset: AssetKind.Bitcoin } + ); + + const satAmount = "10000"; + const finalCltvDelta = 10; + + const { secret, secretHash } = bob.lnCreateSha256Secret(); + await bob.lnCreateHoldInvoice( + satAmount, + secretHash, + 3600, + finalCltvDelta + ); + const paymentPromise = alice.lnSendPayment( + bob, + satAmount, + secretHash, + finalCltvDelta + ); + + await bob.lnSettleInvoice(secret, secretHash); + + const pay = await paymentPromise; + expect(pay.paymentPreimage.toString("hex")).equals(secret); + + await bob.lnAssertInvoiceSettled(secretHash); + }); + }); +}); + +// ******************************************** // +// Bitcoin/bitcoin Alpha Ledger/ Alpha Asset // +// Ethereum/ether Beta Ledger/Beta Asset // +// ******************************************** // +describe("E2E: Bitcoin/bitcoin - Ethereum/ether", () => { + it("rfc003-btc-eth-alice-redeems-bob-redeems", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); + await bob.accept(); + + await alice.fund(); + await bob.fund(); + + await alice.redeem(); + await bob.redeem(); + + await alice.assertSwapped(); + await bob.assertSwapped(); + }); + }); + + // ************************ // + // Refund test // + // ************************ // + + it("rfc003-btc-eth-bob-refunds-alice-refunds", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); + await bob.accept(); + + await alice.fund(); + await bob.fund(); + + await bob.refund(); + await alice.refund(); + + await bob.assertRefunded(); + await alice.assertRefunded(); + }); + }); + + it("rfc003-btc-eth-alice-refunds-bob-refunds", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); + await bob.accept(); + + await alice.fund(); + await bob.fund(); + + await alice.refund(); + await bob.refund(); + + await alice.assertRefunded(); + await bob.assertRefunded(); + }); + }); + + // ************************ // + // Restart cnd test // + // ************************ // + + it("rfc003-btc-eth-cnd-can-be-restarted", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); + await bob.accept(); + + await alice.currentSwapIsAccepted(); + await bob.currentSwapIsAccepted(); + + await alice.restart(); + await bob.restart(); + + await alice.currentSwapIsAccepted(); + await bob.currentSwapIsAccepted(); + }); + }); + + // ************************ // + // Resume cnd test // + // ************************ // + + it("rfc003-btc-eth-resume-alice-down-bob-funds", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); + await bob.accept(); + + await alice.fund(); + alice.stop(); + + // Action happens while alice is down. + await bob.fund(); + + // Blocks are geneated every second here, wait to ensure + // we look into the past for the transaction. + await sleep(2000); + await alice.start(); + + await alice.redeem(); + await bob.redeem(); + + await alice.assertSwapped(); + await bob.assertSwapped(); + }); + }); + + it("rfc003-btc-eth-resume-alice-down-bob-redeems", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); + await bob.accept(); + + await alice.fund(); + await bob.fund(); + + await alice.redeem(); + alice.stop(); + + // Action happens while alice is down. + await bob.redeem(); + + // Blocks are geneated every second here, wait to ensure + // we look into the past for the transaction. + await sleep(2000); + await alice.start(); + + await alice.assertSwapped(); + await bob.assertSwapped(); + }); + }); + + it("rfc003-btc-eth-resume-bob-down-alice-funds", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); + await bob.accept(); + + // Wait for Alice to receive the accept message before stopping Bob's cnd. + await alice.currentSwapIsAccepted(); + + bob.stop(); + + // Action happens while bob is down. + await alice.fund(); + + // Blocks are geneated every second here, wait to ensure + // we look into the past for the transaction. + await sleep(2000); + await bob.start(); + + await bob.fund(); + + await alice.redeem(); + await bob.redeem(); + + await alice.assertSwapped(); + await bob.assertSwapped(); + }); + }); + + it("rfc003-btc-eth-resume-bob-down-alice-redeems", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); + await bob.accept(); + + await alice.fund(); + await bob.fund(); + + bob.stop(); + + // Action happens while bob is down. + await alice.redeem(); + + // Blocks are geneated every second here, wait to ensure + // we look into the past for the transaction. + await sleep(2000); + await bob.start(); + + await bob.redeem(); + + await alice.assertSwapped(); + await bob.assertSwapped(); + }); + }); + + // ************************ // + // Underfunding test // + // ************************ // + + it("rfc003-btc-eth-alice-underfunds-bob-aborts", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); + await bob.accept(); + + await alice.underfund(); + + await bob.assertAlphaIncorrectlyFunded(); + await bob.assertBetaNotDeployed(); + await alice.assertAlphaIncorrectlyFunded(); + await alice.assertBetaNotDeployed(); + + await alice.refund(); + await alice.assertRefunded(); + + await bob.assertBetaNotDeployed(); + }); + }); + + it("rfc003-btc-eth-bob-underfunds-both-refund", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); + await bob.accept(); + await alice.fund(); + + await bob.assertAlphaFunded(); + await alice.assertAlphaFunded(); + + await bob.underfund(); + + await alice.assertBetaIncorrectlyFunded(); + await bob.assertBetaIncorrectlyFunded(); + + await bob.refund(); + await bob.assertRefunded(); + await alice.refund(); + await alice.assertRefunded(); + }); + }); + + // ************************ // + // Overfund test // + // ************************ // + + it("rfc003-btc-eth-alice-overfunds-bob-aborts", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); + await bob.accept(); + + await alice.overfund(); + + await bob.assertAlphaIncorrectlyFunded(); + await bob.assertBetaNotDeployed(); + await alice.assertAlphaIncorrectlyFunded(); + await alice.assertBetaNotDeployed(); + + await alice.refund(); + await alice.assertRefunded(); + + await bob.assertBetaNotDeployed(); + }); + }); + + it("rfc003-btc-eth-bob-overfunds-both-refund", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Ether); + await bob.accept(); + await alice.fund(); + + await bob.assertAlphaFunded(); + await alice.assertAlphaFunded(); + + await bob.overfund(); + + await alice.assertBetaIncorrectlyFunded(); + await bob.assertBetaIncorrectlyFunded(); + + await bob.refund(); + await bob.assertRefunded(); + await alice.refund(); + await alice.assertRefunded(); + }); + }); +}); + +// ******************************************** // +// Ethereum/ether Alpha Ledger/ Alpha Asset // +// Bitcoin/bitcoin Beta Ledger/Beta Asset // +// ******************************************** // +describe("E2E: Ethereum/ether - Bitcoin/bitcoin", () => { + it("rfc003-eth-btc-alice-redeems-bob-redeems", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest(AssetKind.Ether, AssetKind.Bitcoin); + await bob.accept(); + + await alice.fund(); + await bob.fund(); + + await alice.redeem(); + await bob.redeem(); + + await alice.assertSwapped(); + await bob.assertSwapped(); + }); + }); + + // ************************ // + // Ignore Failed ETH TX // + // ************************ // + + it("rfc003-eth-btc-alpha-deploy-fails", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest(AssetKind.Ether, AssetKind.Bitcoin); + await bob.accept(); + + await alice.fundLowGas("0x1b000"); + + await alice.assertAlphaNotDeployed(); + await bob.assertAlphaNotDeployed(); + await bob.assertBetaNotDeployed(); + await alice.assertBetaNotDeployed(); + }); + }); + + // ************************ // + // Refund tests // + // ************************ // + + it("rfc003-eth-btc-bob-refunds-alice-refunds", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest(AssetKind.Ether, AssetKind.Bitcoin); + await bob.accept(); + + await alice.fund(); + await bob.fund(); + + await bob.refund(); + await alice.refund(); + + await bob.assertRefunded(); + await alice.assertRefunded(); + }); + }); + + // ************************ // + // Bitcoin High Fees // + // ************************ // + + it("rfc003-eth-btc-alice-redeems-with-high-fee", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest(AssetKind.Ether, AssetKind.Bitcoin); + await bob.accept(); + + await alice.fund(); + await bob.fund(); + + const responsePromise = alice.redeemWithHighFee(); + + return expect(responsePromise).to.be.rejected; + }); + }); +}); + +// ******************************************** // +// Bitcoin/bitcoin Alpha Ledger/ Alpha Asset // +// Ethereum/erc20 Beta Ledger/Beta Asset // +// ******************************************** // +describe("E2E: Bitcoin/bitcoin - Ethereum/erc20", () => { + it("rfc003-btc-eth-erc20-alice-redeems-bob-redeems", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Erc20); + await bob.accept(); + + await alice.fund(); + await bob.deploy(); + await bob.fund(); + + await alice.redeem(); + await bob.redeem(); + + await alice.assertSwapped(); + await bob.assertSwapped(); + }); + }); + + it("rfc003-btc-eth-erc20-bob-refunds-alice-refunds", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest(AssetKind.Bitcoin, AssetKind.Erc20); + await bob.accept(); + + await alice.fund(); + await bob.deploy(); + await bob.fund(); + + await alice.refund(); + await bob.refund(); + + await alice.assertRefunded(); + await bob.assertRefunded(); + }); + }); +}); + +// ******************************************** // +// Ethereum/erc20 Alpha Ledger/ Alpha Asset // +// Bitcoin/bitcoin Beta Ledger/Beta Asset // +// ******************************************** // +describe("E2E: Ethereum/erc20 - Bitcoin/bitcoin", () => { + it("rfc003-eth-erc20_btc-alice-redeems-bob-redeems", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest(AssetKind.Erc20, AssetKind.Bitcoin); + await bob.accept(); + + await alice.deploy(); + await alice.fund(); + await bob.fund(); + + await alice.redeem(); + await bob.redeem(); + + await alice.assertSwapped(); + await bob.assertSwapped(); + }); + }); + + it("rfc003-eth-erc20_btc-bob-refunds-alice-refunds", async function() { + await twoActorTest(async function({ alice, bob }) { + await alice.sendRequest(AssetKind.Erc20, AssetKind.Bitcoin); + await bob.accept(); + + await alice.deploy(); + await alice.fund(); + await bob.fund(); + + await alice.refund(); + await bob.refund(); + + await alice.assertRefunded(); + await bob.assertRefunded(); + }); + }); +}); diff --git a/api_tests/tsconfig.json b/api_tests/tsconfig.json index c443a9b203..86420f18fa 100644 --- a/api_tests/tsconfig.json +++ b/api_tests/tsconfig.json @@ -5,11 +5,15 @@ "allowSyntheticDefaultImports": true, "noImplicitAny": true, "lib": ["es5", "es2015", "es2017", "es6", "dom"], - "noEmit": true, + "noEmit": false, "noUnusedLocals": true, "noUnusedParameters": true, "resolveJsonModule": true, + "sourceMap": true, + "skipLibCheck": true, "esModuleInterop": true, + "outDir": "dist", "typeRoots": ["./node_modules/@types", "./types"] - } + }, + "exclude": ["dry", "e2e", "sanity"] } diff --git a/api_tests/tslint.json b/api_tests/tslint.json index 15a43587a7..f5813d4747 100644 --- a/api_tests/tslint.json +++ b/api_tests/tslint.json @@ -15,7 +15,15 @@ "no-reference": false, "no-namespace": false, "only-arrow-functions": false, - "file-name-casing": [true, "snake-case"] + "file-name-casing": [true, "snake-case"], + "promise-function-async": true, + "no-floating-promises": true, + "await-promise": true, + "no-promise-as-boolean": true, + "no-return-await": true, + "no-string-throw": true, + "radix": true, + "restrict-plus-operands": true }, "rulesDirectory": [] } diff --git a/api_tests/lib/satoshi_bitcoin.d.ts b/api_tests/types/satoshi_bitcoin/index.d.ts similarity index 100% rename from api_tests/lib/satoshi_bitcoin.d.ts rename to api_tests/types/satoshi_bitcoin/index.d.ts diff --git a/api_tests/types/smack-my-jasmine-up/index.d.ts b/api_tests/types/smack-my-jasmine-up/index.d.ts new file mode 100644 index 0000000000..e64f145118 --- /dev/null +++ b/api_tests/types/smack-my-jasmine-up/index.d.ts @@ -0,0 +1,5 @@ +declare module "smack-my-jasmine-up" { + export default class JasmineSmacker { + static getCurrentTestName(); + } +} diff --git a/api_tests/yarn.lock b/api_tests/yarn.lock index 47604f3ac9..ecebf55c94 100644 --- a/api_tests/yarn.lock +++ b/api_tests/yarn.lock @@ -2,6 +2,32 @@ # yarn lockfile v1 +"@alexbosworth/request@2.88.3": + version "2.88.3" + resolved "https://registry.yarnpkg.com/@alexbosworth/request/-/request-2.88.3.tgz#b408e685c722b116c6b6352cbc5561d3de2691bd" + integrity sha512-51/Y5x0SncVGQc274YckWMo9CooUGp7XppgV9K8v5eBwcXnw9sM/j0LpAvUFE7gjJcmZVYXDmLxtOYtgC0f2dg== + dependencies: + aws-sign2 "0.7.0" + aws4 "1.9.1" + caseless "0.12.0" + combined-stream "1.0.8" + extend "3.0.2" + forever-agent "0.6.1" + form-data "3.0.0" + har-validator "5.1.3" + http-signature "1.3.1" + is-typedarray "1.0.0" + isstream "0.1.2" + json-stringify-safe "5.0.1" + mime-types "2.1.26" + oauth-sign "0.9.0" + performance-now "2.1.0" + qs "6.9.1" + safe-buffer "5.2.0" + tough-cookie "3.0.1" + tunnel-agent "0.6.0" + uuid "3.4.0" + "@babel/code-frame@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" @@ -9,6 +35,81 @@ dependencies: "@babel/highlight" "^7.0.0" +"@babel/code-frame@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" + integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== + dependencies: + "@babel/highlight" "^7.8.3" + +"@babel/core@^7.1.0", "@babel/core@^7.7.5": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.8.6.tgz#27d7df9258a45c2e686b6f18b6c659e563aa4636" + integrity sha512-Sheg7yEJD51YHAvLEV/7Uvw95AeWqYPL3Vk3zGujJKIhJ+8oLw2ALaf3hbucILhKsgSoADOvtKRJuNVdcJkOrg== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.8.6" + "@babel/helpers" "^7.8.4" + "@babel/parser" "^7.8.6" + "@babel/template" "^7.8.6" + "@babel/traverse" "^7.8.6" + "@babel/types" "^7.8.6" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.0" + lodash "^4.17.13" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.6.tgz#57adf96d370c9a63c241cd719f9111468578537a" + integrity sha512-4bpOR5ZBz+wWcMeVtcf7FbjcFzCp+817z2/gHNncIRcM9MmKzUhtWCYAq27RAfUrAFwb+OCG1s9WEaVxfi6cjg== + dependencies: + "@babel/types" "^7.8.6" + jsesc "^2.5.1" + lodash "^4.17.13" + source-map "^0.5.0" + +"@babel/helper-function-name@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca" + integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA== + dependencies: + "@babel/helper-get-function-arity" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-get-function-arity@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" + integrity sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" + integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ== + +"@babel/helper-split-export-declaration@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" + integrity sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helpers@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.8.4.tgz#754eb3ee727c165e0a240d6c207de7c455f36f73" + integrity sha512-VPbe7wcQ4chu4TDQjimHv/5tj73qz88o12EPkO2ValS2QiQS/1F2SsjyIGNnAD0vF/nZS6Cf9i+vW6HIlnaR8w== + dependencies: + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.4" + "@babel/types" "^7.8.3" + "@babel/highlight@^7.0.0": version "7.5.0" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540" @@ -18,11 +119,384 @@ esutils "^2.0.2" js-tokens "^4.0.0" +"@babel/highlight@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797" + integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg== + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.7.5", "@babel/parser@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.6.tgz#ba5c9910cddb77685a008e3c587af8d27b67962c" + integrity sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g== + +"@babel/plugin-syntax-bigint@^7.0.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-object-rest-spread@^7.0.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/template@^7.7.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" + integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/parser" "^7.8.6" + "@babel/types" "^7.8.6" + +"@babel/traverse@^7.1.0", "@babel/traverse@^7.7.4", "@babel/traverse@^7.8.4", "@babel/traverse@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.8.6.tgz#acfe0c64e1cd991b3e32eae813a6eb564954b5ff" + integrity sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.8.6" + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/parser" "^7.8.6" + "@babel/types" "^7.8.6" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.13" + +"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.8.3", "@babel/types@^7.8.6": + version "7.8.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.6.tgz#629ecc33c2557fcde7126e58053127afdb3e6d01" + integrity sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA== + dependencies: + esutils "^2.0.2" + lodash "^4.17.13" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@cnakazawa/watch@^1.0.3": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" + integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== + dependencies: + exec-sh "^0.3.2" + minimist "^1.2.0" + +"@datastructures-js/priority-queue@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@datastructures-js/priority-queue/-/priority-queue-1.0.2.tgz#3511cb9cd7fb03be057afb7874cd8839d1ac91f8" + integrity sha512-HrSXJy5QPBn7DyF5ubjR6g88zW4SXDcYf732MKXTwRYf/MSVc8C1oM7UkDpaIc9OoC4r8kwpYa6oWndQZykd1w== + +"@grpc/proto-loader@0.5.3": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.5.3.tgz#a233070720bf7560c4d70e29e7950c72549a132c" + integrity sha512-8qvUtGg77G2ZT2HqdqYoM/OY97gQd/0crSG34xNmZ4ZOsv3aQT/FQV9QfZPazTGna6MIoyUd+u6AxsoZjJ/VMQ== + dependencies: + lodash.camelcase "^4.3.0" + protobufjs "^6.8.6" + +"@grpc/proto-loader@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.4.0.tgz#a823a51eb2fde58369bef1deb5445fd808d70901" + integrity sha512-Jm6o+75uWT7E6+lt8edg4J1F/9+BedOjaMgwE14pxS/AO43/0ZqK+rCLVVrXLoExwSAZvgvOD2B0ivy3Spsspw== + dependencies: + lodash.camelcase "^4.3.0" + protobufjs "^6.8.6" + "@iarna/toml@^2.2.3": version "2.2.3" resolved "https://registry.yarnpkg.com/@iarna/toml/-/toml-2.2.3.tgz#f060bf6eaafae4d56a7dac618980838b0696e2ab" integrity sha512-FmuxfCuolpLl0AnQ2NHSzoUKWEJDFl63qXjzdoWBVyFCXzMGm1spBzk7LeHNoVCiWCF7mRVms9e6jEV9+MoPbg== +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz#10602de5570baea82f8afbfa2630b24e7a8cfe5b" + integrity sha512-ZR0rq/f/E4f4XcgnDvtMWXCUJpi8eO0rssVhmztsZqLIEFA9UUP9zmpE0VxlM+kv/E1ul2I876Fwil2ayptDVg== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" + integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== + +"@jest/console@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-25.1.0.tgz#1fc765d44a1e11aec5029c08e798246bd37075ab" + integrity sha512-3P1DpqAMK/L07ag/Y9/Jup5iDEG9P4pRAuZiMQnU0JB3UOvCyYCjCoxr7sIA80SeyUCUKrr24fKAxVpmBgQonA== + dependencies: + "@jest/source-map" "^25.1.0" + chalk "^3.0.0" + jest-util "^25.1.0" + slash "^3.0.0" + +"@jest/core@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-25.1.0.tgz#3d4634fc3348bb2d7532915d67781cdac0869e47" + integrity sha512-iz05+NmwCmZRzMXvMo6KFipW7nzhbpEawrKrkkdJzgytavPse0biEnCNr2wRlyCsp3SmKaEY+SGv7YWYQnIdig== + dependencies: + "@jest/console" "^25.1.0" + "@jest/reporters" "^25.1.0" + "@jest/test-result" "^25.1.0" + "@jest/transform" "^25.1.0" + "@jest/types" "^25.1.0" + ansi-escapes "^4.2.1" + chalk "^3.0.0" + exit "^0.1.2" + graceful-fs "^4.2.3" + jest-changed-files "^25.1.0" + jest-config "^25.1.0" + jest-haste-map "^25.1.0" + jest-message-util "^25.1.0" + jest-regex-util "^25.1.0" + jest-resolve "^25.1.0" + jest-resolve-dependencies "^25.1.0" + jest-runner "^25.1.0" + jest-runtime "^25.1.0" + jest-snapshot "^25.1.0" + jest-util "^25.1.0" + jest-validate "^25.1.0" + jest-watcher "^25.1.0" + micromatch "^4.0.2" + p-each-series "^2.1.0" + realpath-native "^1.1.0" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-25.1.0.tgz#4a97f64770c9d075f5d2b662b5169207f0a3f787" + integrity sha512-cTpUtsjU4cum53VqBDlcW0E4KbQF03Cn0jckGPW/5rrE9tb+porD3+hhLtHAwhthsqfyF+bizyodTlsRA++sHg== + dependencies: + "@jest/fake-timers" "^25.1.0" + "@jest/types" "^25.1.0" + jest-mock "^25.1.0" + +"@jest/fake-timers@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.1.0.tgz#a1e0eff51ffdbb13ee81f35b52e0c1c11a350ce8" + integrity sha512-Eu3dysBzSAO1lD7cylZd/CVKdZZ1/43SF35iYBNV1Lvvn2Undp3Grwsv8PrzvbLhqwRzDd4zxrY4gsiHc+wygQ== + dependencies: + "@jest/types" "^25.1.0" + jest-message-util "^25.1.0" + jest-mock "^25.1.0" + jest-util "^25.1.0" + lolex "^5.0.0" + +"@jest/reporters@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-25.1.0.tgz#9178ecf136c48f125674ac328f82ddea46e482b0" + integrity sha512-ORLT7hq2acJQa8N+NKfs68ZtHFnJPxsGqmofxW7v7urVhzJvpKZG9M7FAcgh9Ee1ZbCteMrirHA3m5JfBtAaDg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^25.1.0" + "@jest/environment" "^25.1.0" + "@jest/test-result" "^25.1.0" + "@jest/transform" "^25.1.0" + "@jest/types" "^25.1.0" + chalk "^3.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.2" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^4.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.0" + jest-haste-map "^25.1.0" + jest-resolve "^25.1.0" + jest-runtime "^25.1.0" + jest-util "^25.1.0" + jest-worker "^25.1.0" + slash "^3.0.0" + source-map "^0.6.0" + string-length "^3.1.0" + terminal-link "^2.0.0" + v8-to-istanbul "^4.0.1" + optionalDependencies: + node-notifier "^6.0.0" + +"@jest/source-map@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-25.1.0.tgz#b012e6c469ccdbc379413f5c1b1ffb7ba7034fb0" + integrity sha512-ohf2iKT0xnLWcIUhL6U6QN+CwFWf9XnrM2a6ybL9NXxJjgYijjLSitkYHIdzkd8wFliH73qj/+epIpTiWjRtAA== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.2.3" + source-map "^0.6.0" + +"@jest/test-result@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-25.1.0.tgz#847af2972c1df9822a8200457e64be4ff62821f7" + integrity sha512-FZzSo36h++U93vNWZ0KgvlNuZ9pnDnztvaM7P/UcTx87aPDotG18bXifkf1Ji44B7k/eIatmMzkBapnAzjkJkg== + dependencies: + "@jest/console" "^25.1.0" + "@jest/transform" "^25.1.0" + "@jest/types" "^25.1.0" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-25.1.0.tgz#4df47208542f0065f356fcdb80026e3c042851ab" + integrity sha512-WgZLRgVr2b4l/7ED1J1RJQBOharxS11EFhmwDqknpknE0Pm87HLZVS2Asuuw+HQdfQvm2aXL2FvvBLxOD1D0iw== + dependencies: + "@jest/test-result" "^25.1.0" + jest-haste-map "^25.1.0" + jest-runner "^25.1.0" + jest-runtime "^25.1.0" + +"@jest/transform@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-25.1.0.tgz#221f354f512b4628d88ce776d5b9e601028ea9da" + integrity sha512-4ktrQ2TPREVeM+KxB4zskAT84SnmG1vaz4S+51aTefyqn3zocZUnliLLm5Fsl85I3p/kFPN4CRp1RElIfXGegQ== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^25.1.0" + babel-plugin-istanbul "^6.0.0" + chalk "^3.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.3" + jest-haste-map "^25.1.0" + jest-regex-util "^25.1.0" + jest-util "^25.1.0" + micromatch "^4.0.2" + pirates "^4.0.1" + realpath-native "^1.1.0" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + +"@jest/types@^25.1.0": + version "25.1.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.1.0.tgz#b26831916f0d7c381e11dbb5e103a72aed1b4395" + integrity sha512-VpOtt7tCrgvamWZh1reVsGADujKigBUFTi19mlRjqEGsE8qH4r3s+skY33dNdXOwyZIvuftZ5tqdF1IgsMejMA== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" + "@types/yargs" "^15.0.0" + chalk "^3.0.0" + +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= + +"@radar/lnrpc@^0.9.0-beta": + version "0.9.0-beta" + resolved "https://registry.yarnpkg.com/@radar/lnrpc/-/lnrpc-0.9.0-beta.tgz#870e9eea4f9659516c46888dc7db31dd3cdd7062" + integrity sha512-ZsHOUHdtjEwwdRzO/lsn398Zxu7eAkfLr5uOBHb9aeSrP14CgW6EUGGzmQIWcDPQAJzcsLVLt048Y76iHSz7Sw== + dependencies: + "@grpc/proto-loader" "^0.4.0" + "@types/google-protobuf" "^3.2.7" + grpc "^1.24.2" + pkg-dir "^2.0.0" + ts-protoc-gen "^0.8.0" + +"@sinonjs/commons@^1.7.0": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.1.tgz#da5fd19a5f71177a53778073978873964f49acf1" + integrity sha512-Debi3Baff1Qu1Unc3mjJ96MgpbwTn43S1+9yJ0llWygPwDNu2aaWBD6yc9y/Z8XDRNhx7U+u2UDg2OGQXkclUQ== + dependencies: + type-detect "4.0.8" + +"@types/babel__core@^7.1.0": + version "7.1.6" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.6.tgz#16ff42a5ae203c9af1c6e190ed1f30f83207b610" + integrity sha512-tTnhWszAqvXnhW7m5jQU9PomXSiKXk2sFxpahXvI20SZKu9ylPi8WtIxueZ6ehDWikPT0jeFujMj3X4ZHuf3Tg== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.1" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.1.tgz#4901767b397e8711aeb99df8d396d7ba7b7f0e04" + integrity sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.2.tgz#4ff63d6b52eddac1de7b975a5223ed32ecea9307" + integrity sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.0.9" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.9.tgz#be82fab304b141c3eee81a4ce3b034d0eba1590a" + integrity sha512-jEFQ8L1tuvPjOI8lnpaf73oCJe+aoxL6ygqSy6c8LcW98zaC+4mzWuQIRCEvKeCOu+lbqdXcg4Uqmm1S8AP1tw== + dependencies: + "@babel/types" "^7.3.0" + "@types/bitcoinjs-lib@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@types/bitcoinjs-lib/-/bitcoinjs-lib-5.0.0.tgz#f2905d673d1c4b42a91d64d95f1c464f1a48cb56" @@ -30,6 +504,14 @@ dependencies: bitcoinjs-lib "*" +"@types/bytebuffer@^5.0.40": + version "5.0.40" + resolved "https://registry.yarnpkg.com/@types/bytebuffer/-/bytebuffer-5.0.40.tgz#d6faac40dcfb09cd856cdc4c01d3690ba536d3ee" + integrity sha512-h48dyzZrPMz25K6Q4+NCwWaxwXany2FhQg/ErOcdZS1ZpsaDnDMZg8JYLMTGz7uvXKrcKGJUZJlZObyfgdaN9g== + dependencies: + "@types/long" "*" + "@types/node" "*" + "@types/chai-as-promised@^7.1.2": version "7.1.2" resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.2.tgz#2f564420e81eaf8650169e5a3a6b93e096e5068b" @@ -51,20 +533,25 @@ dependencies: "@types/chai" "*" -"@types/chai@*", "@types/chai@4", "@types/chai@^4.2.9": - version "4.2.9" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.9.tgz#194332625ed2ae914aef00b8d5ca3b77e7924cc6" - integrity sha512-NeXgZj+MFL4izGqA4sapdYzkzQG+MtGra9vhQ58dnmDY++VgJaRUws+aLVV5zRJCYJl/8s9IjMmhiUw1WsKSmw== +"@types/chai@*", "@types/chai@4", "@types/chai@^4.2.10": + version "4.2.10" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.10.tgz#1122da40faabb81795580dc9f06c1e71e2ebbbe4" + integrity sha512-TlWWgb21+0LdkuFqEqfmy7NEgfB/7Jjux15fWQAh3P93gbmXuwTM/vxEdzW89APIcI2BgKR48yjeAkdeH+4qvQ== + +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== "@types/cookiejar@*": version "2.1.1" resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.1.tgz#90b68446364baf9efd8e8349bb36bd3852b75b80" integrity sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw== -"@types/dockerode@^2.5.22": - version "2.5.22" - resolved "https://registry.yarnpkg.com/@types/dockerode/-/dockerode-2.5.22.tgz#356e90491bf532556d9fbe4863690a0dfb8ec127" - integrity sha512-YIHpziPXJ3Gowv5pnaWRv1zzTxaF32Jf9vy/h4OT9WH7xvnxX6kvM0utLzf8dI4Urx/f7SOHJnmPJqr2S7bbSw== +"@types/dockerode@^2.5.24": + version "2.5.24" + resolved "https://registry.yarnpkg.com/@types/dockerode/-/dockerode-2.5.24.tgz#702fb8827b260ec28e2702d7e2848bf24ec1ff25" + integrity sha512-2iwkmqjc6viw40KnAcyLW1sp9mptb6CPARvpRQDAzKsf0y6dphK0qzgouLiI2gaoNB0iiumZGd2nduGypKk9Aw== dependencies: "@types/node" "*" @@ -82,6 +569,48 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/google-protobuf@^3.2.7": + version "3.7.2" + resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.7.2.tgz#cd8a360c193ce4d672575a20a79f49ba036d38d2" + integrity sha512-ifFemzjNchFBCtHS6bZNhSZCBu7tbtOe0e8qY0z2J4HtFXmPJjm6fXSaQsTG7yhShBEZtt2oP/bkwu5k+emlkQ== + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" + integrity sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz#7a8cbf6a406f36c8add871625b278eaf0b0d255a" + integrity sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA== + dependencies: + "@types/istanbul-lib-coverage" "*" + "@types/istanbul-lib-report" "*" + +"@types/jest@^25.1.4": + version "25.1.4" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.1.4.tgz#9e9f1e59dda86d3fd56afce71d1ea1b331f6f760" + integrity sha512-QDDY2uNAhCV7TMCITrxz+MRk1EizcsevzfeS6LykIlq2V1E5oO4wXG8V2ZEd9w7Snxeeagk46YbMgZ8ESHx3sw== + dependencies: + jest-diff "^25.1.0" + pretty-format "^25.1.0" + +"@types/jsdom@^16.1.0": + version "16.1.0" + resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-16.1.0.tgz#89d0ad0707eceb223c0db9b76611f8d487dd7d1b" + integrity sha512-GiBD8K4tFb0ah+rFAqkoP4tCc6DpCy96lITCCCAf1yqgvmpWNHh4S49sPIBXn4KIfvbVEcDRK+jgDHAbfVFdOw== + dependencies: + "@types/node" "*" + "@types/parse5" "*" + "@types/tough-cookie" "*" + "@types/json-schema@^7.0.3": version "7.0.3" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636" @@ -94,30 +623,40 @@ dependencies: log4js "*" +"@types/long@*", "@types/long@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" + integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== + "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== -"@types/mocha@^7.0.1": - version "7.0.1" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.1.tgz#5d7ec2a789a1f77c59b7ad071b9d50bf1abbfc9e" - integrity sha512-L/Nw/2e5KUaprNJoRA33oly+M8X8n0K+FwLTbYqwTcR14wdPWeRkigBLfSFpN/Asf9ENZTMZwLxjtjeYucAA4Q== - "@types/node@*", "@types/node@>=4.5.0": - version "12.6.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.6.9.tgz#ffeee23afdc19ab16e979338e7b536fdebbbaeaf" - integrity sha512-+YB9FtyxXGyD54p8rXwWaN1EWEyar5L58GlGWgtH2I9rGmLGBQcw63+0jw+ujqVavNuO47S1ByAjm9zdHMnskw== + version "13.7.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.7.tgz#1628e6461ba8cc9b53196dfeaeec7b07fa6eea99" + integrity sha512-Uo4chgKbnPNlxQwoFmYIwctkQVkMMmsAoGGU4JKwLuvBefF0pCq4FybNSnfkfRCpC7ZW7kttcC/TrRtAJsvGtg== "@types/node@10.12.18": version "10.12.18" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67" integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ== -"@types/node@^10.14": - version "10.14.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9" - integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw== +"@types/node@^10.1.0": + version "10.17.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.17.tgz#7a183163a9e6ff720d86502db23ba4aade5999b8" + integrity sha512-gpNnRnZP3VWzzj5k3qrpRC6Rk3H/uclhAVo1aIvwzK5p5cOrs9yEyQ8H/HBsBY0u5rrWxXEiVPQ0dEB6pkjE8Q== + +"@types/node@^13.9": + version "13.9.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.0.tgz#5b6ee7a77faacddd7de719017d0bc12f52f81589" + integrity sha512-0ARSQootUG1RljH2HncpsY2TJBfGQIKOOi7kxzUY6z54ePu/ZD+wJA8zI2Q6v8rol2qpG/rvqsReco8zNMPvhQ== + +"@types/parse5@*": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.2.tgz#a877a4658f8238c8266faef300ae41c84d72ec8a" + integrity sha512-BOl+6KDs4ItndUWUFchy3aEqGdHhw0BC4Uu+qoDonN/f0rbUnJbm71Ulj8Tt9jLFRaAxPLKvdS1bBLfx1qXR9g== "@types/prettier@^1.16.1": version "1.18.2" @@ -139,6 +678,11 @@ dependencies: "@types/node" "*" +"@types/stack-utils@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" + integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== + "@types/superagent@^3.8.3": version "3.8.7" resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-3.8.7.tgz#1f1ed44634d5459b3a672eb7235a8e7cfd97704c" @@ -164,15 +708,32 @@ resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.1.0.tgz#19cf73a7bcf641965485119726397a096f0049bd" integrity sha512-6IwZ9HzWbCq6XoQWhxLpDjuADodH/MKXRUIDFudvgjcVdjFknvmR+DNsoUeer4XPrEnrZs04Jj+kfV9pFsrhmA== +"@types/tough-cookie@*": + version "2.3.6" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.6.tgz#c880579e087d7a0db13777ff8af689f4ffc7b0d5" + integrity sha512-wHNBMnkoEBiRAd3s8KTKwIuO9biFtTf0LehITzBhSco+HQI0xkXZbLOD55SW3Aqw3oUkHstkm5SPv58yaAdFPQ== + "@types/tv4@*": version "1.2.29" resolved "https://registry.yarnpkg.com/@types/tv4/-/tv4-1.2.29.tgz#4c6d2222b03245dd2104f4fd67f54d1658985911" integrity sha512-NtJmi+XbYocrLb5Au4Q64srX4FlCPDvrSF/OnK3H0QJwrw40tIUoQPDoUHnZ5wpAB2KThtVyeS+kOEQyZabORg== -"@types/urijs@^1.19.5": - version "1.19.5" - resolved "https://registry.yarnpkg.com/@types/urijs/-/urijs-1.19.5.tgz#f2083392f5859be59cbba0b1d463b922c8aef842" - integrity sha512-LAyzQkr9rZDoHrv8xRcHStLo8Z4PbP3ZHMqw8WRr1T7Jn4O1z13Qkv+ed9e12pBXWaaqsBj1l4ADU/FlA/jn3w== +"@types/urijs@^1.19.6": + version "1.19.6" + resolved "https://registry.yarnpkg.com/@types/urijs/-/urijs-1.19.6.tgz#4cee39e4e5ad8d276d617d159ffcb423ca2fd9f1" + integrity sha512-kdnK+JtEiUgnpB7r99SAZjjz9nhZ/7MWo/hxTSNfvslAa4r8jpDXDEJ2cQrjemes4eX2Y5Om3udmcc8QalPzOA== + +"@types/yargs-parser@*": + version "15.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" + integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== + +"@types/yargs@^15.0.0": + version "15.0.4" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.4.tgz#7e5d0f8ca25e9d5849f2ea443cf7c402decd8299" + integrity sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg== + dependencies: + "@types/yargs-parser" "*" "@uphold/request-logger@^2.0.0": version "2.0.0" @@ -189,7 +750,17 @@ JSONStream@1.3.2: jsonparse "^1.2.0" through ">=2.2.7 <3" -accepts@~1.3.7: +abab@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a" + integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg== + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +accepts@~1.3.5, accepts@~1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== @@ -197,6 +768,29 @@ accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" +acorn-globals@^4.3.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" + integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== + dependencies: + acorn "^6.0.1" + acorn-walk "^6.0.1" + +acorn-walk@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" + integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== + +acorn@^6.0.1: + version "6.4.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.0.tgz#b659d2ffbafa24baf5db1cdbb2c94a983ecd2784" + integrity sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw== + +acorn@^7.1.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" + integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg== + aes-js@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" @@ -212,12 +806,14 @@ ajv@^6.5.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ansi-colors@3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" - integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== +ansi-escapes@^4.2.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" + integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== + dependencies: + type-fest "^0.11.0" -ansi-regex@^2.1.1: +ansi-regex@^2.0.0, ansi-regex@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= @@ -232,19 +828,40 @@ ansi-regex@^4.1.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== -ansi-styles@^3.2.0, ansi-styles@^3.2.1: +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + +ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + any-promise@^1.0.0, any-promise@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= -anymatch@~3.1.1: +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +anymatch@^3.0.3: version "3.1.1" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== @@ -252,6 +869,19 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + arg@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.0.tgz#583c518199419e0037abb74062c37f8519e575f0" @@ -264,11 +894,44 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + +array-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" + integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + +ascli@~1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ascli/-/ascli-1.0.1.tgz#bcfa5974a62f18e81cabaeb49732ab4a88f906bc" + integrity sha1-vPpZdKYvGOgcq660lzKrSoj5Brw= + dependencies: + colour "~0.7.1" + optjs "~3.2.2" + asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" @@ -286,16 +949,51 @@ assertion-error@^1.1.0: resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +async-mutex@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.1.4.tgz#a47d1eebf584f7dcdd760e3642dc2c58613bef5c" + integrity sha512-zVWTmAnxxHaeB2B1te84oecI8zTDJ/8G49aVBblRX6be0oq6pAybNcUSxwfgVOmOjSCvN4aYZAqwtyNI8e1YGw== + +async@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" + integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== + +asyncjs-util@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/asyncjs-util/-/asyncjs-util-1.1.3.tgz#514758a32a70fcde1eb092fe5b28b55d4315a732" + integrity sha512-iOd9KndvLYO5vSDsNFa16VOkMKTDMvM2RU7pBLGqOlA5YzVxjIUGWXmPlx1yZR4y9+19s1a3yvVPOurGi8kGlA== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -aws-sign2@~0.7.0: +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +aws-sign2@0.7.0, aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= +aws4@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" + integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== + aws4@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" @@ -309,6 +1007,46 @@ axios@^0.19.0: follow-redirects "1.5.10" is-buffer "^2.0.2" +babel-jest@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-25.1.0.tgz#206093ac380a4b78c4404a05b3277391278f80fb" + integrity sha512-tz0VxUhhOE2y+g8R2oFrO/2VtVjA1lkJeavlhExuRBg3LdNJY9gwQ+Vcvqt9+cqy71MCTJhewvTB7Qtnnr9SWg== + dependencies: + "@jest/transform" "^25.1.0" + "@jest/types" "^25.1.0" + "@types/babel__core" "^7.1.0" + babel-plugin-istanbul "^6.0.0" + babel-preset-jest "^25.1.0" + chalk "^3.0.0" + slash "^3.0.0" + +babel-plugin-istanbul@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765" + integrity sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^4.0.0" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.1.0.tgz#fb62d7b3b53eb36c97d1bc7fec2072f9bd115981" + integrity sha512-oIsopO41vW4YFZ9yNYoLQATnnN46lp+MZ6H4VvPKFkcc2/fkl3CfE/NZZSmnEIEsJRmJAgkVEK0R7Zbl50CpTw== + dependencies: + "@types/babel__traverse" "^7.0.6" + +babel-preset-jest@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-25.1.0.tgz#d0aebfebb2177a21cde710996fce8486d34f1d33" + integrity sha512-eCGn64olaqwUMaugXsTtGAM2I0QTahjEtnRu0ql8Ie+gDWAc1N6wqN0k2NilnyTunM69Pad7gJY7LOtwLimoFQ== + dependencies: + "@babel/plugin-syntax-bigint" "^7.0.0" + "@babel/plugin-syntax-object-rest-spread" "^7.0.0" + babel-plugin-jest-hoist "^25.1.0" + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -333,6 +1071,33 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +basic-auth@^2.0.0, basic-auth@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" + integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg== + dependencies: + safe-buffer "5.1.2" + +basicauth-middleware@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/basicauth-middleware/-/basicauth-middleware-3.1.0.tgz#8bb6e4cb9cdab37e5c8be71b6277da5e3c04927a" + integrity sha512-sDy0dfIxDUKrJRqmBgekGc9I+C7HkwF9+T1lRGy6W4hQjugJYMAwQCtvdPOXK77GbgwdN9ShYaXo2QknX0Yftw== + dependencies: + basic-auth "^2.0.0" + bcfg@~0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/bcfg/-/bcfg-0.1.6.tgz#f77a6323bddef14f3886222e7ef8ccc0bc2143ec" @@ -413,7 +1178,7 @@ bdns@~0.1.5: dependencies: bsert "~0.0.10" -bech32@^1.1.2: +bech32@1.1.3, bech32@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.3.tgz#bd47a8986bbb3eec34a56a097a84b8d3e9a2dfcd" integrity sha512-yuVFUvrNcoJi0sv5phmqc6P+Fl1HjRDRNOOkHY2X/3LBy2bIGNSFx4fZ95HMaXHupuS7cZR15AsvtmCIF4UEyg== @@ -461,11 +1226,6 @@ bignumber.js@^9.0.0: resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075" integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A== -binary-extensions@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" - integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== - bindings@^1.3.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" @@ -538,7 +1298,7 @@ bitcoin-ops@^1.3.0, bitcoin-ops@^1.4.0: resolved "https://registry.yarnpkg.com/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz#e45de620398e22fd4ca6023de43974ff42240278" integrity sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow== -bitcoinjs-lib@*, bitcoinjs-lib@^5.1.7: +bitcoinjs-lib@*, bitcoinjs-lib@5.1.7, bitcoinjs-lib@^5.1.7: version "5.1.7" resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-5.1.7.tgz#dfa023d6ad887eaef8249513d708c9ecd2673a08" integrity sha512-sNlTQuvhaoIjOdIdyENsX74Dlikv7l6AzO0/uZQscuvfBID6aMANoCz1rooCTH5upTV5rKCj4z3BXBmXJxq23g== @@ -607,6 +1367,16 @@ bmutex@~0.1.6: dependencies: bsert "~0.0.10" +bn.js@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.0.0.tgz#5c3d398021b3ddb548c1296a16f857e908f35c70" + integrity sha512-bVwDX8AF+72fIUNuARelKAlQUNtPOfG2fRxorbVvFk4zpHbqLrPdOGfVg5vrKwVzLLePqPBiATaOZNELQzmS0A== + +bn.js@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.1.tgz#48efc4031a9c4041b9c99c6941d903463ab62eb5" + integrity sha512-IUTD/REb78Z2eodka1QZyyEk66pciRcP6Sroka0aI3tG/iwIdYLrBD62RsubR7vqdt3WyX8p4jxeatzmRSphtA== + bn.js@^4.11.8, bn.js@^4.4.0: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" @@ -628,6 +1398,25 @@ body-parser@1.19.0: raw-body "2.4.0" type-is "~1.6.17" +bolt07@1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/bolt07/-/bolt07-1.4.4.tgz#d4c875b507b795e2525d3d9804a24acd4c772ec6" + integrity sha512-1jn3ef9lhhcd4uC9JcOHQXtFgOMuN8AEBMRnWNO/zTVOOkl0zLKEkv30PrBfwdr0i/OAIEbhTHIR/2PCGLVfxQ== + dependencies: + bn.js "5.0.0" + +bolt07@1.4.5: + version "1.4.5" + resolved "https://registry.yarnpkg.com/bolt07/-/bolt07-1.4.5.tgz#e4c1cfac71f76a52ac4d5e830bed07fb3893c290" + integrity sha512-cthfbW9W4zyhhhNykWRe+QukrMM8WOq9p3g40i1zP0XNKNvJNYOTziV9AXqYWPZ4BV39PY33YsJzIhuEjNfp7Q== + dependencies: + bn.js "5.1.1" + +bolt09@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/bolt09/-/bolt09-0.0.1.tgz#cd62d5022928fed776ed1ecc59f3cf2e4176a258" + integrity sha512-o9In+fstEAq3jdWZv5CJ9D5EXr3Q5y0giGfXYJvmHw3HhSeocO211XvcWl+4iX7SeflL7b1GYXOY6G/5LQMA5Q== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -636,7 +1425,23 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@~3.0.2: +braces@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +braces@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -648,10 +1453,17 @@ brorand@^1.0.1: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= -browser-stdout@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" - integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== +browser-process-hrtime@^0.1.2: + version "0.1.3" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz#616f00faef1df7ec1b5bf9cfe2bdc3170f26c7b4" + integrity sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw== + +browser-resolve@^1.11.3: + version "1.11.3" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" + integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ== + dependencies: + resolve "1.1.7" brq@~0.1.7: version "0.1.8" @@ -660,6 +1472,13 @@ brq@~0.1.7: dependencies: bsert "~0.0.10" +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + bs32@~0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/bs32/-/bs32-0.1.5.tgz#b36da53d547e60282ae3225219331f7cfa77a22d" @@ -683,6 +1502,13 @@ bs58check@<3.0.0, bs58check@^2.0.0, bs58check@^2.1.1: create-hash "^1.1.0" safe-buffer "^5.1.2" +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + bsert@~0.0.10: version "0.0.10" resolved "https://registry.yarnpkg.com/bsert/-/bsert-0.0.10.tgz#231ac82873a1418c6ade301ab5cd9ae385895597" @@ -744,7 +1570,7 @@ buffer-fill@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" integrity sha1-+PeLdniYiO858gXNY39o5wISKyw= -buffer-from@^1.0.0: +buffer-from@1.x, buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== @@ -803,26 +1629,78 @@ byline@^5.0.0: resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE= +bytebuffer@~5: + version "5.0.1" + resolved "https://registry.yarnpkg.com/bytebuffer/-/bytebuffer-5.0.1.tgz#582eea4b1a873b6d020a48d58df85f0bba6cfddd" + integrity sha1-WC7qSxqHO20CCkjVjfhfC7ps/d0= + dependencies: + long "~3" + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= + bytes@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + call-me-maybe@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= -camelcase@^5.0.0: +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= + +camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -caseless@~0.12.0: +capture-exit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" + integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== + dependencies: + rsvp "^4.8.4" + +caseless@0.12.0, caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= +cbor@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/cbor/-/cbor-5.0.1.tgz#243eea46b19c6e54ffb18fb07fa52c1c627a6f05" + integrity sha512-l4ghwqioCyuAaD3LvY4ONwv8NMuERz62xjbMHGdWBqERJPygVmoFER1b4+VS6iW0rXwoVGuKZPPPTofwWOg3YQ== + dependencies: + bignumber.js "^9.0.0" + nofilter "^1.0.3" + chai-as-promised@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz#08645d825deb8696ee61725dbf590c012eb00ca0" @@ -883,7 +1761,7 @@ chai@^4.2.0: pathval "^1.1.0" type-detect "^4.0.5" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0: +chalk@^2.0.0, chalk@^2.3.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -892,31 +1770,29 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + check-error@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= -chokidar@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" - integrity sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.2.0" - optionalDependencies: - fsevents "~2.1.1" - chownr@^1.0.1, chownr@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6" integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A== +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + cids@~0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/cids/-/cids-0.7.1.tgz#d8bba49a35a0e82110879b5001abf1039c62347f" @@ -940,6 +1816,16 @@ class-is@^1.1.0: resolved "https://registry.yarnpkg.com/class-is/-/class-is-1.1.0.tgz#9d3c0fba0440d211d843cec3dedfa48055005825" integrity sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw== +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + cli-color@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-1.4.0.tgz#7d10738f48526824f8fe7da51857cb0f572fe01f" @@ -952,14 +1838,46 @@ cli-color@^1.4.0: memoizee "^0.4.14" timers-ext "^0.1.5" -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== +cliui@^3.0.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +collect-v8-coverage@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.0.tgz#150ee634ac3650b71d9c985eb7f608942334feb1" + integrity sha512-VKIhJgvk8E1W28m5avZ2Gv2Ruv5YiF56ug2oclvaG9md69BuZImMG2sk9g7QNKLUbtYAKQjXjYxbYZVUlMMKmQ== + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" color-convert@^1.9.0: version "1.9.3" @@ -968,23 +1886,41 @@ color-convert@^1.9.0: dependencies: color-name "1.1.3" +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -combined-stream@^1.0.6, combined-stream@~1.0.6: +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colour@~0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/colour/-/colour-0.7.1.tgz#9cb169917ec5d12c0736d3e8685746df1cadf778" + integrity sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g= + +combined-stream@1.0.8, combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" -comit-sdk@^0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/comit-sdk/-/comit-sdk-0.10.1.tgz#22a88a697e943342d5b6e6e3e72d4d3974cabaad" - integrity sha512-xhJu68EO+lu1KL4AmK+Chc+f6UKcLtryk5zYg6/GhE7NPRTwclYQ4BWrH2/cNcbau2zbUsnQP/+FkCwTW+j4rQ== +comit-sdk@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/comit-sdk/-/comit-sdk-0.14.0.tgz#8183e92ab6543b32a19883372bee61ece3faf741" + integrity sha512-cyGw0yvDIQJUntuV5h4+XckInZ6iu6NqS8dnw3QIiQjXz7AskFfF5WotTeRt7LnqYtbZWz2fnRTEChvf2/R3Bg== dependencies: + "@radar/lnrpc" "^0.9.0-beta" axios "^0.19.0" bcoin "https://github.com/bcoin-org/bcoin#2496acc7a98a43f00a7b5728eb256877c1bbf001" bignumber.js "^9.0.0" @@ -993,7 +1929,8 @@ comit-sdk@^0.10.1: ethers "^4.0.38" express "^4.17.1" moment "^2.24.0" - urijs "^1.19.1" + p-event "^4.1.0" + urijs "^1.19.2" commander@^2.12.1: version "2.20.0" @@ -1005,15 +1942,35 @@ commander@^4.1.1: resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== -component-emitter@^1.2.0: +component-emitter@^1.2.0, component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= concat-stream@~1.6.2: version "1.6.2" @@ -1025,6 +1982,11 @@ concat-stream@~1.6.2: readable-stream "^2.2.2" typedarray "^0.0.6" +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + content-disposition@0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" @@ -1037,6 +1999,13 @@ content-type@^1.0.4, content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== +convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + dependencies: + safe-buffer "~5.1.1" + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -1052,11 +2021,24 @@ cookiejar@^2.1.0, cookiejar@^2.1.1: resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA== +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +cors@2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + create-hash@^1.1.0, create-hash@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" @@ -1080,7 +2062,7 @@ create-hmac@^1.1.3, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" -cross-spawn@^6.0.5: +cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== @@ -1091,6 +2073,32 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14" + integrity sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +cssom@^0.4.1: + version "0.4.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" + integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.2.0.tgz#e4c44debccd6b7911ed617a4395e5754bba59992" + integrity sha512-sEb3XFPx3jNnCAMtqrXPDeSgQr+jojtCeNf8cvMNMh1cG970+lljssvQDzPq6lmmJu2Vhqood/gtEomBiHOGnA== + dependencies: + cssom "~0.3.6" + d@1: version "1.0.0" resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" @@ -1105,6 +2113,15 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +data-urls@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" + integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ== + dependencies: + abab "^2.0.0" + whatwg-mimetype "^2.2.0" + whatwg-url "^7.0.0" + date-format@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/date-format/-/date-format-2.1.0.tgz#31d5b5ea211cf5fd764cd38baf9d033df7e125cf" @@ -1115,20 +2132,13 @@ date-format@^3.0.0: resolved "https://registry.yarnpkg.com/date-format/-/date-format-3.0.0.tgz#eb8780365c7d2b1511078fb491e6479780f3ad95" integrity sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w== -debug@2.6.9, debug@^2.2.0: +debug@2.6.9, debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@3.2.6, debug@^3.1.0, debug@^3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - debug@=3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -1136,7 +2146,14 @@ debug@=3.1.0: dependencies: ms "2.0.0" -debug@^4.1.1: +debug@^3.1.0, debug@^3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.0, debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== @@ -1151,11 +2168,16 @@ debugnyan@^1.0.0: bunyan "^1.8.1" debug "^2.2.0" -decamelize@^1.2.0: +decamelize@^1.1.1, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + deep-eql@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" @@ -1163,6 +2185,16 @@ deep-eql@^3.0.1: dependencies: type-detect "^4.0.0" +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + default-gateway@^5.0.2: version "5.0.3" resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-5.0.3.tgz#18434c9430a18035a2861f7839bf7669b3436e6f" @@ -1170,18 +2202,45 @@ default-gateway@^5.0.2: dependencies: execa "^2.0.3" -define-properties@^1.1.2: +define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== dependencies: object-keys "^1.0.12" +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -1192,10 +2251,20 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= -diff@3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" - integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.1.0.tgz#fd29a46f1c913fd66c22645dc75bffbe43051f32" + integrity sha512-nFIfVk5B/NStCsJ+zaPO4vYuLjlzQ6uFvPxzYyHlejNZ/UGa7G/n7peOXVrVNvRuyfstt+mZQYGpjxg9Z6N8Kw== diff@^4.0.1: version "4.0.1" @@ -1221,6 +2290,18 @@ dockerode@^2.5.8: docker-modem "^1.0.8" tar-fs "~1.16.3" +domexception@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" + integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug== + dependencies: + webidl-conversions "^4.0.2" + +dotenv@8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" + integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== + dtrace-provider@~0.8: version "0.8.7" resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.8.7.tgz#dc939b4d3e0620cfe0c1cd803d0d2d7ed04ffd04" @@ -1241,7 +2322,7 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -elliptic@6.5.2, elliptic@^6.4.0: +elliptic@6.5.2, elliptic@^6.4.0, elliptic@^6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== @@ -1254,10 +2335,10 @@ elliptic@6.5.2, elliptic@^6.4.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.0" -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== encodeurl@~1.0.2: version "1.0.2" @@ -1271,22 +2352,27 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" -es-abstract@^1.5.1: - version "1.13.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" - integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== +es-abstract@^1.17.0-next.1, es-abstract@^1.17.2: + version "1.17.4" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184" + integrity sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ== dependencies: - es-to-primitive "^1.2.0" + es-to-primitive "^1.2.1" function-bind "^1.1.1" has "^1.0.3" - is-callable "^1.1.4" - is-regex "^1.0.4" - object-keys "^1.0.12" - -es-to-primitive@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" - integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== + has-symbols "^1.0.1" + is-callable "^1.1.5" + is-regex "^1.0.5" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimleft "^2.1.1" + string.prototype.trimright "^2.1.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== dependencies: is-callable "^1.1.4" is-date-object "^1.0.1" @@ -1333,16 +2419,33 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: +escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -esprima@^4.0.0: +escodegen@^1.11.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.1.tgz#ba01d0c8278b5e95a9a45350142026659027a457" + integrity sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" @@ -1353,10 +2456,10 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -ethers@^4.0.38, ethers@^4.0.44: - version "4.0.44" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.44.tgz#f2608cbc0b4d099b7e10a01c0efc3a1037013b4e" - integrity sha512-kCkMPkpYjBkxzqjcuYUfDY7VHDbf5EXnfRPUOazdqdf59SvXaT+w5lgauxLlk1UjxnAiNfeNS87rkIXnsTaM7Q== +ethers@^4.0.38, ethers@^4.0.45: + version "4.0.45" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.45.tgz#8d4cd764d7c7690836b583d4849203c225eb56e2" + integrity sha512-N/Wmc6Mw4pQO+Sss1HnKDCSS6KSCx0luoBMiPNq+1GbOaO3YaZOyplBEhj+NEoYsizZYODtkITg2oecPeNnidQ== dependencies: aes-js "3.0.0" bn.js "^4.4.0" @@ -1376,6 +2479,24 @@ event-emitter@^0.3.5: d "1" es5-ext "~0.10.14" +exec-sh@^0.3.2: + version "0.3.4" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" + integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A== + +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + execa@^2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/execa/-/execa-2.0.4.tgz#2f5cc589c81db316628627004ea4e37b93391d8e" @@ -1391,7 +2512,53 @@ execa@^2.0.3: signal-exit "^3.0.2" strip-final-newline "^2.0.0" -express@^4.17.1: +execa@^3.2.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-3.4.0.tgz#c08ed4550ef65d858fac269ffc8572446f37eb89" + integrity sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + p-finally "^2.0.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +expect@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-25.1.0.tgz#7e8d7b06a53f7d66ec927278db3304254ee683ee" + integrity sha512-wqHzuoapQkhc3OKPlrpetsfueuEiMf3iWh0R8+duCu9PIjXoP7HgD5aeypwTnXUAjC8aMsiVDaWwlbJ1RlQ38g== + dependencies: + "@jest/types" "^25.1.0" + ansi-styles "^4.0.0" + jest-get-type "^25.1.0" + jest-matcher-utils "^25.1.0" + jest-message-util "^25.1.0" + jest-regex-util "^25.1.0" + +express@4.17.1, express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== @@ -1427,11 +2594,40 @@ express@^4.17.1: utils-merge "1.0.1" vary "~1.1.2" -extend@^3.0.0, extend@~3.0.2: +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@3.0.2, extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -1447,16 +2643,43 @@ fast-deep-equal@^2.0.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= +fast-json-stable-stringify@2.x: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + fast-json-stable-stringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + dependencies: + bser "2.1.1" + file-uri-to-path@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -1477,19 +2700,20 @@ finalhandler@~1.1.2: statuses "~1.5.0" unpipe "~1.0.0" -find-up@3.0.0, find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== +find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= dependencies: - locate-path "^3.0.0" + locate-path "^2.0.0" -flat@^4.1.0: +find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2" - integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw== + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== dependencies: - is-buffer "~2.0.3" + locate-path "^5.0.0" + path-exists "^4.0.0" flatted@^2.0.1: version "2.0.1" @@ -1503,11 +2727,25 @@ follow-redirects@1.5.10: dependencies: debug "=3.1.0" -forever-agent@~0.6.1: +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + +forever-agent@0.6.1, forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +form-data@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" + integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@^2.3.1, form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -1532,6 +2770,13 @@ forwarded@~0.1.2: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + dependencies: + map-cache "^0.2.2" + fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -1551,12 +2796,19 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-minipass@^1.2.5: + version "1.2.7" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" + integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== + dependencies: + minipass "^2.6.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@~2.1.1: +fsevents@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== @@ -1566,6 +2818,25 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +gensync@^1.0.0-beta.1: + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" + integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== + get-caller-file@^2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -1586,6 +2857,13 @@ get-port@^5.1.1: resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193" integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ== +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + get-stream@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" @@ -1593,6 +2871,11 @@ get-stream@^5.0.0: dependencies: pump "^3.0.0" +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -1600,25 +2883,6 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob-parent@~5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" - integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== - dependencies: - is-glob "^4.0.1" - -glob@7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" - integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@^6.0.1: version "6.0.4" resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" @@ -1630,7 +2894,7 @@ glob@^6.0.1: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.1, glob@^7.1.3, glob@^7.1.6: +glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -1642,22 +2906,49 @@ glob@^7.1.1, glob@^7.1.3, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +google-protobuf@^3.6.1: + version "3.11.4" + resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.11.4.tgz#598ca405a3cfa917a2132994d008b5932ef42014" + integrity sha512-lL6b04rDirurUBOgsY2+LalI6Evq8eH5TcNzi7TYQ3BsIWelT0KSOQSBsXuavEkNf+odQU6c0lgz3UsZXeNX9Q== + graceful-fs@^4.1.15, graceful-fs@^4.1.6, graceful-fs@^4.2.0: version "4.2.2" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02" integrity sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q== -growl@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" - integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== +graceful-fs@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + +growly@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= + +grpc@1.24.2, grpc@^1.24.2: + version "1.24.2" + resolved "https://registry.yarnpkg.com/grpc/-/grpc-1.24.2.tgz#76d047bfa7b05b607cbbe3abb99065dcefe0c099" + integrity sha512-EG3WH6AWMVvAiV15d+lr+K77HJ/KV/3FvMpjKjulXHbTwgDZkhkcWbwhxFAoTdxTkQvy0WFcO3Nog50QBbHZWw== + dependencies: + "@types/bytebuffer" "^5.0.40" + lodash.camelcase "^4.3.0" + lodash.clone "^4.5.0" + nan "^2.13.2" + node-pre-gyp "^0.14.0" + protobufjs "^5.0.3" har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= -har-validator@~5.1.0: +har-validator@5.1.3, har-validator@~5.1.0, har-validator@~5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== @@ -1670,12 +2961,58 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + has-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= -has@^1.0.1, has@^1.0.3: +has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== @@ -1706,11 +3043,6 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -he@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - hi-base32@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/hi-base32/-/hi-base32-0.5.0.tgz#61329f76a31f31008533f1c36f2473e259d64571" @@ -1725,6 +3057,18 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +html-encoding-sniffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" + integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw== + dependencies: + whatwg-encoding "^1.0.1" + +html-escaper@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.0.tgz#71e87f931de3fe09e56661ab9a29aadec707b491" + integrity sha512-a4u9BeERWGu/S8JiWEAQcdrg9v4QArtP9keViQjGMdff20fBdd8waotXaNmODqBe6uZ3Nafi7K/ho4gCQHV3Ig== + http-errors@1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" @@ -1747,6 +3091,15 @@ http-errors@~1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" +http-signature@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.1.tgz#739fe2f8897ba84798e3e54b699a9008a8724ff9" + integrity sha512-Y29YKEc8MQsjch/VzkUVJ+2MXd9WcR42fK5u36CZf4G8bXw2DXMTWuESiB0R6m59JAWxlPPw5/Fri/t/AyyueA== + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.14.1" + http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -1756,13 +3109,38 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -iconv-lite@0.4.24: +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + +iconv-lite@0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" +ignore-walk@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" + integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== + dependencies: + minimatch "^3.0.4" + +import-local@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" + integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -1781,7 +3159,29 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ip-regex@^2.0.0: +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= + +invoices@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invoices/-/invoices-1.0.0.tgz#08368d96a7074bd29fa0459803cb727dc5600e74" + integrity sha512-blukVcU2sw3Vs6La0E4JHY/YNCpq3UyQXm2HR2WUtgMI0XzaZBOohVteja3FMXTO5BYFkV+OAs+1QQ5bybr5lA== + dependencies: + bech32 "1.1.3" + bitcoinjs-lib "5.1.7" + bn.js "5.1.1" + bolt07 "1.4.4" + bolt09 "0.0.1" + secp256k1 "4.0.0" + +ip-regex@^2.0.0, ip-regex@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= @@ -1801,49 +3201,122 @@ ipaddr.js@1.9.0: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA== -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= dependencies: - binary-extensions "^2.0.0" + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-base64@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-base64/-/is-base64-1.1.0.tgz#8ce1d719895030a457c59a7dcaf39b66d99d56b4" + integrity sha512-Nlhg7Z2dVC4/PTvIFkgVVNvPHSO2eR/Yd0XzhGiXCXEvWnptXlXa/clQ8aePPiMuxEGcWfzWbGw2Fe3d+Y3v1g== + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== is-buffer@^2.0.2: version "2.0.4" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== -is-buffer@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725" - integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw== - is-callable@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== +is-callable@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" + integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== + +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + is-date-object@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= -is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== is-ip@^2.0.0: version "2.0.0" @@ -1859,22 +3332,41 @@ is-ip@^3.1.0: dependencies: ip-regex "^4.0.0" +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + dependencies: + kind-of "^3.0.2" + is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + is-promise@^2.1: version "2.1.0" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= -is-regex@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" - integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= +is-regex@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" + integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== dependencies: - has "^1.0.1" + has "^1.0.3" + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= is-stream@^2.0.0: version "2.0.0" @@ -1888,17 +3380,27 @@ is-symbol@^1.0.2: dependencies: has-symbols "^1.0.0" -is-typedarray@~1.0.0: +is-typedarray@1.0.0, is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is-wsl@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d" + integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog== + isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= -isarray@~1.0.0: +isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= @@ -1908,10 +3410,419 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +isstream@0.1.2, isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +istanbul-lib-coverage@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec" + integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg== + +istanbul-lib-instrument@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.1.tgz#61f13ac2c96cfefb076fe7131156cc05907874e6" + integrity sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg== + dependencies: + "@babel/core" "^7.7.5" + "@babel/parser" "^7.7.5" + "@babel/template" "^7.7.4" + "@babel/traverse" "^7.7.4" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz#75743ce6d96bb86dc7ee4352cf6366a23f0b1ad9" + integrity sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.0.tgz#d4d16d035db99581b6194e119bbf36c963c5eb70" + integrity sha512-2osTcC8zcOSUkImzN2EWQta3Vdi4WjjKw99P2yWx5mLnigAM0Rd5uYFn1cf2i/Ois45GkNjaoTqc5CxgMSX80A== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-25.1.0.tgz#73dae9a7d9949fdfa5c278438ce8f2ff3ec78131" + integrity sha512-bdL1aHjIVy3HaBO3eEQeemGttsq1BDlHgWcOjEOIAcga7OOEGWHD2WSu8HhL7I1F0mFFyci8VKU4tRNk+qtwDA== + dependencies: + "@jest/types" "^25.1.0" + execa "^3.2.0" + throat "^5.0.0" + +jest-cli@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-25.1.0.tgz#75f0b09cf6c4f39360906bf78d580be1048e4372" + integrity sha512-p+aOfczzzKdo3AsLJlhs8J5EW6ffVidfSZZxXedJ0mHPBOln1DccqFmGCoO8JWd4xRycfmwy1eoQkMsF8oekPg== + dependencies: + "@jest/core" "^25.1.0" + "@jest/test-result" "^25.1.0" + "@jest/types" "^25.1.0" + chalk "^3.0.0" + exit "^0.1.2" + import-local "^3.0.2" + is-ci "^2.0.0" + jest-config "^25.1.0" + jest-util "^25.1.0" + jest-validate "^25.1.0" + prompts "^2.0.1" + realpath-native "^1.1.0" + yargs "^15.0.0" + +jest-config@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-25.1.0.tgz#d114e4778c045d3ef239452213b7ad3ec1cbea90" + integrity sha512-tLmsg4SZ5H7tuhBC5bOja0HEblM0coS3Wy5LTCb2C8ZV6eWLewHyK+3qSq9Bi29zmWQ7ojdCd3pxpx4l4d2uGw== + dependencies: + "@babel/core" "^7.1.0" + "@jest/test-sequencer" "^25.1.0" + "@jest/types" "^25.1.0" + babel-jest "^25.1.0" + chalk "^3.0.0" + glob "^7.1.1" + jest-environment-jsdom "^25.1.0" + jest-environment-node "^25.1.0" + jest-get-type "^25.1.0" + jest-jasmine2 "^25.1.0" + jest-regex-util "^25.1.0" + jest-resolve "^25.1.0" + jest-util "^25.1.0" + jest-validate "^25.1.0" + micromatch "^4.0.2" + pretty-format "^25.1.0" + realpath-native "^1.1.0" + +jest-diff@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.1.0.tgz#58b827e63edea1bc80c1de952b80cec9ac50e1ad" + integrity sha512-nepXgajT+h017APJTreSieh4zCqnSHEJ1iT8HDlewu630lSJ4Kjjr9KNzm+kzGwwcpsDE6Snx1GJGzzsefaEHw== + dependencies: + chalk "^3.0.0" + diff-sequences "^25.1.0" + jest-get-type "^25.1.0" + pretty-format "^25.1.0" + +jest-docblock@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-25.1.0.tgz#0f44bea3d6ca6dfc38373d465b347c8818eccb64" + integrity sha512-370P/mh1wzoef6hUKiaMcsPtIapY25suP6JqM70V9RJvdKLrV4GaGbfUseUVk4FZJw4oTZ1qSCJNdrClKt5JQA== + dependencies: + detect-newline "^3.0.0" + +jest-each@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-25.1.0.tgz#a6b260992bdf451c2d64a0ccbb3ac25e9b44c26a" + integrity sha512-R9EL8xWzoPySJ5wa0DXFTj7NrzKpRD40Jy+zQDp3Qr/2QmevJgkN9GqioCGtAJ2bW9P/MQRznQHQQhoeAyra7A== + dependencies: + "@jest/types" "^25.1.0" + chalk "^3.0.0" + jest-get-type "^25.1.0" + jest-util "^25.1.0" + pretty-format "^25.1.0" + +jest-environment-jsdom@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-25.1.0.tgz#6777ab8b3e90fd076801efd3bff8e98694ab43c3" + integrity sha512-ILb4wdrwPAOHX6W82GGDUiaXSSOE274ciuov0lztOIymTChKFtC02ddyicRRCdZlB5YSrv3vzr1Z5xjpEe1OHQ== + dependencies: + "@jest/environment" "^25.1.0" + "@jest/fake-timers" "^25.1.0" + "@jest/types" "^25.1.0" + jest-mock "^25.1.0" + jest-util "^25.1.0" + jsdom "^15.1.1" + +jest-environment-node@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-25.1.0.tgz#797bd89b378cf0bd794dc8e3dca6ef21126776db" + integrity sha512-U9kFWTtAPvhgYY5upnH9rq8qZkj6mYLup5l1caAjjx9uNnkLHN2xgZy5mo4SyLdmrh/EtB9UPpKFShvfQHD0Iw== + dependencies: + "@jest/environment" "^25.1.0" + "@jest/fake-timers" "^25.1.0" + "@jest/types" "^25.1.0" + jest-mock "^25.1.0" + jest-util "^25.1.0" + +jest-get-type@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.1.0.tgz#1cfe5fc34f148dc3a8a3b7275f6b9ce9e2e8a876" + integrity sha512-yWkBnT+5tMr8ANB6V+OjmrIJufHtCAqI5ic2H40v+tRqxDmE0PGnIiTyvRWFOMtmVHYpwRqyazDbTnhpjsGvLw== + +jest-haste-map@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-25.1.0.tgz#ae12163d284f19906260aa51fd405b5b2e5a4ad3" + integrity sha512-/2oYINIdnQZAqyWSn1GTku571aAfs8NxzSErGek65Iu5o8JYb+113bZysRMcC/pjE5v9w0Yz+ldbj9NxrFyPyw== + dependencies: + "@jest/types" "^25.1.0" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.3" + jest-serializer "^25.1.0" + jest-util "^25.1.0" + jest-worker "^25.1.0" + micromatch "^4.0.2" + sane "^4.0.3" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.1.2" + +jest-jasmine2@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-25.1.0.tgz#681b59158a430f08d5d0c1cce4f01353e4b48137" + integrity sha512-GdncRq7jJ7sNIQ+dnXvpKO2MyP6j3naNK41DTTjEAhLEdpImaDA9zSAZwDhijjSF/D7cf4O5fdyUApGBZleaEg== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^25.1.0" + "@jest/source-map" "^25.1.0" + "@jest/test-result" "^25.1.0" + "@jest/types" "^25.1.0" + chalk "^3.0.0" + co "^4.6.0" + expect "^25.1.0" + is-generator-fn "^2.0.0" + jest-each "^25.1.0" + jest-matcher-utils "^25.1.0" + jest-message-util "^25.1.0" + jest-runtime "^25.1.0" + jest-snapshot "^25.1.0" + jest-util "^25.1.0" + pretty-format "^25.1.0" + throat "^5.0.0" + +jest-leak-detector@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-25.1.0.tgz#ed6872d15aa1c72c0732d01bd073dacc7c38b5c6" + integrity sha512-3xRI264dnhGaMHRvkFyEKpDeaRzcEBhyNrOG5oT8xPxOyUAblIAQnpiR3QXu4wDor47MDTiHbiFcbypdLcLW5w== + dependencies: + jest-get-type "^25.1.0" + pretty-format "^25.1.0" + +jest-matcher-utils@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.1.0.tgz#fa5996c45c7193a3c24e73066fc14acdee020220" + integrity sha512-KGOAFcSFbclXIFE7bS4C53iYobKI20ZWleAdAFun4W1Wz1Kkej8Ng6RRbhL8leaEvIOjGXhGf/a1JjO8bkxIWQ== + dependencies: + chalk "^3.0.0" + jest-diff "^25.1.0" + jest-get-type "^25.1.0" + pretty-format "^25.1.0" + +jest-message-util@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.1.0.tgz#702a9a5cb05c144b9aa73f06e17faa219389845e" + integrity sha512-Nr/Iwar2COfN22aCqX0kCVbXgn8IBm9nWf4xwGr5Olv/KZh0CZ32RKgZWMVDXGdOahicM10/fgjdimGNX/ttCQ== + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/test-result" "^25.1.0" + "@jest/types" "^25.1.0" + "@types/stack-utils" "^1.0.1" + chalk "^3.0.0" + micromatch "^4.0.2" + slash "^3.0.0" + stack-utils "^1.0.1" + +jest-mock@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-25.1.0.tgz#411d549e1b326b7350b2e97303a64715c28615fd" + integrity sha512-28/u0sqS+42vIfcd1mlcg4ZVDmSUYuNvImP4X2lX5hRMLW+CN0BeiKVD4p+ujKKbSPKd3rg/zuhCF+QBLJ4vag== + dependencies: + "@jest/types" "^25.1.0" + +jest-pnp-resolver@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a" + integrity sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ== + +jest-regex-util@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-25.1.0.tgz#efaf75914267741838e01de24da07b2192d16d87" + integrity sha512-9lShaDmDpqwg+xAd73zHydKrBbbrIi08Kk9YryBEBybQFg/lBWR/2BDjjiSE7KIppM9C5+c03XiDaZ+m4Pgs1w== + +jest-resolve-dependencies@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-25.1.0.tgz#8a1789ec64eb6aaa77fd579a1066a783437e70d2" + integrity sha512-Cu/Je38GSsccNy4I2vL12ZnBlD170x2Oh1devzuM9TLH5rrnLW1x51lN8kpZLYTvzx9j+77Y5pqBaTqfdzVzrw== + dependencies: + "@jest/types" "^25.1.0" + jest-regex-util "^25.1.0" + jest-snapshot "^25.1.0" + +jest-resolve@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-25.1.0.tgz#23d8b6a4892362baf2662877c66aa241fa2eaea3" + integrity sha512-XkBQaU1SRCHj2Evz2Lu4Czs+uIgJXWypfO57L7JYccmAXv4slXA6hzNblmcRmf7P3cQ1mE7fL3ABV6jAwk4foQ== + dependencies: + "@jest/types" "^25.1.0" + browser-resolve "^1.11.3" + chalk "^3.0.0" + jest-pnp-resolver "^1.2.1" + realpath-native "^1.1.0" + +jest-runner@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-25.1.0.tgz#fef433a4d42c89ab0a6b6b268e4a4fbe6b26e812" + integrity sha512-su3O5fy0ehwgt+e8Wy7A8CaxxAOCMzL4gUBftSs0Ip32S0epxyZPDov9Znvkl1nhVOJNf4UwAsnqfc3plfQH9w== + dependencies: + "@jest/console" "^25.1.0" + "@jest/environment" "^25.1.0" + "@jest/test-result" "^25.1.0" + "@jest/types" "^25.1.0" + chalk "^3.0.0" + exit "^0.1.2" + graceful-fs "^4.2.3" + jest-config "^25.1.0" + jest-docblock "^25.1.0" + jest-haste-map "^25.1.0" + jest-jasmine2 "^25.1.0" + jest-leak-detector "^25.1.0" + jest-message-util "^25.1.0" + jest-resolve "^25.1.0" + jest-runtime "^25.1.0" + jest-util "^25.1.0" + jest-worker "^25.1.0" + source-map-support "^0.5.6" + throat "^5.0.0" + +jest-runtime@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-25.1.0.tgz#02683218f2f95aad0f2ec1c9cdb28c1dc0ec0314" + integrity sha512-mpPYYEdbExKBIBB16ryF6FLZTc1Rbk9Nx0ryIpIMiDDkOeGa0jQOKVI/QeGvVGlunKKm62ywcioeFVzIbK03bA== + dependencies: + "@jest/console" "^25.1.0" + "@jest/environment" "^25.1.0" + "@jest/source-map" "^25.1.0" + "@jest/test-result" "^25.1.0" + "@jest/transform" "^25.1.0" + "@jest/types" "^25.1.0" + "@types/yargs" "^15.0.0" + chalk "^3.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.3" + jest-config "^25.1.0" + jest-haste-map "^25.1.0" + jest-message-util "^25.1.0" + jest-mock "^25.1.0" + jest-regex-util "^25.1.0" + jest-resolve "^25.1.0" + jest-snapshot "^25.1.0" + jest-util "^25.1.0" + jest-validate "^25.1.0" + realpath-native "^1.1.0" + slash "^3.0.0" + strip-bom "^4.0.0" + yargs "^15.0.0" + +jest-serializer@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-25.1.0.tgz#73096ba90e07d19dec4a0c1dd89c355e2f129e5d" + integrity sha512-20Wkq5j7o84kssBwvyuJ7Xhn7hdPeTXndnwIblKDR2/sy1SUm6rWWiG9kSCgJPIfkDScJCIsTtOKdlzfIHOfKA== + +jest-snapshot@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-25.1.0.tgz#d5880bd4b31faea100454608e15f8d77b9d221d9" + integrity sha512-xZ73dFYN8b/+X2hKLXz4VpBZGIAn7muD/DAg+pXtDzDGw3iIV10jM7WiHqhCcpDZfGiKEj7/2HXAEPtHTj0P2A== + dependencies: + "@babel/types" "^7.0.0" + "@jest/types" "^25.1.0" + chalk "^3.0.0" + expect "^25.1.0" + jest-diff "^25.1.0" + jest-get-type "^25.1.0" + jest-matcher-utils "^25.1.0" + jest-message-util "^25.1.0" + jest-resolve "^25.1.0" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + pretty-format "^25.1.0" + semver "^7.1.1" + +jest-util@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.1.0.tgz#7bc56f7b2abd534910e9fa252692f50624c897d9" + integrity sha512-7did6pLQ++87Qsj26Fs/TIwZMUFBXQ+4XXSodRNy3luch2DnRXsSnmpVtxxQ0Yd6WTipGpbhh2IFP1mq6/fQGw== + dependencies: + "@jest/types" "^25.1.0" + chalk "^3.0.0" + is-ci "^2.0.0" + mkdirp "^0.5.1" + +jest-validate@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-25.1.0.tgz#1469fa19f627bb0a9a98e289f3e9ab6a668c732a" + integrity sha512-kGbZq1f02/zVO2+t1KQGSVoCTERc5XeObLwITqC6BTRH3Adv7NZdYqCpKIZLUgpLXf2yISzQ465qOZpul8abXA== + dependencies: + "@jest/types" "^25.1.0" + camelcase "^5.3.1" + chalk "^3.0.0" + jest-get-type "^25.1.0" + leven "^3.1.0" + pretty-format "^25.1.0" + +jest-watcher@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-25.1.0.tgz#97cb4a937f676f64c9fad2d07b824c56808e9806" + integrity sha512-Q9eZ7pyaIr6xfU24OeTg4z1fUqBF/4MP6J801lyQfg7CsnZ/TCzAPvCfckKdL5dlBBEKBeHV0AdyjFZ5eWj4ig== + dependencies: + "@jest/test-result" "^25.1.0" + "@jest/types" "^25.1.0" + ansi-escapes "^4.2.1" + chalk "^3.0.0" + jest-util "^25.1.0" + string-length "^3.1.0" + +jest-worker@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.1.0.tgz#75d038bad6fdf58eba0d2ec1835856c497e3907a" + integrity sha512-ZHhHtlxOWSxCoNOKHGbiLzXnl42ga9CxDr27H36Qn+15pQZd3R/F24jrmjDelw9j/iHUIWMWs08/u2QN50HHOg== + dependencies: + merge-stream "^2.0.0" + supports-color "^7.0.0" + +jest@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-25.1.0.tgz#b85ef1ddba2fdb00d295deebbd13567106d35be9" + integrity sha512-FV6jEruneBhokkt9MQk0WUFoNTwnF76CLXtwNMfsc0um0TlB/LG2yxUd0KqaFjEJ9laQmVWQWS0sG/t2GsuI0w== + dependencies: + "@jest/core" "^25.1.0" + import-local "^3.0.2" + jest-cli "^25.1.0" + +js-sha256@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966" + integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA== js-sha3@0.5.7: version "0.5.7" @@ -1923,7 +3834,7 @@ js-tokens@^4.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@3.13.1, js-yaml@^3.12.1, js-yaml@^3.13.1: +js-yaml@^3.12.1, js-yaml@^3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== @@ -1936,6 +3847,43 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= +jsdom@^15.1.1: + version "15.2.1" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-15.2.1.tgz#d2feb1aef7183f86be521b8c6833ff5296d07ec5" + integrity sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g== + dependencies: + abab "^2.0.0" + acorn "^7.1.0" + acorn-globals "^4.3.2" + array-equal "^1.0.0" + cssom "^0.4.1" + cssstyle "^2.0.0" + data-urls "^1.1.0" + domexception "^1.0.1" + escodegen "^1.11.1" + html-encoding-sniffer "^1.0.2" + nwsapi "^2.2.0" + parse5 "5.1.0" + pn "^1.1.0" + request "^2.88.0" + request-promise-native "^1.0.7" + saxes "^3.1.9" + symbol-tree "^3.2.2" + tough-cookie "^3.0.1" + w3c-hr-time "^1.0.1" + w3c-xmlserializer "^1.1.2" + webidl-conversions "^4.0.2" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^7.0.0" + ws "^7.0.0" + xml-name-validator "^3.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + json-bigint@^0.2.0: version "0.2.3" resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-0.2.3.tgz#118d7f6ff1d38659f19f94cf73e64a75a3f988a8" @@ -1952,10 +3900,10 @@ json-schema-ref-parser@^6.1.0: js-yaml "^3.12.1" ono "^4.0.11" -json-schema-to-typescript@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/json-schema-to-typescript/-/json-schema-to-typescript-8.0.1.tgz#99681693b63a701b16b8490fd75ffeee90ae67aa" - integrity sha512-QQUeTTU74FzOFRvobyn120NAoGHQZkD2WlbVSwiYzztRQtVC2/5mVapcpav/bV2KyDVzGv3o6r5p6XghUffE/A== +json-schema-to-typescript@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/json-schema-to-typescript/-/json-schema-to-typescript-8.1.0.tgz#c92174a3f70d5eb68646c1ab16cb6bcba6d1cbee" + integrity sha512-SGGX82OofMggPDUIO6+GYc14g8bew4ETtEif9G8I9SSXYFVHPSfoNE0AaNcfTFwI/xHSepHRse4aMurAtc8+mw== dependencies: "@types/json-schema" "^7.0.3" "@types/node" ">=4.5.0" @@ -1979,11 +3927,18 @@ json-schema@0.2.3: resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= -json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: +json-stringify-safe@5.0.1, json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= +json5@2.x, json5@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.1.tgz#81b6cb04e9ba496f1c7005d07b4368a2638f90b6" + integrity sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ== + dependencies: + minimist "^1.2.0" + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -2011,31 +3966,150 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= + dependencies: + invert-kv "^1.0.0" + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lightning@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lightning/-/lightning-1.2.4.tgz#1debec35da84d8fabced378d191e6fc392394af2" + integrity sha512-J3NWINK2lE3NwtXpY1dsqUwZdosDymDsimEops56nuqeD09LENeQY4zBXD3WWS0ckYXh/Ar8LnCO2E/qMpXjyg== + dependencies: + "@grpc/proto-loader" "0.5.3" + async "3.2.0" + asyncjs-util "1.1.3" + bn.js "5.1.1" + body-parser "1.19.0" + bolt07 "1.4.5" + bolt09 "0.0.1" + cbor "5.0.1" + express "4.17.1" + grpc "1.24.2" + invoices "1.0.0" + is-base64 "1.1.0" + +ln-service@^47.15.2: + version "47.15.2" + resolved "https://registry.yarnpkg.com/ln-service/-/ln-service-47.15.2.tgz#d90095340b4479811ea97216fdc4d2c4b68fe29b" + integrity sha512-sydFwqsSYYe/tFg/lnKloAgGO/DKXhlWbUzDE1mI0bz9nXAX0EdTVyZhRiSA9NCbyPj8hFj+sU2sws5XOdG04A== + dependencies: + "@alexbosworth/request" "2.88.3" + "@datastructures-js/priority-queue" "1.0.2" + "@grpc/proto-loader" "0.5.3" + async "3.2.0" + asyncjs-util "1.1.3" + basicauth-middleware "3.1.0" + bech32 "1.1.3" + bitcoinjs-lib "5.1.7" + bn.js "5.1.1" + body-parser "1.19.0" + bolt07 "1.4.5" + bolt09 "0.0.1" + compression "1.7.4" + cors "2.8.5" + dotenv "8.2.0" + express "4.17.1" + grpc "1.24.2" + is-base64 "1.1.0" + lightning "1.2.4" + lodash "4.17.15" + macaroon "3.0.4" + morgan "1.9.1" + promptly "3.0.3" + safe-compare "1.1.4" + secp256k1 "4.0.0" + ws "7.2.1" + loady@~0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/loady/-/loady-0.0.1.tgz#24a99c14cfed9cd0bffed365b1836035303f7e5d" integrity sha512-PW5Z13Jd0v6ZcA1P6ZVUc3EV8BJwQuAiwUvvT6VQGHoaZ1d/tu7r1QZctuKfQqwy9SFBWeAGfcIdLxhp7ZW3Rw== -locate-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" - integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= dependencies: - p-locate "^3.0.0" + p-locate "^2.0.0" path-exists "^3.0.0" -lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.15: +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= + +lodash.clone@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" + integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= + +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= + +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= + +lodash@4.17.15, lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.15: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== -log-symbols@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" - integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== - dependencies: - chalk "^2.0.1" - log4js@*, log4js@^6.1.2: version "6.1.2" resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.1.2.tgz#04688e1f4b8080c127b7dccb0db1c759cbb25dc4" @@ -2047,6 +4121,23 @@ log4js@*, log4js@^6.1.2: rfdc "^1.1.4" streamroller "^2.2.3" +lolex@^5.0.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367" + integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A== + dependencies: + "@sinonjs/commons" "^1.7.0" + +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + +long@~3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b" + integrity sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s= + lru-queue@0.1: version "0.1.0" resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" @@ -2054,6 +4145,15 @@ lru-queue@0.1: dependencies: es5-ext "~0.10.2" +macaroon@3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/macaroon/-/macaroon-3.0.4.tgz#dac1a4b17cd973c1000703f40b19bdbb1d6191ce" + integrity sha512-Tja2jvupseKxltPZbu5RPSz2Pgh6peYA3O46YCTcYL8PI1VqtGwDqRhGfP8pows26xx9wTiygk+en62Bq+Y8JA== + dependencies: + sjcl "^1.0.6" + tweetnacl "^1.0.0" + tweetnacl-util "^0.15.0" + make-dir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.0.tgz#1b5f39f6b9270ed33f9f054c5c0f84304989f801" @@ -2061,11 +4161,35 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" +make-error@1.x: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + make-error@^1.1.1: version "1.3.5" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g== +makeerror@1.0.x: + version "1.0.11" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw= + dependencies: + tmpl "1.0.x" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -2114,6 +4238,33 @@ methods@^1.1.1, methods@^1.1.2, methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= +micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + mime-db@1.40.0: version "1.40.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" @@ -2124,6 +4275,18 @@ mime-db@1.42.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.42.0.tgz#3e252907b4c7adb906597b4b65636272cf9e7bac" integrity sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ== +mime-db@1.43.0, "mime-db@>= 1.43.0 < 2": + version "1.43.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" + integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== + +mime-types@2.1.26: + version "2.1.26" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" + integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + dependencies: + mime-db "1.43.0" + mime-types@^2.1.12, mime-types@~2.1.19: version "2.1.24" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" @@ -2158,7 +4321,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.4: +"minimatch@2 || 3", minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -2170,53 +4333,57 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@^1.2.0: +minimist@^1.1.1, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= -mkdirp@0.5.1, mkdirp@^0.5.1, mkdirp@~0.5.1: +minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" + integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.2.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" + integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== + dependencies: + minipass "^2.9.0" + +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@0.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= dependencies: minimist "0.0.8" -mocha@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.0.1.tgz#276186d35a4852f6249808c6dd4a1376cbf6c6ce" - integrity sha512-9eWmWTdHLXh72rGrdZjNbG3aa1/3NRPpul1z0D979QpEnFdCG0Q5tv834N+94QEN2cysfV72YocQ3fn87s70fg== - dependencies: - ansi-colors "3.2.3" - browser-stdout "1.3.1" - chokidar "3.3.0" - debug "3.2.6" - diff "3.5.0" - escape-string-regexp "1.0.5" - find-up "3.0.0" - glob "7.1.3" - growl "1.10.5" - he "1.2.0" - js-yaml "3.13.1" - log-symbols "2.2.0" - minimatch "3.0.4" - mkdirp "0.5.1" - ms "2.1.1" - node-environment-flags "1.0.6" - object.assign "4.1.0" - strip-json-comments "2.0.1" - supports-color "6.0.0" - which "1.3.1" - wide-align "1.1.3" - yargs "13.3.0" - yargs-parser "13.1.1" - yargs-unparser "1.6.0" - moment@^2.10.6, moment@^2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== +morgan@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.9.1.tgz#0a8d16734a1d9afbc824b99df87e738e58e2da59" + integrity sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA== + dependencies: + basic-auth "~2.0.0" + debug "2.6.9" + depd "~1.1.2" + on-finished "~2.3.0" + on-headers "~1.0.1" + mrmr@~0.1.6, mrmr@~0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/mrmr/-/mrmr-0.1.8.tgz#206b7975157543d2cffe762eec23966000b3fd12" @@ -2241,10 +4408,10 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -multiaddr@^7.3.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/multiaddr/-/multiaddr-7.3.0.tgz#277c3a897338ba502f4dd76abf0f9b934684a683" - integrity sha512-HlV0oMy5dq+vV1t5hFL8T559j7cdgODnCQEXqDATL7oZyN6hWKJOZ4XE45LkdVJc7/A4cCLZmVg7RCBjj0wfyQ== +multiaddr@^7.4.0: + version "7.4.0" + resolved "https://registry.yarnpkg.com/multiaddr/-/multiaddr-7.4.0.tgz#29356287b2ee3c7f1a670a4d1d40990c12b06c08" + integrity sha512-SooWP6eVhfMdf8ftyTmstZhrwMm7CUk3T5yU6naTjJ2cwTekciBjOjG4Pa8Sy3p+U0trJmZuILkqxtJ0Zpm9vQ== dependencies: bs58 "^4.0.1" cids "~0.7.1" @@ -2276,6 +4443,11 @@ multihashes@~0.4.14: bs58 "^4.0.1" varint "^5.0.0" +mute-stream@~0.0.4: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + mv@~2: version "2.1.1" resolved "https://registry.yarnpkg.com/mv/-/mv-2.1.1.tgz#ae6ce0d6f6d5e0a4f7d893798d03c1ea9559b6a2" @@ -2304,11 +4476,42 @@ nan@^2.10.0, nan@^2.13.1, nan@^2.13.2: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + ncp@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M= +needle@^2.2.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.3.2.tgz#3342dea100b7160960a450dc8c22160ac712a528" + integrity sha512-DUzITvPVDUy6vczKKYTnWc/pBZ0EnjMJnQ3y+Jo5zfKFimJs7S3HFCxCRZYB9FUZcrzUQr3WsmvZgddMEIZv6w== + dependencies: + debug "^3.2.6" + iconv-lite "^0.4.4" + sax "^1.2.4" + negotiator@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" @@ -2324,24 +4527,111 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +node-addon-api@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.0.tgz#f9afb8d777a91525244b01775ea0ddbe1125483b" + integrity sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA== + node-duration@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/node-duration/-/node-duration-1.0.4.tgz#3e94ecc0e473691c89c4560074503362071cecac" integrity sha512-eUXYNSY7DL53vqfTosggWkvyIW3bhAcqBDIlolgNYlZhianXTrCL50rlUJWD1eRqkIxMppXTfiFbp+9SjpPrgA== -node-environment-flags@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" - integrity sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw== +node-gyp-build@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.0.tgz#2c2b05f461f4178641a6ce2d7159f04094e9376d" + integrity sha512-4oiumOLhCDU9Rronz8PZ5S4IvT39H5+JEv/hps9V8s7RSLhsac0TCP78ulnHXOo8X1wdpPiTayGlM1jr4IbnaQ== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + +node-modules-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" + integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= + +node-notifier@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-6.0.0.tgz#cea319e06baa16deec8ce5cd7f133c4a46b68e12" + integrity sha512-SVfQ/wMw+DesunOm5cKqr6yDcvUTDl/yc97ybGHMrteNEY6oekXpNpS3lZwgLlwz0FLgHoiW28ZpmBHUDg37cw== + dependencies: + growly "^1.3.0" + is-wsl "^2.1.1" + semver "^6.3.0" + shellwords "^0.1.1" + which "^1.3.1" + +node-pre-gyp@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83" + integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA== dependencies: - object.getownpropertydescriptors "^2.0.3" - semver "^5.7.0" + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4.4.2" + +nofilter@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-1.0.3.tgz#34e54b4cc9757de0cad38cc0d19462489b1b7f5d" + integrity sha512-FlUlqwRK6reQCaFLAhMcF+6VkVG2caYjKQY3YsRDTl4/SEch595Qb3oLjJRDr8dkHAAOVj2pOx3VknfnSgkE5g== -normalize-path@^3.0.0, normalize-path@~3.0.0: +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +npm-bundled@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" + integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== + dependencies: + npm-normalize-package-bin "^1.0.1" + +npm-normalize-package-bin@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" + integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== + +npm-packlist@^1.1.6: + version "1.4.8" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" + integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + npm-normalize-package-bin "^1.0.1" + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + dependencies: + path-key "^2.0.0" + npm-run-path@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-3.1.0.tgz#7f91be317f6a466efed3c9f2980ad8a4ee8b0fa5" @@ -2349,22 +4639,70 @@ npm-run-path@^3.0.0: dependencies: path-key "^3.0.0" -oauth-sign@~0.9.0: +npm-run-path@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +nwsapi@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + +oauth-sign@0.9.0, oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.0.1: +object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -object-keys@^1.0.11, object-keys@^1.0.12: +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-inspect@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" + integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@4.1.0: +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" + +object.assign@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== @@ -2374,13 +4712,20 @@ object.assign@4.1.0: has-symbols "^1.0.0" object-keys "^1.0.11" -object.getownpropertydescriptors@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" - integrity sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY= +object.getownpropertydescriptors@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" + integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== dependencies: - define-properties "^1.1.2" - es-abstract "^1.5.1" + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.1" on-finished@~2.3.0: version "2.3.0" @@ -2389,6 +4734,11 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" +on-headers@~1.0.1, on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -2410,46 +4760,146 @@ ono@^4.0.11: dependencies: format-util "^1.0.3" +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +optjs@~3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/optjs/-/optjs-3.2.2.tgz#69a6ce89c442a44403141ad2f9b370bd5bb6f4ee" + integrity sha1-aabOicRCpEQDFBrS+bNwvVu29O4= + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= + dependencies: + lcid "^1.0.0" + +os-tmpdir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +p-each-series@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48" + integrity sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ== + +p-event@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.1.0.tgz#e92bb866d7e8e5b732293b1c8269d38e9982bf8e" + integrity sha512-4vAd06GCsgflX4wHN1JqrMzBh/8QZ4j+rzp0cd2scXRwuBEv+QR3wrVA5aLhWDLw4y2WgDKvzWF3CCLmVM1UgA== + dependencies: + p-timeout "^2.0.1" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + p-finally@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561" integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw== -p-limit@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2" - integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ== +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-limit@^2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" + integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== dependencies: p-try "^2.0.0" -p-locate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" - integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= dependencies: - p-limit "^2.0.0" + p-limit "^1.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-timeout@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" + integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA== + dependencies: + p-finally "^1.0.0" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +parse5@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" + integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== + parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-key@^2.0.1: +path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= @@ -2459,6 +4909,11 @@ path-key@^3.0.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.0.tgz#99a10d870a803bdd5ee6f0470e58dfcd2f9a54d3" integrity sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg== +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -2481,26 +4936,122 @@ pem-ts@^2.0.0: dependencies: base64-js "^1.3.1" -performance-now@^2.1.0: +performance-now@2.1.0, performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.4: +picomatch@^2.0.4, picomatch@^2.0.5: version "2.2.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA== +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + +pirates@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" + integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== + dependencies: + node-modules-regexp "^1.0.0" + +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s= + dependencies: + find-up "^2.1.0" + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pn@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" + integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + prettier@^1.19.1: version "1.19.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== +pretty-format@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.1.0.tgz#ed869bdaec1356fc5ae45de045e2c8ec7b07b0c8" + integrity sha512-46zLRSGLd02Rp+Lhad9zzuNZ+swunitn8zIpfD2B4OPCRLXbM87RJT2aBLBWYOznNUML/2l/ReMyWNC80PJBUQ== + dependencies: + "@jest/types" "^25.1.0" + ansi-regex "^5.0.0" + ansi-styles "^4.0.0" + react-is "^16.12.0" + process-nextick-args@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== +promptly@3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/promptly/-/promptly-3.0.3.tgz#e178f722e73d82c60d019462044bccfdd9872f42" + integrity sha512-EWnzOsxVKUjqKeE6SStH1/cO4+DE44QolaoJ4ojGd9z6pcNkpgfJKr1ncwxrOFHSTIzoudo7jG8y0re30/LO1g== + dependencies: + pify "^3.0.0" + read "^1.0.4" + +prompts@^2.0.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.1.tgz#b63a9ce2809f106fa9ae1277c275b167af46ea05" + integrity sha512-qIP2lQyCwYbdzcqHIUi2HAxiWixhoM9OdLCWf8txXsapC/X9YdsCoeyRIXE/GP+Q0J37Q7+XN/MFqbUa7IzXNA== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.4" + +protobufjs@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-5.0.3.tgz#e4dfe9fb67c90b2630d15868249bcc4961467a17" + integrity sha512-55Kcx1MhPZX0zTbVosMQEO5R6/rikNXd9b6RQK4KSPcrSIIwoXTtebIczUrXlwaSrbz4x8XUVThGPob1n8I4QA== + dependencies: + ascli "~1" + bytebuffer "~5" + glob "^7.0.5" + yargs "^3.10.0" + +protobufjs@^6.8.6: + version "6.8.8" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.8.8.tgz#c8b4f1282fd7a90e6f5b109ed11c84af82908e7c" + integrity sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.0" + "@types/node" "^10.1.0" + long "^4.0.0" + proxy-addr@~2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34" @@ -2514,6 +5065,11 @@ psl@^1.1.24: resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.32.tgz#3f132717cf2f9c169724b2b6caf373cf694198db" integrity sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g== +psl@^1.1.28: + version "1.7.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" + integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== + pump@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954" @@ -2535,7 +5091,7 @@ punycode@^1.4.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= -punycode@^2.1.0: +punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== @@ -2552,6 +5108,11 @@ qs@6.7.0, qs@^6.5.1: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qs@6.9.1: + version "6.9.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.1.tgz#20082c65cb78223635ab1a9eaca8875a29bf8ec9" + integrity sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA== + qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -2579,6 +5140,41 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-is@^16.12.0: + version "16.13.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.0.tgz#0f37c3613c34fe6b37cd7f763a0d6293ab15c527" + integrity sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA== + +read@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" + integrity sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ= + dependencies: + mute-stream "~0.0.4" + +readable-stream@^2.0.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.5: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" @@ -2611,17 +5207,56 @@ readable-stream@~1.0.26-4: isarray "0.0.1" string_decoder "~0.10.x" -readdirp@~3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" - integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== +readline-promise@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/readline-promise/-/readline-promise-1.0.4.tgz#c22190438642da6fb99462cfbbfb6232b4edb9bd" + integrity sha512-b6fycDK7CZWpVXbTl8qnW2jovXPduWKpZGyVZbjK/V4A9iiTU4gur+JEkjjgGKLiDZOftkRCT/dxGLG6hR9HyA== + +realpath-native@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c" + integrity sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA== + dependencies: + util.promisify "^1.0.0" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +request-promise-core@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" + integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ== dependencies: - picomatch "^2.0.4" + lodash "^4.17.15" -readline-promise@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/readline-promise/-/readline-promise-1.0.4.tgz#c22190438642da6fb99462cfbbfb6232b4edb9bd" - integrity sha512-b6fycDK7CZWpVXbTl8qnW2jovXPduWKpZGyVZbjK/V4A9iiTU4gur+JEkjjgGKLiDZOftkRCT/dxGLG6hR9HyA== +request-promise-native@^1.0.7: + version "1.0.8" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" + integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== + dependencies: + request-promise-core "1.1.3" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" request@^2.53.0: version "2.88.0" @@ -2649,6 +5284,32 @@ request@^2.53.0: tunnel-agent "^0.6.0" uuid "^3.3.2" +request@^2.88.0: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -2659,6 +5320,35 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +resolve@1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= + +resolve@1.x: + version "1.15.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" + integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== + dependencies: + path-parse "^1.0.6" + resolve@^1.3.2: version "1.11.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.1.tgz#ea10d8110376982fef578df8fc30b9ac30a07a3e" @@ -2666,19 +5356,24 @@ resolve@^1.3.2: dependencies: path-parse "^1.0.6" +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + rfdc@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2" integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug== -rimraf@^2.6.3: +rimraf@^2.6.1, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: glob "^7.1.3" -rimraf@^3.0.2: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -2700,26 +5395,60 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +rsvp@^4.8.4: + version "4.8.5" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" + integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== + safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@~5.2.0: +safe-buffer@5.2.0, safe-buffer@~5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== +safe-compare@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/safe-compare/-/safe-compare-1.1.4.tgz#5e0128538a82820e2e9250cd78e45da6786ba593" + integrity sha512-b9wZ986HHCo/HbKrRpBJb2kqXMK9CEWIE1egeEvZsYn69ay3kdfl9nG3RyOcR+jInTDf7a86WQ1d4VJX7goSSQ== + dependencies: + buffer-alloc "^1.2.0" + safe-json-stringify@~1: version "1.2.0" resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd" integrity sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg== +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + "safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sane@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" + integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== + dependencies: + "@cnakazawa/watch" "^1.0.3" + anymatch "^2.0.0" + capture-exit "^2.0.0" + exec-sh "^0.3.2" + execa "^1.0.0" + fb-watchman "^2.0.0" + micromatch "^3.1.4" + minimist "^1.1.1" + walker "~1.0.5" + satoshi-bitcoin@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/satoshi-bitcoin/-/satoshi-bitcoin-1.0.4.tgz#d002b677075d5cbbf2c211a8df3254bcdf50b1e4" @@ -2727,21 +5456,52 @@ satoshi-bitcoin@^1.0.4: dependencies: big.js "^3.1.3" +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +saxes@^3.1.9: + version "3.1.11" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-3.1.11.tgz#d59d1fd332ec92ad98a2e0b2ee644702384b1c5b" + integrity sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g== + dependencies: + xmlchars "^2.1.1" + scrypt-js@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.4.tgz#32f8c5149f0797672e551c07e230f834b6af5f16" integrity sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw== -semver@^5.1.0, semver@^5.3.0, semver@^5.5.0, semver@^5.7.0: +secp256k1@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.0.tgz#1c5def3be9f86c679839110fd6be30d53f34f1a9" + integrity sha512-0w0zse+Iku13O58SVE9/DhyCKWNsKb+n/vMqLOGICgSqxWuXZs+eajBf9uVOgk5QfNvTY/mx0QSqYxkcz802dw== + dependencies: + elliptic "^6.5.2" + node-addon-api "^2.0.0" + node-gyp-build "^4.2.0" + +semver@^5.1.0, semver@^5.3.0, semver@^5.5.0: version "5.7.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== -semver@^6.0.0: +semver@^5.4.1, semver@^5.5: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.0.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.1.1: + version "7.1.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.3.tgz#e4345ce73071c53f336445cfc19efb1c311df2a6" + integrity sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA== + send@0.17.1: version "0.17.1" resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" @@ -2771,11 +5531,21 @@ serve-static@1.14.1: parseurl "~1.3.3" send "0.17.1" -set-blocking@^2.0.0: +set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + setimmediate@1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.4.tgz#20e81de622d4a02588ce0c8da8973cbcf1d3138f" @@ -2801,16 +5571,94 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= -signal-exit@^3.0.2: +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shellwords@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== + +signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= +sisteransi@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.4.tgz#386713f1ef688c7c0304dc4c0632898941cad2e3" + integrity sha512-/ekMoM4NJ59ivGSfKapeG+FWtrmWvA1p6FBZwXrqojw90vJu8lBmrTxCMuBCydKtkaUe2zt4PlxeTKpjwMbyig== + +sjcl@^1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/sjcl/-/sjcl-1.0.8.tgz#f2ec8d7dc1f0f21b069b8914a41a8f236b0e252a" + integrity sha512-LzIjEQ0S0DpIgnxMEayM1rq9aGwGRG4OnZhCdjx7glTaJtf4zRfpg87ImfjSJjoW9vKpagd82McDOwbRT5kQKQ== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +smack-my-jasmine-up@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/smack-my-jasmine-up/-/smack-my-jasmine-up-0.0.3.tgz#44c9420220f7bd90b7ffe329b343828eb3c05972" + integrity sha512-DLaxxk5L3vrnjgqRwgUsFtmw4CvjnOmaXdxHp89rKTxt03erWQ0xgE0ZKZtllv+3BH3xljF5fR5pHIe27OLRbg== + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +source-map-resolve@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + source-map-support@^0.5.6: version "0.5.12" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599" @@ -2819,22 +5667,44 @@ source-map-support@^0.5.6: buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.6.0: +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + +source-map@^0.5.0, source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +source-map@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + split-ca@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6" integrity sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY= +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -sshpk@^1.7.0: +sshpk@^1.14.1, sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== @@ -2849,11 +5719,24 @@ sshpk@^1.7.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" +stack-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" + integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== + standard-error@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/standard-error/-/standard-error-1.1.0.tgz#23e5168fa1c0820189e5812701a79058510d0d34" integrity sha1-I+UWj6HAggGJ5YEnAaeQWFENDTQ= +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + "statuses@>= 1.5.0 < 2", statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" @@ -2864,6 +5747,11 @@ stdin@0.0.1: resolved "https://registry.yarnpkg.com/stdin/-/stdin-0.0.1.tgz#d3041981aaec3dfdbc77a1b38d6372e38f5fb71e" integrity sha1-0wQZgarsPf28d6GzjWNy449ftx4= +stealthy-require@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" + integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= + stream-to-array@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/stream-to-array/-/stream-to-array-2.3.0.tgz#bbf6b39f5f43ec30bc71babcb37557acecf34353" @@ -2880,6 +5768,23 @@ streamroller@^2.2.3: debug "^4.1.1" fs-extra "^8.1.0" +string-length@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-3.1.0.tgz#107ef8c23456e187a8abd4a61162ff4ac6e25837" + integrity sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA== + dependencies: + astral-regex "^1.0.0" + strip-ansi "^5.2.0" + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + "string-width@^1.0.2 || 2": version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" @@ -2888,14 +5793,30 @@ streamroller@^2.2.3: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" + integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + +string.prototype.trimleft@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" + integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + +string.prototype.trimright@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" + integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" string_decoder@^1.1.1: version "1.3.0" @@ -2916,6 +5837,13 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" @@ -2923,19 +5851,36 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: +strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-json-comments@2.0.1: +strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= @@ -2956,13 +5901,6 @@ superagent@^3.7.0: qs "^6.5.1" readable-stream "^2.3.5" -supports-color@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a" - integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg== - dependencies: - has-flag "^3.0.0" - supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -2970,6 +5908,26 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz#f663df252af5f37c5d49bbd7eeefa9e0b9e59e47" + integrity sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +symbol-tree@^3.2.2: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + tail@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/tail/-/tail-2.0.3.tgz#37567adc4624a70b35f1d146c3376fa3d6ef7c04" @@ -3019,6 +5977,19 @@ tar-stream@^2.0.0: inherits "^2.0.3" readable-stream "^3.1.1" +tar@^4.4.2: + version "4.4.13" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" + integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== + dependencies: + chownr "^1.1.1" + fs-minipass "^1.2.5" + minipass "^2.8.6" + minizlib "^1.2.1" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.3" + temp-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" @@ -3048,10 +6019,27 @@ tempfile@*: temp-dir "^2.0.0" uuid "^3.3.2" -testcontainers@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/testcontainers/-/testcontainers-2.3.2.tgz#a89df77325f1e263fc25e13f88e5846efd40cbf8" - integrity sha512-DCpqyErmPbcYkeu+KZL/1Q8IAkRImrBo2ohqDqp111Gw76QpXhYl6ADWBRnWLR091/ocjk290RLIUV5O5YWL1A== +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +testcontainers@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/testcontainers/-/testcontainers-2.6.0.tgz#0f444d97e0d037880fcffe10ee7cb9f0d5e149f0" + integrity sha512-kRO9+pXz8w5JQfbsyTFIlqU6Ooic3XflhsCDE+LavoFUmhAvOkF/ycafPHU2RR5GZ42TwRUXask0PJZEvpcCTg== dependencies: byline "^5.0.0" debug "^4.1.1" @@ -3076,6 +6064,11 @@ thenify-all@^1.0.0: dependencies: any-promise "^1.0.0" +throat@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" + integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== + "through@>=2.2.7 <3": version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -3107,11 +6100,36 @@ tmp@^0.1.0: dependencies: rimraf "^2.6.3" +tmpl@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE= + to-buffer@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -3119,11 +6137,38 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + toidentifier@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== +tough-cookie@3.0.1, tough-cookie@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2" + integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg== + dependencies: + ip-regex "^2.1.0" + psl "^1.1.28" + punycode "^2.1.1" + +tough-cookie@^2.3.3, tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + tough-cookie@~2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" @@ -3132,6 +6177,29 @@ tough-cookie@~2.4.3: psl "^1.1.24" punycode "^1.4.1" +tr46@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" + integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk= + dependencies: + punycode "^2.1.0" + +ts-jest@^25.2.1: + version "25.2.1" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-25.2.1.tgz#49bf05da26a8b7fbfbc36b4ae2fcdc2fef35c85d" + integrity sha512-TnntkEEjuXq/Gxpw7xToarmHbAafgCaAzOpnajnFC6jI7oo1trMzAHA04eWpc3MhV6+yvhE8uUBAmN+teRJh0A== + dependencies: + bs-logger "0.x" + buffer-from "1.x" + fast-json-stable-stringify "2.x" + json5 "2.x" + lodash.memoize "4.x" + make-error "1.x" + mkdirp "0.x" + resolve "1.x" + semver "^5.5" + yargs-parser "^16.1.0" + ts-node@^8.6.2: version "8.6.2" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.6.2.tgz#7419a01391a818fbafa6f826a33c1a13e9464e35" @@ -3143,6 +6211,13 @@ ts-node@^8.6.2: source-map-support "^0.5.6" yn "3.1.1" +ts-protoc-gen@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/ts-protoc-gen/-/ts-protoc-gen-0.8.0.tgz#2a9a31ee8a4d4760c484f1d0c7199633afaa5e3e" + integrity sha512-LUFM4Jy3qMSVyRf5ql973cJjltS98MiCz8kPf1Rc9AC9BeLu0WJfoHLf0Tvx2cGH0jSK9BpA0o1tHQQfjeO47Q== + dependencies: + google-protobuf "^3.6.1" + tslib@^1.10.0, tslib@^1.8.1: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" @@ -3193,7 +6268,7 @@ tsutils@^3.0.0: dependencies: tslib "^1.8.1" -tunnel-agent@^0.6.0: +tunnel-agent@0.6.0, tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= @@ -3205,16 +6280,38 @@ tv4@^1.3.0: resolved "https://registry.yarnpkg.com/tv4/-/tv4-1.3.0.tgz#d020c846fadd50c855abb25ebaecc68fc10f7963" integrity sha1-0CDIRvrdUMhVq7JeuuzGj8EPeWM= +tweetnacl-util@^0.15.0: + version "0.15.1" + resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz#b80fcdb5c97bcc508be18c44a4be50f022eea00b" + integrity sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw== + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -type-detect@^4.0.0, type-detect@^4.0.5: +tweetnacl@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" + integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== +type-fest@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" + integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== + type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -3223,6 +6320,13 @@ type-is@~1.6.17, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -3233,10 +6337,20 @@ typeforce@^1.11.3, typeforce@^1.11.5: resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc" integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g== -typescript@^3.7.5: - version "3.7.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" - integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== +typescript@^3.8.3: + version "3.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" + integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== + +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" universalify@^0.1.0: version "0.1.2" @@ -3248,6 +6362,14 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" @@ -3255,16 +6377,36 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -urijs@^1.19.1, urijs@^1.19.2: +urijs@^1.19.2: version "1.19.2" resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.2.tgz#f9be09f00c4c5134b7cb3cf475c1dd394526265a" integrity sha512-s/UIq9ap4JPZ7H1EB5ULo/aOUbWqfDi7FKzMC2Nz+0Si8GiT1rIEaprt8hy3Vy2Ex2aJPpOQv4P4DuOZ+K1c6w== +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= +util.promisify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" + integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.2" + has-symbols "^1.0.1" + object.getownpropertydescriptors "^2.1.0" + utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" @@ -3275,10 +6417,19 @@ uuid@2.0.1: resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.1.tgz#c2a30dedb3e535d72ccf82e343941a50ba8533ac" integrity sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w= -uuid@^3.0.1, uuid@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" - integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== +uuid@3.4.0, uuid@^3.0.1, uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +v8-to-istanbul@^4.0.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-4.1.2.tgz#387d173be5383dbec209d21af033dcb892e3ac82" + integrity sha512-G9R+Hpw0ITAmPSr47lSlc5A1uekSYzXxTMlFxso2xoffwo4jQnzbv1p9yXIinO8UMZKfAFewaCHwWvnH4Jb4Ug== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + source-map "^0.7.3" varint@^5.0.0: version "5.0.0" @@ -3292,7 +6443,7 @@ varuint-bitcoin@^1.0.4: dependencies: safe-buffer "^5.1.1" -vary@~1.1.2: +vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= @@ -3306,19 +6457,75 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +w3c-hr-time@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045" + integrity sha1-gqwr/2PZUOqeMYmlimViX+3xkEU= + dependencies: + browser-process-hrtime "^0.1.2" + +w3c-xmlserializer@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz#30485ca7d70a6fd052420a3d12fd90e6339ce794" + integrity sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg== + dependencies: + domexception "^1.0.1" + webidl-conversions "^4.0.2" + xml-name-validator "^3.0.0" + +walker@^1.0.7, walker@~1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs= + dependencies: + makeerror "1.0.x" + +webidl-conversions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== + +whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + +whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + +whatwg-url@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" + integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@1.3.1, which@^1.2.9: +which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" -wide-align@1.1.3: +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== @@ -3332,20 +6539,63 @@ wif@^2.0.1, wif@^2.0.6: dependencies: bs58check "<3.0.0" -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== +window-size@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" + integrity sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY= + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +ws@7.2.1, ws@^7.0.0: + version "7.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.1.tgz#03ed52423cd744084b2cf42ed197c8b65a936b8e" + integrity sha512-sucePNSafamSKoOqoNfBd8V0StlkzJKL2ZAhGQinCfNQ+oacw+Pk7lcdAElecBF2VkLNZRiIb5Oi1Q5lVUVt2A== + +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + +xmlchars@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + xmlhttprequest@1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" @@ -3356,43 +6606,58 @@ xtend@^4.0.0: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== +y18n@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= + y18n@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== -yargs-parser@13.1.1, yargs-parser@^13.1.1: - version "13.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" - integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== +yallist@^3.0.0, yallist@^3.0.3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yargs-parser@^16.1.0: + version "16.1.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-16.1.0.tgz#73747d53ae187e7b8dbe333f95714c76ea00ecf1" + integrity sha512-H/V41UNZQPkUMIT5h5hiwg4QKIY1RPvoBV4XcjUbRM8Bk2oKqqyZ0DIEbTFZB0XjbtSPG8SAa/0DxCQmiRgzKg== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-unparser@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" - integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== - dependencies: - flat "^4.1.0" - lodash "^4.17.15" - yargs "^13.3.0" - -yargs@13.3.0, yargs@^13.3.0: - version "13.3.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83" - integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA== +yargs@^15.0.0: + version "15.1.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.1.0.tgz#e111381f5830e863a89550bd4b136bb6a5f37219" + integrity sha512-T39FNN1b6hCW4SOIk1XyTOWxtXdcen0t+XYrysQmChzSipvhBO8Bj0nK1ozAasdk24dNWuMZvr4k24nz+8HHLg== dependencies: - cliui "^5.0.0" - find-up "^3.0.0" + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" get-caller-file "^2.0.1" require-directory "^2.1.1" require-main-filename "^2.0.0" set-blocking "^2.0.0" - string-width "^3.0.0" + string-width "^4.2.0" which-module "^2.0.0" y18n "^4.0.0" - yargs-parser "^13.1.1" + yargs-parser "^16.1.0" + +yargs@^3.10.0: + version "3.32.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.32.0.tgz#03088e9ebf9e756b69751611d2a5ef591482c995" + integrity sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU= + dependencies: + camelcase "^2.0.1" + cliui "^3.0.3" + decamelize "^1.1.1" + os-locale "^1.4.0" + string-width "^1.0.1" + window-size "^0.1.4" + y18n "^3.2.0" yn@3.1.1: version "3.1.1" diff --git a/cnd/Cargo.toml b/cnd/Cargo.toml index ca399f7271..98e621a33a 100644 --- a/cnd/Cargo.toml +++ b/cnd/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["CoBloX developers "] name = "cnd" -version = "0.6.0" +version = "0.7.0" edition = "2018" description = "Reference implementation of a COMIT network daemon." @@ -14,19 +14,19 @@ bitcoin = { version = "0.23", features = ["use-serde"] } blockchain_contracts = "0.3.1" chrono = { version = "0.4", features = ["serde"] } config = { version = "0.10", features = ["toml"], default-features = false } -derivative = "1" +derivative = "2" diesel = { version = "1.4", features = ["sqlite", "chrono"] } diesel_migrations = "1.4.0" directories = "2.0" -ethbloom = "0.6.4" -fern = { version = "0.5", features = ["colored"] } -futures = { version = "0.1" } -futures-core = { version = "0.3", features = ["compat", "async-await"], default-features = false, package = "futures" } -genawaiter = "0.2" +ethbloom = "0.8.1" +fern = { version = "0.6", features = ["colored"] } +futures = { version = "0.3", features = ["async-await"], default-features = false } +genawaiter = "0.99" hex = "0.4" http-api-problem = { version = "0.15", features = ["with_warp"] } +impl-template = "1.0.0-alpha" lazy_static = "1" -libp2p = { version = "0.13" } +libp2p = { version = "0.16", default-features = false } libp2p-comit = { path = "../libp2p-comit" } libsqlite3-sys = { version = ">=0.8.0, <0.13.0", features = ["bundled"] } log = { version = "0.4", features = ["serde"] } @@ -34,23 +34,25 @@ lru = "0.4.3" num = "0.2" paste = "0.1" pem = "0.7" +primitive-types = { version = "0.6.2", features = ["serde"] } rand = "0.7" reqwest = { version = "0.10", default-features = false, features = ["json", "native-tls"] } serde = { version = "1", features = ["derive"] } +serde-hex = "0.1.0" serde_json = "1" serdebug = "1" sha2 = "0.8" siren = { version = "0.2", package = "siren-types" } structopt = "0.3" -strum = "0.17" -strum_macros = "0.17" +strum = "0.18" +strum_macros = "0.18" thiserror = "1" tiny-keccak = { version = "2.0", features = ["keccak"] } -tokio = { version = "0.2", features = ["rt-core", "time", "macros", "sync"] } -tokio-compat = "0.1" +tokio = { version = "0.2", features = ["rt-threaded", "time", "macros", "sync"] } toml = "0.5" tracing = "0.1" tracing-core = "0.1" +tracing-futures = { version = "0.2", features = ["std-future"] } tracing-log = "0.1" tracing-subscriber = "0.2" url = { version = "2", features = ["serde"] } @@ -58,24 +60,9 @@ uuid = { version = "0.8", features = ["serde", "v4"] } void = "1.0.2" warp = { version = "0.2", default-features = false } -# These versions need to be "in sync". -# web3 0.8 gives us primitive-types 0.3.0 -# primitive-types 0.3.0 with the "rlp" feature gives us "rlp" version 0.4.2 -[dependencies.web3] -default-features = false -features = ["http"] -version = "0.8" - -[dependencies.primitive-types] -features = ["rlp"] -version = "0.3.0" - -[dependencies.rlp] -version = "0.4.2" - [dev-dependencies] -base64 = "0.11" -bitcoincore-rpc = "0.9.0" +base64 = "0.12" +bitcoincore-rpc = "0.9.1" matches = "0.1.8" quickcheck = "0.9.2" regex = "1.3" diff --git a/cnd/src/asset/mod.rs b/cnd/src/asset/mod.rs index f2d2673e5f..aa9f323fb7 100644 --- a/cnd/src/asset/mod.rs +++ b/cnd/src/asset/mod.rs @@ -6,21 +6,6 @@ pub use self::{ }; use crate::asset; use derivative::Derivative; -use std::{ - fmt::{Debug, Display}, - hash::Hash, -}; - -pub trait Asset: - Clone + Debug + Display + Send + Sync + 'static + PartialEq + Eq + Hash + Into + Ord -{ -} - -impl Asset for Bitcoin {} - -impl Asset for Ether {} - -impl Asset for Erc20 {} #[derive(Clone, Derivative, PartialEq)] #[derivative(Debug = "transparent")] diff --git a/cnd/src/bitcoin.rs b/cnd/src/bitcoin.rs index bcd5fd5ca5..3381370512 100644 --- a/cnd/src/bitcoin.rs +++ b/cnd/src/bitcoin.rs @@ -17,10 +17,13 @@ use std::{fmt, str::FromStr}; pub struct PublicKey(bitcoin::PublicKey); impl PublicKey { - pub fn from_secret_key( + pub fn from_secret_key( secp: &secp256k1::Secp256k1, secret_key: &secp256k1::SecretKey, - ) -> Self { + ) -> Self + where + C: secp256k1::Signing, + { secp256k1::PublicKey::from_secret_key(secp, secret_key).into() } } diff --git a/cnd/src/btsieve/bitcoin/bitcoind_connector.rs b/cnd/src/btsieve/bitcoin/bitcoind_connector.rs index 65957679fb..853d044008 100644 --- a/cnd/src/btsieve/bitcoin/bitcoind_connector.rs +++ b/cnd/src/btsieve/bitcoin/bitcoind_connector.rs @@ -1,18 +1,19 @@ use crate::btsieve::{ bitcoin::bitcoin_http_request_for_hex_encoded_object, BlockByHash, LatestBlock, }; +use async_trait::async_trait; use bitcoin::{BlockHash, Network}; -use futures::Future; -use futures_core::{compat::Future01CompatExt, FutureExt, TryFutureExt}; use reqwest::{Client, Url}; use serde::Deserialize; +use crate::config::validation::FetchNetworkId; -#[derive(Deserialize)] -struct ChainInfo { +#[derive(Copy, Clone, Debug, Deserialize)] +pub struct ChainInfo { bestblockhash: BlockHash, + pub chain: Network, } -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct BitcoindConnector { chaininfo_url: Url, raw_block_by_hash_url: Url, @@ -35,63 +36,63 @@ impl BitcoindConnector { } } +#[async_trait] impl LatestBlock for BitcoindConnector { type Block = bitcoin::Block; - type BlockHash = bitcoin::BlockHash; - fn latest_block( - &mut self, - ) -> Box + Send + 'static> { + async fn latest_block(&self) -> anyhow::Result { let chaininfo_url = self.chaininfo_url.clone(); - let this = self.clone(); - - let latest_block = async move { - let chain_info = this - .client - .get(chaininfo_url) - .send() - .await? - .json::() - .await?; - - let block = this - .block_by_hash(chain_info.bestblockhash) - .compat() - .await?; - - Ok(block) - }; - - Box::new(latest_block.boxed().compat()) + + let chain_info = self + .client + .get(chaininfo_url) + .send() + .await? + .json::() + .await?; + + let block = self.block_by_hash(chain_info.bestblockhash).await?; + + Ok(block) } } +#[async_trait] impl BlockByHash for BitcoindConnector { type Block = bitcoin::Block; type BlockHash = bitcoin::BlockHash; - fn block_by_hash( - &self, - block_hash: Self::BlockHash, - ) -> Box + Send + 'static> { + async fn block_by_hash(&self, block_hash: Self::BlockHash) -> anyhow::Result { let url = self.raw_block_by_hash_url(&block_hash); + let block = + bitcoin_http_request_for_hex_encoded_object::(url, &self.client).await?; + + tracing::debug!( + "Fetched block {} with {} transactions from bitcoind", + block_hash, + block.txdata.len() + ); + + Ok(block) + } +} +#[async_trait] +impl FetchNetworkId for BitcoindConnector { + async fn network_id(&self) -> anyhow::Result { let client = self.client.clone(); - let block = async move { - let block = - bitcoin_http_request_for_hex_encoded_object::(url, client).await?; - tracing::debug!( - "Fetched block {} with {} transactions from bitcoind", - block_hash, - block.txdata.len() - ); + let chaininfo_url = self.chaininfo_url.clone(); - Ok(block) - } - .boxed() - .compat(); + let chain_info: ChainInfo = client + .get(chaininfo_url) + .send() + .await? + .json::() + .await?; + + tracing::debug!("Fetched chain info: {:?} from bitcoind", chain_info); - Box::new(block) + Ok(chain_info.chain) } } diff --git a/cnd/src/btsieve/bitcoin/cache.rs b/cnd/src/btsieve/bitcoin/cache.rs index 8955896fa1..0d013212f7 100644 --- a/cnd/src/btsieve/bitcoin/cache.rs +++ b/cnd/src/btsieve/bitcoin/cache.rs @@ -1,11 +1,7 @@ use crate::btsieve::{BlockByHash, LatestBlock}; +use async_trait::async_trait; use bitcoin::{util::hash::BitcoinHash, Block, BlockHash as Hash, BlockHash}; use derivative::Derivative; -use futures::Future; -use futures_core::{ - compat::Future01CompatExt, - future::{FutureExt, TryFutureExt}, -}; use lru::LruCache; use std::sync::Arc; use tokio::sync::Mutex; @@ -28,35 +24,48 @@ impl Cache { } } +#[async_trait] impl LatestBlock for Cache where - C: LatestBlock + Clone, + C: LatestBlock, { type Block = Block; - type BlockHash = BlockHash; - fn latest_block( - &mut self, - ) -> Box + Send + 'static> { - let cache = Arc::clone(&self.block_cache); - let mut connector = self.connector.clone(); + async fn latest_block(&self) -> anyhow::Result { + let block = self.connector.latest_block().await?; + + let block_hash = block.bitcoin_hash(); + let mut guard = self.block_cache.lock().await; + if !guard.contains(&block_hash) { + guard.put(block_hash, block.clone()); + } - let future = async move { - let block = connector.latest_block().compat().await?; + Ok(block) + } +} - let block_hash = block.bitcoin_hash(); - let mut guard = cache.lock().await; - if !guard.contains(&block_hash) { - guard.put(block_hash, block.clone()); - } +#[async_trait] +impl BlockByHash for Cache +where + C: BlockByHash, +{ + type Block = Block; + type BlockHash = BlockHash; - Ok(block) + async fn block_by_hash(&self, block_hash: Self::BlockHash) -> anyhow::Result { + if let Some(block) = self.block_cache.lock().await.get(&block_hash) { + tracing::trace!("Found block in cache: {:x}", block_hash); + return Ok(block.clone()); } - .boxed() - .compat(); - Box::new(future) + let block = self.connector.block_by_hash(block_hash.clone()).await?; + tracing::trace!("Fetched block from connector: {:x}", block_hash); + + // We dropped the lock so at this stage the block may have been inserted by + // another thread, no worries, inserting the same block twice does not hurt. + let mut guard = self.block_cache.lock().await; + guard.put(block_hash, block.clone()); + + Ok(block) } } - -impl_block_by_hash!(); diff --git a/cnd/src/btsieve/bitcoin/mod.rs b/cnd/src/btsieve/bitcoin/mod.rs index 310a8f0b1f..4726258670 100644 --- a/cnd/src/btsieve/bitcoin/mod.rs +++ b/cnd/src/btsieve/bitcoin/mod.rs @@ -1,155 +1,152 @@ mod bitcoind_connector; mod cache; -mod transaction_ext; -mod transaction_pattern; pub use self::{ - bitcoind_connector::BitcoindConnector, cache::Cache, transaction_ext::TransactionExt, - transaction_pattern::TransactionPattern, + bitcoind_connector::{BitcoindConnector, ChainInfo}, + cache::Cache, +}; +use crate::btsieve::{ + find_relevant_blocks, BlockByHash, BlockHash, LatestBlock, Predates, PreviousBlockHash, }; -use crate::btsieve::{BlockByHash, LatestBlock, Predates}; use bitcoin::{ + blockdata::script::Instruction, consensus::{encode::deserialize, Decodable}, - BitcoinHash, + BitcoinHash, OutPoint, }; use chrono::NaiveDateTime; -use futures_core::compat::Future01CompatExt; +use genawaiter::{sync::Gen, GeneratorState}; use reqwest::{Client, Url}; -use std::collections::HashSet; -pub async fn matching_transaction( - mut blockchain_connector: C, - pattern: TransactionPattern, - start_of_swap: NaiveDateTime, -) -> anyhow::Result -where - C: LatestBlock - + BlockByHash - + Clone, -{ - // Verify that we can successfully connect to the blockchain connector and check - // if the transaction is in the latest block. - let latest_block = blockchain_connector.latest_block().compat().await?; - if let Some(transaction) = check_block_against_pattern(&latest_block.clone(), &pattern) { - return Ok(transaction.clone()); - }; +type Hash = bitcoin::BlockHash; +type Block = bitcoin::Block; - // We didn't find the transaction, now we need to do two things; keep polling - // for latest block so that we see transactions in new blocks and also go - // back up the blockchain until 'start_of_swap' i.e., look back in the - // past. +impl BlockHash for Block { + type BlockHash = Hash; - let mut prev_blockhashes: HashSet = HashSet::new(); - let mut missing_block_futures: Vec<_> = Vec::new(); + fn block_hash(&self) -> Hash { + self.bitcoin_hash() + } +} - let mut oldest_block: Option = Some(latest_block.clone()); - prev_blockhashes.insert(latest_block.bitcoin_hash()); +impl PreviousBlockHash for Block { + type BlockHash = Hash; - let prev_blockhash = latest_block.header.prev_blockhash; - let future = blockchain_connector.block_by_hash(prev_blockhash).compat(); - missing_block_futures.push((future, prev_blockhash)); + fn previous_block_hash(&self) -> Hash { + self.header.prev_blockhash + } +} - loop { - // Delay so that we don't overload the CPU in the event that - // latest_block() and block_by_hash() resolve quickly. - - tokio::time::delay_for(std::time::Duration::from_secs(1)).await; - - let mut new_missing_block_futures = Vec::new(); - for (block_future, blockhash) in missing_block_futures.into_iter() { - match block_future.await { - Ok(block) => { - match check_block_against_pattern(&block, &pattern) { - Some(transaction) => return Ok(transaction.clone()), - None => { - let prev_blockhash = block.header.prev_blockhash; - let unknown_parent = prev_blockhashes.insert(prev_blockhash); - - if unknown_parent { - let future = - blockchain_connector.block_by_hash(prev_blockhash).compat(); - new_missing_block_futures.push((future, prev_blockhash)); - } - } - }; - } - Err(e) => { - tracing::warn!("Could not get block with hash {}: {}", blockhash, e); +pub async fn watch_for_spent_outpoint( + blockchain_connector: &C, + start_of_swap: NaiveDateTime, + from_outpoint: OutPoint, + unlock_script: Vec>, +) -> anyhow::Result<(bitcoin::Transaction, bitcoin::TxIn)> +where + C: LatestBlock + BlockByHash, +{ + let (transaction, txin) = watch(blockchain_connector, start_of_swap, |transaction| { + transaction + .input + .iter() + .filter(|txin| txin.previous_output == from_outpoint) + .find(|txin| { + unlock_script.iter().all(|item| { + txin.witness.contains(item) + || unlock_script.iter().all(|item| { + txin.script_sig + .iter(true) + .any(|instruction| match instruction { + Instruction::PushBytes(data) => (item as &[u8]) == data, + Instruction::Op(_) => false, + Instruction::Error(_) => false, + }) + }) + }) + }) + .cloned() + }) + .await?; - let future = blockchain_connector.block_by_hash(blockhash).compat(); - new_missing_block_futures.push((future, blockhash)); - } - }; - } - missing_block_futures = new_missing_block_futures; - - // Look back into the past (upto timestamp) for one block. - - if let Some(block) = oldest_block.as_ref() { - if !block.predates(start_of_swap) { - match blockchain_connector - .block_by_hash(block.header.prev_blockhash) - .compat() - .await - { - Ok(block) => match check_block_against_pattern(&block, &pattern) { - Some(transaction) => return Ok(transaction.clone()), - None => { - oldest_block.replace(block); - } - }, - Err(e) => tracing::warn!( - "Could not get block with hash {}: {}", - block.bitcoin_hash(), - e - ), - }; - } - } + Ok((transaction, txin)) +} - // Check if a new block has been mined. +pub async fn watch_for_created_outpoint( + blockchain_connector: &C, + start_of_swap: NaiveDateTime, + compute_address: bitcoin::Address, +) -> anyhow::Result<(bitcoin::Transaction, bitcoin::OutPoint)> +where + C: LatestBlock + BlockByHash, +{ + let (transaction, out_point) = watch(blockchain_connector, start_of_swap, |transaction| { + let txid = transaction.txid(); + transaction + .output + .iter() + .enumerate() + .map(|(index, txout)| { + // Casting a usize to u32 can lead to truncation on 64bit platforms + // However, bitcoin limits the number of inputs to u32 anyway, so this + // is not a problem for us. + #[allow(clippy::cast_possible_truncation)] + (index as u32, txout) + }) + .find(|(_, txout)| txout.script_pubkey == compute_address.script_pubkey()) + .map(|(vout, _txout)| OutPoint { txid, vout }) + }) + .await?; - if let Ok(latest_block) = blockchain_connector.latest_block().compat().await { - // If we can insert then we have not seen this block. - if prev_blockhashes.insert(latest_block.bitcoin_hash()) { - if let Some(transaction) = check_block_against_pattern(&latest_block, &pattern) { - return Ok(transaction.clone()); - }; + Ok((transaction, out_point)) +} - // In case we missed a block somehow, check this blocks parent. - if !prev_blockhashes.contains(&latest_block.header.prev_blockhash) { - let prev_blockhash = latest_block.header.prev_blockhash; - let future = blockchain_connector.block_by_hash(prev_blockhash).compat(); +async fn watch( + connector: &C, + start_of_swap: NaiveDateTime, + sieve: S, +) -> anyhow::Result<(bitcoin::Transaction, M)> +where + C: LatestBlock + BlockByHash, + S: Fn(&bitcoin::Transaction) -> Option, +{ + let mut block_generator = + Gen::new({ |co| async { find_relevant_blocks(connector, co, start_of_swap).await } }); - missing_block_futures.push((future, prev_blockhash)); + loop { + match block_generator.async_resume().await { + GeneratorState::Yielded(block) => { + for transaction in block.txdata.into_iter() { + if let Some(result) = sieve(&transaction) { + tracing::trace!("transaction matched {:x}", transaction.txid()); + return Ok((transaction, result)); + } } } + GeneratorState::Complete(Err(e)) => return Err(e), + // By matching against the never type explicitly, we assert that the `Ok` value of the + // result is actually the never type and has not been changed since this line was + // written. The never type can never be constructed, so we can never reach this line. + GeneratorState::Complete(Ok(never)) => match never {}, } } } -fn check_block_against_pattern<'b>( - block: &'b bitcoin::Block, - pattern: &TransactionPattern, -) -> Option<&'b bitcoin::Transaction> { - block.txdata.iter().find(|transaction| { - let result = pattern.matches(transaction); - - tracing::debug!( - "matching {:?} against transaction {} yielded {}", - pattern, - transaction.txid(), - result - ); - - result - }) +impl Predates for Block { + fn predates(&self, timestamp: NaiveDateTime) -> bool { + let unix_timestamp = timestamp.timestamp(); + let block_time = self.header.time as i64; + + block_time < unix_timestamp + } } -pub async fn bitcoin_http_request_for_hex_encoded_object( +pub async fn bitcoin_http_request_for_hex_encoded_object( request_url: Url, - client: Client, -) -> anyhow::Result { + client: &Client, +) -> anyhow::Result +where + T: Decodable, +{ let response_text = client.get(request_url).send().await?.text().await?; let decoded_response = decode_response(response_text)?; @@ -168,24 +165,18 @@ pub enum Error { Deserialization(#[from] bitcoin::consensus::encode::Error), } -pub fn decode_response(response_text: String) -> Result { +pub fn decode_response(response_text: String) -> Result +where + T: Decodable, +{ let bytes = hex::decode(response_text.trim()).map_err(Error::Hex)?; deserialize(bytes.as_slice()).map_err(Error::Deserialization) } -impl Predates for bitcoin::Block { - fn predates(&self, timestamp: NaiveDateTime) -> bool { - let unix_timestamp = timestamp.timestamp(); - let block_time = self.header.time as i64; - - block_time < unix_timestamp - } -} - #[cfg(test)] mod tests { - use super::*; + use crate::transaction; use spectral::prelude::*; #[test] @@ -194,7 +185,7 @@ mod tests { let transaction = r#"02000000014135047eff77c95bce4955f630bc3e334690d31517176dbc23e9345493c48ecf000000004847304402200da78118d6970bca6f152a6ca81fa8c4dde856680eb6564edb329ce1808207c402203b3b4890dd203cc4c9361bbbeb7ebce70110d4b07f411208b2540b10373755ba01feffffff02644024180100000017a9142464790f3a3fddb132691fac9fd02549cdc09ff48700a3e1110000000017a914c40a2c4fd9dcad5e1694a41ca46d337eb59369d78765000000 "#.to_owned(); - let bytes = decode_response::(transaction); + let bytes = decode_response::(transaction); assert_that(&bytes).is_ok(); } @@ -205,7 +196,7 @@ mod tests { let block = r#"00000020837603de6069115e22e7fbf063c2a6e3bc3b3206f0b7e08d6ab6c168c2e50d4a9b48676dedc93d05f677778c1d83df28fd38d377548340052823616837666fb8be1b795dffff7f200000000001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401650101ffffffff0200f2052a0100000023210205980e76eee77386241a3a7a5af65e910fb7be411b98e609f7c0d97c50ab8ebeac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000 "#.to_owned(); - let bytes = decode_response::(block); + let bytes = decode_response::(block); assert_that(&bytes).is_ok(); } diff --git a/cnd/src/btsieve/bitcoin/transaction_ext.rs b/cnd/src/btsieve/bitcoin/transaction_ext.rs deleted file mode 100644 index dfe9cd326a..0000000000 --- a/cnd/src/btsieve/bitcoin/transaction_ext.rs +++ /dev/null @@ -1,255 +0,0 @@ -use bitcoin::{ - blockdata::{script::Instruction, transaction::Transaction}, - util::address::Address as BitcoinAddress, - OutPoint, TxIn, TxOut, -}; - -pub trait TransactionExt { - fn spends_to(&self, address: &BitcoinAddress) -> bool; - fn spends_from(&self, outpoint: &OutPoint) -> bool; - fn spends_from_with(&self, outpoint: &OutPoint, script: &[Vec]) -> bool; - fn spends_with(&self, script: &[Vec]) -> bool; - fn find_output(&self, to_address: &BitcoinAddress) -> Option<(u32, &TxOut)>; -} - -impl TransactionExt for Transaction { - fn spends_to(&self, address: &BitcoinAddress) -> bool { - let address_script_pubkey = address.script_pubkey(); - - self.output - .iter() - .map(|out| &out.script_pubkey) - .any(|script_pub_key| script_pub_key == &address_script_pubkey) - } - - fn spends_from(&self, outpoint: &OutPoint) -> bool { - self.input - .iter() - .map(|input| &input.previous_output) - .any(|previous_outpoint| previous_outpoint == outpoint) - } - - fn spends_from_with(&self, outpoint: &OutPoint, unlock_script: &[Vec]) -> bool { - self.input - .iter() - .filter(|previous_outpoint| &previous_outpoint.previous_output == outpoint) - .any(|txin| any_unlock_script_matches(txin, unlock_script)) - } - - fn spends_with(&self, unlock_script: &[Vec]) -> bool { - self.input - .iter() - .any(|txin| any_unlock_script_matches(txin, unlock_script)) - } - - fn find_output(&self, to_address: &BitcoinAddress) -> Option<(u32, &TxOut)> { - let to_address_script_pubkey = to_address.script_pubkey(); - - self.output - .iter() - .enumerate() - .map(|(index, txout)| { - // Casting a usize to u32 can lead to truncation on 64bit platforms - // However, bitcoin limits the number of inputs to u32 anyway, so this - // is not a problem for us. - #[allow(clippy::cast_possible_truncation)] - (index as u32, txout) - }) - .find(|(_, txout)| txout.script_pubkey == to_address_script_pubkey) - } -} - -fn any_unlock_script_matches(txin: &TxIn, unlock_script: &[Vec]) -> bool { - unlock_script.iter().all(|item| { - txin.witness.contains(item) - || unlock_script.iter().all(|item| { - txin.script_sig - .iter(true) - .any(|instruction| match instruction { - Instruction::PushBytes(data) => (item as &[u8]) == data, - Instruction::Op(_) => false, - Instruction::Error(_) => false, - }) - }) - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use bitcoin::{ - blockdata::transaction::TxOut, - consensus::deserialize, - hashes::{hex::FromHex, sha256d::Hash as Sha256dHash}, - }; - use spectral::prelude::*; - - const WITNESS_TX: & str = "0200000000010124e06fe5594b941d06c7385dc7307ec694a41f7d307423121855ee17e47e06ad0100000000ffffffff0137aa0b000000000017a914050377baa6e8c5a07aed125d0ef262c6d5b67a038705483045022100d780139514f39ed943179e4638a519101bae875ec1220b226002bcbcb147830b0220273d1efb1514a77ee3dd4adee0e896b7e76be56c6d8e73470ae9bd91c91d700c01210344f8f459494f74ebb87464de9b74cdba3709692df4661159857988966f94262f20ec9e9fb3c669b2354ea026ab3da82968a2e7ab9398d5cbed4e78e47246f2423e01015b63a82091d6a24697ed31932537ae598d3de3131e1fcd0641b9ac4be7afcb376386d71e8876a9149f4a0cf348b478336cb1d87ea4c8313a7ca3de1967029000b27576a91465252e57f727a27f32c77098e14d88d8dbec01816888ac00000000"; - const STANDRD_TX: & str = "01000000013dcd7d87904c9cb7f4b79f36b5a03f96e2e729284c09856238d5353e1182b00200000000fd5e0100483045022100deeb1f13b5927b5e32d877f3c42a4b028e2e0ce5010fdb4e7f7b5e2921c1dcd2022068631cb285e8c1be9f061d2968a18c3163b780656f30a049effee640e80d9bff01483045022100ee80e164622c64507d243bd949217d666d8b16486e153ac6a1f8e04c351b71a502203691bef46236ca2b4f5e60a82a853a33d6712d6a1e7bf9a65e575aeb7328db8c014cc9524104a882d414e478039cd5b52a92ffb13dd5e6bd4515497439dffd691a0f12af9575fa349b5694ed3155b136f09e63975a1700c9f4d4df849323dac06cf3bd6458cd41046ce31db9bdd543e72fe3039a1f1c047dab87037c36a669ff90e28da1848f640de68c2fe913d363a51154a0c62d7adea1b822d05035077418267b1a1379790187410411ffd36c70776538d079fbae117dc38effafb33304af83ce4894589747aee1ef992f63280567f52f5ba870678b4ab4ff6c8ea600bd217870a8b4f1f09f3a8e8353aeffffffff0130d90000000000001976a914569076ba39fc4ff6a2291d9ea9196d8c08f9c7ab88ac00000000"; - - fn parse_raw_tx(raw_tx: &str) -> Transaction { - let hex_tx = hex::decode(raw_tx).unwrap(); - let tx: Result = deserialize(&hex_tx); - tx.unwrap() - } - - fn create_unlock_script_stack(data: Vec<&str>) -> Vec> { - data.iter().map(|data| hex::decode(data).unwrap()).collect() - } - - fn create_outpoint(tx: &str, vout: u32) -> OutPoint { - OutPoint { - txid: Sha256dHash::from_hex(tx).unwrap().into(), - vout, - } - } - - fn create_valid_p2wsh_unlock_script() -> Vec> { - create_unlock_script_stack(vec![ - "0344f8f459494f74ebb87464de9b74cdba3709692df4661159857988966f94262f", - "01", - ]) - } - - fn create_invalid_p2wsh_unlock_script() -> Vec> { - create_unlock_script_stack(vec![ - "0344f8f459494f74ebb87464de9b74cdba3709692df4661159857988966f94262f", - "00", - ]) - } - - fn create_valid_p2sh_unlock_script() -> Vec> { - create_unlock_script_stack(vec!["3045022100deeb1f13b5927b5e32d877f3c42a4b028e2e0ce5010fdb4e7f7b5e2921c1dcd2022068631cb285e8c1be9f061d2968a18c3163b780656f30a049effee640e80d9bff01", - "3045022100ee80e164622c64507d243bd949217d666d8b16486e153ac6a1f8e04c351b71a502203691bef46236ca2b4f5e60a82a853a33d6712d6a1e7bf9a65e575aeb7328db8c01"]) - } - - fn create_invalid_p2sh_unlock_script() -> Vec> { - create_unlock_script_stack(vec!["3045022100deeb1f13b5927b5e32d877f3c42a4b028e2e0ce5010fdb4e7f7b5e2921c1dcd2022068631cb285e8c1be9f061d2968a18c3163b780656f30a049effee640e80d9bff01", - "3045022100deeb1f13b5927b5e32d877f3c42a4b028e2e0ce5010fdb4e7f7b5e2921c1dcd2022068631cb285e8c1be9f061d2968a18c3163b780656f30a049effee640e80d9bff01", - "0101"]) - } - - fn create_valid_p2sh_outpoint() -> OutPoint { - create_outpoint( - "02b082113e35d5386285094c2829e7e2963fa0b5369fb7f4b79c4c90877dcd3d", - 0u32, - ) - } - - fn create_invalid_p2sh_outpoint() -> OutPoint { - create_outpoint( - "ad067ee417ee5518122374307d1fa494c67e30c75d38c7061d944b59e56fe024", - 1u32, - ) - } - - #[test] - fn tx_with_txout_should_return_true() { - let address: BitcoinAddress = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa".parse().unwrap(); - let tx = Transaction { - version: 1, - lock_time: 0, - input: Vec::new(), - output: vec![TxOut { - value: 0, - script_pubkey: address.script_pubkey(), - }], - }; - - assert_that(&tx.spends_to(&address)).is_true(); - } - - #[test] - fn tx_spending_to_other_address_returns_false() { - let address1: BitcoinAddress = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa".parse().unwrap(); - let address2: BitcoinAddress = "bc1qu5t5yrh75zca6msxzszx5mm0egu2vepu09lwqh" - .parse() - .unwrap(); - - let tx = Transaction { - version: 1, - lock_time: 0, - input: Vec::new(), - output: vec![TxOut { - value: 0, - script_pubkey: address1.script_pubkey(), - }], - }; - - assert_that(&tx.spends_to(&address2)).is_false(); - } - - #[test] - fn a_witness_tx_with_unlock_script_then_unlock_script_contains_matches() { - let tx = parse_raw_tx(WITNESS_TX); - - let unlock_script = create_valid_p2wsh_unlock_script(); - - assert_that(&tx.spends_with(&unlock_script)).is_true(); - } - - #[test] - fn a_witness_tx_with_different_unlock_script_then_unlock_script_contains_wont_match() { - let tx = parse_raw_tx(WITNESS_TX); - - let unlock_script = create_invalid_p2wsh_unlock_script(); - - assert_that(&tx.spends_with(&unlock_script)).is_false(); - } - - #[test] - fn a_p2sh_tx_with_unlock_script_then_unlock_script_matches() { - let tx = parse_raw_tx(STANDRD_TX); - - let unlock_script = create_valid_p2sh_unlock_script(); - - assert_that(&tx.spends_with(&unlock_script)).is_true(); - } - - #[test] - fn a_p2sh_tx_with_additional_unlock_script_then_unlock_script_wont_match() { - let tx = parse_raw_tx(STANDRD_TX); - - let unlock_script = create_invalid_p2sh_unlock_script(); - - assert_that(&tx.spends_with(&unlock_script)).is_false(); - } - - #[test] - fn a_tx_with_spends_from_outpoint_matches() { - let tx = parse_raw_tx(STANDRD_TX); - - let outpoint = create_valid_p2sh_outpoint(); - - assert_that(&tx.spends_from(&outpoint)).is_true(); - } - - #[test] - fn a_tx_which_spends_from_different_outpoint_does_not_match() { - let tx = parse_raw_tx(STANDRD_TX); - - let outpoint = create_invalid_p2sh_outpoint(); - - assert_that(&tx.spends_from(&outpoint)).is_false(); - } - - #[test] - fn a_tx_spending_from_tx_with_specific_script_then_spend_from_with_matches() { - let tx = parse_raw_tx(STANDRD_TX); - - let unlock_script = create_valid_p2sh_unlock_script(); - let outpoint = create_valid_p2sh_outpoint(); - - assert_that(&tx.spends_from_with(&outpoint, &unlock_script)).is_true(); - } - - #[test] - fn a_tx_spending_from_tx_with_different_script_then_spends_from_with_does_not_match() { - let tx = parse_raw_tx(STANDRD_TX); - - let unlock_script = create_invalid_p2sh_unlock_script(); - let outpoint = create_valid_p2sh_outpoint(); - - assert_that(&tx.spends_from_with(&outpoint, &unlock_script)).is_false(); - } -} diff --git a/cnd/src/btsieve/bitcoin/transaction_pattern.rs b/cnd/src/btsieve/bitcoin/transaction_pattern.rs deleted file mode 100644 index 46d1b57510..0000000000 --- a/cnd/src/btsieve/bitcoin/transaction_pattern.rs +++ /dev/null @@ -1,155 +0,0 @@ -use crate::btsieve::bitcoin::transaction_ext::TransactionExt; -use ::bitcoin::{Address, OutPoint, Transaction}; - -#[derive(Clone, Default, Eq, PartialEq, serde::Serialize, serdebug::SerDebug)] -/// If the field is set to Some(foo) then only transactions matching foo are -/// returned. Otherwise, when the field is set to None, no pattern matching is -/// done for this field. -pub struct TransactionPattern { - #[serde(skip_serializing_if = "Option::is_none")] - pub to_address: Option
, - #[serde(skip_serializing_if = "Option::is_none")] - pub from_outpoint: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub unlock_script: Option>>, -} - -impl TransactionPattern { - /// Does matching based on patterns in self. If all fields are None any - /// transaction matches i.e., returns true. - pub fn matches(&self, transaction: &Transaction) -> bool { - match self { - Self { - to_address, - from_outpoint, - unlock_script, - } => { - if let Some(to_address) = to_address { - if !transaction.spends_to(to_address) { - return false; - } - } - - match (from_outpoint, unlock_script) { - (Some(from_outpoint), Some(unlock_script)) => { - if !transaction.spends_from_with(from_outpoint, unlock_script) { - return false; - } - } - (Some(from_outpoint), None) => { - if !transaction.spends_from(from_outpoint) { - return false; - } - } - (None, Some(unlock_script)) => { - if !transaction.spends_with(unlock_script) { - return false; - } - } - (None, None) => return true, - }; - - true - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use bitcoin::{ - consensus::deserialize, - hashes::{hex::FromHex, sha256d}, - }; - use spectral::prelude::*; - - const WITNESS_TX: & str = "0200000000010124e06fe5594b941d06c7385dc7307ec694a41f7d307423121855ee17e47e06ad0100000000ffffffff0137aa0b000000000017a914050377baa6e8c5a07aed125d0ef262c6d5b67a038705483045022100d780139514f39ed943179e4638a519101bae875ec1220b226002bcbcb147830b0220273d1efb1514a77ee3dd4adee0e896b7e76be56c6d8e73470ae9bd91c91d700c01210344f8f459494f74ebb87464de9b74cdba3709692df4661159857988966f94262f20ec9e9fb3c669b2354ea026ab3da82968a2e7ab9398d5cbed4e78e47246f2423e01015b63a82091d6a24697ed31932537ae598d3de3131e1fcd0641b9ac4be7afcb376386d71e8876a9149f4a0cf348b478336cb1d87ea4c8313a7ca3de1967029000b27576a91465252e57f727a27f32c77098e14d88d8dbec01816888ac00000000"; - - fn parse_raw_tx(raw_tx: &str) -> Transaction { - let hex_tx = hex::decode(raw_tx).unwrap(); - let tx: Result = deserialize(&hex_tx); - tx.unwrap() - } - - fn create_unlock_script_stack(data: Vec<&str>) -> Vec> { - data.iter().map(|data| hex::decode(data).unwrap()).collect() - } - - fn create_outpoint(tx: &str, vout: u32) -> OutPoint { - OutPoint { - txid: sha256d::Hash::from_hex(tx).unwrap().into(), - vout, - } - } - - #[test] - fn given_transaction_with_to_then_to_address_pattern_matches() { - let tx = parse_raw_tx(WITNESS_TX); - - let pattern = TransactionPattern { - to_address: Some("329XTScM6cJgu8VZvaqYWpfuxT1eQDSJkP".parse().unwrap()), - from_outpoint: None, - unlock_script: None, - }; - - let result = pattern.matches(&tx); - assert_that(&result).is_true(); - } - - #[test] - fn given_a_witness_transaction_with_unlock_script_then_unlock_script_pattern_matches() { - let tx = parse_raw_tx(WITNESS_TX); - let unlock_script = create_unlock_script_stack(vec![ - "0344f8f459494f74ebb87464de9b74cdba3709692df4661159857988966f94262f", - "01", - ]); - - let pattern = TransactionPattern { - to_address: None, - from_outpoint: None, - unlock_script: Some(unlock_script), - }; - - let result = pattern.matches(&tx); - assert_that(&result).is_true(); - } - - #[test] - fn given_a_witness_transaction_with_different_unlock_script_then_unlock_script_pattern_wont_match( - ) { - let tx = parse_raw_tx(WITNESS_TX); - let unlock_script = create_unlock_script_stack(vec!["102030405060708090", "00"]); - - let pattern = TransactionPattern { - to_address: None, - from_outpoint: None, - unlock_script: Some(unlock_script), - }; - - let result = pattern.matches(&tx); - assert_that(&result).is_false(); - } - - #[test] - fn given_a_witness_transaction_with_unlock_script_then_spends_from_with_pattern_match() { - let tx = parse_raw_tx(WITNESS_TX); - let unlock_script = create_unlock_script_stack(vec![ - "0344f8f459494f74ebb87464de9b74cdba3709692df4661159857988966f94262f", - "01", - ]); - let outpoint = create_outpoint( - "ad067ee417ee5518122374307d1fa494c67e30c75d38c7061d944b59e56fe024", - 1u32, - ); - - let pattern = TransactionPattern { - to_address: None, - from_outpoint: Some(outpoint), - unlock_script: Some(unlock_script), - }; - - let result = pattern.matches(&tx); - assert_that(&result).is_true(); - } -} diff --git a/cnd/src/btsieve/block_by_hash.rs b/cnd/src/btsieve/block_by_hash.rs deleted file mode 100644 index 7ad50a6ff9..0000000000 --- a/cnd/src/btsieve/block_by_hash.rs +++ /dev/null @@ -1,44 +0,0 @@ -macro_rules! impl_block_by_hash { - () => { - impl BlockByHash for Cache - where - C: BlockByHash + Clone, - { - type Block = Block; - type BlockHash = Hash; - - fn block_by_hash( - &self, - block_hash: Self::BlockHash, - ) -> Box + Send + 'static> { - let connector = self.connector.clone(); - let cache = Arc::clone(&self.block_cache); - Box::new(Box::pin(block_by_hash(connector, cache, block_hash)).compat()) - } - } - - async fn block_by_hash( - connector: C, - cache: Arc>>, - block_hash: Hash, - ) -> anyhow::Result - where - C: BlockByHash + Clone, - { - if let Some(block) = cache.lock().await.get(&block_hash) { - tracing::trace!("Found block in cache: {:x}", block_hash); - return Ok(block.clone()); - } - - let block = connector.block_by_hash(block_hash.clone()).compat().await?; - tracing::trace!("Fetched block from connector: {:x}", block_hash); - - // We dropped the lock so at this stage the block may have been inserted by - // another thread, no worries, inserting the same block twice does not hurt. - let mut guard = cache.lock().await; - guard.put(block_hash, block.clone()); - - Ok(block) - } - }; -} diff --git a/cnd/src/btsieve/ethereum/cache.rs b/cnd/src/btsieve/ethereum/cache.rs index 2710df95af..92f8f0a84d 100644 --- a/cnd/src/btsieve/ethereum/cache.rs +++ b/cnd/src/btsieve/ethereum/cache.rs @@ -1,23 +1,19 @@ use crate::{ btsieve::{ - ethereum::{self, Hash}, - BlockByHash, LatestBlock, ReceiptByHash, + ethereum::{self, Hash, ReceiptByHash}, + BlockByHash, LatestBlock, }, ethereum::TransactionReceipt, }; +use async_trait::async_trait; use derivative::Derivative; -use futures::Future; -use futures_core::{ - compat::Future01CompatExt, - future::{FutureExt, TryFutureExt}, -}; use lru::LruCache; use std::sync::Arc; use tokio::sync::Mutex; // This makes it a bit obscure that we have an option, the compile will point it // out though; this alias allows us to use the macros :) -type Block = Option; +type Block = ethereum::Block; #[derive(Derivative, Clone)] #[derivative(Debug)] @@ -26,7 +22,7 @@ pub struct Cache { #[derivative(Debug = "ignore")] pub block_cache: Arc>>, #[derivative(Debug = "ignore")] - pub receipt_cache: Arc>>>, + pub receipt_cache: Arc>>, } impl Cache { @@ -45,82 +41,75 @@ impl Cache { } } +#[async_trait] impl LatestBlock for Cache where - C: LatestBlock + Clone, + C: LatestBlock, { type Block = Block; - type BlockHash = Hash; - - fn latest_block( - &mut self, - ) -> Box + Send + 'static> { - let cache = self.block_cache.clone(); - let mut connector = self.connector.clone(); - let future = async move { - let block = connector.latest_block().compat().await?; + async fn latest_block(&self) -> anyhow::Result { + let block = self.connector.latest_block().await?; - if let Some(block) = block.clone() { - let block_hash = block.hash.expect("no blocks without hash"); - let mut guard = cache.lock().await; - if !guard.contains(&block_hash) { - guard.put(block_hash, Some(block.clone())); - } - }; - - Ok(block) + let block_hash = block.hash.expect("no blocks without hash"); + let mut guard = self.block_cache.lock().await; + if !guard.contains(&block_hash) { + guard.put(block_hash, block.clone()); } - .boxed() - .compat(); - Box::new(future) + Ok(block) } } -impl_block_by_hash!(); - -impl ReceiptByHash for Cache +#[async_trait] +impl BlockByHash for Cache where - C: ReceiptByHash, TransactionHash = Hash> + Clone, + C: BlockByHash, { - type Receipt = Option; - type TransactionHash = Hash; - - fn receipt_by_hash( - &self, - transaction_hash: Self::TransactionHash, - ) -> Box + Send + 'static> { - let connector = self.connector.clone(); - let cache = Arc::clone(&self.receipt_cache); - Box::new(Box::pin(receipt_by_hash(connector, cache, transaction_hash)).compat()) + type Block = Block; + type BlockHash = Hash; + + async fn block_by_hash(&self, block_hash: Self::BlockHash) -> anyhow::Result { + if let Some(block) = self.block_cache.lock().await.get(&block_hash) { + tracing::trace!("Found block in cache: {:x}", block_hash); + return Ok(block.clone()); + } + + let block = self.connector.block_by_hash(block_hash.clone()).await?; + tracing::trace!("Fetched block from connector: {:x}", block_hash); + + // We dropped the lock so at this stage the block may have been inserted by + // another thread, no worries, inserting the same block twice does not hurt. + let mut guard = self.block_cache.lock().await; + guard.put(block_hash, block.clone()); + + Ok(block) } } -async fn receipt_by_hash( - connector: C, - cache: Arc>>>, - transaction_hash: Hash, -) -> anyhow::Result> +#[async_trait] +impl ReceiptByHash for Cache where - C: ReceiptByHash, TransactionHash = Hash> + Clone, + C: ReceiptByHash, { - if let Some(receipt) = cache.lock().await.get(&transaction_hash) { - tracing::trace!("Found receipt in cache: {:x}", transaction_hash); - return Ok(receipt.clone()); - } + async fn receipt_by_hash(&self, transaction_hash: Hash) -> anyhow::Result { + if let Some(receipt) = self.receipt_cache.lock().await.get(&transaction_hash) { + tracing::trace!("Found receipt in cache: {:x}", transaction_hash); + return Ok(receipt.clone()); + } - let receipt = connector - .receipt_by_hash(transaction_hash.clone()) - .compat() - .await?; + let receipt = self + .connector + .receipt_by_hash(transaction_hash.clone()) + .await?; - tracing::trace!("Fetched receipt from connector: {:x}", transaction_hash); + tracing::trace!("Fetched receipt from connector: {:x}", transaction_hash); - // We dropped the lock so at this stage the receipt may have been inserted by - // another thread, no worries, inserting the same receipt twice does not hurt. - let mut guard = cache.lock().await; - guard.put(transaction_hash, receipt.clone()); + // We dropped the lock so at this stage the receipt may have been inserted by + // another thread, no worries, inserting the same receipt twice does not hurt. + let mut guard = self.receipt_cache.lock().await; + guard.put(transaction_hash, receipt.clone()); - Ok(receipt) + Ok(receipt) + } } diff --git a/cnd/src/btsieve/ethereum/mod.rs b/cnd/src/btsieve/ethereum/mod.rs index ec20ab8d3a..5430aa2fd3 100644 --- a/cnd/src/btsieve/ethereum/mod.rs +++ b/cnd/src/btsieve/ethereum/mod.rs @@ -1,256 +1,223 @@ mod cache; -mod transaction_pattern; mod web3_connector; pub use self::{ cache::Cache, - transaction_pattern::{Event, Topic, TransactionPattern, TRANSACTION_STATUS_OK}, - web3_connector::Web3Connector, + web3_connector::{Web3Connector}, }; use crate::{ - btsieve::{BlockByHash, LatestBlock, Predates, ReceiptByHash}, - ethereum::{Transaction, TransactionAndReceipt, TransactionReceipt, H256, U256}, - Never, + btsieve::{ + find_relevant_blocks, BlockByHash, BlockHash, LatestBlock, Predates, PreviousBlockHash, + }, + ethereum::{Address, Bytes, Input, Log, Transaction, TransactionReceipt, H256, U256}, }; use anyhow; +use async_trait::async_trait; use chrono::NaiveDateTime; -use futures_core::compat::Future01CompatExt; -use genawaiter::{ - sync::{Co, Gen}, - GeneratorState, -}; -use std::collections::HashSet; +use genawaiter::{sync::Gen, GeneratorState}; type Hash = H256; -type Block = crate::ethereum::Block; +type Block = crate::ethereum::Block; + +#[async_trait] +pub trait ReceiptByHash: Send + Sync + 'static { + async fn receipt_by_hash(&self, transaction_hash: Hash) -> anyhow::Result; +} + +impl BlockHash for Block { + type BlockHash = Hash; + + fn block_hash(&self) -> H256 { + self.hash + .expect("Connector returned latest block with null hash") + } +} + +impl PreviousBlockHash for Block { + type BlockHash = Hash; + + fn previous_block_hash(&self) -> H256 { + self.parent_hash + } +} -pub async fn matching_transaction( - connector: C, - pattern: TransactionPattern, +pub async fn watch_for_contract_creation( + blockchain_connector: &C, start_of_swap: NaiveDateTime, -) -> anyhow::Result + bytecode: Bytes, +) -> anyhow::Result<(Transaction, Address)> where - C: LatestBlock> - + BlockByHash, BlockHash = Hash> - + ReceiptByHash, TransactionHash = Hash> - + Clone, + C: LatestBlock + BlockByHash + ReceiptByHash, { - let mut block_generator = Gen::new({ - let connector = connector.clone(); - |co| async move { find_relevant_blocks(connector, &co, start_of_swap).await } - }); + let (transaction, receipt) = + matching_transaction_and_receipt(blockchain_connector, start_of_swap, |transaction| { + // transaction.to address is None if, and only if, the transaction + // creates a contract. + transaction.to.is_none() && transaction.input == bytecode + }) + .await?; - loop { - match block_generator.async_resume().await { - GeneratorState::Yielded(block) => { - if let Some(transaction_and_receipt) = - check_block_against_pattern(connector.clone(), block, pattern.clone()).await? - { - return Ok(transaction_and_receipt); - } else { - continue; - } - } - GeneratorState::Complete(Err(e)) => return Err(e), - // By matching against the never type explicitly, we assert that the `Ok` value of the - // result is actually the never type and has not been changed since this - // line was written. The never type can never be constructed, so we cannot - // reach this line never anyway. - GeneratorState::Complete(Ok(never)) => match never {}, - } + match receipt.contract_address { + Some(location) => Ok((transaction, location)), + None => Err(anyhow::anyhow!("contract address missing from receipt")), } } -/// This function uses the `connector` to find blocks relevant to a swap. To do -/// this we must get the latest block, for each latest block we receive we must -/// ensure that we saw its parent i.e., that we did not miss any blocks between -/// this latest block and the previous latest block we received. Finally, we -/// must also get each block back until the time that the swap started i.e., -/// look into the past (in case any action occurred on chain while we were not -/// watching). -/// -/// It yields those blocks as part of the process. -async fn find_relevant_blocks( - mut connector: C, - co: &Co, +pub async fn watch_for_event( + blockchain_connector: &C, start_of_swap: NaiveDateTime, -) -> anyhow::Result + event: Event, +) -> anyhow::Result<(Transaction, Log)> where - C: LatestBlock> - + BlockByHash, BlockHash = Hash> - + ReceiptByHash, TransactionHash = Hash> - + Clone, + C: LatestBlock + BlockByHash + ReceiptByHash, { - let mut seen_blocks: HashSet = HashSet::new(); - - let block = connector - .latest_block() - .compat() - .await? - .ok_or_else(|| anyhow::anyhow!("Connector returned null latest block"))?; - co.yield_(block.clone()).await; - - let blockhash = block - .hash - .ok_or_else(|| anyhow::anyhow!("Connector returned latest block with null hash"))?; - seen_blocks.insert(blockhash); - - // Look back in time until we get a block that predates start_of_swap. - let parent_hash = block.parent_hash; - walk_back_until( - predates_start_of_swap(start_of_swap), - connector.clone(), - co, - parent_hash, + matching_transaction_and_log( + blockchain_connector, + start_of_swap, + event.topics.clone(), + |receipt| find_log_for_event_in_receipt(&event, receipt), ) - .await?; + .await +} - loop { - let block = connector - .latest_block() - .compat() - .await? - .ok_or_else(|| anyhow::anyhow!("Connector returned null latest block"))?; - co.yield_(block.clone()).await; +/// Fetch receipt from connector using transaction hash. +async fn fetch_receipt( + blockchain_connector: &C, + hash: Hash, +) -> anyhow::Result +where + C: ReceiptByHash, +{ + let receipt = blockchain_connector.receipt_by_hash(hash).await?; + Ok(receipt) +} - let blockhash = block - .hash - .ok_or_else(|| anyhow::anyhow!("Connector returned latest block with null hash"))?; - seen_blocks.insert(blockhash); +fn find_log_for_event_in_receipt(event: &Event, receipt: TransactionReceipt) -> Option { + match event { + Event { topics, .. } if topics.is_empty() => None, + Event { address, topics } => receipt.logs.into_iter().find(|log| { + if address != &log.address { + return false; + } - // Look back along the blockchain for missing blocks. - let parent_hash = block.parent_hash; - if !seen_blocks.contains(&parent_hash) { - walk_back_until( - seen_block_or_predates_start_of_swap(seen_blocks.clone(), start_of_swap), - connector.clone(), - co, - parent_hash, - ) - .await?; - } + if log.topics.len() != topics.len() { + return false; + } - // The duration of this timeout could/should depend on the network - tokio::time::delay_for(std::time::Duration::from_secs(1)).await; + log.topics.iter().enumerate().all(|(index, tx_topic)| { + let topic = &topics[index]; + topic.as_ref().map_or(true, |topic| tx_topic == &topic.0) + }) + }), } } -/// Walks the blockchain backwards from the given hash until the predicate given -/// in `stop_condition` returns `true`. -/// -/// This functions yields all blocks as part of its process. -async fn walk_back_until( - stop_condition: P, - connector: C, - co: &Co, - starting_blockhash: Hash, -) -> anyhow::Result<()> +pub async fn matching_transaction_and_receipt( + connector: &C, + start_of_swap: NaiveDateTime, + matcher: F, +) -> anyhow::Result<(Transaction, TransactionReceipt)> where - C: BlockByHash, BlockHash = Hash>, - P: Fn(&Block) -> anyhow::Result, + C: LatestBlock + BlockByHash + ReceiptByHash, + F: Fn(&Transaction) -> bool, { - let mut blockhash = starting_blockhash; + let mut block_generator = + Gen::new({ |co| async { find_relevant_blocks(connector, co, start_of_swap).await } }); loop { - let block = connector - .block_by_hash(blockhash) - .compat() - .await? - .ok_or_else(|| anyhow::anyhow!("Could not fetch block with hash {}", blockhash))?; - - co.yield_(block.clone()).await; - - if stop_condition(&block)? { - return Ok(()); - } else { - blockhash = block.parent_hash + match block_generator.async_resume().await { + GeneratorState::Yielded(block) => { + for transaction in block.transactions.into_iter() { + if matcher(&transaction) { + let receipt = fetch_receipt(connector, transaction.hash).await?; + if !receipt.is_status_ok() { + // This can be caused by a failed attempt to complete an action, + // for example, sending a transaction with low gas. + tracing::warn!( + "transaction matched {:x} but status was NOT OK", + transaction.hash, + ); + continue; + } + tracing::trace!("transaction matched {:x}", transaction.hash,); + return Ok((transaction, receipt)); + } + } + } + GeneratorState::Complete(Err(e)) => return Err(e), + // By matching against the never type explicitly, we assert that the `Ok` value of the + // result is actually the never type and has not been changed since this line was + // written. The never type can never be constructed, so we can never reach this line. + GeneratorState::Complete(Ok(never)) => match never {}, } } } -/// Constructs a predicate that returns `true` if the given block predates the -/// start_of_swap timestamp. -fn predates_start_of_swap(start_of_swap: NaiveDateTime) -> impl Fn(&Block) -> anyhow::Result { - move |block| Ok(block.predates(start_of_swap)) -} - -/// Constructs a predicate that returns `true` if we have seen the given block -/// or the block predates the start_of_swap timestamp. -fn seen_block_or_predates_start_of_swap( - seen_blocks: HashSet, +async fn matching_transaction_and_log( + connector: &C, start_of_swap: NaiveDateTime, -) -> impl Fn(&Block) -> anyhow::Result { - move |block: &Block| { - let have_seen_block = seen_blocks.contains( - &block - .hash - .ok_or_else(|| anyhow::anyhow!("block without hash"))?, - ); - let predates_start_of_swap = predates_start_of_swap(start_of_swap)(block)?; - - Ok(have_seen_block || predates_start_of_swap) - } -} - -async fn check_block_against_pattern( - connector: C, - block: Block, - pattern: TransactionPattern, -) -> anyhow::Result> + topics: Vec>, + matcher: F, +) -> anyhow::Result<(Transaction, Log)> where - C: ReceiptByHash, TransactionHash = Hash>, + C: LatestBlock + BlockByHash + ReceiptByHash, + F: Fn(TransactionReceipt) -> Option, { - let needs_receipt = pattern.needs_receipts(&block); - let block_hash = block - .hash - .ok_or_else(|| anyhow::anyhow!("block without hash"))?; - - if needs_receipt { - tracing::debug!( - "bloom-filter of block {:x} suggests to fetch receipts for {:?}", - block_hash, - pattern - ); - } else { - tracing::debug!( - "bloom-filter of block {:x} suggests to not fetch receipts for {:?}", - block_hash, - pattern - ); - } + let mut block_generator = + Gen::new({ |co| async { find_relevant_blocks(connector, co, start_of_swap).await } }); - for transaction in block.transactions.into_iter() { - let tx_hash = transaction.hash; - - let receipt = connector - .receipt_by_hash(tx_hash) - .compat() - .await? - .ok_or_else(|| { - anyhow::anyhow!( - "Could not get transaction receipt for transaction {:x}", - tx_hash - ) - })?; - - let result = pattern.matches(&transaction, &receipt); + loop { + match block_generator.async_resume().await { + GeneratorState::Yielded(block) => { + let block_hash = block + .hash + .ok_or_else(|| anyhow::anyhow!("block without hash"))?; - tracing::debug!( - "matching {:?} against transaction {:x} yielded {}", - pattern, - tx_hash, - result - ); + let maybe_contains_transaction = topics.iter().all(|topic| { + topic.as_ref().map_or(true, |topic| { + block + .logs_bloom + .contains_input(Input::Raw(topic.0.as_ref())) + }) + }); + if !maybe_contains_transaction { + tracing::trace!( + "bloom filter indicates that block does not contain transaction: + {:x}", + block_hash, + ); + continue; + } - if result { - return Ok(Some(TransactionAndReceipt { - transaction, - receipt, - })); + tracing::trace!( + "bloom filter indicates that we should check the block for transactions: {:x}", + block_hash, + ); + for transaction in block.transactions.into_iter() { + let receipt = fetch_receipt(connector, transaction.hash).await?; + let status_is_ok = receipt.is_status_ok(); + if let Some(log) = matcher(receipt) { + if !status_is_ok { + // This can be caused by a failed attempt to complete an action, + // for example, sending a transaction with low gas. + tracing::warn!( + "transaction matched {:x} but status was NOT OK", + transaction.hash, + ); + continue; + } + tracing::trace!("transaction matched {:x}", transaction.hash,); + return Ok((transaction, log)); + } + } + } + GeneratorState::Complete(Err(e)) => return Err(e), + // By matching against the never type explicitly, we assert that the `Ok` value of the + // result is actually the never type and has not been changed since this line was + // written. The never type can never be constructed, so we can never reach this line. + GeneratorState::Complete(Ok(never)) => match never {}, } } - - Ok(None) } impl Predates for Block { @@ -260,3 +227,39 @@ impl Predates for Block { self.timestamp < U256::from(unix_timestamp) } } + +#[derive(Clone, Copy, Default, Eq, PartialEq, serde::Serialize, serdebug::SerDebug)] +#[serde(transparent)] +pub struct Topic(pub H256); + +/// Event works similar to web3 filters: +/// https://web3js.readthedocs.io/en/1.0/web3-eth-subscribe.html?highlight=filter#subscribe-logs +/// For example, this `Event` would match this `Log`: +/// ```rust, ignore +/// +/// Event { +/// address: "0xe46FB33e4DB653De84cB0E0E8b810A6c4cD39d59", +/// topics: [ +/// None, +/// Some("0x000000000000000000000000e46fb33e4db653de84cb0e0e8b810a6c4cd39d59"), +/// None, +/// ], +/// } +/// +/// Log: { +/// address: "0xe46FB33e4DB653De84cB0E0E8b810A6c4cD39d59", +/// data: "0x123", +/// topics: [ +/// "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", +/// "0x000000000000000000000000e46fb33e4db653de84cb0e0e8b810a6c4cd39d59", +/// "0x000000000000000000000000d51ecee7414c4445534f74208538683702cbb3e4", +/// ] +/// ... // Other data omitted +/// } +/// ``` +#[derive(Clone, Default, Eq, PartialEq, serde::Serialize, serdebug::SerDebug)] +pub struct Event { + pub address: Address, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub topics: Vec>, +} diff --git a/cnd/src/btsieve/ethereum/transaction_pattern.rs b/cnd/src/btsieve/ethereum/transaction_pattern.rs deleted file mode 100644 index 2fc2addb93..0000000000 --- a/cnd/src/btsieve/ethereum/transaction_pattern.rs +++ /dev/null @@ -1,709 +0,0 @@ -use crate::ethereum::{Address, Block, Bytes, Transaction, TransactionReceipt, H256}; -use ethbloom::Input; - -pub const TRANSACTION_STATUS_OK: u32 = 1; - -#[derive(Clone, Default, Eq, PartialEq, serde::Serialize, serdebug::SerDebug)] -/// If the field is set to Some(foo) then only transactions matching foo are -/// returned. Otherwise, when the field is set to None, no pattern matching is -/// done for this field. -pub struct TransactionPattern { - #[serde(skip_serializing_if = "Option::is_none")] - pub from_address: Option
, - #[serde(skip_serializing_if = "Option::is_none")] - pub to_address: Option
, - #[serde(skip_serializing_if = "Option::is_none")] - pub is_contract_creation: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub transaction_data: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub transaction_data_length: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub events: Option>, -} - -impl TransactionPattern { - /// Does matching based on patterns in self. If all fields are None any - /// transaction matches i.e., returns true. - pub fn matches(&self, transaction: &Transaction, receipt: &TransactionReceipt) -> bool { - match self { - Self { - from_address, - to_address, - is_contract_creation, - transaction_data, - transaction_data_length, - events, - } => { - if receipt.status != Some(TRANSACTION_STATUS_OK.into()) { - return false; - } - - if let Some(from_address) = from_address { - if transaction.from != *from_address { - return false; - } - } - - if let Some(to_address) = to_address { - if transaction.to != Some(*to_address) { - return false; - } - } - - if let Some(is_contract_creation) = is_contract_creation { - // transaction.to address is None if, and only if, the transaction creates a - // contract - if transaction.to.is_none() != *is_contract_creation { - return false; - } - } - - if let Some(transaction_data) = transaction_data { - if transaction.input != *transaction_data { - return false; - } - } - - if let Some(transaction_data_length) = transaction_data_length { - if transaction.input.0.len() != *transaction_data_length { - return false; - } - } - - if let Some(events) = events { - if !events_exist_in_receipt(events, receipt) { - return false; - } - } - - // If all fields are set to None, any transaction matches. - true - } - } - } - - pub fn needs_receipts(&self, block: &Block) -> bool { - match self.events { - None => false, - Some(ref events) if events.is_empty() && block.logs_bloom.is_empty() => false, - Some(ref events) => !events.iter().all(|event| { - event.topics.iter().all(|topic| { - topic.as_ref().map_or(true, |topic| { - block - .logs_bloom - .contains_input(Input::Raw(topic.0.as_ref())) - }) - }) - }), - } - } -} - -fn events_exist_in_receipt(events: &[Event], receipt: &TransactionReceipt) -> bool { - events.iter().all(|event| match event { - Event { - address: None, - data: None, - topics, - } if topics.is_empty() => false, - Event { - address, - data, - topics, - } => receipt.logs.iter().any(|tx_log| { - if address - .as_ref() - .map_or(false, |address| address != &tx_log.address) - { - return false; - } - - if data.as_ref().map_or(false, |data| data != &tx_log.data) { - return false; - } - - if tx_log.topics.len() == topics.len() { - tx_log.topics.iter().enumerate().all(|(index, tx_topic)| { - let topic = &topics[index]; - topic.as_ref().map_or(true, |topic| tx_topic == &topic.0) - }) - } else { - false - } - }), - }) -} - -#[derive(Clone, Copy, Default, Eq, PartialEq, serde::Serialize, serdebug::SerDebug)] -#[serde(transparent)] -pub struct Topic(pub H256); - -/// Event work similar as web3 filters: -/// https://web3js.readthedocs.io/en/1.0/web3-eth-subscribe.html?highlight=filter#subscribe-logs -/// E.g. this `Event` would match this `Log`: -/// ```rust, ignore -/// Event { -/// address: "0xe46FB33e4DB653De84cB0E0E8b810A6c4cD39d59", -/// data: None, -/// topics: [ -/// None, -/// Some(0x000000000000000000000000e46fb33e4db653de84cb0e0e8b810a6c4cd39d59), -/// None() -/// ], -/// ``` -/// ```rust, ignore -/// Log: -/// [ { address: "0xe46FB33e4DB653De84cB0E0E8b810A6c4cD39d59", -/// data: "0x123", -/// .. -/// topics: -/// [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", -/// "0x000000000000000000000000e46fb33e4db653de84cb0e0e8b810a6c4cd39d59", -/// "0x000000000000000000000000d51ecee7414c4445534f74208538683702cbb3e4" ], -/// }, -/// .. ] //Other data omitted -/// } -/// ``` -#[derive(Clone, Default, Eq, PartialEq, serde::Serialize, serdebug::SerDebug)] -pub struct Event { - #[serde(skip_serializing_if = "Option::is_none")] - pub address: Option
, - #[serde(skip_serializing_if = "Option::is_none")] - pub data: Option, - #[serde(skip_serializing_if = "Vec::is_empty")] - pub topics: Vec>, -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - ethereum::{ - Address, Block, Bytes, Log, Transaction, TransactionReceipt, H160, H2048, H256, - }, - quickcheck::Quickcheck, - }; - use spectral::prelude::*; - use std::str::FromStr; - - const TRANSACTION_STATUS_FAILED: u32 = 0; - - #[test] - fn given_pattern_from_arbitrary_address_contract_creation_transaction_matches() { - fn prop(from_address: Quickcheck
, transaction: Quickcheck) -> bool { - let from_address = from_address.0; - - let pattern = TransactionPattern { - from_address: Some(from_address), - to_address: None, - is_contract_creation: Some(true), - transaction_data: None, - transaction_data_length: None, - events: None, - }; - - let mut transaction = transaction.0; - - transaction.from = from_address; - transaction.to = None; - - let mut receipt = TransactionReceipt::default(); - receipt.status = Some(TRANSACTION_STATUS_OK.into()); - - pattern.matches(&transaction, &receipt) - } - - quickcheck::quickcheck(prop as fn(Quickcheck
, Quickcheck) -> bool) - } - - #[test] - fn given_pattern_from_address_doesnt_match() { - let from_address = "a00f2cac7bad9285ecfd59e8860f5b2d8622e099".parse().unwrap(); - - let pattern = TransactionPattern { - from_address: Some(from_address), - to_address: None, - is_contract_creation: None, - transaction_data: None, - transaction_data_length: None, - events: None, - }; - - let transaction = Transaction { - from: "a00f2cac7bad9285ecfd59e8860f5b2dffffffff".parse().unwrap(), - ..Transaction::default() - }; - - let receipt = TransactionReceipt::default(); - - let result = pattern.matches(&transaction, &receipt); - assert_that!(&result).is_false(); - } - - #[test] - fn given_pattern_to_address_transaction_matches() { - let to_address = "a00f2cac7bad9285ecfd59e8860f5b2d8622e099".parse().unwrap(); - - let pattern = TransactionPattern { - from_address: None, - to_address: Some(to_address), - is_contract_creation: None, - transaction_data: None, - transaction_data_length: None, - events: None, - }; - - let transaction = Transaction { - from: "0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".parse().unwrap(), - to: Some(to_address), - ..Transaction::default() - }; - - let mut receipt = TransactionReceipt::default(); - receipt.status = Some(TRANSACTION_STATUS_OK.into()); - - let result = pattern.matches(&transaction, &receipt); - assert_that!(&result).is_true(); - } - - #[test] - fn given_pattern_to_address_transaction_doesnt_match() { - let to_address = "a00f2cac7bad9285ecfd59e8860f5b2d8622e099".parse().unwrap(); - - let pattern = TransactionPattern { - from_address: None, - to_address: Some(to_address), - is_contract_creation: None, - transaction_data: None, - transaction_data_length: None, - events: None, - }; - - let transaction = Transaction { - from: "0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".parse().unwrap(), - to: Some("0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".parse().unwrap()), - ..Transaction::default() - }; - - let receipt = TransactionReceipt::default(); - - let result = pattern.matches(&transaction, &receipt); - assert_that!(&result).is_false(); - } - - #[test] - fn given_pattern_to_address_transaction_with_to_none_doesnt_match() { - let to_address = "a00f2cac7bad9285ecfd59e8860f5b2d8622e099".parse().unwrap(); - - let pattern = TransactionPattern { - from_address: None, - to_address: Some(to_address), - is_contract_creation: None, - transaction_data: None, - transaction_data_length: None, - events: None, - }; - - let transaction = Transaction { - to: None, - ..Transaction::default() - }; - - let receipt = TransactionReceipt::default(); - - let result = pattern.matches(&transaction, &receipt); - assert_that!(&result).is_false(); - } - - #[test] - fn given_pattern_transaction_data_transaction_matches() { - let pattern_data = TransactionPattern { - from_address: None, - to_address: None, - is_contract_creation: None, - transaction_data: Some(Bytes::from(vec![1, 2, 3, 4, 5])), - transaction_data_length: None, - events: None, - }; - - let pattern_data_length = TransactionPattern { - from_address: None, - to_address: None, - is_contract_creation: None, - transaction_data: None, - transaction_data_length: Some(5), - events: None, - }; - - let refund_pattern = TransactionPattern { - from_address: None, - to_address: Some("0bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".parse().unwrap()), - is_contract_creation: Some(false), - transaction_data: Some(Bytes::from(vec![])), - transaction_data_length: None, - events: None, - }; - - let transaction = Transaction { - to: Some("0bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".parse().unwrap()), - input: Bytes::from(vec![1, 2, 3, 4, 5]), - ..Transaction::default() - }; - - let mut receipt = TransactionReceipt::default(); - receipt.status = Some(TRANSACTION_STATUS_OK.into()); - - let result = pattern_data.matches(&transaction, &receipt); - assert_that!(&result).is_true(); - - let result = pattern_data_length.matches(&transaction, &receipt); - assert_that!(&result).is_true(); - - let result = refund_pattern.matches(&transaction, &receipt); - assert_that!(&result).is_false(); - } - - lazy_static::lazy_static! { - pub static ref REDEEM_BLOOM: H2048 = { - H2048::from_str( - "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ - 000000000000000000000000000000000000000000000000000000000000000000000000000000000000100\ - 000000000000000000000000000000000000000000000000000000000000000800000000000000000000000\ - 000000000000000000000000000000000000000000000000000000000000000000000000408000000000000\ - 000000000000000000000000000000000000000000000000000100000000040000000000000000000000000\ - 00000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap() - }; - } - lazy_static::lazy_static! { - pub static ref CONTRACT_ADDRESS: H160 = Address::from_str("e46FB33e4DB653De84cB0E0E8b810A6c4cD39d59").unwrap(); - } - lazy_static::lazy_static! { - pub static ref REDEEM_LOG_MSG: H256 = H256::from_str(blockchain_contracts::ethereum::rfc003::REDEEMED_LOG_MSG).unwrap(); - } - lazy_static::lazy_static! { - pub static ref UNKNOWN_LOG_MSG: H256 = H256::from_str("0000000000000000000000000000000000000000000000000000000000000001").unwrap(); - } - - // unfortunately, Log doesn't derive Default - fn default_log() -> Log { - Log { - address: Default::default(), - topics: vec![], - data: Default::default(), - block_hash: None, - block_number: None, - transaction_hash: None, - transaction_index: None, - log_index: None, - transaction_log_index: None, - log_type: None, - removed: None, - } - } - - impl Event { - fn new() -> Self { - Event { - address: None, - data: None, - topics: vec![], - } - } - - fn for_contract(mut self, address: Address) -> Self { - self.address = Some(address); - self - } - - fn with_topics(mut self, topics: Vec>) -> Self { - self.topics = topics; - self - } - } - - fn transaction_pattern_from_event(event: Event) -> TransactionPattern { - TransactionPattern { - from_address: None, - to_address: None, - is_contract_creation: None, - transaction_data: None, - transaction_data_length: None, - events: Some(vec![event]), - } - } - - #[test] - fn given_a_block_without_bloom_filter_can_skip_block() { - let tx = Transaction { - to: Some(*CONTRACT_ADDRESS), - ..Transaction::default() - }; - let block = Block { - logs_bloom: H2048::zero(), - transactions: vec![tx], - ..Block::default() - }; - - let event = Event::new() - .for_contract(*CONTRACT_ADDRESS) - .with_topics(vec![Some(Topic(*REDEEM_LOG_MSG))]); - let pattern = transaction_pattern_from_event(event); - - assert_that!(pattern.needs_receipts(&block)).is_true(); - } - - #[test] - fn pattern_event_found_in_receipt() { - let events = vec![Event::new() - .for_contract(*CONTRACT_ADDRESS) - .with_topics(vec![Some(Topic(*REDEEM_LOG_MSG))])]; - - let log = Log { - address: *CONTRACT_ADDRESS, - topics: vec![*REDEEM_LOG_MSG], - ..default_log() - }; - - let receipt = TransactionReceipt { - logs: vec![log], - ..TransactionReceipt::default() - }; - - assert_that!(events_exist_in_receipt(&events, &receipt)).is_true(); - } - - #[test] - fn pattern_events_not_found_in_empty_receipt() { - let events = vec![Event::new() - .for_contract(*CONTRACT_ADDRESS) - .with_topics(vec![Some(Topic(*REDEEM_LOG_MSG))])]; - - let receipt = TransactionReceipt::default(); - - assert_that!(events_exist_in_receipt(&events, &receipt)).is_false(); - } - - #[test] - fn pattern_event_with_two_logs_found_in_receipt() { - let events = vec![ - Event::new() - .for_contract(*CONTRACT_ADDRESS) - .with_topics(vec![Some(Topic(*REDEEM_LOG_MSG))]), - Event::new() - .for_contract(*CONTRACT_ADDRESS) - .with_topics(vec![Some(Topic(*UNKNOWN_LOG_MSG))]), - ]; - - let log1 = Log { - address: *CONTRACT_ADDRESS, - topics: vec![*REDEEM_LOG_MSG], - data: Bytes::default(), - ..default_log() - }; - let log2 = Log { - address: *CONTRACT_ADDRESS, - topics: vec![*UNKNOWN_LOG_MSG], - data: Bytes::default(), - ..default_log() - }; - - let receipt = TransactionReceipt { - logs: vec![log1, log2], - ..TransactionReceipt::default() - }; - - assert_that!(events_exist_in_receipt(&events, &receipt)).is_true(); - } - - #[test] - fn pattern_event_not_found_in_receipt_if_address_differs() { - let events = vec![Event::new() - .for_contract(Address::repeat_byte(1)) - .with_topics(vec![Some(Topic(*REDEEM_LOG_MSG))])]; - - let log = Log { - address: *CONTRACT_ADDRESS, - topics: vec![*REDEEM_LOG_MSG], - data: Bytes::default(), - ..default_log() - }; - let receipt = TransactionReceipt { - logs: vec![log], - ..TransactionReceipt::default() - }; - - assert_that!(events_exist_in_receipt(&events, &receipt)).is_false(); - } - - #[test] - fn pattern_event_not_found_in_receipt_if_address_and_topics_differ() { - let events = vec![Event::new() - .for_contract(Address::repeat_byte(1)) - .with_topics(vec![Some(Topic(*REDEEM_LOG_MSG))])]; - - let log = Log { - address: *CONTRACT_ADDRESS, - topics: vec![*UNKNOWN_LOG_MSG], - data: Bytes::default(), - ..default_log() - }; - - let receipt = TransactionReceipt { - logs: vec![log], - ..TransactionReceipt::default() - }; - - assert_that!(events_exist_in_receipt(&events, &receipt)).is_false(); - } - - #[test] - fn pattern_transfer_log_event_found_in_receipt() { - let from_address = - H256::from_str("00000000000000000000000000a329c0648769a73afac7f9381e08fb43dbea72") - .unwrap(); - let to_address = - H256::from_str("0000000000000000000000000A81e8be41b21f651a71aaB1A85c6813b8bBcCf8") - .unwrap(); - - let events = vec![Event { - address: Some(*CONTRACT_ADDRESS), - data: Some(Bytes::from(vec![1, 2, 3])), - topics: vec![ - Some(Topic(*REDEEM_LOG_MSG)), - Some(Topic(from_address)), - Some(Topic(to_address)), - ], - }]; - - let log = Log { - address: *CONTRACT_ADDRESS, - topics: vec![*REDEEM_LOG_MSG, from_address, to_address], - data: Bytes::from(vec![1, 2, 3]), - ..default_log() - }; - - let receipt = TransactionReceipt { - logs: vec![log], - ..TransactionReceipt::default() - }; - - assert_that!(events_exist_in_receipt(&events, &receipt)).is_true(); - } - - #[test] - fn pattern_event_with_partial_topics_found_in_receipt() { - let from_address = - H256::from_str("00000000000000000000000000a329c0648769a73afac7f9381e08fb43dbea72") - .unwrap(); - let to_address = - H256::from_str("0000000000000000000000000A81e8be41b21f651a71aaB1A85c6813b8bBcCf8") - .unwrap(); - - let events = vec![Event::new() - .for_contract(*CONTRACT_ADDRESS) - .with_topics(vec![None, None, Some(Topic(to_address))])]; - - let log = Log { - address: *CONTRACT_ADDRESS, - topics: vec![*REDEEM_LOG_MSG, from_address, to_address], - data: Bytes::from(vec![1, 2, 3]), - ..default_log() - }; - - let receipt = TransactionReceipt { - logs: vec![log], - ..TransactionReceipt::default() - }; - - assert_that!(events_exist_in_receipt(&events, &receipt)).is_true(); - } - - #[test] - fn pattern_event_with_fewer_topics_not_found_in_receipt() { - let from_address = - H256::from_str("00000000000000000000000000a329c0648769a73afac7f9381e08fb43dbea72") - .unwrap(); - let to_address = - H256::from_str("0000000000000000000000000A81e8be41b21f651a71aaB1A85c6813b8bBcCf8") - .unwrap(); - - let events = vec![Event { - address: Some(*CONTRACT_ADDRESS), - data: None, - topics: vec![Some(Topic(to_address))], - }]; - - let log = Log { - address: *CONTRACT_ADDRESS, - topics: vec![*REDEEM_LOG_MSG, from_address, to_address], - data: Bytes::from(vec![1, 2, 3]), - ..default_log() - }; - - let receipt = TransactionReceipt { - logs: vec![log], - ..TransactionReceipt::default() - }; - - assert_that!(events_exist_in_receipt(&events, &receipt)).is_false(); - } - - #[test] - fn given_pattern_without_events_cannot_skip_block() { - let block = Block::default(); - let pattern = TransactionPattern { - events: None, - ..TransactionPattern::default() - }; - - assert_that!(pattern.needs_receipts(&block)).is_false(); - } - - #[test] - fn given_pattern_with_empty_events_and_block_with_no_events_cannot_skip_block() { - let block = Block::default(); - let pattern = TransactionPattern { - events: Some(Vec::new()), - ..TransactionPattern::default() - }; - - assert_that!(pattern.needs_receipts(&block)).is_false(); - } - - #[test] - fn events_found_in_receipt_returns_true_for_empty_events() { - let events = Vec::new(); - let receipt = TransactionReceipt::default(); - - assert_that!(events_exist_in_receipt(&events, &receipt)).is_true(); - } - - #[test] - fn given_failed_transaction_doesnt_match() { - fn prop(from_address: Quickcheck
, transaction: Quickcheck) -> bool { - let from_address = from_address.0; - - let pattern = TransactionPattern { - from_address: Some(from_address), - to_address: None, - is_contract_creation: Some(true), - transaction_data: None, - transaction_data_length: None, - events: None, - }; - - let mut transaction = transaction.0; - - transaction.from = from_address; - transaction.to = None; - - let mut receipt = TransactionReceipt::default(); - receipt.status = Some(TRANSACTION_STATUS_FAILED.into()); - - !pattern.matches(&transaction, &receipt) - } - - quickcheck::quickcheck(prop as fn(Quickcheck
, Quickcheck) -> bool) - } -} diff --git a/cnd/src/btsieve/ethereum/web3_connector.rs b/cnd/src/btsieve/ethereum/web3_connector.rs index 1cced3a1be..f0af1f9ddc 100644 --- a/cnd/src/btsieve/ethereum/web3_connector.rs +++ b/cnd/src/btsieve/ethereum/web3_connector.rs @@ -1,202 +1,93 @@ use crate::{ - btsieve::{BlockByHash, LatestBlock, ReceiptByHash}, - ethereum::{BlockId, BlockNumber}, + btsieve::{ethereum::ReceiptByHash, BlockByHash, LatestBlock}, + ethereum::{TransactionReceipt, H256}, + jsonrpc, }; -use anyhow::Context; -use futures::Future; -use futures_core::{FutureExt, TryFutureExt}; -use reqwest::{Client, Url}; -use serde::Serialize; -use std::sync::Arc; - -#[derive(Clone, Debug)] +use crate::swap_protocols::ledger::ethereum::ChainId; +use async_trait::async_trait; +use crate::config::validation::FetchNetworkId; + +#[derive(Debug)] pub struct Web3Connector { - web3: Arc, - url: Url, + client: jsonrpc::Client, } impl Web3Connector { - pub fn new(node_url: Url) -> Self { + pub fn new(node_url: reqwest::Url) -> Self { Self { - web3: Arc::new(Client::new()), - url: node_url, + client: jsonrpc::Client::new(node_url), } } } +#[async_trait] impl LatestBlock for Web3Connector { - type Block = Option>; - type BlockHash = crate::ethereum::H256; - - fn latest_block( - &mut self, - ) -> Box + Send + 'static> { - let web3 = self.web3.clone(); - let url = self.url.clone(); - - let future = async move { - let request = JsonRpcRequest::new("eth_getBlockByNumber", vec![ - serialize(BlockId::Number(BlockNumber::Latest))?, - serialize(true)?, - ]); - - let response = web3 - .post(url) - .json(&request) - .send() - .await? - .json::>>() - .await?; - - let block = match response { - JsonRpcResponse::Success { result } => result, - JsonRpcResponse::Error { code, message } => { - tracing::warn!( - "eth_getBlockByNumber request failed with {}: {}", - code, - message - ); - return Ok(None); - } - }; - - tracing::trace!( - "Fetched block from web3: {:x}", - block.hash.expect("blocks to have a hash") - ); - - Ok(Some(block)) - } - .boxed() - .compat(); - - Box::new(future) - } -} - -#[derive(serde::Serialize)] -struct JsonRpcRequest { - id: String, - jsonrpc: String, - method: String, - params: T, -} - -impl JsonRpcRequest { - fn new(method: &str, params: T) -> Self { - Self { - id: "1".to_owned(), - jsonrpc: "2.0".to_owned(), - method: method.to_owned(), - params, - } + type Block = crate::ethereum::Block; + + async fn latest_block(&self) -> anyhow::Result { + let block: Self::Block = self + .client + .send(jsonrpc::Request::new("eth_getBlockByNumber", vec![ + jsonrpc::serialize("latest")?, + jsonrpc::serialize(true)?, + ])) + .await?; + + tracing::trace!( + "Fetched block from web3: {:x}", + block.hash.expect("blocks to have a hash") + ); + + Ok(block) } } -#[derive(serde::Deserialize)] -#[serde(untagged)] -enum JsonRpcResponse { - Success { result: T }, - Error { code: i64, message: String }, -} - +#[async_trait] impl BlockByHash for Web3Connector { - type Block = Option>; + type Block = crate::ethereum::Block; type BlockHash = crate::ethereum::H256; - fn block_by_hash( - &self, - block_hash: Self::BlockHash, - ) -> Box + Send + 'static> { - let web3 = self.web3.clone(); - let url = self.url.clone(); - - let future = async move { - let request = JsonRpcRequest::new("eth_getBlockByHash", vec![ - serialize(&block_hash)?, - serialize(true)?, - ]); - - let response = web3 - .post(url) - .json(&request) - .send() - .await? - .json::>>() - .await?; - - let block = match response { - JsonRpcResponse::Success { result } => result, - JsonRpcResponse::Error { code, message } => { - tracing::warn!( - "eth_getBlockByHash request failed with {}: {}", - code, - message - ); - return Ok(None); - } - }; - - tracing::trace!("Fetched block from web3: {:x}", block_hash); - - Ok(Some(block)) - } - .boxed() - .compat(); + async fn block_by_hash(&self, block_hash: Self::BlockHash) -> anyhow::Result { + let block = self + .client + .send(jsonrpc::Request::new("eth_getBlockByHash", vec![ + jsonrpc::serialize(&block_hash)?, + jsonrpc::serialize(true)?, + ])) + .await?; - Box::new(future) + tracing::trace!("Fetched block from web3: {:x}", block_hash); + + Ok(block) } } +#[async_trait] impl ReceiptByHash for Web3Connector { - type Receipt = Option; - type TransactionHash = crate::ethereum::H256; - - fn receipt_by_hash( - &self, - transaction_hash: Self::TransactionHash, - ) -> Box + Send + 'static> { - let web3 = self.web3.clone(); - let url = self.url.clone(); - - let future = async move { - let request = JsonRpcRequest::new("eth_getTransactionReceipt", vec![serialize( - transaction_hash, - )?]); - - let response = web3 - .post(url) - .json(&request) - .send() - .await? - .json::>() - .await?; - - let receipt = match response { - JsonRpcResponse::Success { result } => result, - JsonRpcResponse::Error { code, message } => { - tracing::warn!( - "eth_getTransactionReceipt request failed with {}: {}", - code, - message - ); - return Ok(None); - } - }; - - tracing::trace!("Fetched receipt from web3: {:x}", transaction_hash); - - Ok(Some(receipt)) - } - .boxed() - .compat(); + async fn receipt_by_hash(&self, transaction_hash: H256) -> anyhow::Result { + let receipt = self + .client + .send(jsonrpc::Request::new("eth_getTransactionReceipt", vec![ + jsonrpc::serialize(transaction_hash)?, + ])) + .await?; + + tracing::trace!("Fetched receipt from web3: {:x}", transaction_hash); - Box::new(future) + Ok(receipt) } } -fn serialize(t: T) -> anyhow::Result { - let value = serde_json::to_value(t).context("failed to serialize parameter")?; +#[async_trait] +impl FetchNetworkId for Web3Connector { + async fn network_id(&self) -> anyhow::Result { + let chain_id: ChainId = self + .client + .send::, ChainId>(jsonrpc::Request::new("net_version", vec![])) + .await?; - Ok(value) + tracing::debug!("Fetched net_version from web3: {:?}", chain_id); + + Ok(chain_id) + } } diff --git a/cnd/src/btsieve/mod.rs b/cnd/src/btsieve/mod.rs index 4240a530c6..f8e6d46e56 100644 --- a/cnd/src/btsieve/mod.rs +++ b/cnd/src/btsieve/mod.rs @@ -1,44 +1,157 @@ #![warn(rust_2018_idioms)] #![forbid(unsafe_code)] -#[macro_use] -pub mod block_by_hash; pub mod bitcoin; pub mod ethereum; +use crate::Never; +use async_trait::async_trait; use chrono::NaiveDateTime; -use futures::Future; +use genawaiter::sync::Co; +use std::{collections::HashSet, hash::Hash}; +#[async_trait] pub trait LatestBlock: Send + Sync + 'static { type Block; - type BlockHash; - fn latest_block( - &mut self, - ) -> Box + Send + 'static>; + async fn latest_block(&self) -> anyhow::Result; } +#[async_trait] pub trait BlockByHash: Send + Sync + 'static { type Block; type BlockHash; - fn block_by_hash( - &self, - block_hash: Self::BlockHash, - ) -> Box + Send + 'static>; -} - -pub trait ReceiptByHash: Send + Sync + 'static { - type Receipt; - type TransactionHash; - - fn receipt_by_hash( - &self, - transaction_hash: Self::TransactionHash, - ) -> Box + Send + 'static>; + async fn block_by_hash(&self, block_hash: Self::BlockHash) -> anyhow::Result; } /// Checks if a given block predates a certain timestamp. pub trait Predates { fn predates(&self, timestamp: NaiveDateTime) -> bool; } + +/// Abstracts over the ability of getting the hash of the current block. +pub trait BlockHash { + type BlockHash; + + fn block_hash(&self) -> Self::BlockHash; +} + +/// Abstracts over the ability of getting the hash of the previous block. +pub trait PreviousBlockHash { + type BlockHash; + + fn previous_block_hash(&self) -> Self::BlockHash; +} + +/// This function uses the `connector` to find blocks relevant to a swap. To do +/// this we must get the latest block, for each latest block we receive we must +/// ensure that we saw its parent i.e., that we did not miss any blocks between +/// this latest block and the previous latest block we received. Finally, we +/// must also get each block back until the time that the swap started i.e., +/// look into the past (in case any action occurred on chain while we were not +/// watching). +/// +/// It yields those blocks as part of the process. +pub async fn find_relevant_blocks( + connector: &C, + co: Co, + start_of_swap: NaiveDateTime, +) -> anyhow::Result +where + C: LatestBlock + BlockByHash, + B: Predates + BlockHash + PreviousBlockHash + Clone, + H: Eq + Hash + Copy, +{ + let block = connector.latest_block().await?; + + // Look back in time until we get a block that predates start_of_swap. + let mut seen_blocks = + walk_back_until(predates_start_of_swap(start_of_swap), block, connector, &co).await?; + + // Look forward in time, but keep going back for missed blocks + loop { + let block = connector.latest_block().await?; + + let missed_blocks = walk_back_until( + seen_block_or_predates_start_of_swap(&seen_blocks, start_of_swap), + block, + connector, + &co, + ) + .await?; + + seen_blocks.extend(missed_blocks); + + // The duration of this timeout could/should depend on the network + tokio::time::delay_for(std::time::Duration::from_secs(1)).await; + } +} + +/// Walks the blockchain backwards from the given hash until the predicate given +/// in `stop_condition` returns `true`. +/// +/// This function yields all blocks as part of its process. +/// This function returns the block-hashes of all visited blocks. +async fn walk_back_until( + should_stop_here: P, + starting_block: B, + connector: &C, + co: &Co, +) -> anyhow::Result> +where + C: BlockByHash, + P: Fn(&B) -> bool, + B: BlockHash + PreviousBlockHash, + H: Eq + Hash + Copy, +{ + let mut seen_blocks = HashSet::new(); + + let mut current_blockhash = starting_block.block_hash(); + let mut current_block = starting_block; + + loop { + seen_blocks.insert(current_blockhash); + + // we have to compute these variables before we consume the block with + // `co.yield_` + current_blockhash = current_block.previous_block_hash(); + let should_stop_here = should_stop_here(¤t_block); + + // we have to yield the block before exiting + co.yield_(current_block).await; + + if should_stop_here { + return Ok(seen_blocks); + } + + current_block = connector.block_by_hash(current_blockhash).await? + } +} + +/// Constructs a predicate that returns `true` if the given block predates the +/// start_of_swap timestamp. +fn predates_start_of_swap(start_of_swap: NaiveDateTime) -> impl Fn(&B) -> bool +where + B: Predates, +{ + move |block| block.predates(start_of_swap) +} + +/// Constructs a predicate that returns `true` if we have seen the given block +/// or the block predates the start_of_swap timestamp. +fn seen_block_or_predates_start_of_swap<'sb, B, H>( + seen_blocks: &'sb HashSet, + start_of_swap: NaiveDateTime, +) -> impl Fn(&B) -> bool + 'sb +where + B: Predates + BlockHash, + H: Eq + Hash, +{ + move |block: &B| { + let have_seen_block = seen_blocks.contains(&block.block_hash()); + let predates_start_of_swap = predates_start_of_swap(start_of_swap)(block); + + have_seen_block || predates_start_of_swap + } +} diff --git a/cnd/src/comit_api/mod.rs b/cnd/src/comit_api/mod.rs index 5250f80bb5..676e4ec5af 100644 --- a/cnd/src/comit_api/mod.rs +++ b/cnd/src/comit_api/mod.rs @@ -29,7 +29,7 @@ impl FromHeader for LedgerKind { )) } }, - "ethereum" => LedgerKind::Ethereum(Ethereum::new(header.take_parameter("network")?)), + "ethereum" => LedgerKind::Ethereum(Ethereum::new(header.take_parameter("chain_id")?)), unknown => { return Err(serde_json::Error::custom(format!( "unknown ledger: {}", @@ -55,7 +55,7 @@ impl ToHeader for LedgerKind { } LedgerKind::Ethereum(ethereum) => { - Header::with_str_value("ethereum").with_parameter("network", ethereum.chain_id)? + Header::with_str_value("ethereum").with_parameter("chain_id", ethereum.chain_id)? } }) } @@ -235,7 +235,7 @@ mod tests { assert_eq!( header, Header::with_str_value("ethereum") - .with_parameter("network", 3) + .with_parameter("chain_id", 3) .unwrap() ); } diff --git a/cnd/src/config/file.rs b/cnd/src/config/file.rs index 7585df801f..c09e87be9f 100644 --- a/cnd/src/config/file.rs +++ b/cnd/src/config/file.rs @@ -1,7 +1,11 @@ -use crate::config::{Bitcoin, Data, Ethereum, Network, Socket}; +use crate::{ + config::{Bitcoind, Data, Lightning, Network, Parity}, + swap_protocols::ledger::ethereum, +}; use config as config_rs; use log::LevelFilter; -use std::{ffi::OsStr, path::Path}; +use serde::{Deserialize, Serialize}; +use std::{ffi::OsStr, net::SocketAddr, path::Path}; /// This struct aims to represent the configuration file as it appears on disk. /// @@ -16,6 +20,20 @@ pub struct File { pub logging: Option, pub bitcoin: Option, pub ethereum: Option, + pub lightning: Option, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct Bitcoin { + #[serde(with = "crate::config::serde_bitcoin_network")] + pub network: bitcoin::Network, + pub bitcoind: Option, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct Ethereum { + pub chain_id: ethereum::ChainId, + pub parity: Option, } impl File { @@ -27,10 +45,14 @@ impl File { logging: Option::None, bitcoin: Option::None, ethereum: Option::None, + lightning: Option::None, } } - pub fn read>(config_file: D) -> Result { + pub fn read(config_file: D) -> Result + where + D: AsRef, + { let config_file = Path::new(&config_file); let mut config = config_rs::Config::new(); @@ -80,7 +102,7 @@ impl From for LevelFilter { #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)] pub struct HttpApi { - pub socket: Socket, + pub socket: SocketAddr, pub cors: Option, } @@ -112,10 +134,14 @@ pub enum None { #[cfg(test)] mod tests { use super::*; - use crate::config::Settings; + use crate::{ + config::{Bitcoind, Lnd, Parity, Settings}, + swap_protocols::ledger::ethereum, + }; + use reqwest::Url; use spectral::prelude::*; use std::{ - net::{IpAddr, Ipv4Addr}, + net::{IpAddr, Ipv4Addr, SocketAddr}, path::PathBuf, }; @@ -168,9 +194,8 @@ mod tests { [network] listen = ["/ip4/0.0.0.0/tcp/9939"] -[http_api.socket] -address = "127.0.0.1" -port = 8000 +[http_api] +socket = "127.0.0.1:8000" [http_api.cors] allowed_origins = "all" @@ -182,22 +207,29 @@ dir = "/tmp/comit/" level = "Debug" [bitcoin] -network = "mainnet" -node_url = "http://example.com/" +network = "regtest" + +[bitcoin.bitcoind] +node_url = "http://localhost:18443/" [ethereum] -node_url = "http://example.com/" -"#; +chain_id = 17 + +[ethereum.parity] +node_url = "http://localhost:8545/" + +[lightning] +network = "regtest" +[lightning.lnd] +rest_api_socket = "127.0.0.1:8080" +"#; let file = File { network: Some(Network { listen: vec!["/ip4/0.0.0.0/tcp/9939".parse().unwrap()], }), http_api: Some(HttpApi { - socket: Socket { - address: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - port: 8000, - }, + socket: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8000), cors: Some(Cors { allowed_origins: AllowedOrigins::All(All::All), }), @@ -209,11 +241,26 @@ node_url = "http://example.com/" level: Some(Level::Debug), }), bitcoin: Some(Bitcoin { - network: bitcoin::Network::Bitcoin, - node_url: "http://example.com".parse().unwrap(), + network: bitcoin::Network::Regtest, + bitcoind: Some(Bitcoind { + node_url: "http://localhost:18443".parse().unwrap(), + }), }), ethereum: Some(Ethereum { - node_url: "http://example.com".parse().unwrap(), + chain_id: ethereum::ChainId::regtest(), + parity: Some(Parity { + node_url: "http://localhost:8545".parse().unwrap(), + }), + }), + lightning: Some(Lightning { + network: bitcoin::Network::Regtest, + lnd: Some(Lnd { + rest_api_socket: Some(SocketAddr::new( + IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + 8080, + )), + dir: None, + }), }), }; @@ -237,4 +284,104 @@ node_url = "http://example.com/" assert_eq!(file, file_with_effective_settings) } + + #[test] + fn bitcoin_deserializes_correctly() { + let file_contents = vec![ + r#" + network = "mainnet" + [bitcoind] + node_url = "http://example.com:8332" + "#, + r#" + network = "testnet" + [bitcoind] + node_url = "http://example.com:18332" + "#, + r#" + network = "regtest" + [bitcoind] + node_url = "http://example.com:18443" + "#, + ]; + + let expected = vec![ + Bitcoin { + network: bitcoin::Network::Bitcoin, + bitcoind: Some(Bitcoind { + node_url: Url::parse("http://example.com:8332").unwrap(), + }), + }, + Bitcoin { + network: bitcoin::Network::Testnet, + bitcoind: Some(Bitcoind { + node_url: Url::parse("http://example.com:18332").unwrap(), + }), + }, + Bitcoin { + network: bitcoin::Network::Regtest, + bitcoind: Some(Bitcoind { + node_url: Url::parse("http://example.com:18443").unwrap(), + }), + }, + ]; + + let actual = file_contents + .into_iter() + .map(toml::from_str) + .collect::, toml::de::Error>>() + .unwrap(); + + assert_eq!(actual, expected); + } + + #[test] + fn ethereum_deserializes_correctly() { + let file_contents = vec![ + r#" + chain_id = 17 + [parity] + node_url = "http://example.com:8545" + "#, + r#" + chain_id = 3 + [parity] + node_url = "http://example.com:8545" + "#, + r#" + chain_id = 1 + [parity] + node_url = "http://example.com:8545" + "#, + ]; + + let expected = vec![ + Ethereum { + chain_id: ethereum::ChainId::regtest(), + parity: Some(Parity { + node_url: Url::parse("http://example.com:8545").unwrap(), + }), + }, + Ethereum { + chain_id: ethereum::ChainId::ropsten(), + parity: Some(Parity { + node_url: Url::parse("http://example.com:8545").unwrap(), + }), + }, + Ethereum { + chain_id: ethereum::ChainId::mainnet(), + parity: Some(Parity { + node_url: Url::parse("http://example.com:8545").unwrap(), + }), + }, + ]; + + let actual = file_contents + .into_iter() + .map(toml::from_str) + .collect::, toml::de::Error>>() + .unwrap(); + + assert_eq!(actual, expected); + } } diff --git a/cnd/src/config/mod.rs b/cnd/src/config/mod.rs index ea94cd341d..5fa7ca1eb8 100644 --- a/cnd/src/config/mod.rs +++ b/cnd/src/config/mod.rs @@ -1,12 +1,22 @@ pub mod file; mod serde_bitcoin_network; pub mod settings; +pub mod validation; +use crate::swap_protocols::ledger::ethereum; use libp2p::Multiaddr; use serde::{Deserialize, Serialize}; -use std::{net::IpAddr, path::PathBuf}; +use std::{ + net::{IpAddr, Ipv4Addr, SocketAddr}, + path::PathBuf, +}; pub use self::{file::File, settings::Settings}; +use reqwest::Url; + +lazy_static::lazy_static! { + pub static ref LND_SOCKET: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); +} #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct Data { @@ -18,28 +28,108 @@ pub struct Network { pub listen: Vec, } -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] -pub struct Socket { - pub address: IpAddr, - pub port: u16, -} - #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct Bitcoin { #[serde(with = "crate::config::serde_bitcoin_network")] pub network: bitcoin::Network, + pub bitcoind: Bitcoind, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct Bitcoind { pub node_url: reqwest::Url, } +impl Default for Bitcoin { + fn default() -> Self { + Self { + network: bitcoin::Network::Regtest, + bitcoind: Bitcoind { + node_url: Url::parse("http://localhost:18443") + .expect("static string to be a valid url"), + }, + } + } +} + +impl From for file::Bitcoin { + fn from(bitcoin: Bitcoin) -> Self { + file::Bitcoin { + network: bitcoin.network, + bitcoind: Some(bitcoin.bitcoind), + } + } +} + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct Ethereum { + pub chain_id: ethereum::ChainId, + pub parity: Parity, +} + +impl From for file::Ethereum { + fn from(ethereum: Ethereum) -> Self { + file::Ethereum { + chain_id: ethereum.chain_id, + parity: Some(ethereum.parity), + } + } +} + +impl Default for Ethereum { + fn default() -> Self { + Self { + chain_id: ethereum::ChainId::regtest(), + parity: Parity { + node_url: Url::parse("http://localhost:8545") + .expect("static string to be a valid url"), + }, + } + } +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct Parity { pub node_url: reqwest::Url, } +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct Lightning { + pub network: bitcoin::Network, + pub lnd: Option, +} + +impl Default for Lightning { + fn default() -> Self { + Self { + network: bitcoin::Network::Regtest, + lnd: Some(Lnd::default()), + } + } +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct Lnd { + pub rest_api_socket: Option, + pub dir: Option, +} + +impl Default for Lnd { + fn default() -> Self { + Self { + rest_api_socket: Some(*LND_SOCKET), + dir: Some(default_lnd_dir()), + } + } +} + +fn default_lnd_dir() -> PathBuf { + crate::lnd_dir().expect("no home directory") +} + #[cfg(test)] mod tests { use super::*; - use reqwest::Url; #[test] fn network_deserializes_correctly() { @@ -74,41 +164,76 @@ mod tests { } #[test] - fn bitcoin_deserializes_correctly() { + fn lnd_deserializes_correctly() { let file_contents = vec![ r#" - network = "mainnet" - node_url = "http://example.com:8545" + rest_api_socket = "127.0.0.1:8080" + dir = "~/.local/share/comit/lnd" "#, r#" - network = "testnet" - node_url = "http://example.com:8545" + rest_api_socket = "127.0.0.1:8080" "#, r#" - network = "regtest" - node_url = "http://example.com:8545" + dir = "~/.local/share/comit/lnd" "#, ]; let expected = vec![ - Bitcoin { - network: bitcoin::Network::Bitcoin, - node_url: Url::parse("http://example.com:8545").unwrap(), + Lnd { + rest_api_socket: Some(*LND_SOCKET), + dir: Some(PathBuf::from("~/.local/share/comit/lnd")), }, - Bitcoin { - network: bitcoin::Network::Testnet, - node_url: Url::parse("http://example.com:8545").unwrap(), + Lnd { + rest_api_socket: Some(*LND_SOCKET), + dir: None, + }, + Lnd { + rest_api_socket: None, + dir: Some(PathBuf::from("~/.local/share/comit/lnd")), + }, + ]; + + let actual = file_contents + .into_iter() + .map(toml::from_str) + .collect::, toml::de::Error>>() + .unwrap(); + + assert_eq!(actual, expected); + } + + #[test] + fn lightning_deserializes_correctly() { + let file_contents = vec![ + r#" + network = "regtest" + "#, + r#" + network = "regtest" + [lnd] + rest_api_socket = "127.0.0.1:8080" + dir = "/path/to/lnd" + "#, + ]; + + let expected = vec![ + Lightning { + network: bitcoin::Network::Regtest, + lnd: None, }, - Bitcoin { + Lightning { network: bitcoin::Network::Regtest, - node_url: Url::parse("http://example.com:8545").unwrap(), + lnd: Some(Lnd { + rest_api_socket: Some(*LND_SOCKET), + dir: Some(PathBuf::from("/path/to/lnd")), + }), }, ]; let actual = file_contents .into_iter() .map(toml::from_str) - .collect::, toml::de::Error>>() + .collect::, toml::de::Error>>() .unwrap(); assert_eq!(actual, expected); diff --git a/cnd/src/config/serde_bitcoin_network.rs b/cnd/src/config/serde_bitcoin_network.rs index fb304a2b9d..dc24a76c4d 100644 --- a/cnd/src/config/serde_bitcoin_network.rs +++ b/cnd/src/config/serde_bitcoin_network.rs @@ -31,10 +31,10 @@ where // This is the API serde expects, can't do much about the trivial copy :( #[allow(clippy::trivially_copy_pass_by_ref)] -pub fn serialize( - value: &bitcoin::Network, - serializer: S, -) -> Result { +pub fn serialize(value: &bitcoin::Network, serializer: S) -> Result +where + S: Serializer, +{ serializer.serialize_str(match value { bitcoin::Network::Bitcoin => "mainnet", bitcoin::Network::Testnet => "testnet", diff --git a/cnd/src/config/settings.rs b/cnd/src/config/settings.rs index 80877d00ca..a8eb360edd 100644 --- a/cnd/src/config/settings.rs +++ b/cnd/src/config/settings.rs @@ -1,8 +1,13 @@ -use crate::config::{file, Bitcoin, Data, Ethereum, File, Network, Socket}; +use crate::config::{ + default_lnd_dir, file, Bitcoin, Bitcoind, Data, Ethereum, File, Lightning, Lnd, Network, + Parity, LND_SOCKET, +}; use anyhow::Context; use log::LevelFilter; -use reqwest::Url; -use std::net::{IpAddr, Ipv4Addr}; +use std::{ + net::{IpAddr, Ipv4Addr, SocketAddr}, + path::PathBuf, +}; /// This structs represents the settings as they are used through out the code. /// @@ -18,6 +23,54 @@ pub struct Settings { pub logging: Logging, pub bitcoin: Bitcoin, pub ethereum: Ethereum, + pub lightning: Lightning, +} + +fn derive_url_bitcoin(bitcoin: Option) -> Bitcoin { + match bitcoin { + None => Bitcoin::default(), + Some(bitcoin) => { + let node_url = match bitcoin.bitcoind { + Some(bitcoind) => bitcoind.node_url, + None => match bitcoin.network { + bitcoin::Network::Bitcoin => "http://localhost:8332" + .parse() + .expect("to be valid static string"), + bitcoin::Network::Testnet => "http://localhost:18332" + .parse() + .expect("to be valid static string"), + bitcoin::Network::Regtest => "http://localhost:18443" + .parse() + .expect("to be valid static string"), + }, + }; + Bitcoin { + network: bitcoin.network, + bitcoind: Bitcoind { node_url }, + } + } + } +} + +fn derive_url_ethereum(ethereum: Option) -> Ethereum { + match ethereum { + None => Ethereum::default(), + Some(ethereum) => { + let node_url = match ethereum.parity { + None => { + // default is always localhost:8545 + "http://localhost:8545" + .parse() + .expect("to be valid static string") + } + Some(parity) => parity.node_url, + }; + Ethereum { + chain_id: ethereum.chain_id, + parity: Parity { node_url }, + } + } + } } impl From for File { @@ -29,6 +82,7 @@ impl From for File { logging: Logging { level }, bitcoin, ethereum, + lightning, } = settings; File { @@ -47,25 +101,29 @@ impl From for File { logging: Some(file::Logging { level: Some(level.into()), }), - bitcoin: Some(bitcoin), - ethereum: Some(ethereum), + bitcoin: Some(bitcoin.into()), + ethereum: Some(ethereum.into()), + lightning: Some(Lightning { + network: lightning.network, + lnd: lightning.lnd.map(|lnd| Lnd { + rest_api_socket: lnd.rest_api_socket, + dir: lnd.dir, + }), + }), } } } #[derive(Clone, Debug, PartialEq)] pub struct HttpApi { - pub socket: Socket, + pub socket: SocketAddr, pub cors: Cors, } impl Default for HttpApi { fn default() -> Self { Self { - socket: Socket { - address: IpAddr::V4(Ipv4Addr::UNSPECIFIED), - port: 8000, - }, + socket: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 8000), cors: Cors::default(), } } @@ -107,6 +165,7 @@ impl Settings { logging, bitcoin, ethereum, + lightning, } = config_file; Ok(Self { @@ -157,26 +216,79 @@ impl Settings { }, } }, - bitcoin: bitcoin.unwrap_or_else(|| Bitcoin { - network: bitcoin::Network::Regtest, - node_url: Url::parse("http://localhost:18443") - .expect("static string to be a valid url"), - }), - ethereum: ethereum.unwrap_or_else(|| Ethereum { - node_url: Url::parse("http://localhost:8545") - .expect("static string to be a valid url"), - }), + bitcoin: derive_url_bitcoin(bitcoin), + ethereum: derive_url_ethereum(ethereum), + lightning: match lightning { + None => Lightning::default(), + Some(lightning) => Lightning { + network: lightning.network, + lnd: match lightning.lnd { + None => Some(Lnd::default()), + Some(lnd) => Some(Lnd { + rest_api_socket: lnd.rest_api_socket.or_else(|| Some(*LND_SOCKET)), + dir: lnd.dir.or_else(|| Some(default_lnd_dir())), + }), + }, + }, + }, }) } + + pub fn lnd_macaroon_path(&self) -> Option { + let macaroon = "readonly.macaroon"; + let dirs = self.lnd_known_location(); + locate_file(dirs, macaroon) + } + + pub fn lnd_tls_cert_path(&self) -> Option { + let cert = "tls.cert"; + let dirs = self.lnd_known_location(); + locate_file(dirs, cert) + } + + pub fn lnd_tls_key_path(&self) -> Option { + let key = "tls.key"; + let dirs = self.lnd_known_location(); + locate_file(dirs, key) + } + + fn lnd_known_location(&self) -> Vec { + let mut v = vec![]; + + if let Some(cnd_data_dir) = crate::data_dir() { + // We want to use generic terms for like `tls.cert` for lnd files + // so put them all in a directory. + let lnd_dir = cnd_data_dir.join("lnd"); + v.push(lnd_dir); + } + + if let Some(lnd_dir) = crate::lnd_dir() { + let network = format!("{}", self.lightning.network); + v.push( + lnd_dir + .join("data") + .join("chain") + .join("bitcoin") + .join(&network), + ); + } + v + } +} + +/// Looks sequentially in `dirs` for `file`. +fn locate_file(dirs: Vec, file: &str) -> Option { + let path = dirs.iter().find(|dir| dir.join(file).exists()); + path.cloned() } #[cfg(test)] mod tests { use super::*; - use crate::config::file; + use crate::{config::file, swap_protocols::ledger::ethereum}; use spectral::prelude::*; - use std::net::{IpAddr, Ipv4Addr}; + use std::{net::IpAddr, path::PathBuf}; #[test] fn logging_section_defaults_to_info() { @@ -199,10 +311,7 @@ mod tests { fn cors_section_defaults_to_no_allowed_foreign_origins() { let config_file = File { http_api: Some(file::HttpApi { - socket: Socket { - address: IpAddr::V4(Ipv4Addr::UNSPECIFIED), - port: 8000, - }, + socket: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 8000), cors: None, }), ..File::default() @@ -231,10 +340,7 @@ mod tests { .is_ok() .map(|settings| &settings.http_api) .is_equal_to(HttpApi { - socket: Socket { - address: IpAddr::V4(Ipv4Addr::UNSPECIFIED), - port: 8000, - }, + socket: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 8000), cors: Cors { allowed_origins: AllowedOrigins::None, }, @@ -257,4 +363,194 @@ mod tests { listen: vec!["/ip4/0.0.0.0/tcp/9939".parse().unwrap()], }) } + + #[test] + fn bitcoin_defaults() { + let config_file = File { ..File::default() }; + + let settings = Settings::from_config_file_and_defaults(config_file); + + assert_that(&settings) + .is_ok() + .map(|settings| &settings.bitcoin) + .is_equal_to(Bitcoin { + network: bitcoin::Network::Regtest, + bitcoind: Bitcoind { + node_url: "http://localhost:18443".parse().unwrap(), + }, + }) + } + + #[test] + fn bitcoin_defaults_network_only() { + let defaults = vec![ + (bitcoin::Network::Bitcoin, "http://localhost:8332"), + (bitcoin::Network::Testnet, "http://localhost:18332"), + (bitcoin::Network::Regtest, "http://localhost:18443"), + ]; + + for (network, url) in defaults { + let config_file = File { + bitcoin: Some(file::Bitcoin { + network, + bitcoind: None, + }), + ..File::default() + }; + + let settings = Settings::from_config_file_and_defaults(config_file); + + assert_that(&settings) + .is_ok() + .map(|settings| &settings.bitcoin) + .is_equal_to(Bitcoin { + network, + bitcoind: Bitcoind { + node_url: url.parse().unwrap(), + }, + }) + } + } + + #[test] + fn ethereum_defaults() { + let config_file = File { ..File::default() }; + + let settings = Settings::from_config_file_and_defaults(config_file); + + assert_that(&settings) + .is_ok() + .map(|settings| &settings.ethereum) + .is_equal_to(Ethereum { + chain_id: ethereum::ChainId::regtest(), + parity: Parity { + node_url: "http://localhost:8545".parse().unwrap(), + }, + }) + } + + #[test] + fn ethereum_defaults_chain_id_only() { + let defaults = vec![ + (ethereum::ChainId::mainnet(), "http://localhost:8545"), + (ethereum::ChainId::ropsten(), "http://localhost:8545"), + (ethereum::ChainId::regtest(), "http://localhost:8545"), + ]; + + for (chain_id, url) in defaults { + let ethereum = Some(file::Ethereum { + chain_id, + parity: None, + }); + let config_file = File { + ethereum, + ..File::default() + }; + + let settings = Settings::from_config_file_and_defaults(config_file); + + assert_that(&settings) + .is_ok() + .map(|settings| &settings.ethereum) + .is_equal_to(Ethereum { + chain_id, + parity: Parity { + node_url: url.parse().unwrap(), + }, + }) + } + } + + #[test] + fn lightning_section_defaults() { + let config_file = File { + lightning: None, + ..File::default() + }; + + let settings = Settings::from_config_file_and_defaults(config_file); + + assert_that(&settings) + .is_ok() + .map(|settings| &settings.lightning) + .is_equal_to(Lightning { + network: bitcoin::Network::Regtest, + lnd: Some(Lnd { + rest_api_socket: Some(*LND_SOCKET), + dir: Some(crate::lnd_default_dir().unwrap()), + }), + }) + } + + #[test] + fn lightning_lnd_section_defaults() { + let config_file = File { + lightning: Some(Lightning { + network: bitcoin::Network::Regtest, + lnd: None, + }), + ..File::default() + }; + + let settings = Settings::from_config_file_and_defaults(config_file); + + assert_that(&settings) + .is_ok() + .map(|settings| &settings.lightning) + .is_equal_to(Lightning::default()) + } + + #[test] + fn lnd_dir_defaults() { + let config_file = File { + lightning: Some(Lightning { + network: bitcoin::Network::Bitcoin, + lnd: Some(Lnd { + rest_api_socket: Some(*LND_SOCKET), + dir: None, + }), + }), + ..File::default() + }; + + let settings = Settings::from_config_file_and_defaults(config_file); + + assert_that(&settings) + .is_ok() + .map(|settings| &settings.lightning) + .is_equal_to(Lightning { + network: bitcoin::Network::Bitcoin, + lnd: Some(Lnd { + rest_api_socket: Some(*LND_SOCKET), + dir: Some(crate::lnd_default_dir().unwrap()), + }), + }) + } + + #[test] + fn lnd_rest_api_socket_defaults() { + let config_file = File { + lightning: Some(Lightning { + network: bitcoin::Network::Bitcoin, + lnd: Some(Lnd { + rest_api_socket: None, + dir: Some(PathBuf::from("~/.cache/comit/lnd")), + }), + }), + ..File::default() + }; + + let settings = Settings::from_config_file_and_defaults(config_file); + + assert_that(&settings) + .is_ok() + .map(|settings| &settings.lightning) + .is_equal_to(Lightning { + network: bitcoin::Network::Bitcoin, + lnd: Some(Lnd { + rest_api_socket: Some(*LND_SOCKET), + dir: Some(PathBuf::from("~/.cache/comit/lnd")), + }), + }) + } } diff --git a/cnd/src/config/validation.rs b/cnd/src/config/validation.rs new file mode 100644 index 0000000000..d5140b79bc --- /dev/null +++ b/cnd/src/config/validation.rs @@ -0,0 +1,32 @@ +use thiserror::Error; +use std::fmt::Debug; +use async_trait::async_trait; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Connected network does not match network specified in settings (expected {connected_network:?}, got {specified_network:?})")] + Validation { + connected_network: T, + specified_network: T, + }, +} +pub async fn validate_blockchain_config(connector: &C, specified: S) -> anyhow::Result<()> + where + C: FetchNetworkId, + S: PartialEq + Debug + Send + Sync + 'static, +{ + let actual = connector.network_id().await?; + if actual == specified { + Ok(()) + } else { + Err(anyhow::Error::from(Error::Validation { + connected_network: actual, + specified_network: specified, + })) + } +} + +#[async_trait] +pub trait FetchNetworkId: Send + Sync + 'static { + async fn network_id(&self) -> anyhow::Result; +} diff --git a/cnd/src/db/integration_tests/db_roundtrips.rs b/cnd/src/db/integration_tests/db_roundtrips.rs index 7035e44e61..4b882bdf0c 100644 --- a/cnd/src/db/integration_tests/db_roundtrips.rs +++ b/cnd/src/db/integration_tests/db_roundtrips.rs @@ -5,6 +5,7 @@ use crate::{ swap_types::{DetermineTypes, SwapTypes}, AssetKind, BitcoinLedgerKind, LedgerKind, Retrieve, Save, Sqlite, Swap, }, + identity, quickcheck::Quickcheck, swap_protocols::{ ledger::Ethereum, @@ -15,14 +16,14 @@ use std::path::Path; use crate::swap_protocols::ledger::bitcoin::{Mainnet, Regtest, Testnet}; macro_rules! db_roundtrip_test { - ($alpha_ledger:ident, $beta_ledger:ident, $alpha_asset:ident, $beta_asset:ident, $expected_swap_types_fn:expr) => { + ($alpha_ledger:ident, $beta_ledger:ident, $alpha_asset:ident, $beta_asset:ident, $alpha_identity:ident, $beta_identity:ident, $expected_swap_types_fn:expr) => { paste::item! { #[test] #[allow(non_snake_case, clippy::redundant_closure_call)] fn []() { fn prop(swap: Quickcheck, - request: Quickcheck>, - accept: Quickcheck>, + request: Quickcheck>, + accept: Quickcheck>, ) -> anyhow::Result { // unpack the swap from the generic newtype @@ -71,65 +72,120 @@ macro_rules! db_roundtrip_test { quickcheck::quickcheck(prop as fn( Quickcheck, - Quickcheck>, - Quickcheck>, + Quickcheck>, + Quickcheck>, ) -> anyhow::Result); } } }; } -db_roundtrip_test!(Regtest, Ethereum, BitcoinAsset, Ether, |role| { - SwapTypes { - alpha_ledger: LedgerKind::Bitcoin(BitcoinLedgerKind::Regtest), - beta_ledger: LedgerKind::Ethereum, - alpha_asset: AssetKind::Bitcoin, - beta_asset: AssetKind::Ether, - role, +// do_roundtrip_test! does not seem to like being called with `::` in an ident. +use identity::{Bitcoin as BitcoinIdentity, Ethereum as EthereumIdentity}; + +db_roundtrip_test!( + Mainnet, + Ethereum, + BitcoinAsset, + Ether, + BitcoinIdentity, + EthereumIdentity, + |role| { + SwapTypes { + alpha_ledger: LedgerKind::Bitcoin(BitcoinLedgerKind::Mainnet), + beta_ledger: LedgerKind::Ethereum, + alpha_asset: AssetKind::Bitcoin, + beta_asset: AssetKind::Ether, + role, + } } -}); -db_roundtrip_test!(Testnet, Ethereum, BitcoinAsset, Ether, |role| { - SwapTypes { - alpha_ledger: LedgerKind::Bitcoin(BitcoinLedgerKind::Testnet), - beta_ledger: LedgerKind::Ethereum, - alpha_asset: AssetKind::Bitcoin, - beta_asset: AssetKind::Ether, - role, +); + +db_roundtrip_test!( + Testnet, + Ethereum, + BitcoinAsset, + Ether, + BitcoinIdentity, + EthereumIdentity, + |role| { + SwapTypes { + alpha_ledger: LedgerKind::Bitcoin(BitcoinLedgerKind::Testnet), + beta_ledger: LedgerKind::Ethereum, + alpha_asset: AssetKind::Bitcoin, + beta_asset: AssetKind::Ether, + role, + } } -}); -db_roundtrip_test!(Mainnet, Ethereum, BitcoinAsset, Ether, |role| { - SwapTypes { - alpha_ledger: LedgerKind::Bitcoin(BitcoinLedgerKind::Mainnet), - beta_ledger: LedgerKind::Ethereum, - alpha_asset: AssetKind::Bitcoin, - beta_asset: AssetKind::Ether, - role, +); + +db_roundtrip_test!( + Regtest, + Ethereum, + BitcoinAsset, + Ether, + BitcoinIdentity, + EthereumIdentity, + |role| { + SwapTypes { + alpha_ledger: LedgerKind::Bitcoin(BitcoinLedgerKind::Regtest), + beta_ledger: LedgerKind::Ethereum, + alpha_asset: AssetKind::Bitcoin, + beta_asset: AssetKind::Ether, + role, + } } -}); -db_roundtrip_test!(Ethereum, Mainnet, Ether, BitcoinAsset, |role| { - SwapTypes { - alpha_ledger: LedgerKind::Ethereum, - beta_ledger: LedgerKind::Bitcoin(BitcoinLedgerKind::Mainnet), - alpha_asset: AssetKind::Ether, - beta_asset: AssetKind::Bitcoin, - role, +); + +db_roundtrip_test!( + Mainnet, + Ethereum, + BitcoinAsset, + Erc20, + BitcoinIdentity, + EthereumIdentity, + |role| { + SwapTypes { + alpha_ledger: LedgerKind::Bitcoin(BitcoinLedgerKind::Mainnet), + beta_ledger: LedgerKind::Ethereum, + alpha_asset: AssetKind::Bitcoin, + beta_asset: AssetKind::Erc20, + role, + } } -}); -db_roundtrip_test!(Mainnet, Ethereum, BitcoinAsset, Erc20, |role| { - SwapTypes { - alpha_ledger: LedgerKind::Bitcoin(BitcoinLedgerKind::Mainnet), - beta_ledger: LedgerKind::Ethereum, - alpha_asset: AssetKind::Bitcoin, - beta_asset: AssetKind::Erc20, - role, +); + +db_roundtrip_test!( + Ethereum, + Mainnet, + Ether, + BitcoinAsset, + EthereumIdentity, + BitcoinIdentity, + |role| { + SwapTypes { + alpha_ledger: LedgerKind::Ethereum, + beta_ledger: LedgerKind::Bitcoin(BitcoinLedgerKind::Mainnet), + alpha_asset: AssetKind::Ether, + beta_asset: AssetKind::Bitcoin, + role, + } } -}); -db_roundtrip_test!(Ethereum, Mainnet, Erc20, BitcoinAsset, |role| { - SwapTypes { - alpha_ledger: LedgerKind::Ethereum, - beta_ledger: LedgerKind::Bitcoin(BitcoinLedgerKind::Mainnet), - alpha_asset: AssetKind::Erc20, - beta_asset: AssetKind::Bitcoin, - role, +); +db_roundtrip_test!( + Ethereum, + Mainnet, + Erc20, + BitcoinAsset, + EthereumIdentity, + BitcoinIdentity, + |role| { + SwapTypes { + alpha_ledger: LedgerKind::Ethereum, + beta_ledger: LedgerKind::Bitcoin(BitcoinLedgerKind::Mainnet), + alpha_asset: AssetKind::Erc20, + beta_asset: AssetKind::Bitcoin, + role, + } } -}); +); diff --git a/cnd/src/db/integration_tests/serialization_format_stability.rs b/cnd/src/db/integration_tests/serialization_format_stability.rs index beba945d09..cda4aae4ac 100644 --- a/cnd/src/db/integration_tests/serialization_format_stability.rs +++ b/cnd/src/db/integration_tests/serialization_format_stability.rs @@ -65,9 +65,10 @@ fn secrethash() { /// is symmetric to the `Display` implementation. /// /// Our custom sql type `Text` relies on this behaviour being symmetric. -fn roundtrip_test(stored_value: &str) +fn roundtrip_test(stored_value: &str) where ::Err: fmt::Debug, + T: fmt::Display + FromStr, { // First, we verify that we can create T from the given value. let read = T::from_str(stored_value).unwrap(); diff --git a/cnd/src/db/load_swaps.rs b/cnd/src/db/load_swaps.rs index 9953aa6365..a16c854ddc 100644 --- a/cnd/src/db/load_swaps.rs +++ b/cnd/src/db/load_swaps.rs @@ -1,5 +1,5 @@ use crate::{ - asset::{self, Asset}, + asset::{self}, db::{ schema, wrapper_types::{ @@ -8,11 +8,12 @@ use crate::{ }, Sqlite, }, + identity, swap_protocols::{ ledger::{bitcoin, Ethereum}, rfc003::{ messages::{Accept, Request}, - Ledger, SecretHash, + SecretHash, }, HashFunction, SwapId, }, @@ -20,6 +21,7 @@ use crate::{ use async_trait::async_trait; use chrono::NaiveDateTime; use diesel::{self, prelude::*, RunQueryDsl}; +use impl_template::impl_template; use schema::{ rfc003_bitcoin_ethereum_accept_messages, rfc003_bitcoin_ethereum_bitcoin_erc20_request_messages, @@ -29,14 +31,18 @@ use schema::{ rfc003_ethereum_bitcoin_ether_bitcoin_request_messages, }; -pub type AcceptedSwap = (Request, Accept, NaiveDateTime); +pub type AcceptedSwap = ( + Request, + Accept, + NaiveDateTime, +); #[async_trait] -pub trait LoadAcceptedSwap { +pub trait LoadAcceptedSwap { async fn load_accepted_swap( &self, swap_id: &SwapId, - ) -> anyhow::Result>; + ) -> anyhow::Result>; } diesel::allow_tables_to_appear_in_same_query!( @@ -79,90 +85,22 @@ struct BitcoinEthereumBitcoinEtherAcceptedSwap { at: NaiveDateTime, } +#[impl_template] impl From - for AcceptedSwap -{ - fn from(record: BitcoinEthereumBitcoinEtherAcceptedSwap) -> Self { - ( - Request { - swap_id: *record.swap_id, - alpha_ledger: bitcoin::Regtest, - beta_ledger: Ethereum { - chain_id: record.ethereum_chain_id.0.into(), - }, - alpha_asset: record.bitcoin_amount.0.into(), - beta_asset: record.ether_amount.0.into(), - hash_function: *record.hash_function, - alpha_ledger_refund_identity: record.bitcoin_refund_identity.0.into(), - beta_ledger_redeem_identity: record.ethereum_redeem_identity.0.into(), - alpha_expiry: record.bitcoin_expiry.into(), - beta_expiry: record.ethereum_expiry.0.into(), - secret_hash: *record.secret_hash, - }, - Accept { - swap_id: *record.swap_id, - alpha_ledger_redeem_identity: record.bitcoin_redeem_identity.0.into(), - beta_ledger_refund_identity: record.ethereum_refund_identity.0.into(), - }, - record.at, - ) - } -} - -#[async_trait] -impl LoadAcceptedSwap for Sqlite { - async fn load_accepted_swap( - &self, - key: &SwapId, - ) -> anyhow::Result> - { - use schema::{ - rfc003_bitcoin_ethereum_accept_messages as accept_messages, - rfc003_bitcoin_ethereum_bitcoin_ether_request_messages as request_messages, - }; - - let record: BitcoinEthereumBitcoinEtherAcceptedSwap = self - .do_in_transaction(|connection| { - let key = Text(key); - - request_messages::table - .inner_join( - accept_messages::table - .on(request_messages::swap_id.eq(accept_messages::swap_id)), - ) - .select(( - request_messages::swap_id, - request_messages::bitcoin_network, - request_messages::ethereum_chain_id, - request_messages::bitcoin_amount, - request_messages::ether_amount, - request_messages::hash_function, - request_messages::bitcoin_refund_identity, - request_messages::ethereum_redeem_identity, - request_messages::bitcoin_expiry, - request_messages::ethereum_expiry, - request_messages::secret_hash, - accept_messages::bitcoin_redeem_identity, - accept_messages::ethereum_refund_identity, - accept_messages::at, - )) - .filter(accept_messages::swap_id.eq(key)) - .first(connection) - }) - .await?; - - Ok(record.into()) - } -} - -impl From - for AcceptedSwap + for AcceptedSwap< + ((bitcoin::Mainnet, bitcoin::Testnet, bitcoin::Regtest)), + Ethereum, + asset::Bitcoin, + asset::Ether, + identity::Bitcoin, + identity::Ethereum, + > { fn from(record: BitcoinEthereumBitcoinEtherAcceptedSwap) -> Self { ( Request { swap_id: *record.swap_id, - alpha_ledger: bitcoin::Testnet, + alpha_ledger: __TYPE0__, beta_ledger: Ethereum { chain_id: record.ethereum_chain_id.0.into(), }, @@ -185,89 +123,31 @@ impl From } } +#[impl_template] #[async_trait] -impl LoadAcceptedSwap for Sqlite { - async fn load_accepted_swap( - &self, - key: &SwapId, - ) -> anyhow::Result> - { - use schema::{ - rfc003_bitcoin_ethereum_accept_messages as accept_messages, - rfc003_bitcoin_ethereum_bitcoin_ether_request_messages as request_messages, - }; - - let record: BitcoinEthereumBitcoinEtherAcceptedSwap = self - .do_in_transaction(|connection| { - let key = Text(key); - - request_messages::table - .inner_join( - accept_messages::table - .on(request_messages::swap_id.eq(accept_messages::swap_id)), - ) - .select(( - request_messages::swap_id, - request_messages::bitcoin_network, - request_messages::ethereum_chain_id, - request_messages::bitcoin_amount, - request_messages::ether_amount, - request_messages::hash_function, - request_messages::bitcoin_refund_identity, - request_messages::ethereum_redeem_identity, - request_messages::bitcoin_expiry, - request_messages::ethereum_expiry, - request_messages::secret_hash, - accept_messages::bitcoin_redeem_identity, - accept_messages::ethereum_refund_identity, - accept_messages::at, - )) - .filter(accept_messages::swap_id.eq(key)) - .first(connection) - }) - .await?; - - Ok(record.into()) - } -} - -impl From - for AcceptedSwap +impl + LoadAcceptedSwap< + ((bitcoin::Mainnet, bitcoin::Testnet, bitcoin::Regtest)), + Ethereum, + asset::Bitcoin, + asset::Ether, + identity::Bitcoin, + identity::Ethereum, + > for Sqlite { - fn from(record: BitcoinEthereumBitcoinEtherAcceptedSwap) -> Self { - ( - Request { - swap_id: *record.swap_id, - alpha_ledger: bitcoin::Mainnet, - beta_ledger: Ethereum { - chain_id: record.ethereum_chain_id.0.into(), - }, - alpha_asset: record.bitcoin_amount.0.into(), - beta_asset: record.ether_amount.0.into(), - hash_function: *record.hash_function, - alpha_ledger_refund_identity: record.bitcoin_refund_identity.0.into(), - beta_ledger_redeem_identity: record.ethereum_redeem_identity.0.into(), - alpha_expiry: record.bitcoin_expiry.into(), - beta_expiry: record.ethereum_expiry.0.into(), - secret_hash: *record.secret_hash, - }, - Accept { - swap_id: *record.swap_id, - alpha_ledger_redeem_identity: record.bitcoin_redeem_identity.0.into(), - beta_ledger_refund_identity: record.ethereum_refund_identity.0.into(), - }, - record.at, - ) - } -} - -#[async_trait] -impl LoadAcceptedSwap for Sqlite { async fn load_accepted_swap( &self, key: &SwapId, - ) -> anyhow::Result> - { + ) -> anyhow::Result< + AcceptedSwap< + __TYPE0__, + Ethereum, + asset::Bitcoin, + asset::Ether, + identity::Bitcoin, + identity::Ethereum, + >, + > { use schema::{ rfc003_bitcoin_ethereum_accept_messages as accept_messages, rfc003_bitcoin_ethereum_bitcoin_ether_request_messages as request_messages, @@ -328,8 +208,16 @@ struct EthereumBitcoinEtherBitcoinAcceptedSwap { at: NaiveDateTime, } +#[impl_template] impl From - for AcceptedSwap + for AcceptedSwap< + Ethereum, + ((bitcoin::Mainnet, bitcoin::Testnet, bitcoin::Regtest)), + asset::Ether, + asset::Bitcoin, + identity::Ethereum, + identity::Bitcoin, + > { fn from(record: EthereumBitcoinEtherBitcoinAcceptedSwap) -> Self { ( @@ -338,7 +226,7 @@ impl From alpha_ledger: Ethereum { chain_id: record.ethereum_chain_id.0.into(), }, - beta_ledger: bitcoin::Regtest, + beta_ledger: __TYPE0__, alpha_asset: record.ether_amount.0.into(), beta_asset: record.bitcoin_amount.0.into(), hash_function: *record.hash_function, @@ -358,13 +246,31 @@ impl From } } +#[impl_template] #[async_trait] -impl LoadAcceptedSwap for Sqlite { +impl + LoadAcceptedSwap< + Ethereum, + ((bitcoin::Mainnet, bitcoin::Testnet, bitcoin::Regtest)), + asset::Ether, + asset::Bitcoin, + identity::Ethereum, + identity::Bitcoin, + > for Sqlite +{ async fn load_accepted_swap( &self, key: &SwapId, - ) -> anyhow::Result> - { + ) -> anyhow::Result< + AcceptedSwap< + Ethereum, + __TYPE0__, + asset::Ether, + asset::Bitcoin, + identity::Ethereum, + identity::Bitcoin, + >, + > { use schema::{ rfc003_ethereum_bitcoin_accept_messages as accept_messages, rfc003_ethereum_bitcoin_ether_bitcoin_request_messages as request_messages, @@ -404,49 +310,100 @@ impl LoadAcceptedSwap } } -impl From - for AcceptedSwap +#[derive(Queryable, Debug, Clone, PartialEq)] +struct BitcoinEthereumBitcoinErc20AcceptedSwap { + // Request fields. + swap_id: Text, + bitcoin_network: Text, + ethereum_chain_id: U32, + bitcoin_amount: Text, + erc20_token_contract: Text, + erc20_amount: Text, + hash_function: Text, + bitcoin_refund_identity: Text<::bitcoin::PublicKey>, + ethereum_redeem_identity: Text, + bitcoin_expiry: U32, + ethereum_expiry: U32, + secret_hash: Text, + // Accept fields. + bitcoin_redeem_identity: Text<::bitcoin::PublicKey>, + ethereum_refund_identity: Text, + + at: NaiveDateTime, +} + +#[impl_template] +impl From + for AcceptedSwap< + ((bitcoin::Mainnet, bitcoin::Testnet, bitcoin::Regtest)), + Ethereum, + asset::Bitcoin, + asset::Erc20, + identity::Bitcoin, + identity::Ethereum, + > { - fn from(record: EthereumBitcoinEtherBitcoinAcceptedSwap) -> Self { + fn from(record: BitcoinEthereumBitcoinErc20AcceptedSwap) -> Self { ( Request { swap_id: *record.swap_id, - alpha_ledger: Ethereum { + alpha_ledger: __TYPE0__, + beta_ledger: Ethereum { chain_id: record.ethereum_chain_id.0.into(), }, - beta_ledger: bitcoin::Testnet, - alpha_asset: record.ether_amount.0.into(), - beta_asset: record.bitcoin_amount.0.into(), + alpha_asset: record.bitcoin_amount.0.into(), + beta_asset: asset::Erc20::new( + record.erc20_token_contract.0.into(), + record.erc20_amount.0.into(), + ), hash_function: *record.hash_function, - alpha_ledger_refund_identity: record.ethereum_refund_identity.0.into(), - beta_ledger_redeem_identity: record.bitcoin_redeem_identity.0.into(), - alpha_expiry: record.ethereum_expiry.0.into(), - beta_expiry: record.bitcoin_expiry.0.into(), + alpha_ledger_refund_identity: record.bitcoin_refund_identity.0.into(), + beta_ledger_redeem_identity: record.ethereum_redeem_identity.0.into(), + alpha_expiry: record.bitcoin_expiry.0.into(), + beta_expiry: record.ethereum_expiry.0.into(), secret_hash: *record.secret_hash, }, Accept { swap_id: *record.swap_id, - alpha_ledger_redeem_identity: record.ethereum_redeem_identity.0.into(), - beta_ledger_refund_identity: record.bitcoin_refund_identity.0.into(), + alpha_ledger_redeem_identity: record.bitcoin_redeem_identity.0.into(), + beta_ledger_refund_identity: record.ethereum_refund_identity.0.into(), }, record.at, ) } } +#[impl_template] #[async_trait] -impl LoadAcceptedSwap for Sqlite { +impl + LoadAcceptedSwap< + ((bitcoin::Mainnet, bitcoin::Testnet, bitcoin::Regtest)), + Ethereum, + asset::Bitcoin, + asset::Erc20, + identity::Bitcoin, + identity::Ethereum, + > for Sqlite +{ async fn load_accepted_swap( &self, key: &SwapId, - ) -> anyhow::Result> - { + ) -> anyhow::Result< + AcceptedSwap< + __TYPE0__, + Ethereum, + asset::Bitcoin, + asset::Erc20, + identity::Bitcoin, + identity::Ethereum, + >, + > { use schema::{ - rfc003_ethereum_bitcoin_accept_messages as accept_messages, - rfc003_ethereum_bitcoin_ether_bitcoin_request_messages as request_messages, + rfc003_bitcoin_ethereum_accept_messages as accept_messages, + rfc003_bitcoin_ethereum_bitcoin_erc20_request_messages as request_messages, }; - let record: EthereumBitcoinEtherBitcoinAcceptedSwap = self + let record: BitcoinEthereumBitcoinErc20AcceptedSwap = self .do_in_transaction(|connection| { let key = Text(key); @@ -457,18 +414,19 @@ impl LoadAcceptedSwap ) .select(( request_messages::swap_id, - request_messages::ethereum_chain_id, request_messages::bitcoin_network, - request_messages::ether_amount, + request_messages::ethereum_chain_id, request_messages::bitcoin_amount, + request_messages::erc20_token_contract, + request_messages::erc20_amount, request_messages::hash_function, - request_messages::ethereum_refund_identity, - request_messages::bitcoin_redeem_identity, - request_messages::ethereum_expiry, + request_messages::bitcoin_refund_identity, + request_messages::ethereum_redeem_identity, request_messages::bitcoin_expiry, + request_messages::ethereum_expiry, request_messages::secret_hash, - accept_messages::ethereum_redeem_identity, - accept_messages::bitcoin_refund_identity, + accept_messages::bitcoin_redeem_identity, + accept_messages::ethereum_refund_identity, accept_messages::at, )) .filter(accept_messages::swap_id.eq(key)) @@ -480,18 +438,51 @@ impl LoadAcceptedSwap } } -impl From - for AcceptedSwap +#[derive(Queryable, Debug, Clone, PartialEq)] +struct EthereumBitcoinErc20BitcoinAcceptedSwap { + // Request fields. + swap_id: Text, + ethereum_chain_id: U32, + bitcoin_network: Text, + erc20_token_contract: Text, + erc20_amount: Text, + bitcoin_amount: Text, + hash_function: Text, + ethereum_refund_identity: Text, + bitcoin_redeem_identity: Text<::bitcoin::PublicKey>, + ethereum_expiry: U32, + bitcoin_expiry: U32, + secret_hash: Text, + // Accept fields. + ethereum_redeem_identity: Text, + bitcoin_refund_identity: Text<::bitcoin::PublicKey>, + + at: NaiveDateTime, +} + +#[impl_template] +impl From + for AcceptedSwap< + Ethereum, + ((bitcoin::Mainnet, bitcoin::Testnet, bitcoin::Regtest)), + asset::Erc20, + asset::Bitcoin, + identity::Ethereum, + identity::Bitcoin, + > { - fn from(record: EthereumBitcoinEtherBitcoinAcceptedSwap) -> Self { + fn from(record: EthereumBitcoinErc20BitcoinAcceptedSwap) -> Self { ( Request { swap_id: *record.swap_id, alpha_ledger: Ethereum { chain_id: record.ethereum_chain_id.0.into(), }, - beta_ledger: bitcoin::Mainnet, - alpha_asset: record.ether_amount.0.into(), + beta_ledger: __TYPE0__, + alpha_asset: asset::Erc20::new( + record.erc20_token_contract.0.into(), + record.erc20_amount.0.into(), + ), beta_asset: record.bitcoin_amount.0.into(), hash_function: *record.hash_function, alpha_ledger_refund_identity: record.ethereum_refund_identity.0.into(), @@ -510,536 +501,31 @@ impl From } } +#[impl_template] #[async_trait] -impl LoadAcceptedSwap for Sqlite { - async fn load_accepted_swap( - &self, - key: &SwapId, - ) -> anyhow::Result> - { - use schema::{ - rfc003_ethereum_bitcoin_accept_messages as accept_messages, - rfc003_ethereum_bitcoin_ether_bitcoin_request_messages as request_messages, - }; - - let record: EthereumBitcoinEtherBitcoinAcceptedSwap = self - .do_in_transaction(|connection| { - let key = Text(key); - - request_messages::table - .inner_join( - accept_messages::table - .on(request_messages::swap_id.eq(accept_messages::swap_id)), - ) - .select(( - request_messages::swap_id, - request_messages::ethereum_chain_id, - request_messages::bitcoin_network, - request_messages::ether_amount, - request_messages::bitcoin_amount, - request_messages::hash_function, - request_messages::ethereum_refund_identity, - request_messages::bitcoin_redeem_identity, - request_messages::ethereum_expiry, - request_messages::bitcoin_expiry, - request_messages::secret_hash, - accept_messages::ethereum_redeem_identity, - accept_messages::bitcoin_refund_identity, - accept_messages::at, - )) - .filter(accept_messages::swap_id.eq(key)) - .first(connection) - }) - .await?; - - Ok(record.into()) - } -} - -#[derive(Queryable, Debug, Clone, PartialEq)] -struct BitcoinEthereumBitcoinErc20AcceptedSwap { - // Request fields. - swap_id: Text, - bitcoin_network: Text, - ethereum_chain_id: U32, - bitcoin_amount: Text, - erc20_token_contract: Text, - erc20_amount: Text, - hash_function: Text, - bitcoin_refund_identity: Text<::bitcoin::PublicKey>, - ethereum_redeem_identity: Text, - bitcoin_expiry: U32, - ethereum_expiry: U32, - secret_hash: Text, - // Accept fields. - bitcoin_redeem_identity: Text<::bitcoin::PublicKey>, - ethereum_refund_identity: Text, - - at: NaiveDateTime, -} - -impl From - for AcceptedSwap -{ - fn from(record: BitcoinEthereumBitcoinErc20AcceptedSwap) -> Self { - ( - Request { - swap_id: *record.swap_id, - alpha_ledger: bitcoin::Regtest, - beta_ledger: Ethereum { - chain_id: record.ethereum_chain_id.0.into(), - }, - alpha_asset: record.bitcoin_amount.0.into(), - beta_asset: asset::Erc20::new( - record.erc20_token_contract.0.into(), - record.erc20_amount.0.into(), - ), - hash_function: *record.hash_function, - alpha_ledger_refund_identity: record.bitcoin_refund_identity.0.into(), - beta_ledger_redeem_identity: record.ethereum_redeem_identity.0.into(), - alpha_expiry: record.bitcoin_expiry.0.into(), - beta_expiry: record.ethereum_expiry.0.into(), - secret_hash: *record.secret_hash, - }, - Accept { - swap_id: *record.swap_id, - alpha_ledger_redeem_identity: record.bitcoin_redeem_identity.0.into(), - beta_ledger_refund_identity: record.ethereum_refund_identity.0.into(), - }, - record.at, - ) - } -} - -#[async_trait] -impl LoadAcceptedSwap for Sqlite { - async fn load_accepted_swap( - &self, - key: &SwapId, - ) -> anyhow::Result> - { - use schema::{ - rfc003_bitcoin_ethereum_accept_messages as accept_messages, - rfc003_bitcoin_ethereum_bitcoin_erc20_request_messages as request_messages, - }; - - let record: BitcoinEthereumBitcoinErc20AcceptedSwap = self - .do_in_transaction(|connection| { - let key = Text(key); - - request_messages::table - .inner_join( - accept_messages::table - .on(request_messages::swap_id.eq(accept_messages::swap_id)), - ) - .select(( - request_messages::swap_id, - request_messages::bitcoin_network, - request_messages::ethereum_chain_id, - request_messages::bitcoin_amount, - request_messages::erc20_token_contract, - request_messages::erc20_amount, - request_messages::hash_function, - request_messages::bitcoin_refund_identity, - request_messages::ethereum_redeem_identity, - request_messages::bitcoin_expiry, - request_messages::ethereum_expiry, - request_messages::secret_hash, - accept_messages::bitcoin_redeem_identity, - accept_messages::ethereum_refund_identity, - accept_messages::at, - )) - .filter(accept_messages::swap_id.eq(key)) - .first(connection) - }) - .await?; - - Ok(record.into()) - } -} - -impl From - for AcceptedSwap -{ - fn from(record: BitcoinEthereumBitcoinErc20AcceptedSwap) -> Self { - ( - Request { - swap_id: *record.swap_id, - alpha_ledger: bitcoin::Testnet, - beta_ledger: Ethereum { - chain_id: record.ethereum_chain_id.0.into(), - }, - alpha_asset: record.bitcoin_amount.0.into(), - beta_asset: asset::Erc20::new( - record.erc20_token_contract.0.into(), - record.erc20_amount.0.into(), - ), - hash_function: *record.hash_function, - alpha_ledger_refund_identity: record.bitcoin_refund_identity.0.into(), - beta_ledger_redeem_identity: record.ethereum_redeem_identity.0.into(), - alpha_expiry: record.bitcoin_expiry.0.into(), - beta_expiry: record.ethereum_expiry.0.into(), - secret_hash: *record.secret_hash, - }, - Accept { - swap_id: *record.swap_id, - alpha_ledger_redeem_identity: record.bitcoin_redeem_identity.0.into(), - beta_ledger_refund_identity: record.ethereum_refund_identity.0.into(), - }, - record.at, - ) - } -} - -#[async_trait] -impl LoadAcceptedSwap for Sqlite { - async fn load_accepted_swap( - &self, - key: &SwapId, - ) -> anyhow::Result> - { - use schema::{ - rfc003_bitcoin_ethereum_accept_messages as accept_messages, - rfc003_bitcoin_ethereum_bitcoin_erc20_request_messages as request_messages, - }; - - let record: BitcoinEthereumBitcoinErc20AcceptedSwap = self - .do_in_transaction(|connection| { - let key = Text(key); - - request_messages::table - .inner_join( - accept_messages::table - .on(request_messages::swap_id.eq(accept_messages::swap_id)), - ) - .select(( - request_messages::swap_id, - request_messages::bitcoin_network, - request_messages::ethereum_chain_id, - request_messages::bitcoin_amount, - request_messages::erc20_token_contract, - request_messages::erc20_amount, - request_messages::hash_function, - request_messages::bitcoin_refund_identity, - request_messages::ethereum_redeem_identity, - request_messages::bitcoin_expiry, - request_messages::ethereum_expiry, - request_messages::secret_hash, - accept_messages::bitcoin_redeem_identity, - accept_messages::ethereum_refund_identity, - accept_messages::at, - )) - .filter(accept_messages::swap_id.eq(key)) - .first(connection) - }) - .await?; - - Ok(record.into()) - } -} - -impl From - for AcceptedSwap +impl + LoadAcceptedSwap< + Ethereum, + ((bitcoin::Mainnet, bitcoin::Testnet, bitcoin::Regtest)), + asset::Erc20, + asset::Bitcoin, + identity::Ethereum, + identity::Bitcoin, + > for Sqlite { - fn from(record: BitcoinEthereumBitcoinErc20AcceptedSwap) -> Self { - ( - Request { - swap_id: *record.swap_id, - alpha_ledger: bitcoin::Mainnet, - beta_ledger: Ethereum { - chain_id: record.ethereum_chain_id.0.into(), - }, - alpha_asset: record.bitcoin_amount.0.into(), - beta_asset: asset::Erc20::new( - record.erc20_token_contract.0.into(), - record.erc20_amount.0.into(), - ), - hash_function: *record.hash_function, - alpha_ledger_refund_identity: record.bitcoin_refund_identity.0.into(), - beta_ledger_redeem_identity: record.ethereum_redeem_identity.0.into(), - alpha_expiry: record.bitcoin_expiry.0.into(), - beta_expiry: record.ethereum_expiry.0.into(), - secret_hash: *record.secret_hash, - }, - Accept { - swap_id: *record.swap_id, - alpha_ledger_redeem_identity: record.bitcoin_redeem_identity.0.into(), - beta_ledger_refund_identity: record.ethereum_refund_identity.0.into(), - }, - record.at, - ) - } -} - -#[async_trait] -impl LoadAcceptedSwap for Sqlite { - async fn load_accepted_swap( - &self, - key: &SwapId, - ) -> anyhow::Result> - { - use schema::{ - rfc003_bitcoin_ethereum_accept_messages as accept_messages, - rfc003_bitcoin_ethereum_bitcoin_erc20_request_messages as request_messages, - }; - - let record: BitcoinEthereumBitcoinErc20AcceptedSwap = self - .do_in_transaction(|connection| { - let key = Text(key); - - request_messages::table - .inner_join( - accept_messages::table - .on(request_messages::swap_id.eq(accept_messages::swap_id)), - ) - .select(( - request_messages::swap_id, - request_messages::bitcoin_network, - request_messages::ethereum_chain_id, - request_messages::bitcoin_amount, - request_messages::erc20_token_contract, - request_messages::erc20_amount, - request_messages::hash_function, - request_messages::bitcoin_refund_identity, - request_messages::ethereum_redeem_identity, - request_messages::bitcoin_expiry, - request_messages::ethereum_expiry, - request_messages::secret_hash, - accept_messages::bitcoin_redeem_identity, - accept_messages::ethereum_refund_identity, - accept_messages::at, - )) - .filter(accept_messages::swap_id.eq(key)) - .first(connection) - }) - .await?; - - Ok(record.into()) - } -} - -#[derive(Queryable, Debug, Clone, PartialEq)] -struct EthereumBitcoinErc20BitcoinAcceptedSwap { - // Request fields. - swap_id: Text, - ethereum_chain_id: U32, - bitcoin_network: Text, - erc20_token_contract: Text, - erc20_amount: Text, - bitcoin_amount: Text, - hash_function: Text, - ethereum_refund_identity: Text, - bitcoin_redeem_identity: Text<::bitcoin::PublicKey>, - ethereum_expiry: U32, - bitcoin_expiry: U32, - secret_hash: Text, - // Accept fields. - ethereum_redeem_identity: Text, - bitcoin_refund_identity: Text<::bitcoin::PublicKey>, - - at: NaiveDateTime, -} - -impl From - for AcceptedSwap -{ - fn from(record: EthereumBitcoinErc20BitcoinAcceptedSwap) -> Self { - ( - Request { - swap_id: *record.swap_id, - alpha_ledger: Ethereum { - chain_id: record.ethereum_chain_id.0.into(), - }, - beta_ledger: bitcoin::Regtest, - alpha_asset: asset::Erc20::new( - record.erc20_token_contract.0.into(), - record.erc20_amount.0.into(), - ), - beta_asset: record.bitcoin_amount.0.into(), - hash_function: *record.hash_function, - alpha_ledger_refund_identity: record.ethereum_refund_identity.0.into(), - beta_ledger_redeem_identity: record.bitcoin_redeem_identity.0.into(), - alpha_expiry: record.ethereum_expiry.0.into(), - beta_expiry: record.bitcoin_expiry.0.into(), - secret_hash: *record.secret_hash, - }, - Accept { - swap_id: *record.swap_id, - alpha_ledger_redeem_identity: record.ethereum_redeem_identity.0.into(), - beta_ledger_refund_identity: record.bitcoin_refund_identity.0.into(), - }, - record.at, - ) - } -} - -#[async_trait] -impl LoadAcceptedSwap for Sqlite { - async fn load_accepted_swap( - &self, - key: &SwapId, - ) -> anyhow::Result> - { - use schema::{ - rfc003_ethereum_bitcoin_accept_messages as accept_messages, - rfc003_ethereum_bitcoin_erc20_bitcoin_request_messages as request_messages, - }; - - let record: EthereumBitcoinErc20BitcoinAcceptedSwap = self - .do_in_transaction(|connection| { - let key = Text(key); - - request_messages::table - .inner_join( - accept_messages::table - .on(request_messages::swap_id.eq(accept_messages::swap_id)), - ) - .select(( - request_messages::swap_id, - request_messages::ethereum_chain_id, - request_messages::bitcoin_network, - request_messages::erc20_token_contract, - request_messages::erc20_amount, - request_messages::bitcoin_amount, - request_messages::hash_function, - request_messages::ethereum_refund_identity, - request_messages::bitcoin_redeem_identity, - request_messages::ethereum_expiry, - request_messages::bitcoin_expiry, - request_messages::secret_hash, - accept_messages::ethereum_redeem_identity, - accept_messages::bitcoin_refund_identity, - accept_messages::at, - )) - .filter(accept_messages::swap_id.eq(key)) - .first(connection) - }) - .await?; - - Ok(record.into()) - } -} - -impl From - for AcceptedSwap -{ - fn from(record: EthereumBitcoinErc20BitcoinAcceptedSwap) -> Self { - ( - Request { - swap_id: *record.swap_id, - alpha_ledger: Ethereum { - chain_id: record.ethereum_chain_id.0.into(), - }, - beta_ledger: bitcoin::Testnet, - alpha_asset: asset::Erc20::new( - record.erc20_token_contract.0.into(), - record.erc20_amount.0.into(), - ), - beta_asset: record.bitcoin_amount.0.into(), - hash_function: *record.hash_function, - alpha_ledger_refund_identity: record.ethereum_refund_identity.0.into(), - beta_ledger_redeem_identity: record.bitcoin_redeem_identity.0.into(), - alpha_expiry: record.ethereum_expiry.0.into(), - beta_expiry: record.bitcoin_expiry.0.into(), - secret_hash: *record.secret_hash, - }, - Accept { - swap_id: *record.swap_id, - alpha_ledger_redeem_identity: record.ethereum_redeem_identity.0.into(), - beta_ledger_refund_identity: record.bitcoin_refund_identity.0.into(), - }, - record.at, - ) - } -} - -#[async_trait] -impl LoadAcceptedSwap for Sqlite { - async fn load_accepted_swap( - &self, - key: &SwapId, - ) -> anyhow::Result> - { - use schema::{ - rfc003_ethereum_bitcoin_accept_messages as accept_messages, - rfc003_ethereum_bitcoin_erc20_bitcoin_request_messages as request_messages, - }; - - let record: EthereumBitcoinErc20BitcoinAcceptedSwap = self - .do_in_transaction(|connection| { - let key = Text(key); - - request_messages::table - .inner_join( - accept_messages::table - .on(request_messages::swap_id.eq(accept_messages::swap_id)), - ) - .select(( - request_messages::swap_id, - request_messages::ethereum_chain_id, - request_messages::bitcoin_network, - request_messages::erc20_token_contract, - request_messages::erc20_amount, - request_messages::bitcoin_amount, - request_messages::hash_function, - request_messages::ethereum_refund_identity, - request_messages::bitcoin_redeem_identity, - request_messages::ethereum_expiry, - request_messages::bitcoin_expiry, - request_messages::secret_hash, - accept_messages::ethereum_redeem_identity, - accept_messages::bitcoin_refund_identity, - accept_messages::at, - )) - .filter(accept_messages::swap_id.eq(key)) - .first(connection) - }) - .await?; - - Ok(record.into()) - } -} - -impl From - for AcceptedSwap -{ - fn from(record: EthereumBitcoinErc20BitcoinAcceptedSwap) -> Self { - ( - Request { - swap_id: *record.swap_id, - alpha_ledger: Ethereum { - chain_id: record.ethereum_chain_id.0.into(), - }, - beta_ledger: bitcoin::Mainnet, - alpha_asset: asset::Erc20::new( - record.erc20_token_contract.0.into(), - record.erc20_amount.0.into(), - ), - beta_asset: record.bitcoin_amount.0.into(), - hash_function: *record.hash_function, - alpha_ledger_refund_identity: record.ethereum_refund_identity.0.into(), - beta_ledger_redeem_identity: record.bitcoin_redeem_identity.0.into(), - alpha_expiry: record.ethereum_expiry.0.into(), - beta_expiry: record.bitcoin_expiry.0.into(), - secret_hash: *record.secret_hash, - }, - Accept { - swap_id: *record.swap_id, - alpha_ledger_redeem_identity: record.ethereum_redeem_identity.0.into(), - beta_ledger_refund_identity: record.bitcoin_refund_identity.0.into(), - }, - record.at, - ) - } -} - -#[async_trait] -impl LoadAcceptedSwap for Sqlite { async fn load_accepted_swap( &self, key: &SwapId, - ) -> anyhow::Result> - { + ) -> anyhow::Result< + AcceptedSwap< + Ethereum, + __TYPE0__, + asset::Erc20, + asset::Bitcoin, + identity::Ethereum, + identity::Bitcoin, + >, + > { use schema::{ rfc003_ethereum_bitcoin_accept_messages as accept_messages, rfc003_ethereum_bitcoin_erc20_bitcoin_request_messages as request_messages, diff --git a/cnd/src/db/mod.rs b/cnd/src/db/mod.rs index ecb3dc2fe9..91ecbe769a 100644 --- a/cnd/src/db/mod.rs +++ b/cnd/src/db/mod.rs @@ -46,7 +46,10 @@ impl Sqlite { /// When this returns, an Sqlite database file 'cnd.sql' exists in 'dir', a /// successful connection to the database has been made, and the database /// migrations have been run. - pub fn new_in_dir>(dir: D) -> anyhow::Result { + pub fn new_in_dir(dir: D) -> anyhow::Result + where + D: AsRef, + { let dir = Path::new(&dir); let path = db_path_from_dir(dir); Sqlite::new(&path) diff --git a/cnd/src/db/save.rs b/cnd/src/db/save.rs index e4053fb702..3e25d473e9 100644 --- a/cnd/src/db/save.rs +++ b/cnd/src/db/save.rs @@ -8,6 +8,7 @@ use crate::{ }, Sqlite, Swap, }, + identity, swap_protocols::{ ledger::{self, Ethereum}, rfc003::{Accept, Decline, Request, SecretHash}, @@ -16,6 +17,7 @@ use crate::{ }; use async_trait::async_trait; use diesel::RunQueryDsl; +use impl_template::impl_template; use libp2p::{self, PeerId}; /// Save swap to database. @@ -74,11 +76,34 @@ struct InsertableBitcoinEthereumBitcoinEtherRequestMessage { secret_hash: Text, } +#[impl_template] #[async_trait] -impl Save> for Sqlite { +impl + Save< + Request< + (( + ledger::bitcoin::Mainnet, + ledger::bitcoin::Testnet, + ledger::bitcoin::Regtest, + )), + Ethereum, + asset::Bitcoin, + asset::Ether, + identity::Bitcoin, + identity::Ethereum, + >, + > for Sqlite +{ async fn save( &self, - message: Request, + message: Request< + __TYPE0__, + Ethereum, + asset::Bitcoin, + asset::Ether, + identity::Bitcoin, + identity::Ethereum, + >, ) -> anyhow::Result<()> { let Request { swap_id, @@ -96,97 +121,7 @@ impl Save> for Sqlite { - async fn save( - &self, - message: Request, - ) -> anyhow::Result<()> { - let Request { - swap_id, - alpha_asset, - beta_ledger, - beta_asset, - hash_function, - alpha_ledger_refund_identity, - beta_ledger_redeem_identity, - alpha_expiry, - beta_expiry, - secret_hash, - .. - } = message; - - let insertable = InsertableBitcoinEthereumBitcoinEtherRequestMessage { - swap_id: Text(swap_id), - bitcoin_network: Text(BitcoinNetwork::from(ledger::bitcoin::Testnet)), - ethereum_chain_id: U32(beta_ledger.chain_id.into()), - bitcoin_amount: Text(alpha_asset.into()), - ether_amount: Text(beta_asset.into()), - hash_function: Text(hash_function), - bitcoin_refund_identity: Text(alpha_ledger_refund_identity.into()), - ethereum_redeem_identity: Text(beta_ledger_redeem_identity.into()), - bitcoin_expiry: U32(alpha_expiry.into()), - ethereum_expiry: U32(beta_expiry.into()), - secret_hash: Text(secret_hash), - }; - - self.do_in_transaction(|connection| { - diesel::insert_into(rfc003_bitcoin_ethereum_bitcoin_ether_request_messages::table) - .values(&insertable) - .execute(connection) - }) - .await?; - - Ok(()) - } -} - -#[async_trait] -impl Save> for Sqlite { - async fn save( - &self, - message: Request, - ) -> anyhow::Result<()> { - let Request { - swap_id, - alpha_asset, - beta_ledger, - beta_asset, - hash_function, - alpha_ledger_refund_identity, - beta_ledger_redeem_identity, - alpha_expiry, - beta_expiry, - secret_hash, - .. - } = message; - - let insertable = InsertableBitcoinEthereumBitcoinEtherRequestMessage { - swap_id: Text(swap_id), - bitcoin_network: Text(BitcoinNetwork::from(ledger::bitcoin::Mainnet)), + bitcoin_network: Text(BitcoinNetwork::from(__TYPE0__)), ethereum_chain_id: U32(beta_ledger.chain_id.into()), bitcoin_amount: Text(alpha_asset.into()), ether_amount: Text(beta_asset.into()), @@ -226,103 +161,34 @@ struct InsertableBitcoinEthereumBitcoinErc20RequestMessage { secret_hash: Text, } +#[impl_template] #[async_trait] -impl Save> for Sqlite { - async fn save( - &self, - message: Request, - ) -> anyhow::Result<()> { - let Request { - swap_id, - alpha_asset, - beta_ledger, - beta_asset, - hash_function, - alpha_ledger_refund_identity, - beta_ledger_redeem_identity, - alpha_expiry, - beta_expiry, - secret_hash, - .. - } = message; - - let insertable = InsertableBitcoinEthereumBitcoinErc20RequestMessage { - swap_id: Text(swap_id), - bitcoin_network: Text(BitcoinNetwork::from(ledger::bitcoin::Regtest)), - ethereum_chain_id: U32(beta_ledger.chain_id.into()), - bitcoin_amount: Text(alpha_asset.into()), - erc20_amount: Text(beta_asset.quantity.into()), - erc20_token_contract: Text(beta_asset.token_contract.into()), - hash_function: Text(hash_function), - bitcoin_refund_identity: Text(alpha_ledger_refund_identity.into()), - ethereum_redeem_identity: Text(beta_ledger_redeem_identity.into()), - bitcoin_expiry: U32(alpha_expiry.into()), - ethereum_expiry: U32(beta_expiry.into()), - secret_hash: Text(secret_hash), - }; - - self.do_in_transaction(|connection| { - diesel::insert_into(rfc003_bitcoin_ethereum_bitcoin_erc20_request_messages::table) - .values(&insertable) - .execute(connection) - }) - .await?; - - Ok(()) - } -} - -#[async_trait] -impl Save> for Sqlite { - async fn save( - &self, - message: Request, - ) -> anyhow::Result<()> { - let Request { - swap_id, - alpha_asset, - beta_ledger, - beta_asset, - hash_function, - alpha_ledger_refund_identity, - beta_ledger_redeem_identity, - alpha_expiry, - beta_expiry, - secret_hash, - .. - } = message; - - let insertable = InsertableBitcoinEthereumBitcoinErc20RequestMessage { - swap_id: Text(swap_id), - bitcoin_network: Text(BitcoinNetwork::from(ledger::bitcoin::Testnet)), - ethereum_chain_id: U32(beta_ledger.chain_id.into()), - bitcoin_amount: Text(alpha_asset.into()), - erc20_amount: Text(beta_asset.quantity.into()), - erc20_token_contract: Text(beta_asset.token_contract.into()), - hash_function: Text(hash_function), - bitcoin_refund_identity: Text(alpha_ledger_refund_identity.into()), - ethereum_redeem_identity: Text(beta_ledger_redeem_identity.into()), - bitcoin_expiry: U32(alpha_expiry.into()), - ethereum_expiry: U32(beta_expiry.into()), - secret_hash: Text(secret_hash), - }; - - self.do_in_transaction(|connection| { - diesel::insert_into(rfc003_bitcoin_ethereum_bitcoin_erc20_request_messages::table) - .values(&insertable) - .execute(connection) - }) - .await?; - - Ok(()) - } -} - -#[async_trait] -impl Save> for Sqlite { +impl + Save< + Request< + (( + ledger::bitcoin::Mainnet, + ledger::bitcoin::Testnet, + ledger::bitcoin::Regtest, + )), + Ethereum, + asset::Bitcoin, + asset::Erc20, + identity::Bitcoin, + identity::Ethereum, + >, + > for Sqlite +{ async fn save( &self, - message: Request, + message: Request< + __TYPE0__, + Ethereum, + asset::Bitcoin, + asset::Erc20, + identity::Bitcoin, + identity::Ethereum, + >, ) -> anyhow::Result<()> { let Request { swap_id, @@ -340,7 +206,7 @@ impl Save, } +#[impl_template] #[async_trait] -impl Save> for Sqlite { - async fn save( - &self, - message: Request, - ) -> anyhow::Result<()> { - let Request { - swap_id, - alpha_ledger, - alpha_asset, - beta_asset, - hash_function, - alpha_ledger_refund_identity, - beta_ledger_redeem_identity, - alpha_expiry, - beta_expiry, - secret_hash, - .. - } = message; - - let insertable = InsertableEthereumBitcoinEtherBitcoinRequestMessage { - swap_id: Text(swap_id), - bitcoin_network: Text(BitcoinNetwork::from(ledger::bitcoin::Regtest)), - ethereum_chain_id: U32(alpha_ledger.chain_id.into()), - ether_amount: Text(alpha_asset.into()), - bitcoin_amount: Text(beta_asset.into()), - hash_function: Text(hash_function), - ethereum_refund_identity: Text(alpha_ledger_refund_identity.into()), - bitcoin_redeem_identity: Text(beta_ledger_redeem_identity.into()), - ethereum_expiry: U32(alpha_expiry.into()), - bitcoin_expiry: U32(beta_expiry.into()), - secret_hash: Text(secret_hash), - }; - - self.do_in_transaction(|connection| { - diesel::insert_into(rfc003_ethereum_bitcoin_ether_bitcoin_request_messages::table) - .values(&insertable) - .execute(connection) - }) - .await?; - - Ok(()) - } -} - -#[async_trait] -impl Save> for Sqlite { - async fn save( - &self, - message: Request, - ) -> anyhow::Result<()> { - let Request { - swap_id, - alpha_ledger, - alpha_asset, - beta_asset, - hash_function, - alpha_ledger_refund_identity, - beta_ledger_redeem_identity, - alpha_expiry, - beta_expiry, - secret_hash, - .. - } = message; - - let insertable = InsertableEthereumBitcoinEtherBitcoinRequestMessage { - swap_id: Text(swap_id), - bitcoin_network: Text(BitcoinNetwork::from(ledger::bitcoin::Testnet)), - ethereum_chain_id: U32(alpha_ledger.chain_id.into()), - ether_amount: Text(alpha_asset.into()), - bitcoin_amount: Text(beta_asset.into()), - hash_function: Text(hash_function), - ethereum_refund_identity: Text(alpha_ledger_refund_identity.into()), - bitcoin_redeem_identity: Text(beta_ledger_redeem_identity.into()), - ethereum_expiry: U32(alpha_expiry.into()), - bitcoin_expiry: U32(beta_expiry.into()), - secret_hash: Text(secret_hash), - }; - - self.do_in_transaction(|connection| { - diesel::insert_into(rfc003_ethereum_bitcoin_ether_bitcoin_request_messages::table) - .values(&insertable) - .execute(connection) - }) - .await?; - - Ok(()) - } -} - -#[async_trait] -impl Save> for Sqlite { +impl + Save< + Request< + Ethereum, + (( + ledger::bitcoin::Mainnet, + ledger::bitcoin::Testnet, + ledger::bitcoin::Regtest, + )), + asset::Ether, + asset::Bitcoin, + identity::Ethereum, + identity::Bitcoin, + >, + > for Sqlite +{ async fn save( &self, - message: Request, + message: Request< + Ethereum, + __TYPE0__, + asset::Ether, + asset::Bitcoin, + identity::Ethereum, + identity::Bitcoin, + >, ) -> anyhow::Result<()> { let Request { swap_id, @@ -492,7 +291,7 @@ impl Save, } +#[impl_template] #[async_trait] -impl Save> for Sqlite { - async fn save( - &self, - message: Request, - ) -> anyhow::Result<()> { - let Request { - swap_id, - alpha_ledger, - alpha_asset, - beta_asset, - hash_function, - alpha_ledger_refund_identity, - beta_ledger_redeem_identity, - alpha_expiry, - beta_expiry, - secret_hash, - .. - } = message; - - let insertable = InsertableEthereumBitcoinErc20BitcoinRequestMessage { - swap_id: Text(swap_id), - ethereum_chain_id: U32(alpha_ledger.chain_id.into()), - bitcoin_network: Text(BitcoinNetwork::from(ledger::bitcoin::Regtest)), - erc20_amount: Text(alpha_asset.quantity.into()), - erc20_token_contract: Text(alpha_asset.token_contract.into()), - bitcoin_amount: Text(beta_asset.into()), - hash_function: Text(hash_function), - ethereum_refund_identity: Text(alpha_ledger_refund_identity.into()), - bitcoin_redeem_identity: Text(beta_ledger_redeem_identity.into()), - ethereum_expiry: U32(alpha_expiry.into()), - bitcoin_expiry: U32(beta_expiry.into()), - secret_hash: Text(secret_hash), - }; - - self.do_in_transaction(|connection| { - diesel::insert_into(rfc003_ethereum_bitcoin_erc20_bitcoin_request_messages::table) - .values(&insertable) - .execute(connection) - }) - .await?; - - Ok(()) - } -} - -#[async_trait] -impl Save> for Sqlite { - async fn save( - &self, - message: Request, - ) -> anyhow::Result<()> { - let Request { - swap_id, - alpha_ledger, - alpha_asset, - beta_asset, - hash_function, - alpha_ledger_refund_identity, - beta_ledger_redeem_identity, - alpha_expiry, - beta_expiry, - secret_hash, - .. - } = message; - - let insertable = InsertableEthereumBitcoinErc20BitcoinRequestMessage { - swap_id: Text(swap_id), - ethereum_chain_id: U32(alpha_ledger.chain_id.into()), - bitcoin_network: Text(BitcoinNetwork::from(ledger::bitcoin::Testnet)), - erc20_amount: Text(alpha_asset.quantity.into()), - erc20_token_contract: Text(alpha_asset.token_contract.into()), - bitcoin_amount: Text(beta_asset.into()), - hash_function: Text(hash_function), - ethereum_refund_identity: Text(alpha_ledger_refund_identity.into()), - bitcoin_redeem_identity: Text(beta_ledger_redeem_identity.into()), - ethereum_expiry: U32(alpha_expiry.into()), - bitcoin_expiry: U32(beta_expiry.into()), - secret_hash: Text(secret_hash), - }; - - self.do_in_transaction(|connection| { - diesel::insert_into(rfc003_ethereum_bitcoin_erc20_bitcoin_request_messages::table) - .values(&insertable) - .execute(connection) - }) - .await?; - - Ok(()) - } -} - -#[async_trait] -impl Save> for Sqlite { +impl + Save< + Request< + Ethereum, + (( + ledger::bitcoin::Mainnet, + ledger::bitcoin::Testnet, + ledger::bitcoin::Regtest, + )), + asset::Erc20, + asset::Bitcoin, + identity::Ethereum, + identity::Bitcoin, + >, + > for Sqlite +{ async fn save( &self, - message: Request, + message: Request< + Ethereum, + __TYPE0__, + asset::Erc20, + asset::Bitcoin, + identity::Ethereum, + identity::Bitcoin, + >, ) -> anyhow::Result<()> { let Request { swap_id, @@ -647,7 +377,7 @@ impl Save> for Sqlite { - async fn save( - &self, - message: Accept, - ) -> anyhow::Result<()> { - let Accept { - swap_id, - alpha_ledger_redeem_identity, - beta_ledger_refund_identity, - } = message; - - let insertable = InsertableEthereumBitcoinAcceptMessage { - swap_id: Text(swap_id), - ethereum_redeem_identity: Text(alpha_ledger_redeem_identity.into()), - bitcoin_refund_identity: Text(beta_ledger_refund_identity.into()), - }; - - self.do_in_transaction(|connection| { - diesel::insert_into(rfc003_ethereum_bitcoin_accept_messages::table) - .values(&insertable) - .execute(&*connection) - }) - .await?; - - Ok(()) - } -} - -#[async_trait] -impl Save> for Sqlite { - async fn save( - &self, - message: Accept, - ) -> anyhow::Result<()> { - let Accept { - swap_id, - alpha_ledger_redeem_identity, - beta_ledger_refund_identity, - } = message; - - let insertable = InsertableEthereumBitcoinAcceptMessage { - swap_id: Text(swap_id), - ethereum_redeem_identity: Text(alpha_ledger_redeem_identity.into()), - bitcoin_refund_identity: Text(beta_ledger_refund_identity.into()), - }; - - self.do_in_transaction(|connection| { - diesel::insert_into(rfc003_ethereum_bitcoin_accept_messages::table) - .values(&insertable) - .execute(&*connection) - }) - .await?; - - Ok(()) - } -} - -#[async_trait] -impl Save> for Sqlite { +impl Save> for Sqlite { async fn save( &self, - message: Accept, + message: Accept, ) -> anyhow::Result<()> { let Accept { swap_id, @@ -774,68 +446,10 @@ struct InsertableBitcoinEthereumAcceptMessage { } #[async_trait] -impl Save> for Sqlite { - async fn save( - &self, - message: Accept, - ) -> anyhow::Result<()> { - let Accept { - swap_id, - alpha_ledger_redeem_identity, - beta_ledger_refund_identity, - } = message; - - let insertable = InsertableBitcoinEthereumAcceptMessage { - swap_id: Text(swap_id), - bitcoin_redeem_identity: Text(alpha_ledger_redeem_identity.into()), - ethereum_refund_identity: Text(beta_ledger_refund_identity.into()), - }; - - self.do_in_transaction(|connection| { - diesel::insert_into(rfc003_bitcoin_ethereum_accept_messages::table) - .values(&insertable) - .execute(&*connection) - }) - .await?; - - Ok(()) - } -} - -#[async_trait] -impl Save> for Sqlite { - async fn save( - &self, - message: Accept, - ) -> anyhow::Result<()> { - let Accept { - swap_id, - alpha_ledger_redeem_identity, - beta_ledger_refund_identity, - } = message; - - let insertable = InsertableBitcoinEthereumAcceptMessage { - swap_id: Text(swap_id), - bitcoin_redeem_identity: Text(alpha_ledger_redeem_identity.into()), - ethereum_refund_identity: Text(beta_ledger_refund_identity.into()), - }; - - self.do_in_transaction(|connection| { - diesel::insert_into(rfc003_bitcoin_ethereum_accept_messages::table) - .values(&insertable) - .execute(&*connection) - }) - .await?; - - Ok(()) - } -} - -#[async_trait] -impl Save> for Sqlite { +impl Save> for Sqlite { async fn save( &self, - message: Accept, + message: Accept, ) -> anyhow::Result<()> { let Accept { swap_id, diff --git a/cnd/src/db/with_swap_types.rs b/cnd/src/db/with_swap_types.rs index aaaeb2b842..416c982b3b 100644 --- a/cnd/src/db/with_swap_types.rs +++ b/cnd/src/db/with_swap_types.rs @@ -9,12 +9,12 @@ macro_rules! _match_role { match $role { Role::Alice => { #[allow(dead_code)] - type ROLE = alice::State; + type ROLE = alice::State; $fn } Role::Bob => { #[allow(dead_code)] - type ROLE = bob::State; + type ROLE = bob::State; $fn } } @@ -27,7 +27,9 @@ macro_rules! with_swap_types { use crate::{ asset, db::{AssetKind, BitcoinLedgerKind, LedgerKind, SwapTypes}, + htlc_location, identity, swap_protocols::ledger::{bitcoin, Ethereum}, + transaction, }; let swap_types: SwapTypes = $swap_types; let role = swap_types.role; @@ -50,7 +52,20 @@ macro_rules! with_swap_types { #[allow(dead_code)] type BA = asset::Ether; #[allow(dead_code)] - type AcceptBody = crate::http_api::routes::rfc003::accept::OnlyRefund; + type AH = htlc_location::Bitcoin; + #[allow(dead_code)] + type BH = htlc_location::Ethereum; + #[allow(dead_code)] + type AI = identity::Bitcoin; + #[allow(dead_code)] + type BI = identity::Ethereum; + #[allow(dead_code)] + type AT = transaction::Bitcoin; + #[allow(dead_code)] + type BT = transaction::Ethereum; + #[allow(dead_code)] + type AcceptBody = + crate::http_api::routes::rfc003::accept::OnlyRefund; _match_role!(role, $fn) } @@ -64,7 +79,20 @@ macro_rules! with_swap_types { #[allow(dead_code)] type BA = asset::Ether; #[allow(dead_code)] - type AcceptBody = crate::http_api::routes::rfc003::accept::OnlyRefund; + type AH = htlc_location::Bitcoin; + #[allow(dead_code)] + type BH = htlc_location::Ethereum; + #[allow(dead_code)] + type AI = identity::Bitcoin; + #[allow(dead_code)] + type BI = identity::Ethereum; + #[allow(dead_code)] + type AT = transaction::Bitcoin; + #[allow(dead_code)] + type BT = transaction::Ethereum; + #[allow(dead_code)] + type AcceptBody = + crate::http_api::routes::rfc003::accept::OnlyRefund; _match_role!(role, $fn) } @@ -78,7 +106,20 @@ macro_rules! with_swap_types { #[allow(dead_code)] type BA = asset::Ether; #[allow(dead_code)] - type AcceptBody = crate::http_api::routes::rfc003::accept::OnlyRefund; + type AH = htlc_location::Bitcoin; + #[allow(dead_code)] + type BH = htlc_location::Ethereum; + #[allow(dead_code)] + type AI = identity::Bitcoin; + #[allow(dead_code)] + type BI = identity::Ethereum; + #[allow(dead_code)] + type AT = transaction::Bitcoin; + #[allow(dead_code)] + type BT = transaction::Ethereum; + #[allow(dead_code)] + type AcceptBody = + crate::http_api::routes::rfc003::accept::OnlyRefund; _match_role!(role, $fn) } @@ -100,7 +141,20 @@ macro_rules! with_swap_types { #[allow(dead_code)] type BA = asset::Erc20; #[allow(dead_code)] - type AcceptBody = crate::http_api::routes::rfc003::accept::OnlyRefund; + type AH = htlc_location::Bitcoin; + #[allow(dead_code)] + type BH = htlc_location::Ethereum; + #[allow(dead_code)] + type AI = identity::Bitcoin; + #[allow(dead_code)] + type BI = identity::Ethereum; + #[allow(dead_code)] + type AT = transaction::Bitcoin; + #[allow(dead_code)] + type BT = transaction::Ethereum; + #[allow(dead_code)] + type AcceptBody = + crate::http_api::routes::rfc003::accept::OnlyRefund; _match_role!(role, $fn) } @@ -114,7 +168,20 @@ macro_rules! with_swap_types { #[allow(dead_code)] type BA = asset::Erc20; #[allow(dead_code)] - type AcceptBody = crate::http_api::routes::rfc003::accept::OnlyRefund; + type AH = htlc_location::Bitcoin; + #[allow(dead_code)] + type BH = htlc_location::Ethereum; + #[allow(dead_code)] + type AI = identity::Bitcoin; + #[allow(dead_code)] + type BI = identity::Ethereum; + #[allow(dead_code)] + type AT = transaction::Bitcoin; + #[allow(dead_code)] + type BT = transaction::Ethereum; + #[allow(dead_code)] + type AcceptBody = + crate::http_api::routes::rfc003::accept::OnlyRefund; _match_role!(role, $fn) } @@ -128,7 +195,20 @@ macro_rules! with_swap_types { #[allow(dead_code)] type BA = asset::Erc20; #[allow(dead_code)] - type AcceptBody = crate::http_api::routes::rfc003::accept::OnlyRefund; + type AH = htlc_location::Bitcoin; + #[allow(dead_code)] + type BH = htlc_location::Ethereum; + #[allow(dead_code)] + type AI = identity::Bitcoin; + #[allow(dead_code)] + type BI = identity::Ethereum; + #[allow(dead_code)] + type AT = transaction::Bitcoin; + #[allow(dead_code)] + type BT = transaction::Ethereum; + #[allow(dead_code)] + type AcceptBody = + crate::http_api::routes::rfc003::accept::OnlyRefund; _match_role!(role, $fn) } @@ -151,7 +231,20 @@ macro_rules! with_swap_types { #[allow(dead_code)] type BA = asset::Bitcoin; #[allow(dead_code)] - type AcceptBody = crate::http_api::routes::rfc003::accept::OnlyRedeem; + type AH = htlc_location::Ethereum; + #[allow(dead_code)] + type BH = htlc_location::Bitcoin; + #[allow(dead_code)] + type AI = identity::Ethereum; + #[allow(dead_code)] + type BI = identity::Bitcoin; + #[allow(dead_code)] + type AT = transaction::Ethereum; + #[allow(dead_code)] + type BT = transaction::Bitcoin; + #[allow(dead_code)] + type AcceptBody = + crate::http_api::routes::rfc003::accept::OnlyRedeem; _match_role!(role, $fn) } @@ -165,7 +258,20 @@ macro_rules! with_swap_types { #[allow(dead_code)] type BA = asset::Bitcoin; #[allow(dead_code)] - type AcceptBody = crate::http_api::routes::rfc003::accept::OnlyRedeem; + type AH = htlc_location::Ethereum; + #[allow(dead_code)] + type BH = htlc_location::Bitcoin; + #[allow(dead_code)] + type AI = identity::Ethereum; + #[allow(dead_code)] + type BI = identity::Bitcoin; + #[allow(dead_code)] + type AT = transaction::Ethereum; + #[allow(dead_code)] + type BT = transaction::Bitcoin; + #[allow(dead_code)] + type AcceptBody = + crate::http_api::routes::rfc003::accept::OnlyRedeem; _match_role!(role, $fn) } @@ -179,7 +285,20 @@ macro_rules! with_swap_types { #[allow(dead_code)] type BA = asset::Bitcoin; #[allow(dead_code)] - type AcceptBody = crate::http_api::routes::rfc003::accept::OnlyRedeem; + type AH = htlc_location::Ethereum; + #[allow(dead_code)] + type BH = htlc_location::Bitcoin; + #[allow(dead_code)] + type AI = identity::Ethereum; + #[allow(dead_code)] + type BI = identity::Bitcoin; + #[allow(dead_code)] + type AT = transaction::Ethereum; + #[allow(dead_code)] + type BT = transaction::Bitcoin; + #[allow(dead_code)] + type AcceptBody = + crate::http_api::routes::rfc003::accept::OnlyRedeem; _match_role!(role, $fn) } @@ -201,7 +320,20 @@ macro_rules! with_swap_types { #[allow(dead_code)] type BA = asset::Bitcoin; #[allow(dead_code)] - type AcceptBody = crate::http_api::routes::rfc003::accept::OnlyRedeem; + type AI = identity::Ethereum; + #[allow(dead_code)] + type BI = identity::Bitcoin; + #[allow(dead_code)] + type AH = htlc_location::Ethereum; + #[allow(dead_code)] + type BH = htlc_location::Bitcoin; + #[allow(dead_code)] + type AT = transaction::Ethereum; + #[allow(dead_code)] + type BT = transaction::Bitcoin; + #[allow(dead_code)] + type AcceptBody = + crate::http_api::routes::rfc003::accept::OnlyRedeem; _match_role!(role, $fn) } @@ -215,7 +347,20 @@ macro_rules! with_swap_types { #[allow(dead_code)] type BA = asset::Bitcoin; #[allow(dead_code)] - type AcceptBody = crate::http_api::routes::rfc003::accept::OnlyRedeem; + type AH = htlc_location::Ethereum; + #[allow(dead_code)] + type BH = htlc_location::Bitcoin; + #[allow(dead_code)] + type AI = identity::Ethereum; + #[allow(dead_code)] + type BI = identity::Bitcoin; + #[allow(dead_code)] + type AT = transaction::Ethereum; + #[allow(dead_code)] + type BT = transaction::Bitcoin; + #[allow(dead_code)] + type AcceptBody = + crate::http_api::routes::rfc003::accept::OnlyRedeem; _match_role!(role, $fn) } @@ -229,7 +374,20 @@ macro_rules! with_swap_types { #[allow(dead_code)] type BA = asset::Bitcoin; #[allow(dead_code)] - type AcceptBody = crate::http_api::routes::rfc003::accept::OnlyRedeem; + type AH = htlc_location::Ethereum; + #[allow(dead_code)] + type BH = htlc_location::Bitcoin; + #[allow(dead_code)] + type AI = identity::Ethereum; + #[allow(dead_code)] + type BI = identity::Bitcoin; + #[allow(dead_code)] + type AT = transaction::Ethereum; + #[allow(dead_code)] + type BT = transaction::Bitcoin; + #[allow(dead_code)] + type AcceptBody = + crate::http_api::routes::rfc003::accept::OnlyRedeem; _match_role!(role, $fn) } diff --git a/cnd/src/db/wrapper_types/custom_sql_types.rs b/cnd/src/db/wrapper_types/custom_sql_types.rs index 18d64690e2..8bbe02efd6 100644 --- a/cnd/src/db/wrapper_types/custom_sql_types.rs +++ b/cnd/src/db/wrapper_types/custom_sql_types.rs @@ -27,7 +27,10 @@ where String: ToSql, T: fmt::Display + fmt::Debug, { - fn to_sql(&self, out: &mut Output<'_, W, DB>) -> serialize::Result { + fn to_sql(&self, out: &mut Output<'_, W, DB>) -> serialize::Result + where + W: std::io::Write, + { let s = self.0.to_string(); s.to_sql(out) } @@ -58,7 +61,10 @@ where DB: Backend, i64: ToSql, { - fn to_sql(&self, out: &mut Output<'_, W, DB>) -> serialize::Result { + fn to_sql(&self, out: &mut Output<'_, W, DB>) -> serialize::Result + where + W: std::io::Write, + { let number = i64::try_from(self.0).expect("every u32 fits into a i64"); number.to_sql(out) diff --git a/cnd/src/db/wrapper_types/mod.rs b/cnd/src/db/wrapper_types/mod.rs index ae233064d7..663496c0da 100644 --- a/cnd/src/db/wrapper_types/mod.rs +++ b/cnd/src/db/wrapper_types/mod.rs @@ -1,4 +1,4 @@ -use crate::{asset, asset::Bitcoin, db::LedgerKind, ethereum, swap_protocols::ledger}; +use crate::{asset, asset::Bitcoin, db::LedgerKind, identity, swap_protocols::ledger}; use std::{fmt, str::FromStr}; pub mod custom_sql_types; @@ -101,10 +101,10 @@ impl fmt::Display for Erc20Amount { /// Together with the `Text` sql type, this will store an ethereum address in /// hex encoding. #[derive(Debug, Clone, Copy, PartialEq)] -pub struct EthereumAddress(ethereum::Address); +pub struct EthereumAddress(identity::Ethereum); impl FromStr for EthereumAddress { - type Err = ::Err; + type Err = ::Err; fn from_str(s: &str) -> Result { s.parse().map(EthereumAddress) @@ -117,14 +117,14 @@ impl fmt::Display for EthereumAddress { } } -impl From for ethereum::Address { +impl From for identity::Ethereum { fn from(address: EthereumAddress) -> Self { address.0 } } -impl From for EthereumAddress { - fn from(address: ethereum::Address) -> Self { +impl From for EthereumAddress { + fn from(address: identity::Ethereum) -> Self { EthereumAddress(address) } } diff --git a/cnd/src/ethereum/contract_address.rs b/cnd/src/ethereum/contract_address.rs deleted file mode 100644 index 6313bfdd7a..0000000000 --- a/cnd/src/ethereum/contract_address.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::ethereum::{Address, U256}; -use rlp::RlpStream; -use tiny_keccak::{Hasher, Keccak}; - -pub trait CalculateContractAddress { - fn calculate_contract_address(&self, nonce: &U256) -> Address; -} - -impl CalculateContractAddress for Address { - fn calculate_contract_address(&self, nonce: &U256) -> Address { - let mut stream = RlpStream::new_list(2); - - let ethereum_address: &[u8] = self.as_ref(); - - let raw_stream = stream.append(ðereum_address).append(nonce).as_raw(); - let value = hash(raw_stream); - - let mut address = Address::default(); - address.assign_from_slice(&value[12..]); - address - } -} - -fn hash(input: &[u8]) -> [u8; 32] { - let mut output = [0u8; 32]; - - let mut hasher = Keccak::v256(); - hasher.update(input); - hasher.finalize(&mut output); - - output -} - -#[cfg(test)] -mod tests { - - use super::*; - use crate::ethereum::Address; - use std::str::FromStr; - - #[test] - fn given_an_address_and_a_nonce_should_give_contract_address() { - let address = Address::from_str("0A81e8be41b21f651a71aaB1A85c6813b8bBcCf8").unwrap(); - - let contract_address = address.calculate_contract_address(&U256::from(0)); - assert_eq!( - contract_address, - Address::from_str("ad5768f87c7cb54477cb36d1fc9fdee740810661").unwrap() - ); - - let contract_address = address.calculate_contract_address(&U256::from(1)); - assert_eq!( - contract_address, - Address::from_str("994a1e7928556ba81b85bf3c665a3f4a0f0d4cd9").unwrap() - ); - } -} diff --git a/cnd/src/ethereum/mod.rs b/cnd/src/ethereum/mod.rs index e8c8058ccf..7177f0098f 100644 --- a/cnd/src/ethereum/mod.rs +++ b/cnd/src/ethereum/mod.rs @@ -1,16 +1,142 @@ #![warn(unused_extern_crates, missing_debug_implementations, rust_2018_idioms)] #![forbid(unsafe_code)] -pub use self::contract_address::*; -pub use web3::types::{ - Address, Block, BlockId, BlockNumber, Bytes, Log, Transaction, TransactionReceipt, - TransactionRequest, H160, H2048, H256, U128, U256, -}; - -mod contract_address; - -#[derive(Debug, PartialEq)] -pub struct TransactionAndReceipt { - pub transaction: Transaction, - pub receipt: TransactionReceipt, +pub use ethbloom::{Bloom as H2048, Input}; +pub use primitive_types::{H160, H256, U128, U256}; +use serde::{Deserialize, Serialize}; +use serde_hex::{CompactPfx, SerHex, SerHexSeq, StrictPfx}; + +pub type Address = H160; + +#[derive(Debug, Default, Copy, Clone, PartialEq, Deserialize)] +pub struct H64(#[serde(with = "SerHex::")] [u8; 8]); + +/// "Receipt" of an executed transaction: details of its execution. +#[derive(Debug, Default, Clone, PartialEq, Deserialize)] +pub struct TransactionReceipt { + /// Contract address created, or `None` if not a deployment. + #[serde(rename = "contractAddress")] + pub contract_address: Option, + /// Logs generated within this transaction. + pub logs: Vec, + /// Status: either 1 (success) or 0 (failure). + #[serde(with = "SerHex::")] + pub status: u8, +} + +impl TransactionReceipt { + pub fn is_status_ok(&self) -> bool { + self.status == 1 + } +} + +/// Description of a Transaction, pending or in the chain. +#[derive(Debug, Default, Clone, PartialEq, Deserialize)] +pub struct Transaction { + /// Hash + pub hash: H256, + /// Recipient (None when contract creation) + pub to: Option, + /// Transfered value + pub value: U256, + /// Input data + pub input: Bytes, +} + +/// A log produced by a transaction. +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct Log { + /// H160 + pub address: H160, + /// Topics + pub topics: Vec, + /// Data + pub data: Bytes, +} + +/// The block returned from RPC calls. +/// +/// This type contains only the fields we are actually using. +#[derive(Debug, Default, Clone, PartialEq, Deserialize)] +pub struct Block { + /// Hash of the block + pub hash: Option, + /// Hash of the parent + #[serde(rename = "parentHash")] + pub parent_hash: H256, + /// Logs bloom + #[serde(rename = "logsBloom")] + pub logs_bloom: H2048, + /// Timestamp + pub timestamp: U256, + /// Transactions + pub transactions: Vec, +} + +/// Raw bytes wrapper +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct Bytes(#[serde(with = "SerHexSeq::")] pub Vec); + +impl>> From for Bytes { + fn from(data: T) -> Self { + Bytes(data.into()) + } +} + +#[cfg(test)] +mod tests { + use super::Log; + use crate::ethereum::TransactionReceipt; + + #[test] + fn deserialise_log() { + let json = r#" + { + "address": "0xc5549e335b2786520f4c5d706c76c9ee69d0a028", + "blockHash": "0x3ae3b6ffb04204f52dee42000e8b971c0f7c2b4aa8dd9455e41a30ee4b31e8a9", + "blockNumber": "0x856ca0", + "data": "0x0000000000000000000000000000000000000000000000000000000ba43b7400", + "logIndex": "0x81", + "removed": false, + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000fb303a1fba5b4804863131145bc27256d3ab6692", + "0x000000000000000000000000d50fb7d948426633ec126aeea140ce4dd0979682" + ], + "transactionHash": "0x5ffd218c617f76c73aa49ee636027440b58eb022778c5e75794563c0d60fcb88", + "transactionIndex": "0x93" + }"#; + + let _: Log = serde_json::from_str(json).unwrap(); + } + + #[test] + fn deserialize_receipt_with_status_1() { + let json = r#" + { + "contractAddress": null, + "logs": [], + "status": "0x1" + } + "#; + + let receipt = serde_json::from_str::(json).unwrap(); + + assert_eq!(receipt.status, 1); + } + + #[test] + fn deserialize_receipt_with_status_0() { + let json = r#" + { + "contractAddress": null, + "logs": [], + "status": "0x0" + } + "#; + + let receipt = serde_json::from_str::(json).unwrap(); + + assert_eq!(receipt.status, 0); + } } diff --git a/cnd/src/http_api/action.rs b/cnd/src/http_api/action.rs index f4941ff10c..32aafd31a5 100644 --- a/cnd/src/http_api/action.rs +++ b/cnd/src/http_api/action.rs @@ -1,8 +1,7 @@ use crate::{ asset, - http_api::{ - ethereum_network, problem, Http, MissingQueryParameters, UnexpectedQueryParameters, - }, + http_api::{problem, Http, MissingQueryParameters, UnexpectedQueryParameters}, + identity, swap_protocols::{ actions::{ bitcoin::{SendToAddress, SpendOutput}, @@ -11,12 +10,13 @@ use crate::{ ledger, SwapId, }, timestamp::Timestamp, + transaction, }; use anyhow::Context; use blockchain_contracts::bitcoin::witness; use http_api_problem::HttpApiProblem; use serde::{Deserialize, Serialize}; -use std::convert::{Infallible, TryInto}; +use std::convert::Infallible; use warp::http::StatusCode; pub trait ToSirenAction { @@ -37,7 +37,6 @@ pub enum ActionExecutionParameters { None {}, } -/// `network` field here for backward compatibility, to be removed with #1580 #[derive(Clone, Debug, Serialize)] #[serde(rename_all = "kebab-case")] #[serde(tag = "type", content = "payload")] @@ -57,16 +56,14 @@ pub enum ActionResponseBody { data: crate::ethereum::Bytes, amount: asset::Ether, gas_limit: crate::ethereum::U256, - network: ethereum_network::Network, chain_id: ledger::ethereum::ChainId, }, EthereumCallContract { - contract_address: crate::ethereum::Address, + contract_address: identity::Ethereum, #[serde(skip_serializing_if = "Option::is_none")] data: Option, gas_limit: crate::ethereum::U256, chain_id: ledger::ethereum::ChainId, - network: ethereum_network::Network, #[serde(skip_serializing_if = "Option::is_none")] min_block_timestamp: Option, }, @@ -75,7 +72,7 @@ pub enum ActionResponseBody { impl ActionResponseBody { fn bitcoin_broadcast_signed_transaction( - transaction: &bitcoin::Transaction, + transaction: &transaction::Bitcoin, network: bitcoin::Network, ) -> Self { let min_median_block_time = if transaction.lock_time == 0 { @@ -247,7 +244,6 @@ impl IntoResponsePayload for ethereum::DeployContract { amount, gas_limit, chain_id, - network: chain_id.try_into()?, }), _ => Err(anyhow::Error::from(UnexpectedQueryParameters { action: "ethereum::ContractDeploy", @@ -281,7 +277,6 @@ impl IntoResponsePayload for ethereum::CallContract { data, gas_limit, chain_id, - network: chain_id.try_into()?, min_block_timestamp, }), _ => Err(anyhow::Error::from(UnexpectedQueryParameters { @@ -316,10 +311,7 @@ impl IntoResponsePayload for Infallible { #[cfg(test)] mod test { use super::*; - use crate::{ - ethereum::{Address as EthereumAddress, U256}, - swap_protocols::ledger::ethereum::ChainId, - }; + use crate::{ethereum::U256, identity, swap_protocols::ledger::ethereum::ChainId}; use bitcoin::Address as BitcoinAddress; use std::str::FromStr; @@ -347,20 +339,20 @@ mod test { #[test] fn call_contract_serializes_correctly_to_json_with_none() { - let addr = EthereumAddress::from_str("0A81e8be41b21f651a71aaB1A85c6813b8bBcCf8").unwrap(); + let addr = + identity::Ethereum::from_str("0A81e8be41b21f651a71aaB1A85c6813b8bBcCf8").unwrap(); let chain_id = ChainId::from(3); let contract = ActionResponseBody::EthereumCallContract { contract_address: addr, data: None, gas_limit: U256::from(1), chain_id, - network: chain_id.try_into().unwrap(), min_block_timestamp: None, }; let serialized = serde_json::to_string(&contract).unwrap(); assert_eq!( serialized, - r#"{"type":"ethereum-call-contract","payload":{"contract_address":"0x0a81e8be41b21f651a71aab1a85c6813b8bbccf8","gas_limit":"0x1","chain_id":3,"network":"ropsten"}}"# + r#"{"type":"ethereum-call-contract","payload":{"contract_address":"0x0a81e8be41b21f651a71aab1a85c6813b8bbccf8","gas_limit":"0x1","chain_id":3}}"# ); } diff --git a/cnd/src/http_api/ethereum_network.rs b/cnd/src/http_api/ethereum_network.rs deleted file mode 100644 index da78f87d82..0000000000 --- a/cnd/src/http_api/ethereum_network.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::swap_protocols::ledger::ethereum::ChainId; -use serde::{Deserialize, Serialize}; -use std::convert::TryFrom; - -#[derive( - Clone, - Copy, - Debug, - Eq, - PartialEq, - Deserialize, - Serialize, - Hash, - strum_macros::IntoStaticStr, - strum_macros::Display, -)] -#[serde(rename_all = "lowercase")] -pub enum Network { - #[strum(serialize = "mainnet")] - Mainnet, - #[strum(serialize = "regtest")] - Regtest, - #[strum(serialize = "ropsten")] - Ropsten, -} - -#[derive(Debug, thiserror::Error)] -#[error("chain with id {0} is unknown")] -pub struct UnknownChainId(String); - -impl Network { - pub fn from_chain_id(s: &str) -> Result { - Ok(match s { - "1" => Network::Mainnet, - "3" => Network::Ropsten, - "17" => Network::Regtest, - _ => return Err(UnknownChainId(s.to_string())), - }) - } -} - -impl TryFrom for Network { - type Error = UnknownChainId; - - fn try_from(value: ChainId) -> Result { - let value = u32::from(value).to_string(); - Network::from_chain_id(value.as_str()) - } -} - -impl From for ChainId { - fn from(network: Network) -> Self { - match network { - Network::Mainnet => ChainId::mainnet(), - Network::Regtest => ChainId::regtest(), - Network::Ropsten => ChainId::ropsten(), - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use spectral::prelude::*; - use std::fmt::Display; - - #[test] - fn string_serialize() { - let mainnet: &'static str = Network::Mainnet.into(); - let regtest: &'static str = Network::Regtest.into(); - let ropsten: &'static str = Network::Ropsten.into(); - - assert_eq!(mainnet, "mainnet"); - assert_eq!(regtest, "regtest"); - assert_eq!(ropsten, "ropsten"); - } - - #[test] - fn from_version() { - assert_that(&Network::from_chain_id("1")).is_ok_containing(Network::Mainnet); - assert_that(&Network::from_chain_id("3")).is_ok_containing(Network::Ropsten); - assert_that(&Network::from_chain_id("17")).is_ok_containing(Network::Regtest); - assert_that(&Network::from_chain_id("-1")).is_err(); - } - - fn assert_display(_t: T) {} - - #[test] - fn test_derives_display() { - assert_display(Network::Regtest); - } -} diff --git a/cnd/src/http_api/mod.rs b/cnd/src/http_api/mod.rs index d7e82dbf1c..d237df86ac 100644 --- a/cnd/src/http_api/mod.rs +++ b/cnd/src/http_api/mod.rs @@ -3,7 +3,6 @@ pub mod routes; #[macro_use] pub mod impl_serialize_http; pub mod action; -mod ethereum_network; mod problem; mod swap_resource; @@ -15,12 +14,13 @@ pub use self::{ pub const PATH: &str = "swaps"; use crate::{ - asset, ethereum, + asset, htlc_location, identity, network::DialInformation, swap_protocols::{ - ledger::{self, ethereum::ChainId}, + ledger::{self, bitcoin::Network, ethereum::ChainId, Bitcoin}, SwapId, SwapProtocol, }, + transaction, }; use libp2p::{Multiaddr, PeerId}; use serde::{ @@ -68,7 +68,7 @@ impl<'de> Deserialize<'de> for Http { } } -impl Serialize for Http { +impl Serialize for Http { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -77,7 +77,7 @@ impl Serialize for Http { } } -impl Serialize for Http { +impl Serialize for Http { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -86,7 +86,7 @@ impl Serialize for Http { } } -impl Serialize for Http { +impl Serialize for Http { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -96,7 +96,7 @@ impl Serialize for Http { } } -impl_serialize_type_with_fields!(bitcoin::OutPoint { "txid" => txid, "vout" => vout }); +impl_serialize_type_with_fields!(htlc_location::Bitcoin { "txid" => txid, "vout" => vout }); impl_serialize_http!(crate::ethereum::H160); impl_serialize_http!(SwapId); @@ -240,9 +240,9 @@ impl<'de> Deserialize<'de> for DialInformation { #[serde(try_from = "HttpLedgerParams")] #[serde(into = "HttpLedgerParams")] pub enum HttpLedger { - BitcoinMainnet, - BitcoinTestnet, - BitcoinRegtest, + BitcoinMainnet(ledger::bitcoin::Mainnet), + BitcoinTestnet(ledger::bitcoin::Testnet), + BitcoinRegtest(ledger::bitcoin::Regtest), Ethereum(ledger::Ethereum), } @@ -278,10 +278,10 @@ pub struct BitcoinLedgerParams { network: Http, } -impl From for BitcoinLedgerParams { - fn from(network: bitcoin::Network) -> Self { +impl From for BitcoinLedgerParams { + fn from(_: B) -> Self { BitcoinLedgerParams { - network: Http(network), + network: Http(B::network()), } } } @@ -289,7 +289,6 @@ impl From for BitcoinLedgerParams { #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)] pub struct EthereumLedgerParams { chain_id: Option, - network: Option, } /// The actual enum that is used by serde to deserialize the `alpha_asset` and @@ -320,7 +319,7 @@ pub struct EtherAssetParams { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct Erc20AssetParams { quantity: asset::Erc20Quantity, - token_contract: ethereum::Address, + token_contract: identity::Ethereum, } impl TryFrom for HttpLedger { @@ -329,9 +328,9 @@ impl TryFrom for HttpLedger { fn try_from(params: HttpLedgerParams) -> Result { Ok(match params { HttpLedgerParams::Bitcoin(BitcoinLedgerParams { network }) => match *network { - bitcoin::Network::Bitcoin => HttpLedger::BitcoinMainnet, - bitcoin::Network::Testnet => HttpLedger::BitcoinTestnet, - bitcoin::Network::Regtest => HttpLedger::BitcoinRegtest, + bitcoin::Network::Bitcoin => HttpLedger::BitcoinMainnet(ledger::bitcoin::Mainnet), + bitcoin::Network::Testnet => HttpLedger::BitcoinTestnet(ledger::bitcoin::Testnet), + bitcoin::Network::Regtest => HttpLedger::BitcoinRegtest(ledger::bitcoin::Regtest), }, HttpLedgerParams::Ethereum(params) => HttpLedger::Ethereum(params.try_into()?), }) @@ -341,15 +340,9 @@ impl TryFrom for HttpLedger { impl From for HttpLedgerParams { fn from(ledger: HttpLedger) -> Self { match ledger { - HttpLedger::BitcoinMainnet => { - HttpLedgerParams::Bitcoin(bitcoin::Network::Bitcoin.into()) - } - HttpLedger::BitcoinTestnet => { - HttpLedgerParams::Bitcoin(bitcoin::Network::Testnet.into()) - } - HttpLedger::BitcoinRegtest => { - HttpLedgerParams::Bitcoin(bitcoin::Network::Regtest.into()) - } + HttpLedger::BitcoinMainnet(ledger) => HttpLedgerParams::Bitcoin(ledger.into()), + HttpLedger::BitcoinTestnet(ledger) => HttpLedgerParams::Bitcoin(ledger.into()), + HttpLedger::BitcoinRegtest(ledger) => HttpLedgerParams::Bitcoin(ledger.into()), HttpLedger::Ethereum(ledger) => HttpLedgerParams::Ethereum(ledger.into()), } } @@ -364,18 +357,10 @@ impl TryFrom for ledger::Ethereum { fn try_from(params: EthereumLedgerParams) -> Result { let chain_id = match params { - EthereumLedgerParams { - network: Some(network), - .. - } => network.into(), EthereumLedgerParams { chain_id: Some(chain_id), - .. } => chain_id, - EthereumLedgerParams { - network: None, - chain_id: None, - } => return Err(InvalidEthereumLedgerParams), + EthereumLedgerParams { chain_id: None } => return Err(InvalidEthereumLedgerParams), }; Ok(Self { chain_id }) @@ -387,7 +372,6 @@ impl From for EthereumLedgerParams { let chain_id = ethereum.chain_id; Self { - network: chain_id.try_into().ok(), chain_id: Some(chain_id), } } @@ -458,20 +442,20 @@ impl From for Erc20AssetParams { } impl From for HttpLedger { - fn from(_: ledger::bitcoin::Mainnet) -> Self { - HttpLedger::BitcoinMainnet + fn from(ledger: ledger::bitcoin::Mainnet) -> Self { + HttpLedger::BitcoinMainnet(ledger) } } impl From for HttpLedger { - fn from(_: ledger::bitcoin::Testnet) -> Self { - HttpLedger::BitcoinTestnet + fn from(ledger: ledger::bitcoin::Testnet) -> Self { + HttpLedger::BitcoinTestnet(ledger) } } impl From for HttpLedger { - fn from(_: ledger::bitcoin::Regtest) -> Self { - HttpLedger::BitcoinRegtest + fn from(ledger: ledger::bitcoin::Regtest) -> Self { + HttpLedger::BitcoinRegtest(ledger) } } @@ -511,6 +495,7 @@ mod tests { ledger::{bitcoin, ethereum, Ethereum}, HashFunction, SwapId, SwapProtocol, }, + transaction, }; use ::bitcoin::{ hashes::{hex::FromHex, sha256d}, @@ -578,9 +563,9 @@ mod tests { ]; let expected = &[ - r#"{"name":"ethereum","chain_id":1,"network":"mainnet"}"#, - r#"{"name":"ethereum","chain_id":3,"network":"ropsten"}"#, - r#"{"name":"ethereum","chain_id":17,"network":"regtest"}"#, + r#"{"name":"ethereum","chain_id":1}"#, + r#"{"name":"ethereum","chain_id":3}"#, + r#"{"name":"ethereum","chain_id":17}"#, ]; let actual = input @@ -594,7 +579,7 @@ mod tests { #[test] fn http_transaction_serializes_correctly_to_json() { - let bitcoin_tx = ::bitcoin::Transaction { + let bitcoin_tx = transaction::Bitcoin { version: 1, lock_time: 0, input: vec![TxIn { @@ -605,9 +590,9 @@ mod tests { }], output: vec![], }; - let ethereum_tx = crate::ethereum::Transaction { + let ethereum_tx = transaction::Ethereum { hash: H256::repeat_byte(1), - ..crate::ethereum::Transaction::default() + ..transaction::Ethereum::default() }; let bitcoin_tx = Http(bitcoin_tx); diff --git a/cnd/src/http_api/route_factory.rs b/cnd/src/http_api/route_factory.rs index 3520d344d5..aed055bb7f 100644 --- a/cnd/src/http_api/route_factory.rs +++ b/cnd/src/http_api/route_factory.rs @@ -91,6 +91,38 @@ pub fn create( .and(dependencies) .and_then(http_api::routes::index::get_info); + let han_ether_halight_bitcoin = swaps + .and(warp::path!( + "han" / "ethereum" / "ether" / "halight" / "lightning" / "bitcoin" + )) + .and(warp::post()) + .and(warp::path::end()) + .and_then(http_api::routes::index::post_lightning_route); + + let herc20_erc20_halight_bitcoin = swaps + .and(warp::path!( + "herc20" / "ethereum" / "erc20" / "halight" / "lightning" / "bitcoin" + )) + .and(warp::post()) + .and(warp::path::end()) + .and_then(http_api::routes::index::post_lightning_route); + + let halight_bitcoin_han_ether = swaps + .and(warp::path!( + "halight" / "lightning" / "bitcoin" / "han" / "ethereum" / "ether" + )) + .and(warp::post()) + .and(warp::path::end()) + .and_then(http_api::routes::index::post_lightning_route); + + let halight_bitcoin_herc20_erc20 = swaps + .and(warp::path!( + "halight" / "lightning" / "bitcoin" / "herc20" / "ethereum" / "erc20" + )) + .and(warp::post()) + .and(warp::path::end()) + .and_then(http_api::routes::index::post_lightning_route); + preflight_cors_route .or(rfc003_get_swap) .or(rfc003_post_swap) @@ -99,6 +131,10 @@ pub fn create( .or(get_peers) .or(get_info_siren) .or(get_info) + .or(han_ether_halight_bitcoin) + .or(herc20_erc20_halight_bitcoin) + .or(halight_bitcoin_han_ether) + .or(halight_bitcoin_herc20_erc20) .recover(http_api::unpack_problem) .with(warp::log("http")) .with(cors) diff --git a/cnd/src/http_api/routes/index/handlers/get_swaps.rs b/cnd/src/http_api/routes/index/handlers/get_swaps.rs index 513d676573..74873ce896 100644 --- a/cnd/src/http_api/routes/index/handlers/get_swaps.rs +++ b/cnd/src/http_api/routes/index/handlers/get_swaps.rs @@ -11,7 +11,7 @@ pub async fn handle_get_swaps(dependencies: Facade) -> anyhow::Result Result { .map_err(problem::from_anyhow) .map_err(into_rejection) } + +// `warp::reply::Json` is used as a return type to please the compiler +// until proper logic is implemented +#[allow(clippy::needless_pass_by_value)] +pub async fn post_lightning_route() -> Result { + tracing::error!("Lightning routes are not yet supported"); + Err(warp::reject::custom( + HttpApiProblem::new("Route not yet supported.") + .set_status(StatusCode::BAD_REQUEST) + .set_detail("This route is not yet supported."), + )) +} diff --git a/cnd/src/http_api/routes/rfc003/accept.rs b/cnd/src/http_api/routes/rfc003/accept.rs index 24306b6241..1b2e5aa525 100644 --- a/cnd/src/http_api/routes/rfc003/accept.rs +++ b/cnd/src/http_api/routes/rfc003/accept.rs @@ -1,11 +1,12 @@ use crate::{ http_api::action::ListRequiredFields, + identity, swap_protocols::{ ledger::{bitcoin, Ethereum}, rfc003::{ actions::Accept, messages::{self, IntoAcceptMessage}, - DeriveIdentities, Ledger, + DeriveIdentities, }, SwapId, }, @@ -13,8 +14,8 @@ use crate::{ use serde::Deserialize; #[derive(Deserialize, Clone, Debug)] -pub struct OnlyRedeem { - pub alpha_ledger_redeem_identity: L::Identity, +pub struct OnlyRedeem { + pub alpha_ledger_redeem_identity: I, } impl ListRequiredFields for Accept { @@ -45,13 +46,13 @@ fn ethereum_bitcoin_accept_required_fields() -> Vec { }] } -impl IntoAcceptMessage for OnlyRedeem { +impl IntoAcceptMessage for OnlyRedeem { fn into_accept_message( self, id: SwapId, secret_source: &dyn DeriveIdentities, - ) -> messages::Accept { - let beta_ledger_refund_identity = crate::bitcoin::PublicKey::from_secret_key( + ) -> messages::Accept { + let beta_ledger_refund_identity = identity::Bitcoin::from_secret_key( &*crate::SECP, &secret_source.derive_refund_identity(), ); @@ -64,8 +65,8 @@ impl IntoAcceptMessage for OnlyRedeem { } #[derive(Deserialize, Clone, Debug)] -pub struct OnlyRefund { - pub beta_ledger_refund_identity: L::Identity, +pub struct OnlyRefund { + pub beta_ledger_refund_identity: I, } impl ListRequiredFields for Accept { @@ -96,13 +97,13 @@ fn bitcoin_ethereum_accept_required_fields() -> Vec { }] } -impl IntoAcceptMessage for OnlyRefund { +impl IntoAcceptMessage for OnlyRefund { fn into_accept_message( self, id: SwapId, secret_source: &dyn DeriveIdentities, - ) -> messages::Accept { - let alpha_ledger_redeem_identity = crate::bitcoin::PublicKey::from_secret_key( + ) -> messages::Accept { + let alpha_ledger_redeem_identity = identity::Bitcoin::from_secret_key( &*crate::SECP, &secret_source.derive_redeem_identity(), ); diff --git a/cnd/src/http_api/routes/rfc003/decline.rs b/cnd/src/http_api/routes/rfc003/decline.rs index a6dc645ac5..cd420a47ba 100644 --- a/cnd/src/http_api/routes/rfc003/decline.rs +++ b/cnd/src/http_api/routes/rfc003/decline.rs @@ -1,6 +1,6 @@ use crate::{ http_api::action::ListRequiredFields, - swap_protocols::rfc003::{actions::Decline, messages::SwapDeclineReason, Ledger}, + swap_protocols::rfc003::{actions::Decline, messages::SwapDeclineReason}, }; use serde::Deserialize; @@ -9,7 +9,7 @@ pub struct DeclineBody { pub reason: Option, } -impl ListRequiredFields for Decline { +impl ListRequiredFields for Decline { fn list_required_fields() -> Vec { vec![siren::Field { name: "reason".to_owned(), diff --git a/cnd/src/http_api/routes/rfc003/handlers/action.rs b/cnd/src/http_api/routes/rfc003/handlers/action.rs index d71c7f8d0f..9bbbde3626 100644 --- a/cnd/src/http_api/routes/rfc003/handlers/action.rs +++ b/cnd/src/http_api/routes/rfc003/handlers/action.rs @@ -19,13 +19,14 @@ use crate::{ actions::{Action, ActionKind}, bob::State, messages::{Decision, IntoAcceptMessage}, - state_store::StateStore, }, + state_store::{Get, Insert}, Facade, SwapId, }, }; use anyhow::Context; use libp2p_comit::frame::Response; +use serde::Serialize; use std::{ fmt::{self, Debug, Display}, string::ToString, @@ -44,7 +45,7 @@ pub async fn handle_action( let types = dependencies.determine_types(&swap_id).await?; with_swap_types!(types, { - let state = StateStore::get::(&dependencies, &swap_id)?.ok_or_else(|| { + let state: ROLE = dependencies.get(&swap_id)?.ok_or_else(|| { anyhow::anyhow!("state store did not contain an entry for {}", swap_id) })?; @@ -80,10 +81,16 @@ pub async fn handle_action( ) })?; - let accepted = - LoadAcceptedSwap::::load_accepted_swap(&dependencies, &swap_id) - .await?; - init_accepted_swap(&dependencies, accepted, types.role)?; + let accepted = LoadAcceptedSwap::::load_accepted_swap( + &dependencies, + &swap_id, + ) + .await?; + init_accepted_swap::<_, _, _, _, _, AH, BH, _, _, AT, BT>( + &dependencies, + accepted, + types.role, + )?; Ok(ActionResponseBody::None) } @@ -116,8 +123,12 @@ pub async fn handle_action( let swap_request = state.request(); let seed = dependencies.derive_swap_seed(swap_id); - let state = State::declined(swap_request, decline_message, seed); - StateStore::insert(&dependencies, swap_id, state); + let state = State::::declined( + swap_request.clone(), + decline_message, + seed, + ); + dependencies.insert(swap_id, state); Ok(ActionResponseBody::None) } @@ -180,9 +191,10 @@ trait SelectAction: } } -fn rfc003_accept_response( - message: rfc003::messages::Accept, -) -> Response { +fn rfc003_accept_response(message: rfc003::messages::Accept) -> Response +where + rfc003::messages::AcceptResponseBody: Serialize, +{ Response::empty() .with_header( "decision", @@ -191,7 +203,7 @@ fn rfc003_accept_response( .expect("Decision should not fail to serialize"), ) .with_body( - serde_json::to_value(rfc003::messages::AcceptResponseBody:: { + serde_json::to_value(rfc003::messages::AcceptResponseBody:: { beta_ledger_refund_identity: message.beta_ledger_refund_identity, alpha_ledger_redeem_identity: message.alpha_ledger_redeem_identity, }) diff --git a/cnd/src/http_api/routes/rfc003/handlers/get_swap.rs b/cnd/src/http_api/routes/rfc003/handlers/get_swap.rs index da2001560b..c3667f2c3b 100644 --- a/cnd/src/http_api/routes/rfc003/handlers/get_swap.rs +++ b/cnd/src/http_api/routes/rfc003/handlers/get_swap.rs @@ -8,5 +8,11 @@ pub async fn handle_get_swap(dependencies: Facade, id: SwapId) -> anyhow::Result let swap = Retrieve::get(&dependencies, &id).await?; let types = dependencies.determine_types(&id).await?; - build_rfc003_siren_entity(&dependencies, swap, types, IncludeState::Yes, OnFail::Error) + build_rfc003_siren_entity( + dependencies.state_store.as_ref(), + swap, + types, + IncludeState::Yes, + OnFail::Error, + ) } diff --git a/cnd/src/http_api/routes/rfc003/handlers/post_swap.rs b/cnd/src/http_api/routes/rfc003/handlers/post_swap.rs index 4c25d88587..aebaa14ddf 100644 --- a/cnd/src/http_api/routes/rfc003/handlers/post_swap.rs +++ b/cnd/src/http_api/routes/rfc003/handlers/post_swap.rs @@ -1,50 +1,61 @@ use crate::{ - asset::Asset, db::{LoadAcceptedSwap, Save, Sqlite, Swap}, - ethereum, + htlc_location, http_api::{HttpAsset, HttpLedger}, + identity, init_swap::init_accepted_swap, network::{DialInformation, SendRequest}, seed::DeriveSwapSeed, swap_protocols::{ - ledger::{self}, rfc003::{ - self, - alice::State, + self, alice, events::{HtlcDeployed, HtlcFunded, HtlcRedeemed, HtlcRefunded}, - state_store::StateStore, - Accept, Decline, DeriveIdentities, DeriveSecret, Ledger, Request, SecretHash, + Accept, Decline, DeriveIdentities, DeriveSecret, Request, SecretHash, }, + state_store::Insert, Facade, HashFunction, Role, SwapId, }, timestamp::Timestamp, + transaction, }; use anyhow::Context; -use futures_core::future::TryFutureExt; -use serde::{Deserialize, Serialize}; -use std::str::FromStr; +use futures::future::TryFutureExt; +use libp2p_comit::frame::OutboundRequest; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::{convert::TryInto, fmt::Debug, str::FromStr}; -async fn initiate_request( +async fn initiate_request( dependencies: Facade, id: SwapId, peer: DialInformation, - swap_request: rfc003::Request, + swap_request: rfc003::Request, ) -> anyhow::Result<()> where - Sqlite: Save> + Save> + Save + Save, - AL: Ledger, - BL: Ledger, - AA: Asset, - BA: Asset, - Facade: LoadAcceptedSwap - + HtlcFunded - + HtlcFunded - + HtlcDeployed - + HtlcDeployed - + HtlcRedeemed - + HtlcRedeemed - + HtlcRefunded - + HtlcRefunded, + Sqlite: + Save> + Save> + Save + Save, + AL: Clone + Send + Sync + 'static, + BL: Clone + Send + Sync + 'static, + AA: Clone + Ord + Send + Sync + 'static, + BA: Clone + Ord + Send + Sync + 'static, + AH: Clone + Send + Sync + 'static, + BH: Clone + Send + Sync + 'static, + AI: Clone + Send + Sync + 'static, + BI: Clone + Send + Sync + 'static, + AT: Clone + Send + Sync + 'static, + BT: Clone + Send + Sync + 'static, + rfc003::messages::AcceptResponseBody: DeserializeOwned, + Accept: Copy, + rfc003::Request: TryInto + Clone, + as TryInto>::Error: Debug, + Facade: LoadAcceptedSwap + + HtlcFunded + + HtlcFunded + + HtlcDeployed + + HtlcDeployed + + HtlcRedeemed + + HtlcRedeemed + + HtlcRefunded + + HtlcRefunded, { tracing::trace!("initiating new request: {}", swap_request.swap_id); @@ -54,8 +65,9 @@ where Save::save(&dependencies, Swap::new(id, Role::Alice, counterparty)).await?; Save::save(&dependencies, swap_request.clone()).await?; - let state = State::proposed(swap_request.clone(), seed); - StateStore::insert(&dependencies, id, state); + let state = + alice::State::<_, _, _, _, AH, BH, _, _, AT, BT>::proposed(swap_request.clone(), seed); + dependencies.insert(id, state); let future = { async move { @@ -67,15 +79,25 @@ where match response { Ok(accept) => { Save::save(&dependencies, accept).await?; - let accepted = - LoadAcceptedSwap::::load_accepted_swap(&dependencies, &id) - .await?; - init_accepted_swap(&dependencies, accepted, Role::Alice)?; + let accepted = LoadAcceptedSwap::::load_accepted_swap( + &dependencies, + &id, + ) + .await?; + init_accepted_swap::<_, _, _, _, _, AH, BH, _, _, AT, BT>( + &dependencies, + accepted, + Role::Alice, + )?; } Err(decline) => { tracing::info!("Swap declined: {}", decline.swap_id); - let state = State::declined(swap_request.clone(), decline, seed); - StateStore::insert(&dependencies, id, state.clone()); + let state = alice::State::<_, _, _, _, AH, BH, _, _, AT, BT>::declined( + swap_request.clone(), + decline, + seed, + ); + dependencies.insert(id, state); Save::save(&dependencies, decline).await?; } }; @@ -102,7 +124,7 @@ pub async fn handle_post_swap( match body { SwapRequestBody { - alpha_ledger: HttpLedger::BitcoinMainnet, + alpha_ledger: HttpLedger::BitcoinMainnet(alpha_ledger), beta_ledger: HttpLedger::Ethereum(beta_ledger), alpha_asset: HttpAsset::Bitcoin(alpha_asset), beta_asset: HttpAsset::Ether(beta_asset), @@ -114,7 +136,7 @@ pub async fn handle_post_swap( let identities = identities.into_bitcoin_ethereum_identities(&seed)?; let request = new_request( id, - ledger::bitcoin::Mainnet, + alpha_ledger, beta_ledger, alpha_asset, beta_asset, @@ -123,10 +145,22 @@ pub async fn handle_post_swap( identities, secret_hash, ); - initiate_request(dependencies, id, peer, request).await?; + initiate_request::< + _, + _, + _, + _, + htlc_location::Bitcoin, + htlc_location::Ethereum, + _, + _, + transaction::Bitcoin, + transaction::Ethereum, + >(dependencies, id, peer, request) + .await?; } SwapRequestBody { - alpha_ledger: HttpLedger::BitcoinTestnet, + alpha_ledger: HttpLedger::BitcoinTestnet(alpha_ledger), beta_ledger: HttpLedger::Ethereum(beta_ledger), alpha_asset: HttpAsset::Bitcoin(alpha_asset), beta_asset: HttpAsset::Ether(beta_asset), @@ -138,7 +172,7 @@ pub async fn handle_post_swap( let identities = identities.into_bitcoin_ethereum_identities(&seed)?; let request = new_request( id, - ledger::bitcoin::Testnet, + alpha_ledger, beta_ledger, alpha_asset, beta_asset, @@ -147,10 +181,22 @@ pub async fn handle_post_swap( identities, secret_hash, ); - initiate_request(dependencies, id, peer, request).await?; + initiate_request::< + _, + _, + _, + _, + htlc_location::Bitcoin, + htlc_location::Ethereum, + _, + _, + transaction::Bitcoin, + transaction::Ethereum, + >(dependencies, id, peer, request) + .await?; } SwapRequestBody { - alpha_ledger: HttpLedger::BitcoinRegtest, + alpha_ledger: HttpLedger::BitcoinRegtest(alpha_ledger), beta_ledger: HttpLedger::Ethereum(beta_ledger), alpha_asset: HttpAsset::Bitcoin(alpha_asset), beta_asset: HttpAsset::Ether(beta_asset), @@ -162,7 +208,7 @@ pub async fn handle_post_swap( let identities = identities.into_bitcoin_ethereum_identities(&seed)?; let request = new_request( id, - ledger::bitcoin::Regtest, + alpha_ledger, beta_ledger, alpha_asset, beta_asset, @@ -171,11 +217,23 @@ pub async fn handle_post_swap( identities, secret_hash, ); - initiate_request(dependencies, id, peer, request).await?; + initiate_request::< + _, + _, + _, + _, + htlc_location::Bitcoin, + htlc_location::Ethereum, + _, + _, + transaction::Bitcoin, + transaction::Ethereum, + >(dependencies, id, peer, request) + .await?; } SwapRequestBody { alpha_ledger: HttpLedger::Ethereum(alpha_ledger), - beta_ledger: HttpLedger::BitcoinMainnet, + beta_ledger: HttpLedger::BitcoinMainnet(beta_ledger), alpha_asset: HttpAsset::Ether(alpha_asset), beta_asset: HttpAsset::Bitcoin(beta_asset), alpha_expiry, @@ -187,7 +245,7 @@ pub async fn handle_post_swap( let request = new_request( id, alpha_ledger, - ledger::bitcoin::Mainnet, + beta_ledger, alpha_asset, beta_asset, alpha_expiry, @@ -195,11 +253,23 @@ pub async fn handle_post_swap( identities, secret_hash, ); - initiate_request(dependencies, id, peer, request).await?; + initiate_request::< + _, + _, + _, + _, + htlc_location::Ethereum, + htlc_location::Bitcoin, + _, + _, + transaction::Ethereum, + transaction::Bitcoin, + >(dependencies, id, peer, request) + .await?; } SwapRequestBody { alpha_ledger: HttpLedger::Ethereum(alpha_ledger), - beta_ledger: HttpLedger::BitcoinTestnet, + beta_ledger: HttpLedger::BitcoinTestnet(beta_ledger), alpha_asset: HttpAsset::Ether(alpha_asset), beta_asset: HttpAsset::Bitcoin(beta_asset), alpha_expiry, @@ -211,7 +281,7 @@ pub async fn handle_post_swap( let request = new_request( id, alpha_ledger, - ledger::bitcoin::Testnet, + beta_ledger, alpha_asset, beta_asset, alpha_expiry, @@ -219,11 +289,23 @@ pub async fn handle_post_swap( identities, secret_hash, ); - initiate_request(dependencies, id, peer, request).await?; + initiate_request::< + _, + _, + _, + _, + htlc_location::Ethereum, + htlc_location::Bitcoin, + _, + _, + transaction::Ethereum, + transaction::Bitcoin, + >(dependencies, id, peer, request) + .await?; } SwapRequestBody { alpha_ledger: HttpLedger::Ethereum(alpha_ledger), - beta_ledger: HttpLedger::BitcoinRegtest, + beta_ledger: HttpLedger::BitcoinRegtest(beta_ledger), alpha_asset: HttpAsset::Ether(alpha_asset), beta_asset: HttpAsset::Bitcoin(beta_asset), alpha_expiry, @@ -235,7 +317,7 @@ pub async fn handle_post_swap( let request = new_request( id, alpha_ledger, - ledger::bitcoin::Regtest, + beta_ledger, alpha_asset, beta_asset, alpha_expiry, @@ -243,10 +325,22 @@ pub async fn handle_post_swap( identities, secret_hash, ); - initiate_request(dependencies, id, peer, request).await?; + initiate_request::< + _, + _, + _, + _, + htlc_location::Ethereum, + htlc_location::Bitcoin, + _, + _, + transaction::Ethereum, + transaction::Bitcoin, + >(dependencies, id, peer, request) + .await?; } SwapRequestBody { - alpha_ledger: HttpLedger::BitcoinMainnet, + alpha_ledger: HttpLedger::BitcoinMainnet(alpha_ledger), beta_ledger: HttpLedger::Ethereum(beta_ledger), alpha_asset: HttpAsset::Bitcoin(alpha_asset), beta_asset: HttpAsset::Erc20(beta_asset), @@ -258,7 +352,7 @@ pub async fn handle_post_swap( let identities = identities.into_bitcoin_ethereum_identities(&seed)?; let request = new_request( id, - ledger::bitcoin::Mainnet, + alpha_ledger, beta_ledger, alpha_asset, beta_asset, @@ -267,10 +361,22 @@ pub async fn handle_post_swap( identities, secret_hash, ); - initiate_request(dependencies, id, peer, request).await?; + initiate_request::< + _, + _, + _, + _, + htlc_location::Bitcoin, + htlc_location::Ethereum, + _, + _, + transaction::Bitcoin, + transaction::Ethereum, + >(dependencies, id, peer, request) + .await?; } SwapRequestBody { - alpha_ledger: HttpLedger::BitcoinTestnet, + alpha_ledger: HttpLedger::BitcoinTestnet(alpha_ledger), beta_ledger: HttpLedger::Ethereum(beta_ledger), alpha_asset: HttpAsset::Bitcoin(alpha_asset), beta_asset: HttpAsset::Erc20(beta_asset), @@ -282,7 +388,7 @@ pub async fn handle_post_swap( let identities = identities.into_bitcoin_ethereum_identities(&seed)?; let request = new_request( id, - ledger::bitcoin::Testnet, + alpha_ledger, beta_ledger, alpha_asset, beta_asset, @@ -291,10 +397,22 @@ pub async fn handle_post_swap( identities, secret_hash, ); - initiate_request(dependencies, id, peer, request).await?; + initiate_request::< + _, + _, + _, + _, + htlc_location::Bitcoin, + htlc_location::Ethereum, + _, + _, + transaction::Bitcoin, + transaction::Ethereum, + >(dependencies, id, peer, request) + .await?; } SwapRequestBody { - alpha_ledger: HttpLedger::BitcoinRegtest, + alpha_ledger: HttpLedger::BitcoinRegtest(alpha_ledger), beta_ledger: HttpLedger::Ethereum(beta_ledger), alpha_asset: HttpAsset::Bitcoin(alpha_asset), beta_asset: HttpAsset::Erc20(beta_asset), @@ -306,7 +424,7 @@ pub async fn handle_post_swap( let identities = identities.into_bitcoin_ethereum_identities(&seed)?; let request = new_request( id, - ledger::bitcoin::Regtest, + alpha_ledger, beta_ledger, alpha_asset, beta_asset, @@ -315,11 +433,23 @@ pub async fn handle_post_swap( identities, secret_hash, ); - initiate_request(dependencies, id, peer, request).await?; + initiate_request::< + _, + _, + _, + _, + htlc_location::Bitcoin, + htlc_location::Ethereum, + _, + _, + transaction::Bitcoin, + transaction::Ethereum, + >(dependencies, id, peer, request) + .await?; } SwapRequestBody { alpha_ledger: HttpLedger::Ethereum(alpha_ledger), - beta_ledger: HttpLedger::BitcoinMainnet, + beta_ledger: HttpLedger::BitcoinMainnet(beta_ledger), alpha_asset: HttpAsset::Erc20(alpha_asset), beta_asset: HttpAsset::Bitcoin(beta_asset), alpha_expiry, @@ -331,7 +461,7 @@ pub async fn handle_post_swap( let request = new_request( id, alpha_ledger, - ledger::bitcoin::Mainnet, + beta_ledger, alpha_asset, beta_asset, alpha_expiry, @@ -339,11 +469,23 @@ pub async fn handle_post_swap( identities, secret_hash, ); - initiate_request(dependencies, id, peer, request).await?; + initiate_request::< + _, + _, + _, + _, + htlc_location::Ethereum, + htlc_location::Bitcoin, + _, + _, + transaction::Ethereum, + transaction::Bitcoin, + >(dependencies, id, peer, request) + .await?; } SwapRequestBody { alpha_ledger: HttpLedger::Ethereum(alpha_ledger), - beta_ledger: HttpLedger::BitcoinTestnet, + beta_ledger: HttpLedger::BitcoinTestnet(beta_ledger), alpha_asset: HttpAsset::Erc20(alpha_asset), beta_asset: HttpAsset::Bitcoin(beta_asset), alpha_expiry, @@ -355,7 +497,7 @@ pub async fn handle_post_swap( let request = new_request( id, alpha_ledger, - ledger::bitcoin::Testnet, + beta_ledger, alpha_asset, beta_asset, alpha_expiry, @@ -363,12 +505,23 @@ pub async fn handle_post_swap( identities, secret_hash, ); - initiate_request(dependencies, id, peer, request).await?; + initiate_request::< + _, + _, + _, + _, + htlc_location::Ethereum, + htlc_location::Bitcoin, + _, + _, + transaction::Ethereum, + transaction::Bitcoin, + >(dependencies, id, peer, request) + .await?; } - SwapRequestBody { alpha_ledger: HttpLedger::Ethereum(alpha_ledger), - beta_ledger: HttpLedger::BitcoinRegtest, + beta_ledger: HttpLedger::BitcoinRegtest(beta_ledger), alpha_asset: HttpAsset::Erc20(alpha_asset), beta_asset: HttpAsset::Bitcoin(beta_asset), alpha_expiry, @@ -380,7 +533,7 @@ pub async fn handle_post_swap( let request = new_request( id, alpha_ledger, - ledger::bitcoin::Regtest, + beta_ledger, alpha_asset, beta_asset, alpha_expiry, @@ -388,7 +541,19 @@ pub async fn handle_post_swap( identities, secret_hash, ); - initiate_request(dependencies, id, peer, request).await?; + initiate_request::< + _, + _, + _, + _, + htlc_location::Ethereum, + htlc_location::Bitcoin, + _, + _, + transaction::Ethereum, + transaction::Bitcoin, + >(dependencies, id, peer, request) + .await?; } _ => { @@ -405,7 +570,7 @@ pub async fn handle_post_swap( } #[allow(clippy::too_many_arguments)] -fn new_request( +fn new_request( id: SwapId, alpha_ledger: AL, beta_ledger: BL, @@ -413,15 +578,9 @@ fn new_request( beta_asset: BA, alpha_expiry: Option, beta_expiry: Option, - identities: Identities, + identities: Identities, secret_hash: SecretHash, -) -> rfc003::Request -where - AL: Ledger, - BL: Ledger, - AA: Asset, - BA: Asset, -{ +) -> rfc003::Request { rfc003::Request { swap_id: id, alpha_ledger, @@ -478,15 +637,15 @@ struct SwapRequestBody { /// for now because those are always provided upfront. #[derive(Clone, Debug, Deserialize, PartialEq)] struct HttpIdentities { - alpha_ledger_refund_identity: Option, - beta_ledger_redeem_identity: Option, + alpha_ledger_refund_identity: Option, + beta_ledger_redeem_identity: Option, } impl HttpIdentities { fn into_bitcoin_ethereum_identities( self, secret_source: &dyn DeriveIdentities, - ) -> anyhow::Result> { + ) -> anyhow::Result> { let HttpIdentities { alpha_ledger_refund_identity, beta_ledger_redeem_identity, @@ -507,7 +666,7 @@ impl HttpIdentities { } }; - let alpha_ledger_refund_identity = crate::bitcoin::PublicKey::from_secret_key( + let alpha_ledger_refund_identity = identity::Bitcoin::from_secret_key( &*crate::SECP, &secret_source.derive_refund_identity(), ); @@ -521,7 +680,7 @@ impl HttpIdentities { fn into_ethereum_bitcoin_identities( self, secret_source: &dyn DeriveIdentities, - ) -> anyhow::Result> { + ) -> anyhow::Result> { let HttpIdentities { alpha_ledger_refund_identity, beta_ledger_redeem_identity, @@ -542,7 +701,7 @@ impl HttpIdentities { } }; - let beta_ledger_redeem_identity = crate::bitcoin::PublicKey::from_secret_key( + let beta_ledger_redeem_identity = identity::Bitcoin::from_secret_key( &*crate::SECP, &secret_source.derive_redeem_identity(), ); @@ -576,7 +735,7 @@ pub struct MissingIdentity { #[error("{kind} was not a valid ethereum address")] pub struct InvalidEthereumAddress { kind: IdentityKind, - source: ::Err, + source: ::Err, } #[derive(strum_macros::Display, Debug, Clone, Copy)] @@ -597,7 +756,10 @@ fn default_beta_expiry() -> Timestamp { #[cfg(test)] mod tests { use super::*; - use crate::{network::DialInformation, swap_protocols::ledger::ethereum::ChainId}; + use crate::{ + network::DialInformation, + swap_protocols::ledger::{self, ethereum::ChainId}, + }; use spectral::prelude::*; #[test] @@ -609,7 +771,7 @@ mod tests { }, "beta_ledger": { "name": "ethereum", - "network": "regtest" + "chain_id": 17 }, "alpha_asset": { "name": "bitcoin", @@ -639,7 +801,7 @@ mod tests { }, "beta_ledger": { "name": "ethereum", - "network": "regtest" + "chain_id": 17 }, "alpha_asset": { "name": "bitcoin", @@ -666,40 +828,12 @@ mod tests { .unwrap(), address_hint: Some("/ip4/8.9.0.1/tcp/9999".parse().unwrap()), }); - } - - #[test] - fn can_deserialize_swap_request_body_with_chain_id() { - let body = r#"{ - "alpha_ledger": { - "name": "bitcoin", - "network": "regtest" - }, - "beta_ledger": { - "name": "ethereum", - "chain_id": 3 - }, - "alpha_asset": { - "name": "bitcoin", - "quantity": "100000000" - }, - "beta_asset": { - "name": "ether", - "quantity": "10000000000000000000" - }, - "beta_ledger_redeem_identity": "0x00a329c0648769a73afac7f9381e08fb43dbea72", - "alpha_expiry": 2000000000, - "beta_expiry": 2000000000, - "peer": "Qma9T5YraSnpRDZqRR4krcSJabThc8nwZuJV3LercPHufi" - }"#; - - let body = serde_json::from_str::(body); assert_that(&body) .is_ok() .map(|b| &b.beta_ledger) .is_equal_to(&HttpLedger::Ethereum(ledger::Ethereum { - chain_id: ChainId::from(3), + chain_id: ChainId::from(17), })); } } diff --git a/cnd/src/http_api/routes/rfc003/swap_state.rs b/cnd/src/http_api/routes/rfc003/swap_state.rs index 7e281e44c9..199f985b6b 100644 --- a/cnd/src/http_api/routes/rfc003/swap_state.rs +++ b/cnd/src/http_api/routes/rfc003/swap_state.rs @@ -1,22 +1,19 @@ #![allow(clippy::type_repetition_in_bounds)] use crate::{ - asset::Asset, http_api::{Http, SwapStatus}, - swap_protocols::rfc003::{self, Ledger, SecretHash}, + swap_protocols::rfc003::{self, SecretHash}, timestamp::Timestamp, }; use serde::Serialize; #[derive(Debug, Serialize)] -#[serde( - bound = "Http: Serialize, Http: Serialize,\ - Http: Serialize, Http: Serialize,\ - Http: Serialize, Http: Serialize" -)] -pub struct SwapState { - pub communication: SwapCommunication, - pub alpha_ledger: LedgerState, - pub beta_ledger: LedgerState, +#[serde(bound = "Http: Serialize, Http: Serialize,\ + Http: Serialize, Http: Serialize,\ + Http: Serialize, Http: Serialize")] +pub struct SwapState { + pub communication: SwapCommunication, + pub alpha_ledger: LedgerState, + pub beta_ledger: LedgerState, } #[derive(Debug, Serialize)] @@ -53,10 +50,10 @@ pub enum SwapCommunicationState { Declined, } -impl From> - for SwapCommunication +impl From> + for SwapCommunication { - fn from(communication: rfc003::SwapCommunication) -> Self { + fn from(communication: rfc003::SwapCommunication) -> Self { use rfc003::SwapCommunication::*; match communication { Proposed { request } => Self { @@ -93,10 +90,11 @@ impl From From> - for LedgerState +impl From> for LedgerState +where + rfc003::LedgerState: Clone, { - fn from(ledger_state: rfc003::LedgerState) -> Self { + fn from(ledger_state: rfc003::LedgerState) -> Self { use self::rfc003::LedgerState::*; let status = ledger_state.clone().into(); match ledger_state { diff --git a/cnd/src/http_api/swap_resource.rs b/cnd/src/http_api/swap_resource.rs index 71244583c4..7280322aed 100644 --- a/cnd/src/http_api/swap_resource.rs +++ b/cnd/src/http_api/swap_resource.rs @@ -1,7 +1,6 @@ #![allow(clippy::type_repetition_in_bounds)] use crate::{ - asset, db::{Swap, SwapTypes}, http_api::{ action::ToSirenAction, @@ -11,8 +10,8 @@ use crate::{ }, swap_protocols::{ actions::Actions, - ledger, - rfc003::{self, state_store::StateStore, ActorState}, + rfc003::{self, ActorState}, + state_store::{Get, InMemoryStateStore}, HashFunction, SwapId, SwapProtocol, }, }; @@ -51,58 +50,23 @@ pub enum SwapStatus { InternalFailure, } -macro_rules! impl_from_request_for_swap_parameters { - ($alpha_ledger:ty, $beta_ledger:ty, $alpha_asset:ty, $beta_asset:ty) => { - impl From> - for SwapParameters - { - fn from( - request: rfc003::Request<$alpha_ledger, $beta_ledger, $alpha_asset, $beta_asset>, - ) -> Self { - Self { - alpha_ledger: HttpLedger::from(request.alpha_ledger), - alpha_asset: HttpAsset::from(request.alpha_asset), - beta_ledger: HttpLedger::from(request.beta_ledger), - beta_asset: HttpAsset::from(request.beta_asset), - } - } +impl From> for SwapParameters +where + HttpLedger: From, + HttpLedger: From, + HttpAsset: From, + HttpAsset: From, +{ + fn from(request: rfc003::Request) -> Self { + Self { + alpha_ledger: HttpLedger::from(request.alpha_ledger), + alpha_asset: HttpAsset::from(request.alpha_asset), + beta_ledger: HttpLedger::from(request.beta_ledger), + beta_asset: HttpAsset::from(request.beta_asset), } - }; + } } -macro_rules! impl_from_request_for_swap_parameters_bitcoin { - ($bitcoin_ledger:ty) => { - impl_from_request_for_swap_parameters!( - $bitcoin_ledger, - ledger::Ethereum, - asset::Bitcoin, - asset::Ether - ); - impl_from_request_for_swap_parameters!( - ledger::Ethereum, - $bitcoin_ledger, - asset::Ether, - asset::Bitcoin - ); - impl_from_request_for_swap_parameters!( - $bitcoin_ledger, - ledger::Ethereum, - asset::Bitcoin, - asset::Erc20 - ); - impl_from_request_for_swap_parameters!( - ledger::Ethereum, - $bitcoin_ledger, - asset::Erc20, - asset::Bitcoin - ); - }; -} - -impl_from_request_for_swap_parameters_bitcoin!(ledger::bitcoin::Mainnet); -impl_from_request_for_swap_parameters_bitcoin!(ledger::bitcoin::Testnet); -impl_from_request_for_swap_parameters_bitcoin!(ledger::bitcoin::Regtest); - pub enum IncludeState { Yes, No, @@ -117,8 +81,8 @@ pub enum OnFail { // This is due to the introduction of a trust per Bitcoin network in the // `with_swap_types!` macro and can be iteratively improved #[allow(clippy::cognitive_complexity)] -pub fn build_rfc003_siren_entity( - state_store: &S, +pub fn build_rfc003_siren_entity( + state_store: &InMemoryStateStore, swap: Swap, types: SwapTypes, include_state: IncludeState, @@ -127,8 +91,8 @@ pub fn build_rfc003_siren_entity( let id = swap.swap_id; with_swap_types!(types, { - let state = state_store - .get::(&id)? + let state: ROLE = state_store + .get(&id)? .ok_or_else(|| anyhow!("state store did not contain an entry for {}", id))?; if state.swap_failed() && on_fail == OnFail::Error { @@ -140,7 +104,7 @@ pub fn build_rfc003_siren_entity( let communication = SwapCommunication::from(state.swap_communication.clone()); let alpha_ledger = LedgerState::from(state.alpha_ledger_state.clone()); let beta_ledger = LedgerState::from(state.beta_ledger_state.clone()); - let parameters = SwapParameters::from(state.clone().request()); + let parameters = SwapParameters::from(state.request().clone()); let actions = state.actions(); let status = SwapStatus::new( @@ -157,7 +121,7 @@ pub fn build_rfc003_siren_entity( role: swap.role.to_string(), counterparty: Http(swap.counterparty), state: match include_state { - IncludeState::Yes => Some(SwapState:: { + IncludeState::Yes => Some(SwapState:: { communication, alpha_ledger, beta_ledger, diff --git a/cnd/src/init_swap.rs b/cnd/src/init_swap.rs index fc1569b7cd..9b275cf928 100644 --- a/cnd/src/init_swap.rs +++ b/cnd/src/init_swap.rs @@ -1,38 +1,57 @@ use crate::{ - asset::Asset, db::AcceptedSwap, seed::DeriveSwapSeed, swap_protocols::{ rfc003::{ - alice, bob, create_swap, + alice, bob, create_alpha_watcher, + create_swap::{create_beta_watcher, SwapEvent}, events::{HtlcDeployed, HtlcFunded, HtlcRedeemed, HtlcRefunded}, - state_store::StateStore, - Ledger, + Accept, Request, }, + state_store::{Insert, Update}, Role, }, }; #[allow(clippy::cognitive_complexity)] -pub fn init_accepted_swap( +pub fn init_accepted_swap( dependencies: &D, - accepted: AcceptedSwap, + accepted: AcceptedSwap, role: Role, ) -> anyhow::Result<()> where - D: StateStore - + Clone + D: Insert> + + Insert> + + Update< + alice::State, + SwapEvent, + > + Update< + bob::State, + SwapEvent, + > + Clone + DeriveSwapSeed - + HtlcFunded - + HtlcFunded - + HtlcDeployed - + HtlcDeployed - + HtlcRedeemed - + HtlcRedeemed - + HtlcRefunded - + HtlcRefunded, + + HtlcFunded + + HtlcFunded + + HtlcDeployed + + HtlcDeployed + + HtlcRedeemed + + HtlcRedeemed + + HtlcRefunded + + HtlcRefunded, + AL: Clone + Send + Sync + 'static, + BL: Clone + Send + Sync + 'static, + AA: Ord + Clone + Send + Sync + 'static, + BA: Ord + Clone + Send + Sync + 'static, + AH: Clone + Send + Sync + 'static, + BH: Clone + Send + Sync + 'static, + AI: Clone + Send + Sync + 'static, + BI: Clone + Send + Sync + 'static, + AT: Clone + Send + Sync + 'static, + BT: Clone + Send + Sync + 'static, + Request: Clone, + Accept: Copy, { - let (request, accept, _at) = accepted.clone(); + let (request, accept, _) = &accepted; let id = request.swap_id; let seed = dependencies.derive_swap_seed(id); @@ -40,22 +59,41 @@ where match role { Role::Alice => { - let state = alice::State::accepted(request, accept, seed); - StateStore::insert(dependencies, id, state); + let state = alice::State::accepted(request.clone(), *accept, seed); + dependencies.insert(id, state); - tokio::task::spawn(create_swap::>( - dependencies.clone(), - accepted, - )); + tokio::task::spawn(create_alpha_watcher::< + D, + alice::State, + AI, + BI, + >(dependencies.clone(), accepted.clone())); + + tokio::task::spawn(create_beta_watcher::< + D, + alice::State, + AI, + BI, + >(dependencies.clone(), accepted)); } Role::Bob => { - let state = bob::State::accepted(request, accept, seed); - StateStore::insert(dependencies, id, state); + let state: bob::State = + bob::State::accepted(request.clone(), *accept, seed); + dependencies.insert(id, state); + + tokio::task::spawn(create_alpha_watcher::< + D, + bob::State, + AI, + BI, + >(dependencies.clone(), accepted.clone())); - tokio::task::spawn(create_swap::>( - dependencies.clone(), - accepted, - )); + tokio::task::spawn(create_beta_watcher::< + D, + bob::State, + AI, + BI, + >(dependencies.clone(), accepted)); } }; diff --git a/cnd/src/jsonrpc.rs b/cnd/src/jsonrpc.rs new file mode 100644 index 0000000000..dcb2e560a7 --- /dev/null +++ b/cnd/src/jsonrpc.rs @@ -0,0 +1,80 @@ +use anyhow::Context; +use serde::{de::DeserializeOwned, Serialize}; + +#[derive(Debug)] +pub struct Client { + inner: reqwest::Client, + url: reqwest::Url, +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("json-rpc request failed with code {code}: {message}")] + JsonRpc { code: i64, message: String }, + #[error("connection error")] + Connection(#[from] reqwest::Error), +} + +impl Client { + pub fn new(base_url: reqwest::Url) -> Self { + Self { + inner: reqwest::Client::new(), + url: base_url, + } + } + + pub async fn send(&self, request: Request) -> Result + where + Req: Serialize, + Res: DeserializeOwned, + { + let response = self + .inner + .post(self.url.clone()) + .json(&request) + .send() + .await? + .json::>() + .await?; + + match response { + Response::Success { result } => Ok(result), + Response::Error { code, message } => Err(Error::JsonRpc { code, message }), + } + } +} + +#[derive(serde::Serialize, Debug)] +pub struct Request { + id: String, + jsonrpc: String, + method: String, + params: T, +} + +impl Request { + pub fn new(method: &str, params: T) -> Self { + Self { + id: "1".to_owned(), + jsonrpc: "2.0".to_owned(), + method: method.to_owned(), + params, + } + } +} + +#[derive(serde::Deserialize, Debug)] +#[serde(untagged)] +pub enum Response { + Success { result: T }, + Error { code: i64, message: String }, +} + +pub fn serialize(t: T) -> anyhow::Result +where + T: Serialize, +{ + let value = serde_json::to_value(t).context("failed to serialize parameter")?; + + Ok(value) +} diff --git a/cnd/src/lib.rs b/cnd/src/lib.rs index 07fdcddf46..e122a83dc7 100644 --- a/cnd/src/lib.rs +++ b/cnd/src/lib.rs @@ -44,14 +44,37 @@ pub mod network; pub mod quickcheck; #[macro_use] pub mod seed; +pub mod jsonrpc; #[cfg(test)] pub mod spectral_ext; pub mod swap_protocols; pub mod timestamp; use anyhow::Context; -use directories::ProjectDirs; -use std::path::{Path, PathBuf}; +use std::{ + env, + path::{Path, PathBuf}, +}; + +/// Define domain specific terms using identity module so that we can refer to +/// things in an ergonomic fashion e.g., `identity::Bitcoin`. +pub mod identity { + pub use crate::{bitcoin::PublicKey as Bitcoin, ethereum::Address as Ethereum}; +} + +/// Define domain specific terms using transaction module so that we can refer +/// to things in an ergonomic fashion e.g., `transaction::Ethereum`. +pub mod transaction { + pub use crate::ethereum::Transaction as Ethereum; + pub use bitcoin::Transaction as Bitcoin; +} + +/// Define domain specific terms using htlc_location module so that we can refer +/// to things in an ergonomic fashion e.g., `htlc_location::Bitcoin`. +pub mod htlc_location { + pub use crate::ethereum::Address as Ethereum; + pub use bitcoin::OutPoint as Bitcoin; +} lazy_static::lazy_static! { pub static ref SECP: ::bitcoin::secp256k1::Secp256k1<::bitcoin::secp256k1::All> = @@ -62,7 +85,8 @@ lazy_static::lazy_static! { // Windows: C:\Users\\AppData\Roaming\comit\config\ // OSX: /Users//Library/Preferences/comit/ fn config_dir() -> Option { - ProjectDirs::from("", "", "comit").map(|proj_dirs| proj_dirs.config_dir().to_path_buf()) + directories::ProjectDirs::from("", "", "comit") + .map(|proj_dirs| proj_dirs.config_dir().to_path_buf()) } pub fn default_config_path() -> anyhow::Result { @@ -75,7 +99,30 @@ pub fn default_config_path() -> anyhow::Result { // Windows: C:\Users\\AppData\Roaming\comit\ // OSX: /Users//Library/Application Support/comit/ pub fn data_dir() -> Option { - ProjectDirs::from("", "", "comit").map(|proj_dirs| proj_dirs.data_dir().to_path_buf()) + directories::ProjectDirs::from("", "", "comit") + .map(|proj_dirs| proj_dirs.data_dir().to_path_buf()) +} + +/// Returns `/Users/[username]/Library/Application Support/Lnd/`. +/// exists. +#[cfg(target_os = "macos")] +pub fn lnd_default_dir() -> Option { + directories::ProjectDirs::from("", "", "Lnd") + .map(|proj_dirs| proj_dirs.data_dir().to_path_buf()) +} + +/// Returns `~/.lnd` if $HOME exists. +#[cfg(target_os = "linux")] +pub fn lnd_default_dir() -> Option { + directories::UserDirs::new().map(|d| d.home_dir().to_path_buf().join(".lnd")) +} + +/// Returns the directory used by lnd. +pub fn lnd_dir() -> Option { + if let Ok(dir) = env::var("LND_DIR") { + return Some(PathBuf::from(dir)); + } + lnd_default_dir() } pub type Never = std::convert::Infallible; diff --git a/cnd/src/load_swaps.rs b/cnd/src/load_swaps.rs index 373f71da47..2e259f36ac 100644 --- a/cnd/src/load_swaps.rs +++ b/cnd/src/load_swaps.rs @@ -17,11 +17,14 @@ pub async fn load_swaps_from_database(facade: Facade) -> anyhow::Result<()> { with_swap_types!(types, { let accepted = - LoadAcceptedSwap::::load_accepted_swap(&facade, &swap_id).await; + LoadAcceptedSwap::::load_accepted_swap(&facade, &swap_id) + .await; match accepted { Ok(accepted) => { - init_accepted_swap(&facade, accepted, types.role)?; + init_accepted_swap::<_, _, _, _, _, AH, BH, _, _, AT, BT>( + &facade, accepted, types.role, + )?; } Err(e) => tracing::error!("failed to load swap: {}, continuing ...", e), }; diff --git a/cnd/src/main.rs b/cnd/src/main.rs index 90a7c6c95d..3e6dd64aef 100644 --- a/cnd/src/main.rs +++ b/cnd/src/main.rs @@ -15,20 +15,27 @@ use crate::cli::Options; use anyhow::Context; use cnd::{ - btsieve::{bitcoin, bitcoin::BitcoindConnector, ethereum, ethereum::Web3Connector}, - config::{self, Settings}, + btsieve::{ + bitcoin::{self, BitcoindConnector}, + ethereum::{self, Web3Connector}, + }, + config::{ + self, + validation::{validate_blockchain_config}, + Settings, + }, db::Sqlite, http_api::route_factory, + jsonrpc, load_swaps, network::Swarm, seed::RootSeed, - swap_protocols::{rfc003::state_store::InMemoryStateStore, Facade}, + swap_protocols::{state_store::InMemoryStateStore, Facade}, }; -use futures_core::{FutureExt, TryFutureExt}; use rand::rngs::OsRng; -use std::{net::SocketAddr, process, sync::Arc}; +use std::{process, sync::Arc}; use structopt::StructOpt; -use tokio_compat::runtime; +use tokio::runtime; mod cli; mod trace; @@ -53,25 +60,49 @@ fn main() -> anyhow::Result<()> { let seed = RootSeed::from_dir_or_generate(&settings.data.dir, OsRng)?; let mut runtime = runtime::Builder::new() - .stack_size(1024 * 1024 * 4) // the default is 2MB but that causes a segfault for some reason + .enable_all() + .threaded_scheduler() + .thread_stack_size(1024 * 1024 * 8) // the default is 2MB but that causes a segfault for some reason .build()?; const BITCOIN_BLOCK_CACHE_CAPACITY: usize = 144; let bitcoin_connector = { - let config::Bitcoin { node_url, network } = settings.clone().bitcoin; - bitcoin::Cache::new( - BitcoindConnector::new(node_url, network)?, + let config::Bitcoin { network, bitcoind } = settings.clone().bitcoin; + Arc::new(bitcoin::Cache::new( + BitcoindConnector::new(bitcoind.node_url, network)?, BITCOIN_BLOCK_CACHE_CAPACITY, - ) + )) + }; + + match runtime.block_on(validate_blockchain_config(&bitcoin_connector.connector, settings.bitcoin.network)) { + Ok(_) => (), + Err(e) => { + if e.is::() { + tracing::warn!("Could not validate Bitcoin node config: {}", e) + } else { + return Err(e) + } + } }; const ETHEREUM_BLOCK_CACHE_CAPACITY: usize = 720; const ETHEREUM_RECEIPT_CACHE_CAPACITY: usize = 720; - let ethereum_connector = ethereum::Cache::new( - Web3Connector::new(settings.clone().ethereum.node_url), + let ethereum_connector = Arc::new(ethereum::Cache::new( + Web3Connector::new(settings.clone().ethereum.parity.node_url), ETHEREUM_BLOCK_CACHE_CAPACITY, ETHEREUM_RECEIPT_CACHE_CAPACITY, - ); + )); + + match runtime.block_on(validate_blockchain_config(ðereum_connector.connector, settings.ethereum.chain_id)) { + Ok(_) => (), + Err(e) => { + if e.is::() { + tracing::warn!("Could not validate Ethereum node config: {}", e) + } else { + return Err(e) + } + } + }; let state_store = Arc::new(InMemoryStateStore::default()); @@ -81,28 +112,23 @@ fn main() -> anyhow::Result<()> { &settings, seed, &mut runtime, - &bitcoin_connector, - ðereum_connector, - &state_store, + Arc::clone(&bitcoin_connector), + Arc::clone(ðereum_connector), + Arc::clone(&state_store), &database, )?; let deps = Facade { bitcoin_connector, ethereum_connector, - state_store: Arc::clone(&state_store), + state_store, seed, swarm, db: database, }; - runtime.block_on( - load_swaps::load_swaps_from_database(deps.clone()) - .boxed() - .compat(), - )?; - - runtime.spawn_std(spawn_warp_instance(settings, deps)); + runtime.block_on(load_swaps::load_swaps_from_database(deps.clone()))?; + runtime.spawn(spawn_warp_instance(settings, deps)); // Block the current thread. ::std::thread::park(); @@ -123,10 +149,7 @@ fn version() { async fn spawn_warp_instance(settings: Settings, dependencies: Facade) { let routes = route_factory::create(dependencies, &settings.http_api.cors.allowed_origins); - let listen_addr = SocketAddr::new( - settings.http_api.socket.address, - settings.http_api.socket.port, - ); + let listen_addr = settings.http_api.socket; tracing::info!("Starting HTTP server on {}", listen_addr); diff --git a/cnd/src/network/mod.rs b/cnd/src/network/mod.rs index e6418d786c..62a818c80d 100644 --- a/cnd/src/network/mod.rs +++ b/cnd/src/network/mod.rs @@ -3,77 +3,77 @@ pub mod transport; pub use transport::ComitTransport; use crate::{ - asset::{Asset, AssetKind}, - btsieve::{bitcoin, bitcoin::BitcoindConnector, ethereum, ethereum::Web3Connector}, + asset::AssetKind, + btsieve::{ + bitcoin::{self, BitcoindConnector}, + ethereum::{self, Web3Connector}, + }, comit_api::LedgerKind, config::Settings, db::{Save, Sqlite, Swap}, + htlc_location, libp2p_comit_ext::{FromHeader, ToHeader}, seed::{DeriveSwapSeed, RootSeed}, swap_protocols::{ ledger, rfc003::{ self, bob, - messages::{Decision, DeclineResponseBody, Request, SwapDeclineReason}, - state_store::{InMemoryStateStore, StateStore}, - Ledger, + messages::{Decision, DeclineResponseBody, Request, RequestBody, SwapDeclineReason}, }, + state_store::{InMemoryStateStore, Insert}, HashFunction, Role, SwapId, SwapProtocol, }, + transaction, }; use async_trait::async_trait; use futures::{ - future::Future, - stream::Stream, - sync::oneshot::{self, Sender}, + channel::oneshot::{self, Sender}, + stream::StreamExt, }; -use futures_core::{compat::Future01CompatExt, FutureExt, TryFutureExt}; use libp2p::{ - core::{ - either::{EitherError, EitherOutput}, - muxing::{StreamMuxerBox, SubstreamRef}, - }, - identity::{self, ed25519}, + core::either::{EitherError, EitherOutput}, + identity::{ed25519, Keypair}, mdns::Mdns, swarm::{ - protocols_handler::DummyProtocolsHandler, ExpandedSwarm, IntoProtocolsHandlerSelect, - NetworkBehaviourEventProcess, + protocols_handler::DummyProtocolsHandler, IntoProtocolsHandlerSelect, + NetworkBehaviourEventProcess, SwarmBuilder, }, Multiaddr, NetworkBehaviour, PeerId, }; use libp2p_comit::{ - frame::{self, CodecError, OutboundRequest, Response, ValidatedInboundRequest}, - handler::{ComitHandler, ProtocolInEvent, ProtocolOutEvent}, + frame::{OutboundRequest, Response, ValidatedInboundRequest}, + handler::{self, ComitHandler, ProtocolInEvent, ProtocolOutEvent}, BehaviourOutEvent, Comit, PendingInboundRequest, }; +use serde::{de::DeserializeOwned, Serialize}; use std::{ collections::{HashMap, HashSet}, - fmt::Display, + convert::{TryFrom, TryInto}, + fmt::{Debug, Display}, io, - sync::{Arc, Mutex}, + pin::Pin, + sync::Arc, + task::{Context, Poll}, +}; +use tokio::{ + runtime::{Handle, Runtime}, + sync::Mutex, }; -use tokio_compat::runtime::{Runtime, TaskExecutor}; + +type ExpandedSwarm = libp2p::swarm::ExpandedSwarm< + ComitNode, + EitherOutput, + EitherOutput, + IntoProtocolsHandlerSelect, + EitherError, +>; #[derive(Clone, derivative::Derivative)] #[derivative(Debug)] #[allow(clippy::type_complexity)] pub struct Swarm { #[derivative(Debug = "ignore")] - swarm: Arc< - Mutex< - ExpandedSwarm< - ComitTransport, - ComitNode>>, - EitherOutput, - EitherOutput, - IntoProtocolsHandlerSelect< - ComitHandler>>, - DummyProtocolsHandler>>, - >, - EitherError, - >, - >, - >, + swarm: Arc>, local_peer_id: PeerId, } @@ -82,25 +82,33 @@ impl Swarm { settings: &Settings, seed: RootSeed, runtime: &mut Runtime, - bitcoin_connector: &bitcoin::Cache, - ethereum_connector: ðereum::Cache, - state_store: &Arc, + bitcoin_connector: Arc>, + ethereum_connector: Arc>, + state_store: Arc, database: &Sqlite, ) -> anyhow::Result { let local_key_pair = derive_key_pair(&seed); let local_peer_id = PeerId::from(local_key_pair.clone().public()); tracing::info!("Starting with peer_id: {}", local_peer_id); - let transport = transport::build_comit_transport(local_key_pair); + let transport = transport::build_comit_transport(local_key_pair)?; let behaviour = ComitNode::new( - bitcoin_connector.clone(), - ethereum_connector.clone(), - Arc::clone(&state_store), + bitcoin_connector, + ethereum_connector, + state_store, seed, database.clone(), - runtime.executor(), + runtime.handle().clone(), )?; - let mut swarm = libp2p::Swarm::new(transport, behaviour, local_peer_id.clone()); + + let mut swarm = SwarmBuilder::new(transport, behaviour, local_peer_id.clone()) + .executor_fn({ + let handle = runtime.handle().clone(); + move |task| { + handle.spawn(task); + } + }) + .build(); for addr in settings.network.listen.clone() { libp2p::Swarm::listen_on(&mut swarm, addr) @@ -109,17 +117,10 @@ impl Swarm { let swarm = Arc::new(Mutex::new(swarm)); - let swarm_worker = futures::stream::poll_fn({ - let swarm = swarm.clone(); - move || swarm.lock().unwrap().poll() - }) - .for_each(|_| Ok(())) - .map_err(|e| { - tracing::error!("failed with {:?}", e); + runtime.spawn(SwarmWorker { + swarm: swarm.clone(), }); - runtime.spawn(swarm_worker); - Ok(Self { swarm, local_peer_id, @@ -127,22 +128,40 @@ impl Swarm { } } -fn derive_key_pair(seed: &RootSeed) -> identity::Keypair { +struct SwarmWorker { + swarm: Arc>, +} + +impl futures::Future for SwarmWorker { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + loop { + let mutex = self.swarm.lock(); + futures::pin_mut!(mutex); + + let mut guard = futures::ready!(mutex.poll(cx)); + futures::ready!(guard.poll_next_unpin(cx)); + } + } +} + +fn derive_key_pair(seed: &RootSeed) -> Keypair { let bytes = seed.sha256_with_seed(&[b"NODE_ID"]); let key = ed25519::SecretKey::from_bytes(bytes).expect("we always pass 32 bytes"); - identity::Keypair::Ed25519(key.into()) + Keypair::Ed25519(key.into()) } #[derive(NetworkBehaviour)] #[allow(missing_debug_implementations)] -pub struct ComitNode { - comit: Comit, - mdns: Mdns, +pub struct ComitNode { + comit: Comit, + mdns: Mdns, #[behaviour(ignore)] - pub bitcoin_connector: bitcoin::Cache, + pub bitcoin_connector: Arc>, #[behaviour(ignore)] - pub ethereum_connector: ethereum::Cache, + pub ethereum_connector: Arc>, #[behaviour(ignore)] pub state_store: Arc, #[behaviour(ignore)] @@ -152,7 +171,7 @@ pub struct ComitNode { #[behaviour(ignore)] response_channels: Arc>>>, #[behaviour(ignore)] - task_executor: TaskExecutor, + task_executor: Handle, } #[derive(Clone, Debug, PartialEq)] @@ -187,14 +206,14 @@ pub struct Reason { pub value: SwapDeclineReason, } -impl ComitNode { +impl ComitNode { pub fn new( - bitcoin_connector: bitcoin::Cache, - ethereum_connector: ethereum::Cache, + bitcoin_connector: Arc>, + ethereum_connector: Arc>, state_store: Arc, seed: RootSeed, db: Sqlite, - task_executor: TaskExecutor, + task_executor: Handle, ) -> Result { let mut swap_headers = HashSet::new(); swap_headers.insert("id".into()); @@ -224,7 +243,7 @@ impl ComitNode { &mut self, peer_id: DialInformation, request: OutboundRequest, - ) -> Box + Send> { + ) -> impl futures::Future> + Send + 'static + Unpin { self.comit .send_request((peer_id.peer_id, peer_id.address_hint), request) } @@ -277,12 +296,20 @@ async fn handle_request( hash_function, body!(request.take_body_as()), ); - insert_state_for_bob( - db.clone(), - seed, - state_store.clone(), - counterparty, - request, + insert_state_for_bob::< + _, + _, + _, + _, + htlc_location::Bitcoin, + htlc_location::Ethereum, + _, + _, + transaction::Bitcoin, + transaction::Ethereum, + _, + >( + db.clone(), seed, state_store.clone(), counterparty, request ) .await .expect("Could not save state to db"); @@ -303,12 +330,20 @@ async fn handle_request( hash_function, body!(request.take_body_as()), ); - insert_state_for_bob( - db.clone(), - seed, - state_store.clone(), - counterparty, - request, + insert_state_for_bob::< + _, + _, + _, + _, + htlc_location::Bitcoin, + htlc_location::Ethereum, + _, + _, + transaction::Bitcoin, + transaction::Ethereum, + _, + >( + db.clone(), seed, state_store.clone(), counterparty, request ) .await .expect("Could not save state to db"); @@ -329,12 +364,20 @@ async fn handle_request( hash_function, body!(request.take_body_as()), ); - insert_state_for_bob( - db.clone(), - seed, - state_store.clone(), - counterparty, - request, + insert_state_for_bob::< + _, + _, + _, + _, + htlc_location::Bitcoin, + htlc_location::Ethereum, + _, + _, + transaction::Bitcoin, + transaction::Ethereum, + _, + >( + db.clone(), seed, state_store.clone(), counterparty, request ) .await .expect("Could not save state to db"); @@ -355,12 +398,20 @@ async fn handle_request( hash_function, body!(request.take_body_as()), ); - insert_state_for_bob( - db.clone(), - seed, - state_store.clone(), - counterparty, - request, + insert_state_for_bob::< + _, + _, + _, + _, + htlc_location::Ethereum, + htlc_location::Bitcoin, + _, + _, + transaction::Ethereum, + transaction::Bitcoin, + _, + >( + db.clone(), seed, state_store.clone(), counterparty, request ) .await .expect("Could not save state to db"); @@ -381,12 +432,20 @@ async fn handle_request( hash_function, body!(request.take_body_as()), ); - insert_state_for_bob( - db.clone(), - seed, - state_store.clone(), - counterparty, - request, + insert_state_for_bob::< + _, + _, + _, + _, + htlc_location::Ethereum, + htlc_location::Bitcoin, + _, + _, + transaction::Ethereum, + transaction::Bitcoin, + _, + >( + db.clone(), seed, state_store.clone(), counterparty, request ) .await .expect("Could not save state to db"); @@ -407,12 +466,20 @@ async fn handle_request( hash_function, body!(request.take_body_as()), ); - insert_state_for_bob( - db.clone(), - seed, - state_store.clone(), - counterparty, - request, + insert_state_for_bob::< + _, + _, + _, + _, + htlc_location::Ethereum, + htlc_location::Bitcoin, + _, + _, + transaction::Ethereum, + transaction::Bitcoin, + _, + >( + db.clone(), seed, state_store.clone(), counterparty, request ) .await .expect("Could not save state to db"); @@ -433,12 +500,20 @@ async fn handle_request( hash_function, body!(request.take_body_as()), ); - insert_state_for_bob( - db.clone(), - seed, - state_store.clone(), - counterparty, - request, + insert_state_for_bob::< + _, + _, + _, + _, + htlc_location::Bitcoin, + htlc_location::Ethereum, + _, + _, + transaction::Bitcoin, + transaction::Ethereum, + _, + >( + db.clone(), seed, state_store.clone(), counterparty, request ) .await .expect("Could not save state to db"); @@ -460,12 +535,20 @@ async fn handle_request( hash_function, body!(request.take_body_as()), ); - insert_state_for_bob( - db.clone(), - seed, - state_store.clone(), - counterparty, - request, + insert_state_for_bob::< + _, + _, + _, + _, + htlc_location::Bitcoin, + htlc_location::Ethereum, + _, + _, + transaction::Bitcoin, + transaction::Ethereum, + _, + >( + db.clone(), seed, state_store.clone(), counterparty, request ) .await .expect("Could not save state to db"); @@ -487,12 +570,20 @@ async fn handle_request( hash_function, body!(request.take_body_as()), ); - insert_state_for_bob( - db.clone(), - seed, - state_store.clone(), - counterparty, - request, + insert_state_for_bob::< + _, + _, + _, + _, + htlc_location::Ethereum, + htlc_location::Bitcoin, + _, + _, + transaction::Ethereum, + transaction::Bitcoin, + _, + >( + db.clone(), seed, state_store.clone(), counterparty, request ) .await .expect("Could not save state to db"); @@ -514,12 +605,20 @@ async fn handle_request( hash_function, body!(request.take_body_as()), ); - insert_state_for_bob( - db.clone(), - seed, - state_store.clone(), - counterparty, - request, + insert_state_for_bob::< + _, + _, + _, + _, + htlc_location::Ethereum, + htlc_location::Bitcoin, + _, + _, + transaction::Ethereum, + transaction::Bitcoin, + _, + >( + db.clone(), seed, state_store.clone(), counterparty, request ) .await .expect("Could not save state to db"); @@ -540,12 +639,20 @@ async fn handle_request( hash_function, body!(request.take_body_as()), ); - insert_state_for_bob( - db.clone(), - seed, - state_store.clone(), - counterparty, - request, + insert_state_for_bob::< + _, + _, + _, + _, + htlc_location::Ethereum, + htlc_location::Bitcoin, + _, + _, + transaction::Ethereum, + transaction::Bitcoin, + _, + >( + db.clone(), seed, state_store.clone(), counterparty, request ) .await .expect("Could not save state to db"); @@ -566,12 +673,20 @@ async fn handle_request( hash_function, body!(request.take_body_as()), ); - insert_state_for_bob( - db.clone(), - seed, - state_store.clone(), - counterparty, - request, + insert_state_for_bob::< + _, + _, + _, + _, + htlc_location::Ethereum, + htlc_location::Bitcoin, + _, + _, + transaction::Ethereum, + transaction::Bitcoin, + _, + >( + db.clone(), seed, state_store.clone(), counterparty, request ) .await .expect("Could not save state to db"); @@ -620,15 +735,27 @@ async fn handle_request( } #[allow(clippy::type_complexity)] -async fn insert_state_for_bob( +async fn insert_state_for_bob( db: DB, seed: RootSeed, state_store: Arc, counterparty: PeerId, - swap_request: Request, + swap_request: Request, ) -> anyhow::Result<()> where - DB: Save> + Save, + AL: Send + 'static, + BL: Send + 'static, + AA: Ord + Send + 'static, + BA: Ord + Send + 'static, + AH: Send + 'static, + BH: Send + 'static, + AI: Send + 'static, + BI: Send + 'static, + AT: Send + 'static, + BT: Send + 'static, + DB: Save> + Save, + Request: Clone, + bob::State: Clone + Sync, { let id = swap_request.swap_id; let seed = seed.derive_swap_seed(id); @@ -636,7 +763,8 @@ where Save::save(&db, Swap::new(id, Role::Bob, counterparty)).await?; Save::save(&db, swap_request.clone()).await?; - let state = bob::State::proposed(swap_request.clone(), seed); + let state = + bob::State::<_, _, _, _, AH, BH, _, _, AT, BT>::proposed(swap_request.clone(), seed); state_store.insert(id, state); Ok(()) @@ -669,7 +797,7 @@ impl ComitPeers for Swarm { async fn comit_peers( &self, ) -> Box)> + Send + 'static> { - let mut swarm = self.swarm.lock().unwrap(); + let mut swarm = self.swarm.lock().await; Box::new(swarm.comit.connected_peers()) } } @@ -684,7 +812,7 @@ pub trait ListenAddresses { #[async_trait] impl ListenAddresses for Swarm { async fn listen_addresses(&self) -> Vec { - let swarm = self.swarm.lock().unwrap(); + let swarm = self.swarm.lock().await; libp2p::Swarm::listeners(&swarm) .chain(libp2p::Swarm::external_addresses(&swarm)) @@ -697,14 +825,14 @@ impl ListenAddresses for Swarm { #[async_trait] #[ambassador::delegatable_trait] pub trait PendingRequestFor { - async fn pending_request_for(&self, swap: SwapId) -> Option>; + async fn pending_request_for(&self, swap: SwapId) -> Option>; } #[async_trait] impl PendingRequestFor for Swarm { async fn pending_request_for(&self, swap: SwapId) -> Option> { - let swarm = self.swarm.lock().unwrap(); - let mut response_channels = swarm.response_channels.lock().unwrap(); + let swarm = self.swarm.lock().await; + let mut response_channels = swarm.response_channels.lock().await; response_channels.remove(&swap) } } @@ -712,26 +840,36 @@ impl PendingRequestFor for Swarm { /// Send swap request to connected peer. #[async_trait] pub trait SendRequest { - async fn send_request( + async fn send_request( &self, peer_identity: DialInformation, - request: rfc003::Request, - ) -> Result, RequestError>; + request: rfc003::Request, + ) -> Result, RequestError> + where + rfc003::messages::AcceptResponseBody: DeserializeOwned, + rfc003::Request: TryInto + Send + 'static + Clone, + as TryInto>::Error: Debug; } #[async_trait] impl SendRequest for Swarm { - async fn send_request( + async fn send_request( &self, dial_information: DialInformation, - request: rfc003::Request, - ) -> Result, RequestError> { + request: rfc003::Request, + ) -> Result, RequestError> + where + rfc003::messages::AcceptResponseBody: DeserializeOwned, + rfc003::Request: TryInto + Send + 'static + Clone, + as TryInto>::Error: Debug, + { let id = request.swap_id; - let request = build_outbound_request(request) + let request = request + .try_into() .expect("constructing a frame::OutoingRequest should never fail!"); let result = { - let mut guard = self.swarm.lock().unwrap(); + let mut guard = self.swarm.lock().await; let swarm = &mut *guard; tracing::debug!( @@ -740,9 +878,7 @@ impl SendRequest for Swarm { id, ); - swarm - .send_request(dial_information.clone(), request) - .compat() + swarm.send_request(dial_information.clone(), request) } .await; @@ -763,7 +899,7 @@ impl SendRequest for Swarm { match decision { Some(Decision::Accepted) => { - match serde_json::from_value::>( + match serde_json::from_value::>( response.body().clone(), ) { Ok(body) => Ok(Ok(rfc003::Accept { @@ -802,58 +938,88 @@ impl SendRequest for Swarm { } } -impl NetworkBehaviourEventProcess for ComitNode { +impl NetworkBehaviourEventProcess for ComitNode { fn inject_event(&mut self, event: BehaviourOutEvent) { match event { BehaviourOutEvent::PendingInboundRequest { request, peer_id } => { let PendingInboundRequest { request, channel } = request; - self.task_executor.spawn( - handle_request( - self.db.clone(), - self.seed, - self.state_store.clone(), - peer_id, - request, - ) - .boxed() - .compat() - .then({ - let response_channels = self.response_channels.clone(); - - move |result| { - match result { - Ok(id) => { - let mut response_channels = response_channels.lock().unwrap(); - response_channels.insert(id, channel); - } - Err(response) => channel.send(response).unwrap_or_else(|_| { - tracing::debug!("failed to send response through channel") - }), - } - Ok(()) + let response_channels = self.response_channels.clone(); + let db = self.db.clone(); + let state_store = self.state_store.clone(); + let seed = self.seed; + + self.task_executor.spawn(async move { + match handle_request(db, seed, state_store, peer_id, request).await { + Ok(id) => { + let mut response_channels = response_channels.lock().await; + response_channels.insert(id, channel); } - }), - ); + Err(response) => channel.send(response).unwrap_or_else(|_| { + tracing::debug!("failed to send response through channel") + }), + } + }); } } } } -impl NetworkBehaviourEventProcess for ComitNode { +impl NetworkBehaviourEventProcess for ComitNode { fn inject_event(&mut self, _event: libp2p::mdns::MdnsEvent) {} } -fn rfc003_swap_request( +impl TryFrom> for OutboundRequest +where + RequestBody: From> + Serialize, + LedgerKind: From + From, + AssetKind: From + From, + Request: Clone, +{ + type Error = anyhow::Error; + + fn try_from(request: Request) -> anyhow::Result { + let request_body = RequestBody::from(request.clone()); + let protocol = SwapProtocol::Rfc003(request.hash_function).to_header()?; + + let alpha_ledger = LedgerKind::from(request.alpha_ledger).to_header()?; + let beta_ledger = LedgerKind::from(request.beta_ledger).to_header()?; + let alpha_asset = AssetKind::from(request.alpha_asset).to_header()?; + let beta_asset = AssetKind::from(request.beta_asset).to_header()?; + + Ok(OutboundRequest::new("SWAP") + .with_header("id", request.swap_id.to_header()?) + .with_header("alpha_ledger", alpha_ledger) + .with_header("beta_ledger", beta_ledger) + .with_header("alpha_asset", alpha_asset) + .with_header("beta_asset", beta_asset) + .with_header("protocol", protocol) + .with_body(serde_json::to_value(request_body)?)) + } +} + +impl From> for RequestBody { + fn from(request: Request) -> Self { + RequestBody { + alpha_ledger_refund_identity: request.alpha_ledger_refund_identity, + beta_ledger_redeem_identity: request.beta_ledger_redeem_identity, + alpha_expiry: request.alpha_expiry, + beta_expiry: request.beta_expiry, + secret_hash: request.secret_hash, + } + } +} + +fn rfc003_swap_request( id: SwapId, alpha_ledger: AL, beta_ledger: BL, alpha_asset: AA, beta_asset: BA, hash_function: HashFunction, - body: rfc003::messages::RequestBody, -) -> rfc003::Request { - rfc003::Request:: { + body: rfc003::messages::RequestBody, +) -> rfc003::Request { + rfc003::Request { swap_id: id, alpha_asset, beta_asset, @@ -867,32 +1033,3 @@ fn rfc003_swap_request( - request: rfc003::Request, -) -> Result { - let alpha_ledger_refund_identity = request.alpha_ledger_refund_identity; - let beta_ledger_redeem_identity = request.beta_ledger_redeem_identity; - let alpha_expiry = request.alpha_expiry; - let beta_expiry = request.beta_expiry; - let secret_hash = request.secret_hash; - let protocol = SwapProtocol::Rfc003(request.hash_function); - - Ok(frame::OutboundRequest::new("SWAP") - .with_header("id", request.swap_id.to_header()?) - .with_header("alpha_ledger", request.alpha_ledger.into().to_header()?) - .with_header("beta_ledger", request.beta_ledger.into().to_header()?) - .with_header("alpha_asset", request.alpha_asset.into().to_header()?) - .with_header("beta_asset", request.beta_asset.into().to_header()?) - .with_header("protocol", protocol.to_header()?) - .with_body(serde_json::to_value(rfc003::messages::RequestBody::< - AL, - BL, - > { - alpha_ledger_refund_identity, - beta_ledger_redeem_identity, - alpha_expiry, - beta_expiry, - secret_hash, - })?)) -} diff --git a/cnd/src/network/transport.rs b/cnd/src/network/transport.rs index 7db50a9d0c..0610f4e17e 100644 --- a/cnd/src/network/transport.rs +++ b/cnd/src/network/transport.rs @@ -30,11 +30,11 @@ pub type ComitTransport = Boxed< /// - DNS name resolution /// - authentication via secio /// - multiplexing via yamux or mplex -pub fn build_comit_transport(keypair: identity::Keypair) -> ComitTransport { +pub fn build_comit_transport(keypair: identity::Keypair) -> anyhow::Result { let transport = TcpConfig::new().nodelay(true); - let transport = DnsConfig::new(transport); + let transport = DnsConfig::new(transport)?; - transport + let transport = transport .upgrade(Version::V1) .authenticate(SecioConfig::new(keypair)) .multiplex(SelectUpgrade::new( @@ -43,5 +43,7 @@ pub fn build_comit_transport(keypair: identity::Keypair) -> ComitTransport { )) .map(|(peer, muxer), _| (peer, StreamMuxerBox::new(muxer))) .timeout(Duration::from_secs(20)) - .boxed() + .boxed(); + + Ok(transport) } diff --git a/cnd/src/quickcheck.rs b/cnd/src/quickcheck.rs index 1addd5a9ae..d996553e5e 100644 --- a/cnd/src/quickcheck.rs +++ b/cnd/src/quickcheck.rs @@ -3,17 +3,21 @@ use crate::{ asset::ethereum::FromWei, db::Swap, ethereum::Bytes, + identity, swap_protocols::{ - ledger::{self, bitcoin, ethereum::ChainId}, + ledger, + ledger::{bitcoin, ethereum::ChainId}, rfc003::{Accept, Request, SecretHash}, HashFunction, Role, SwapId, }, timestamp::Timestamp, + transaction, }; use ::bitcoin::{ hashes::{sha256d, Hash}, secp256k1, }; +use impl_template::impl_template; use libp2p::PeerId; use quickcheck::{Arbitrary, Gen}; use std::ops::Deref; @@ -130,7 +134,7 @@ impl Arbitrary for Quickcheck { impl Arbitrary for Quickcheck { fn arbitrary(g: &mut G) -> Self { - let token_contract = *Quickcheck::::arbitrary(g); + let token_contract = *Quickcheck::::arbitrary(g); let quantity = Quickcheck::::arbitrary(g).0; let erc20_token = crate::asset::Erc20 { token_contract, @@ -147,25 +151,23 @@ impl Arbitrary for Quickcheck { } } -impl Arbitrary for Quickcheck { +impl Arbitrary for Quickcheck { fn arbitrary(g: &mut G) -> Self { let bytes = *Quickcheck::<[u8; 32]>::arbitrary(g); let secret_key = secp256k1::SecretKey::from_slice(&bytes).expect("all bytes are a valid secret key"); - let public_key = crate::bitcoin::PublicKey::from_secret_key( - &secp256k1::Secp256k1::signing_only(), - &secret_key, - ); + let public_key = + identity::Bitcoin::from_secret_key(&secp256k1::Secp256k1::signing_only(), &secret_key); Quickcheck(public_key) } } -impl Arbitrary for Quickcheck { +impl Arbitrary for Quickcheck { fn arbitrary(g: &mut G) -> Self { let bytes = *Quickcheck::<[u8; 20]>::arbitrary(g); - Quickcheck(crate::ethereum::Address::from(bytes)) + Quickcheck(identity::Ethereum::from(bytes)) } } @@ -185,20 +187,12 @@ impl Arbitrary for Quickcheck { } } -impl Arbitrary for Quickcheck { +impl Arbitrary for Quickcheck { fn arbitrary(g: &mut G) -> Self { - Quickcheck(crate::ethereum::Transaction { + Quickcheck(transaction::Ethereum { hash: *Quickcheck::::arbitrary(g), - nonce: *Quickcheck::::arbitrary(g), - block_hash: Option::>::arbitrary(g).map(|i| i.0), - block_number: Option::>::arbitrary(g).map(|i| i.0), - transaction_index: Option::>::arbitrary(g) - .map(|i| i.0), - from: *Quickcheck::::arbitrary(g), to: Option::>::arbitrary(g).map(|i| i.0), value: *Quickcheck::::arbitrary(g), - gas_price: *Quickcheck::::arbitrary(g), - gas: *Quickcheck::::arbitrary(g), input: Bytes(Arbitrary::arbitrary(g)), }) } @@ -220,119 +214,42 @@ impl Arbitrary for Quickcheck { } } -impl Arbitrary - for Quickcheck< - Request, - > -{ - fn arbitrary(g: &mut G) -> Self { - Quickcheck(Request { - swap_id: *Quickcheck::::arbitrary(g), - alpha_ledger: ledger::bitcoin::Regtest, - beta_ledger: ledger::Ethereum { - chain_id: *Quickcheck::::arbitrary(g), - }, - alpha_asset: *Quickcheck::::arbitrary(g), - beta_asset: Quickcheck::::arbitrary(g).0, - hash_function: *Quickcheck::::arbitrary(g), - alpha_ledger_refund_identity: *Quickcheck::::arbitrary(g), - beta_ledger_redeem_identity: *Quickcheck::::arbitrary(g), - alpha_expiry: *Quickcheck::::arbitrary(g), - beta_expiry: *Quickcheck::::arbitrary(g), - secret_hash: *Quickcheck::::arbitrary(g), - }) - } -} - -impl Arbitrary - for Quickcheck< - Request, - > -{ - fn arbitrary(g: &mut G) -> Self { - Quickcheck(Request { - swap_id: *Quickcheck::::arbitrary(g), - alpha_ledger: ledger::bitcoin::Testnet, - beta_ledger: ledger::Ethereum { - chain_id: *Quickcheck::::arbitrary(g), - }, - alpha_asset: *Quickcheck::::arbitrary(g), - beta_asset: Quickcheck::::arbitrary(g).0, - hash_function: *Quickcheck::::arbitrary(g), - alpha_ledger_refund_identity: *Quickcheck::::arbitrary(g), - beta_ledger_redeem_identity: *Quickcheck::::arbitrary(g), - alpha_expiry: *Quickcheck::::arbitrary(g), - beta_expiry: *Quickcheck::::arbitrary(g), - secret_hash: *Quickcheck::::arbitrary(g), - }) +#[impl_template] +impl Arbitrary for Quickcheck<((bitcoin::Mainnet, bitcoin::Testnet, bitcoin::Regtest))> { + fn arbitrary(_g: &mut G) -> Self { + Quickcheck(__TYPE0__) } } -impl Arbitrary - for Quickcheck< - Request, - > -{ +impl Arbitrary for Quickcheck { fn arbitrary(g: &mut G) -> Self { - Quickcheck(Request { - swap_id: *Quickcheck::::arbitrary(g), - alpha_ledger: ledger::bitcoin::Mainnet, - beta_ledger: ledger::Ethereum { - chain_id: *Quickcheck::::arbitrary(g), - }, - alpha_asset: *Quickcheck::::arbitrary(g), - beta_asset: Quickcheck::::arbitrary(g).0, - hash_function: *Quickcheck::::arbitrary(g), - alpha_ledger_refund_identity: *Quickcheck::::arbitrary(g), - beta_ledger_redeem_identity: *Quickcheck::::arbitrary(g), - alpha_expiry: *Quickcheck::::arbitrary(g), - beta_expiry: *Quickcheck::::arbitrary(g), - secret_hash: *Quickcheck::::arbitrary(g), - }) - } -} + let chain_id = *Quickcheck::::arbitrary(g); + let ethereum = ledger::Ethereum { chain_id }; -impl Arbitrary - for Quickcheck< - Request, - > -{ - fn arbitrary(g: &mut G) -> Self { - Quickcheck(Request { - swap_id: *Quickcheck::::arbitrary(g), - alpha_ledger: ledger::Ethereum { - chain_id: *Quickcheck::::arbitrary(g), - }, - beta_ledger: ledger::bitcoin::Mainnet, - alpha_asset: Quickcheck::::arbitrary(g).0, - beta_asset: *Quickcheck::::arbitrary(g), - hash_function: *Quickcheck::::arbitrary(g), - alpha_ledger_refund_identity: *Quickcheck::::arbitrary(g), - beta_ledger_redeem_identity: *Quickcheck::::arbitrary(g), - alpha_expiry: *Quickcheck::::arbitrary(g), - beta_expiry: *Quickcheck::::arbitrary(g), - secret_hash: *Quickcheck::::arbitrary(g), - }) + Quickcheck(ethereum) } } -impl Arbitrary - for Quickcheck< - Request, - > +impl Arbitrary for Quickcheck> +where + Quickcheck: Arbitrary, + Quickcheck: Arbitrary, + Quickcheck: Arbitrary, + Quickcheck: Arbitrary, + Quickcheck: Arbitrary, + Quickcheck: Arbitrary, + Request: Clone + Send + 'static, { fn arbitrary(g: &mut G) -> Self { Quickcheck(Request { swap_id: *Quickcheck::::arbitrary(g), - alpha_ledger: ledger::Ethereum { - chain_id: *Quickcheck::::arbitrary(g), - }, - beta_ledger: ledger::bitcoin::Mainnet, - alpha_asset: Quickcheck::::arbitrary(g).0, - beta_asset: *Quickcheck::::arbitrary(g), + alpha_ledger: Quickcheck::::arbitrary(g).0, + beta_ledger: Quickcheck::::arbitrary(g).0, + alpha_asset: Quickcheck::::arbitrary(g).0, + beta_asset: Quickcheck::::arbitrary(g).0, hash_function: *Quickcheck::::arbitrary(g), - alpha_ledger_refund_identity: *Quickcheck::::arbitrary(g), - beta_ledger_redeem_identity: *Quickcheck::::arbitrary(g), + alpha_ledger_refund_identity: Quickcheck::::arbitrary(g).0, + beta_ledger_redeem_identity: Quickcheck::::arbitrary(g).0, alpha_expiry: *Quickcheck::::arbitrary(g), beta_expiry: *Quickcheck::::arbitrary(g), secret_hash: *Quickcheck::::arbitrary(g), @@ -340,86 +257,18 @@ impl Arbitrary } } -impl Arbitrary - for Quickcheck< - Request, - > +impl Arbitrary for Quickcheck> +where + AI: Copy + Clone + Send, + BI: Copy + Clone + Send, + Quickcheck: Arbitrary, + Quickcheck: Arbitrary, { - fn arbitrary(g: &mut G) -> Self { - Quickcheck(Request { - swap_id: *Quickcheck::::arbitrary(g), - alpha_ledger: ledger::bitcoin::Mainnet, - beta_ledger: ledger::Ethereum { - chain_id: *Quickcheck::::arbitrary(g), - }, - alpha_asset: *Quickcheck::::arbitrary(g), - beta_asset: Quickcheck::::arbitrary(g).0, - hash_function: *Quickcheck::::arbitrary(g), - alpha_ledger_refund_identity: *Quickcheck::::arbitrary(g), - beta_ledger_redeem_identity: *Quickcheck::::arbitrary(g), - alpha_expiry: *Quickcheck::::arbitrary(g), - beta_expiry: *Quickcheck::::arbitrary(g), - secret_hash: *Quickcheck::::arbitrary(g), - }) - } -} - -impl Arbitrary for Quickcheck> { - fn arbitrary(g: &mut G) -> Self { - Quickcheck(Accept { - swap_id: *Quickcheck::::arbitrary(g), - alpha_ledger_redeem_identity: *Quickcheck::::arbitrary(g), - beta_ledger_refund_identity: *Quickcheck::::arbitrary(g), - }) - } -} - -impl Arbitrary for Quickcheck> { - fn arbitrary(g: &mut G) -> Self { - Quickcheck(Accept { - swap_id: *Quickcheck::::arbitrary(g), - alpha_ledger_redeem_identity: *Quickcheck::::arbitrary(g), - beta_ledger_refund_identity: *Quickcheck::::arbitrary(g), - }) - } -} - -impl Arbitrary for Quickcheck> { - fn arbitrary(g: &mut G) -> Self { - Quickcheck(Accept { - swap_id: *Quickcheck::::arbitrary(g), - alpha_ledger_redeem_identity: *Quickcheck::::arbitrary(g), - beta_ledger_refund_identity: *Quickcheck::::arbitrary(g), - }) - } -} - -impl Arbitrary for Quickcheck> { - fn arbitrary(g: &mut G) -> Self { - Quickcheck(Accept { - swap_id: *Quickcheck::::arbitrary(g), - alpha_ledger_redeem_identity: *Quickcheck::::arbitrary(g), - beta_ledger_refund_identity: *Quickcheck::::arbitrary(g), - }) - } -} - -impl Arbitrary for Quickcheck> { - fn arbitrary(g: &mut G) -> Self { - Quickcheck(Accept { - swap_id: *Quickcheck::::arbitrary(g), - alpha_ledger_redeem_identity: *Quickcheck::::arbitrary(g), - beta_ledger_refund_identity: *Quickcheck::::arbitrary(g), - }) - } -} - -impl Arbitrary for Quickcheck> { fn arbitrary(g: &mut G) -> Self { Quickcheck(Accept { swap_id: *Quickcheck::::arbitrary(g), - alpha_ledger_redeem_identity: *Quickcheck::::arbitrary(g), - beta_ledger_refund_identity: *Quickcheck::::arbitrary(g), + alpha_ledger_redeem_identity: *Quickcheck::::arbitrary(g), + beta_ledger_refund_identity: *Quickcheck::::arbitrary(g), }) } } diff --git a/cnd/src/seed.rs b/cnd/src/seed.rs index 2cadce847d..01e66380cf 100644 --- a/cnd/src/seed.rs +++ b/cnd/src/seed.rs @@ -99,7 +99,10 @@ impl RootSeed { self.0.sha256_with_seed(slices) } - pub fn new_random(mut rand: R) -> Result { + pub fn new_random(mut rand: R) -> Result + where + R: Rng, + { let mut arr = [0u8; SEED_LENGTH]; rand.try_fill(&mut arr[..])?; Ok(RootSeed(Seed(arr))) @@ -107,17 +110,21 @@ impl RootSeed { /// Read the seed from the default location if it exists, otherwise /// generate a random seed and write it to the default location. - pub fn from_default_dir_or_generate(rand: R) -> Result { + pub fn from_default_dir_or_generate(rand: R) -> Result + where + R: Rng, + { let path = default_seed_path()?; RootSeed::from_dir_or_generate(&path, rand) } /// Read the seed from the directory if it exists, otherwise /// generate a random seed and write it to that location. - pub fn from_dir_or_generate, R: Rng>( - data_dir: D, - rand: R, - ) -> Result { + pub fn from_dir_or_generate(data_dir: D, rand: R) -> Result + where + D: AsRef, + R: Rng, + { let dir = Path::new(&data_dir); let path = seed_path_from_dir(dir); @@ -133,7 +140,10 @@ impl RootSeed { Ok(random_seed) } - fn from_file>(seed_file: D) -> Result { + fn from_file(seed_file: D) -> Result + where + D: AsRef, + { let file = Path::new(&seed_file); let contents = fs::read_to_string(file)?; let pem = pem::parse(contents)?; diff --git a/cnd/src/swap_protocols/facade.rs b/cnd/src/swap_protocols/facade.rs index dbae366961..281e10a575 100644 --- a/cnd/src/swap_protocols/facade.rs +++ b/cnd/src/swap_protocols/facade.rs @@ -1,7 +1,12 @@ use crate::{ - asset::{self, Asset}, - btsieve::{self, bitcoin::BitcoindConnector, ethereum, ethereum::Web3Connector}, + asset, + btsieve::{ + self, + bitcoin::BitcoindConnector, + ethereum::{self, Web3Connector}, + }, db::{AcceptedSwap, DetermineTypes, LoadAcceptedSwap, Retrieve, Save, Sqlite, Swap, SwapTypes}, + htlc_location, identity, network::{ ComitPeers, DialInformation, ListenAddresses, LocalPeerId, PendingRequestFor, RequestError, SendRequest, Swarm, @@ -16,18 +21,21 @@ use crate::{ Deployed, Funded, HtlcDeployed, HtlcFunded, HtlcRedeemed, HtlcRefunded, Redeemed, Refunded, }, - state_store::{self, InMemoryStateStore, StateStore}, - ActorState, Ledger, + ActorState, }, + state_store::{self, Get, InMemoryStateStore, Insert, Update}, SwapId, }, + transaction, }; use async_trait::async_trait; use chrono::NaiveDateTime; -use futures::sync::oneshot; +use futures::channel::oneshot::Sender; +use impl_template::impl_template; use libp2p::{Multiaddr, PeerId}; -use libp2p_comit::frame::Response; -use std::sync::Arc; +use libp2p_comit::frame::{OutboundRequest, Response}; +use serde::de::DeserializeOwned; +use std::{convert::TryInto, fmt::Debug, sync::Arc}; /// This is a facade that implements all the required traits and forwards them /// to another implementation. This allows us to keep the number of arguments to @@ -41,52 +49,74 @@ use std::sync::Arc; #[delegate(Retrieve, target = "db")] #[delegate(DetermineTypes, target = "db")] pub struct Facade { - pub bitcoin_connector: btsieve::bitcoin::Cache, - pub ethereum_connector: ethereum::Cache, + pub bitcoin_connector: Arc>, + pub ethereum_connector: Arc>, pub state_store: Arc, pub seed: RootSeed, pub swarm: Swarm, pub db: Sqlite, } -impl StateStore for Facade { - fn insert(&self, key: SwapId, value: A) { +impl Insert for Facade +where + S: Send + 'static, +{ + fn insert(&self, key: SwapId, value: S) { self.state_store.insert(key, value) } +} - fn get(&self, key: &SwapId) -> Result, state_store::Error> { +impl Get for Facade +where + S: Clone + Send + 'static, +{ + fn get(&self, key: &SwapId) -> Result, state_store::Error> { self.state_store.get(key) } +} - fn update(&self, key: &SwapId, update: SwapEvent) { - self.state_store.update::(key, update) +impl Update> for Facade +where + S: ActorState + Send, + S::AA: Ord, + S::BA: Ord, +{ + #[allow(clippy::type_complexity)] + fn update(&self, key: &SwapId, update: SwapEvent) { + Update::>::update( + self.state_store.as_ref(), + key, + update, + ) } } #[async_trait] impl SendRequest for Facade { - async fn send_request( + async fn send_request( &self, peer_identity: DialInformation, - request: rfc003::Request, - ) -> Result, RequestError> { + request: rfc003::Request, + ) -> Result, RequestError> + where + rfc003::messages::AcceptResponseBody: DeserializeOwned, + rfc003::Request: TryInto + Send + 'static + Clone, + as TryInto>::Error: Debug, + { self.swarm.send_request(peer_identity, request).await } } #[async_trait] -impl LoadAcceptedSwap for Facade +impl LoadAcceptedSwap for Facade where - AL: Ledger + Send + 'static, - BL: Ledger + Send + 'static, - AA: Asset + Send + 'static, - BA: Asset + Send + 'static, - Sqlite: LoadAcceptedSwap, + Sqlite: LoadAcceptedSwap, + AcceptedSwap: Send + 'static, { async fn load_accepted_swap( &self, swap_id: &SwapId, - ) -> anyhow::Result> { + ) -> anyhow::Result> { self.db.load_accepted_swap(swap_id).await } } @@ -102,236 +132,182 @@ where } } +#[impl_template] #[async_trait::async_trait] -impl HtlcFunded for Facade { - async fn htlc_funded( - &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, - start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { - self.bitcoin_connector - .htlc_funded(htlc_params, htlc_deployment, start_of_swap) - .await - } -} - -#[async_trait::async_trait] -impl HtlcDeployed for Facade { - async fn htlc_deployed( - &self, - htlc_params: HtlcParams, - start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { - self.bitcoin_connector - .htlc_deployed(htlc_params, start_of_swap) - .await - } -} - -#[async_trait::async_trait] -impl HtlcRedeemed for Facade { - async fn htlc_redeemed( - &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, - start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { - self.bitcoin_connector - .htlc_redeemed(htlc_params, htlc_deployment, start_of_swap) - .await - } -} - -#[async_trait::async_trait] -impl HtlcRefunded for Facade { - async fn htlc_refunded( - &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, - start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { - self.bitcoin_connector - .htlc_refunded(htlc_params, htlc_deployment, start_of_swap) - .await - } -} - -#[async_trait::async_trait] -impl HtlcFunded for Facade { - async fn htlc_funded( - &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, - start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { - self.bitcoin_connector - .htlc_funded(htlc_params, htlc_deployment, start_of_swap) - .await - } -} - -#[async_trait::async_trait] -impl HtlcDeployed for Facade { - async fn htlc_deployed( - &self, - htlc_params: HtlcParams, - start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { - self.bitcoin_connector - .htlc_deployed(htlc_params, start_of_swap) - .await - } -} - -#[async_trait::async_trait] -impl HtlcRedeemed for Facade { - async fn htlc_redeemed( - &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, - start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { - self.bitcoin_connector - .htlc_redeemed(htlc_params, htlc_deployment, start_of_swap) - .await - } -} - -#[async_trait::async_trait] -impl HtlcRefunded for Facade { - async fn htlc_refunded( - &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, - start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { - self.bitcoin_connector - .htlc_refunded(htlc_params, htlc_deployment, start_of_swap) - .await - } -} - -#[async_trait::async_trait] -impl HtlcFunded for Facade { +impl + HtlcFunded< + ((bitcoin::Mainnet, bitcoin::Testnet, bitcoin::Regtest)), + asset::Bitcoin, + htlc_location::Bitcoin, + identity::Bitcoin, + transaction::Bitcoin, + > for Facade +{ async fn htlc_funded( &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, + htlc_params: &HtlcParams<__TYPE0__, asset::Bitcoin, identity::Bitcoin>, + htlc_deployment: &Deployed, start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { + ) -> anyhow::Result> { self.bitcoin_connector .htlc_funded(htlc_params, htlc_deployment, start_of_swap) .await } } +#[impl_template] #[async_trait::async_trait] -impl HtlcDeployed for Facade { +impl + HtlcDeployed< + ((bitcoin::Mainnet, bitcoin::Testnet, bitcoin::Regtest)), + asset::Bitcoin, + htlc_location::Bitcoin, + identity::Bitcoin, + transaction::Bitcoin, + > for Facade +{ async fn htlc_deployed( &self, - htlc_params: HtlcParams, + htlc_params: &HtlcParams<__TYPE0__, asset::Bitcoin, identity::Bitcoin>, start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { + ) -> anyhow::Result> { self.bitcoin_connector .htlc_deployed(htlc_params, start_of_swap) .await } } +#[impl_template] #[async_trait::async_trait] -impl HtlcRedeemed for Facade { +impl + HtlcRedeemed< + ((bitcoin::Mainnet, bitcoin::Testnet, bitcoin::Regtest)), + asset::Bitcoin, + htlc_location::Bitcoin, + identity::Bitcoin, + transaction::Bitcoin, + > for Facade +{ async fn htlc_redeemed( &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, + htlc_params: &HtlcParams<__TYPE0__, asset::Bitcoin, identity::Bitcoin>, + htlc_deployment: &Deployed, start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { + ) -> anyhow::Result> { self.bitcoin_connector .htlc_redeemed(htlc_params, htlc_deployment, start_of_swap) .await } } +#[impl_template] #[async_trait::async_trait] -impl HtlcRefunded for Facade { +impl + HtlcRefunded< + ((bitcoin::Mainnet, bitcoin::Testnet, bitcoin::Regtest)), + asset::Bitcoin, + htlc_location::Bitcoin, + identity::Bitcoin, + transaction::Bitcoin, + > for Facade +{ async fn htlc_refunded( &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, + htlc_params: &HtlcParams<__TYPE0__, asset::Bitcoin, identity::Bitcoin>, + htlc_deployment: &Deployed, start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { + ) -> anyhow::Result> { self.bitcoin_connector .htlc_refunded(htlc_params, htlc_deployment, start_of_swap) .await } } +#[impl_template] #[async_trait::async_trait] -impl HtlcFunded for Facade -where - A: Asset + Send + Sync + 'static, - ethereum::Cache: HtlcFunded, +impl + HtlcFunded< + Ethereum, + ((asset::Ether, asset::Erc20)), + htlc_location::Ethereum, + identity::Ethereum, + transaction::Ethereum, + > for Facade { async fn htlc_funded( &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, + htlc_params: &HtlcParams, + htlc_deployment: &Deployed, start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { + ) -> anyhow::Result> { self.ethereum_connector .htlc_funded(htlc_params, htlc_deployment, start_of_swap) .await } } +#[impl_template] #[async_trait::async_trait] -impl HtlcDeployed for Facade -where - A: Asset + Send + Sync + 'static, - ethereum::Cache: HtlcDeployed, +impl + HtlcDeployed< + Ethereum, + ((asset::Ether, asset::Erc20)), + htlc_location::Ethereum, + identity::Ethereum, + transaction::Ethereum, + > for Facade { async fn htlc_deployed( &self, - htlc_params: HtlcParams, + htlc_params: &HtlcParams, start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { + ) -> anyhow::Result> { self.ethereum_connector .htlc_deployed(htlc_params, start_of_swap) .await } } +#[impl_template] #[async_trait::async_trait] -impl HtlcRedeemed for Facade -where - A: Asset + Send + Sync + 'static, - ethereum::Cache: HtlcRedeemed, +impl + HtlcRedeemed< + Ethereum, + ((asset::Ether, asset::Erc20)), + htlc_location::Ethereum, + identity::Ethereum, + transaction::Ethereum, + > for Facade { async fn htlc_redeemed( &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, + htlc_params: &HtlcParams, + htlc_deployment: &Deployed, start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { + ) -> anyhow::Result> { self.ethereum_connector .htlc_redeemed(htlc_params, htlc_deployment, start_of_swap) .await } } +#[impl_template] #[async_trait::async_trait] -impl HtlcRefunded for Facade -where - A: Asset + Send + Sync + 'static, - ethereum::Cache: HtlcRefunded, +impl + HtlcRefunded< + Ethereum, + ((asset::Ether, asset::Erc20)), + htlc_location::Ethereum, + identity::Ethereum, + transaction::Ethereum, + > for Facade { async fn htlc_refunded( &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, + htlc_params: &HtlcParams, + htlc_deployment: &Deployed, start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { + ) -> anyhow::Result> { self.ethereum_connector .htlc_refunded(htlc_params, htlc_deployment, start_of_swap) .await diff --git a/cnd/src/swap_protocols/ledger/bitcoin.rs b/cnd/src/swap_protocols/ledger/bitcoin.rs index 89f4bc222e..ec511a79f0 100644 --- a/cnd/src/swap_protocols/ledger/bitcoin.rs +++ b/cnd/src/swap_protocols/ledger/bitcoin.rs @@ -10,7 +10,7 @@ pub struct Testnet; pub struct Regtest; pub trait Bitcoin: - Sized + std::fmt::Debug + std::hash::Hash + Eq + Sync + Copy + Send + Into + 'static + Sized + std::fmt::Debug + std::hash::Hash + Eq + Sync + Copy + Send + 'static { } diff --git a/cnd/src/swap_protocols/ledger/ethereum.rs b/cnd/src/swap_protocols/ledger/ethereum.rs index 1ca9c91a53..2558e74d1b 100644 --- a/cnd/src/swap_protocols/ledger/ethereum.rs +++ b/cnd/src/swap_protocols/ledger/ethereum.rs @@ -1,8 +1,6 @@ use crate::comit_api::LedgerKind; use serde::{Deserialize, Serialize}; -/// `network` is only kept for backward compatibility with client -/// and must be removed with issue #1580 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)] pub struct Ethereum { pub chain_id: ChainId, diff --git a/cnd/src/swap_protocols/mod.rs b/cnd/src/swap_protocols/mod.rs index 2cd78820d5..2fa1e9e8ab 100644 --- a/cnd/src/swap_protocols/mod.rs +++ b/cnd/src/swap_protocols/mod.rs @@ -2,10 +2,11 @@ pub mod actions; mod facade; pub mod ledger; pub mod rfc003; +pub mod state_store; mod swap_id; pub use self::{facade::*, swap_id::*}; -use crate::comit_api::LedgerKind; + use serde::{Deserialize, Serialize}; #[derive( diff --git a/cnd/src/swap_protocols/rfc003/actions/bitcoin.rs b/cnd/src/swap_protocols/rfc003/actions/bitcoin.rs index b5243ba6be..a2859065b0 100644 --- a/cnd/src/swap_protocols/rfc003/actions/bitcoin.rs +++ b/cnd/src/swap_protocols/rfc003/actions/bitcoin.rs @@ -1,5 +1,5 @@ use crate::{ - asset, + asset, identity, swap_protocols::{ actions::bitcoin::{SendToAddress, SpendOutput}, ledger, @@ -13,12 +13,14 @@ use crate::{ use ::bitcoin::{Amount, OutPoint, Transaction}; use blockchain_contracts::bitcoin::{rfc003::bitcoin_htlc::BitcoinHtlc, witness::PrimedInput}; -impl FundAction - for (B, asset::Bitcoin) +impl FundAction for (B, asset::Bitcoin) +where + B: ledger::Bitcoin + ledger::bitcoin::Network, { - type FundActionOutput = SendToAddress; + type HtlcParams = HtlcParams; + type Output = SendToAddress; - fn fund_action(htlc_params: HtlcParams) -> Self::FundActionOutput { + fn fund_action(htlc_params: Self::HtlcParams) -> Self::Output { let to = htlc_params.compute_address(); SendToAddress { @@ -29,17 +31,21 @@ impl FundAction RefundAction - for (B, asset::Bitcoin) +impl RefundAction for (B, asset::Bitcoin) +where + B: ledger::Bitcoin + ledger::bitcoin::Network, { - type RefundActionOutput = SpendOutput; + type HtlcParams = HtlcParams; + type HtlcLocation = OutPoint; + type FundTransaction = Transaction; + type Output = SpendOutput; fn refund_action( - htlc_params: HtlcParams, - htlc_location: OutPoint, + htlc_params: Self::HtlcParams, + htlc_location: Self::HtlcLocation, secret_source: &dyn DeriveIdentities, - fund_transaction: &Transaction, - ) -> Self::RefundActionOutput { + fund_transaction: &Self::FundTransaction, + ) -> Self::Output { let htlc = BitcoinHtlc::from(htlc_params); SpendOutput { @@ -53,23 +59,26 @@ impl RefundAction RedeemAction - for (B, asset::Bitcoin) +impl RedeemAction for (B, asset::Bitcoin) +where + B: ledger::Bitcoin + ledger::bitcoin::Network, { - type RedeemActionOutput = SpendOutput; + type HtlcParams = HtlcParams; + type HtlcLocation = OutPoint; + type Output = SpendOutput; fn redeem_action( - htlc_params: HtlcParams, - htlc_location: OutPoint, + htlc_params: Self::HtlcParams, + htlc_location: Self::HtlcLocation, secret_source: &dyn DeriveIdentities, secret: Secret, - ) -> Self::RedeemActionOutput { + ) -> Self::Output { let htlc = BitcoinHtlc::from(htlc_params); SpendOutput { output: PrimedInput::new( htlc_location, - htlc_params.asset.into(), + htlc_params.asset.clone().into(), htlc.unlock_with_secret( &*crate::SECP, secret_source.derive_redeem_identity(), diff --git a/cnd/src/swap_protocols/rfc003/actions/erc20.rs b/cnd/src/swap_protocols/rfc003/actions/erc20.rs index d187d3d480..8d8cdfd4a3 100644 --- a/cnd/src/swap_protocols/rfc003/actions/erc20.rs +++ b/cnd/src/swap_protocols/rfc003/actions/erc20.rs @@ -1,6 +1,7 @@ use crate::{ asset, ethereum::Bytes, + htlc_location, identity, swap_protocols::{ actions::ethereum::{CallContract, DeployContract}, ledger::{ethereum::ChainId, Ethereum}, @@ -10,7 +11,9 @@ use crate::{ }; use blockchain_contracts::ethereum::rfc003::erc20_htlc::Erc20Htlc; -pub fn deploy_action(htlc_params: HtlcParams) -> DeployContract { +pub fn deploy_action( + htlc_params: HtlcParams, +) -> DeployContract { let chain_id = htlc_params.ledger.chain_id; let htlc = Erc20Htlc::from(htlc_params); let gas_limit = Erc20Htlc::deploy_tx_gas_limit(); @@ -24,9 +27,9 @@ pub fn deploy_action(htlc_params: HtlcParams) -> DeployC } pub fn fund_action( - htlc_params: HtlcParams, - to_erc20_contract: crate::ethereum::Address, - beta_htlc_location: crate::ethereum::Address, + htlc_params: HtlcParams, + to_erc20_contract: identity::Ethereum, + beta_htlc_location: htlc_location::Ethereum, ) -> CallContract { let chain_id = htlc_params.ledger.chain_id; let gas_limit = Erc20Htlc::fund_tx_gas_limit(); @@ -47,7 +50,7 @@ pub fn fund_action( pub fn refund_action( chain_id: ChainId, expiry: Timestamp, - beta_htlc_location: crate::ethereum::Address, + beta_htlc_location: htlc_location::Ethereum, ) -> CallContract { let data = Bytes::default(); let gas_limit = Erc20Htlc::refund_tx_gas_limit(); @@ -62,7 +65,7 @@ pub fn refund_action( } pub fn redeem_action( - alpha_htlc_location: crate::ethereum::Address, + alpha_htlc_location: htlc_location::Ethereum, secret: Secret, chain_id: ChainId, ) -> CallContract { diff --git a/cnd/src/swap_protocols/rfc003/actions/ether.rs b/cnd/src/swap_protocols/rfc003/actions/ether.rs index 0c37726f52..a19e80a69b 100644 --- a/cnd/src/swap_protocols/rfc003/actions/ether.rs +++ b/cnd/src/swap_protocols/rfc003/actions/ether.rs @@ -1,6 +1,7 @@ use crate::{ asset, - ethereum::{Address as EthereumAddress, Bytes, Transaction}, + ethereum::{Bytes, Transaction}, + htlc_location, identity, swap_protocols::{ actions::ethereum::{CallContract, DeployContract}, ledger::Ethereum, @@ -13,10 +14,11 @@ use crate::{ }; use blockchain_contracts::ethereum::rfc003::ether_htlc::EtherHtlc; -impl FundAction for (Ethereum, asset::Ether) { - type FundActionOutput = DeployContract; +impl FundAction for (Ethereum, asset::Ether) { + type HtlcParams = HtlcParams; + type Output = DeployContract; - fn fund_action(htlc_params: HtlcParams) -> Self::FundActionOutput { + fn fund_action(htlc_params: Self::HtlcParams) -> Self::Output { let htlc = EtherHtlc::from(htlc_params.clone()); let gas_limit = EtherHtlc::deploy_tx_gas_limit(); @@ -28,15 +30,19 @@ impl FundAction for (Ethereum, asset::Ether) { } } } -impl RefundAction for (Ethereum, asset::Ether) { - type RefundActionOutput = CallContract; + +impl RefundAction for (Ethereum, asset::Ether) { + type HtlcParams = HtlcParams; + type HtlcLocation = htlc_location::Ethereum; + type FundTransaction = Transaction; + type Output = CallContract; fn refund_action( - htlc_params: HtlcParams, - htlc_location: EthereumAddress, + htlc_params: Self::HtlcParams, + htlc_location: Self::HtlcLocation, _secret_source: &dyn DeriveIdentities, - _fund_transaction: &Transaction, - ) -> Self::RefundActionOutput { + _fund_transaction: &Self::FundTransaction, + ) -> Self::Output { let gas_limit = EtherHtlc::refund_tx_gas_limit(); CallContract { @@ -48,15 +54,18 @@ impl RefundAction for (Ethereum, asset::Ether) { } } } -impl RedeemAction for (Ethereum, asset::Ether) { - type RedeemActionOutput = CallContract; + +impl RedeemAction for (Ethereum, asset::Ether) { + type HtlcParams = HtlcParams; + type HtlcLocation = htlc_location::Ethereum; + type Output = CallContract; fn redeem_action( - htlc_params: HtlcParams, - htlc_location: EthereumAddress, + htlc_params: Self::HtlcParams, + htlc_location: Self::HtlcLocation, _secret_source: &dyn DeriveIdentities, secret: Secret, - ) -> Self::RedeemActionOutput { + ) -> Self::Output { let data = Bytes::from(secret.as_raw_secret().to_vec()); let gas_limit = EtherHtlc::redeem_tx_gas_limit(); diff --git a/cnd/src/swap_protocols/rfc003/actions/mod.rs b/cnd/src/swap_protocols/rfc003/actions/mod.rs index ae0dd12dba..a14a23892f 100644 --- a/cnd/src/swap_protocols/rfc003/actions/mod.rs +++ b/cnd/src/swap_protocols/rfc003/actions/mod.rs @@ -2,10 +2,7 @@ pub mod bitcoin; pub mod erc20; pub mod ether; -use crate::{ - asset::Asset, - swap_protocols::rfc003::{create_swap::HtlcParams, DeriveIdentities, Ledger, Secret}, -}; +use crate::swap_protocols::rfc003::{DeriveIdentities, Secret}; use std::marker::PhantomData; /// Defines the set of actions available in the RFC003 protocol @@ -24,40 +21,46 @@ pub enum Action { Refund(Refund), } -pub trait FundAction { - type FundActionOutput; +pub trait FundAction { + type HtlcParams; + type Output; - fn fund_action(htlc_params: HtlcParams) -> Self::FundActionOutput; + fn fund_action(htlc_params: Self::HtlcParams) -> Self::Output; } -pub trait RefundAction { - type RefundActionOutput; +pub trait RefundAction { + type HtlcParams; + type HtlcLocation; + type FundTransaction; + type Output; fn refund_action( - htlc_params: HtlcParams, - htlc_location: L::HtlcLocation, + htlc_params: Self::HtlcParams, + htlc_location: Self::HtlcLocation, secret_source: &dyn DeriveIdentities, - fund_transaction: &L::Transaction, - ) -> Self::RefundActionOutput; + fund_transaction: &Self::FundTransaction, + ) -> Self::Output; } -pub trait RedeemAction { - type RedeemActionOutput; +pub trait RedeemAction { + type HtlcParams; + type HtlcLocation; + type Output; fn redeem_action( - htlc_params: HtlcParams, - htlc_location: L::HtlcLocation, + htlc_params: Self::HtlcParams, + htlc_location: Self::HtlcLocation, secret_source: &dyn DeriveIdentities, secret: Secret, - ) -> Self::RedeemActionOutput; + ) -> Self::Output; } #[derive(Clone, Debug, Default)] -pub struct Accept { +pub struct Accept { phantom_data: PhantomData<(AL, BL)>, } -impl Accept { +impl Accept { pub fn new() -> Self { Self { phantom_data: PhantomData, @@ -66,11 +69,11 @@ impl Accept { } #[derive(Clone, Debug, Default)] -pub struct Decline { +pub struct Decline { phantom_data: PhantomData<(AL, BL)>, } -impl Decline { +impl Decline { pub fn new() -> Self { Self { phantom_data: PhantomData, diff --git a/cnd/src/swap_protocols/rfc003/actor_state.rs b/cnd/src/swap_protocols/rfc003/actor_state.rs index 66cb3eacb9..0c3fd8a38f 100644 --- a/cnd/src/swap_protocols/rfc003/actor_state.rs +++ b/cnd/src/swap_protocols/rfc003/actor_state.rs @@ -1,20 +1,20 @@ -use crate::{ - asset::Asset, - swap_protocols::rfc003::{ledger_state::LedgerState, Ledger}, -}; -use std::fmt::Debug; +use crate::swap_protocols::rfc003::ledger_state::LedgerState; -pub trait ActorState: Debug + Clone + Send + Sync + 'static { - type AL: Ledger; - type BL: Ledger; - type AA: Asset; - type BA: Asset; +pub trait ActorState: 'static { + type AL; + type BL; + type AA; + type BA; + type AH; + type BH; + type AT; + type BT; - fn expected_alpha_asset(&self) -> Self::AA; - fn expected_beta_asset(&self) -> Self::BA; + fn expected_alpha_asset(&self) -> &Self::AA; + fn expected_beta_asset(&self) -> &Self::BA; - fn alpha_ledger_mut(&mut self) -> &mut LedgerState; - fn beta_ledger_mut(&mut self) -> &mut LedgerState; + fn alpha_ledger_mut(&mut self) -> &mut LedgerState; + fn beta_ledger_mut(&mut self) -> &mut LedgerState; /// Returns true if the current swap failed at some stage. fn swap_failed(&self) -> bool; diff --git a/cnd/src/swap_protocols/rfc003/alice/actions/erc20.rs b/cnd/src/swap_protocols/rfc003/alice/actions/erc20.rs index 3e644910ea..844e1cb1a3 100644 --- a/cnd/src/swap_protocols/rfc003/alice/actions/erc20.rs +++ b/cnd/src/swap_protocols/rfc003/alice/actions/erc20.rs @@ -1,5 +1,6 @@ use crate::{ - asset::{self, Asset}, + asset::{self}, + htlc_location, identity, swap_protocols::{ actions::{ethereum, Actions}, ledger::Ethereum, @@ -7,17 +8,33 @@ use crate::{ actions::{erc20, Accept, Action, Decline, FundAction, RedeemAction, RefundAction}, alice, create_swap::HtlcParams, - DeriveSecret, Ledger, LedgerState, SwapCommunication, + DeriveSecret, LedgerState, SwapCommunication, }, }, + transaction, }; use std::convert::Infallible; -impl Actions for alice::State +impl Actions + for alice::State< + Ethereum, + BL, + asset::Erc20, + BA, + htlc_location::Ethereum, + BH, + identity::Ethereum, + BI, + transaction::Ethereum, + BT, + > where - BL: Ledger, - BA: Asset, - (BL, BA): RedeemAction, + BL: Clone, + BA: Clone, + BH: Clone, + BI: Clone, + BT: Clone, + (BL, BA): RedeemAction, HtlcLocation = BH>, { #[allow(clippy::type_complexity)] type ActionKind = Action< @@ -25,7 +42,7 @@ where Decline, ethereum::DeployContract, ethereum::CallContract, - <(BL, BA) as RedeemAction>::RedeemActionOutput, + <(BL, BA) as RedeemAction>::Output, ethereum::CallContract, >; @@ -71,20 +88,36 @@ where } } -impl Actions for alice::State +impl Actions + for alice::State< + AL, + Ethereum, + AA, + asset::Erc20, + AH, + htlc_location::Ethereum, + AI, + identity::Ethereum, + AT, + transaction::Ethereum, + > where - AL: Ledger, - AA: Asset, - (AL, AA): FundAction + RefundAction, + AL: Clone, + AA: Clone, + AH: Copy, + AI: Clone, + AT: Clone, + (AL, AA): FundAction> + + RefundAction, HtlcLocation = AH, FundTransaction = AT>, { #[allow(clippy::type_complexity)] type ActionKind = Action< Accept, Decline, Infallible, - <(AL, AA) as FundAction>::FundActionOutput, + <(AL, AA) as FundAction>::Output, ethereum::CallContract, - <(AL, AA) as RefundAction>::RefundActionOutput, + <(AL, AA) as RefundAction>::Output, >; fn actions(&self) -> Vec { @@ -110,7 +143,7 @@ where .. } => vec![Action::Refund(<(AL, AA)>::refund_action( HtlcParams::new_alpha_params(request, response), - htlc_location.clone(), + *htlc_location, &self.secret_source, fund_transaction, ))], diff --git a/cnd/src/swap_protocols/rfc003/alice/actions/generic_impl.rs b/cnd/src/swap_protocols/rfc003/alice/actions/generic_impl.rs index 9037686f81..3e79e47afd 100644 --- a/cnd/src/swap_protocols/rfc003/alice/actions/generic_impl.rs +++ b/cnd/src/swap_protocols/rfc003/alice/actions/generic_impl.rs @@ -1,34 +1,39 @@ -use crate::{ - asset::Asset, - swap_protocols::{ - actions::Actions, - rfc003::{ - actions::{Accept, Action, Decline, FundAction, RedeemAction, RefundAction}, - alice, - create_swap::HtlcParams, - DeriveSecret, Ledger, LedgerState, SwapCommunication, - }, +use crate::swap_protocols::{ + actions::Actions, + rfc003::{ + actions::{Accept, Action, Decline, FundAction, RedeemAction, RefundAction}, + alice, + create_swap::HtlcParams, + DeriveSecret, LedgerState, SwapCommunication, }, }; use std::convert::Infallible; -impl Actions for alice::State +impl Actions + for alice::State where - AL: Ledger, - BL: Ledger, - AA: Asset, - BA: Asset, - (AL, AA): FundAction + RefundAction, - (BL, BA): RedeemAction, + AL: Clone, + BL: Clone, + AA: Clone, + BA: Clone, + AH: Clone, + BH: Clone, + AI: Clone, + BI: Clone, + AT: Clone, + BT: Clone, + (AL, AA): FundAction> + + RefundAction, HtlcLocation = AH, FundTransaction = AT>, + (BL, BA): RedeemAction, HtlcLocation = BH>, { #[allow(clippy::type_complexity)] type ActionKind = Action< Accept, Decline, Infallible, - <(AL, AA) as FundAction>::FundActionOutput, - <(BL, BA) as RedeemAction>::RedeemActionOutput, - <(AL, AA) as RefundAction>::RefundActionOutput, + <(AL, AA) as FundAction>::Output, + <(BL, BA) as RedeemAction>::Output, + <(AL, AA) as RefundAction>::Output, >; fn actions(&self) -> Vec { diff --git a/cnd/src/swap_protocols/rfc003/alice/mod.rs b/cnd/src/swap_protocols/rfc003/alice/mod.rs index 971d07d2d0..a85c19ca2f 100644 --- a/cnd/src/swap_protocols/rfc003/alice/mod.rs +++ b/cnd/src/swap_protocols/rfc003/alice/mod.rs @@ -3,27 +3,27 @@ mod actions; pub use self::actions::*; use crate::{ - asset::Asset, seed::SwapSeed, - swap_protocols::rfc003::{ - ledger::Ledger, ledger_state::LedgerState, messages, ActorState, SwapCommunication, - }, + swap_protocols::rfc003::{ledger_state::LedgerState, messages, ActorState, SwapCommunication}, }; use derivative::Derivative; #[derive(Clone, Derivative)] #[derivative(Debug, PartialEq)] -pub struct State { - pub swap_communication: SwapCommunication, - pub alpha_ledger_state: LedgerState, - pub beta_ledger_state: LedgerState, +pub struct State { + pub swap_communication: SwapCommunication, + pub alpha_ledger_state: LedgerState, + pub beta_ledger_state: LedgerState, #[derivative(Debug = "ignore", PartialEq = "ignore")] pub secret_source: SwapSeed, // Used to derive identities and also to generate the secret. pub failed: bool, } -impl State { - pub fn proposed(request: messages::Request, secret_source: SwapSeed) -> Self { +impl State { + pub fn proposed( + request: messages::Request, + secret_source: SwapSeed, + ) -> Self { Self { swap_communication: SwapCommunication::Proposed { request }, alpha_ledger_state: LedgerState::NotDeployed, @@ -34,8 +34,8 @@ impl State { } pub fn accepted( - request: messages::Request, - response: messages::Accept, + request: messages::Request, + response: messages::Accept, secret_source: SwapSeed, ) -> Self { Self { @@ -48,7 +48,7 @@ impl State { } pub fn declined( - request: messages::Request, + request: messages::Request, response: messages::Decline, secret_source: SwapSeed, ) -> Self { @@ -61,30 +61,47 @@ impl State { } } - pub fn request(&self) -> messages::Request { - self.swap_communication.request().clone() + pub fn request(&self) -> &messages::Request { + self.swap_communication.request() } } -impl ActorState for State { +impl ActorState + for State +where + AL: 'static, + BL: 'static, + AA: 'static, + BA: 'static, + AH: 'static, + BH: 'static, + AI: 'static, + BI: 'static, + AT: 'static, + BT: 'static, +{ type AL = AL; type BL = BL; type AA = AA; type BA = BA; + type AH = AH; + type BH = BH; + type AT = AT; + type BT = BT; - fn expected_alpha_asset(&self) -> Self::AA { - self.swap_communication.request().alpha_asset.clone() + fn expected_alpha_asset(&self) -> &Self::AA { + &self.swap_communication.request().alpha_asset } - fn expected_beta_asset(&self) -> Self::BA { - self.swap_communication.request().beta_asset.clone() + fn expected_beta_asset(&self) -> &Self::BA { + &self.swap_communication.request().beta_asset } - fn alpha_ledger_mut(&mut self) -> &mut LedgerState { + fn alpha_ledger_mut(&mut self) -> &mut LedgerState { &mut self.alpha_ledger_state } - fn beta_ledger_mut(&mut self) -> &mut LedgerState { + fn beta_ledger_mut(&mut self) -> &mut LedgerState { &mut self.beta_ledger_state } diff --git a/cnd/src/swap_protocols/rfc003/bitcoin/htlc_events.rs b/cnd/src/swap_protocols/rfc003/bitcoin/htlc_events.rs index 45cde6551a..8eef52643f 100644 --- a/cnd/src/swap_protocols/rfc003/bitcoin/htlc_events.rs +++ b/cnd/src/swap_protocols/rfc003/bitcoin/htlc_events.rs @@ -1,8 +1,9 @@ use crate::{ asset, btsieve::bitcoin::{ - matching_transaction, BitcoindConnector, Cache, TransactionExt, TransactionPattern, + watch_for_created_outpoint, watch_for_spent_outpoint, BitcoindConnector, Cache, }, + htlc_location, identity, swap_protocols::{ ledger::bitcoin, rfc003::{ @@ -14,21 +15,24 @@ use crate::{ }, }, }, + transaction, }; -use ::bitcoin::OutPoint; -use anyhow::Context; use chrono::NaiveDateTime; +use tracing_futures::Instrument; #[async_trait::async_trait] -impl HtlcFunded +impl + HtlcFunded for Cache +where + B: bitcoin::Bitcoin + bitcoin::Network, { async fn htlc_funded( &self, - _htlc_params: HtlcParams, - htlc_deployment: &Deployed, + _htlc_params: &HtlcParams, + htlc_deployment: &Deployed, _start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { + ) -> anyhow::Result> { let tx = &htlc_deployment.transaction; let asset = asset::Bitcoin::from_sat(tx.output[htlc_deployment.location.vout as usize].value); @@ -41,59 +45,49 @@ impl HtlcFunded HtlcDeployed +impl + HtlcDeployed for Cache +where + B: bitcoin::Bitcoin + bitcoin::Network, { async fn htlc_deployed( &self, - htlc_params: HtlcParams, + htlc_params: &HtlcParams, start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { - let connector = self.clone(); - let pattern = TransactionPattern { - to_address: Some(htlc_params.compute_address()), - from_outpoint: None, - unlock_script: None, - }; - - let transaction = matching_transaction(connector, pattern, start_of_swap) - .await - .context("failed to find transaction to deploy htlc")?; - - let (vout, _txout) = transaction - .find_output(&htlc_params.compute_address()) - .expect("Deployment transaction must contain outpoint described in pattern"); + ) -> anyhow::Result> { + let (transaction, location) = + watch_for_created_outpoint(self, start_of_swap, htlc_params.compute_address()) + .instrument(tracing::info_span!("htlc_deployed")) + .await?; Ok(Deployed { - location: OutPoint { - txid: transaction.txid(), - vout, - }, + location, transaction, }) } } #[async_trait::async_trait] -impl HtlcRedeemed +impl + HtlcRedeemed for Cache +where + B: bitcoin::Bitcoin + bitcoin::Network, { async fn htlc_redeemed( &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, + htlc_params: &HtlcParams, + htlc_deployment: &Deployed, start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { - let connector = self.clone(); - let pattern = TransactionPattern { - to_address: None, - from_outpoint: Some(htlc_deployment.location), - unlock_script: Some(vec![vec![1u8]]), - }; + ) -> anyhow::Result> { + let (transaction, _) = + watch_for_spent_outpoint(self, start_of_swap, htlc_deployment.location, vec![vec![ + 1u8, + ]]) + .instrument(tracing::info_span!("htlc_redeemed")) + .await?; - let transaction = matching_transaction(connector, pattern, start_of_swap) - .await - .context("failed to find transaction to redeem from htlc")?; let secret = extract_secret(&transaction, &htlc_params.secret_hash) .expect("Redeem transaction must contain secret"); @@ -105,24 +99,22 @@ impl HtlcRedeemed HtlcRefunded +impl + HtlcRefunded for Cache +where + B: bitcoin::Bitcoin + bitcoin::Network, { async fn htlc_refunded( &self, - _htlc_params: HtlcParams, - htlc_deployment: &Deployed, + _htlc_params: &HtlcParams, + htlc_deployment: &Deployed, start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { - let connector = self.clone(); - let pattern = TransactionPattern { - to_address: None, - from_outpoint: Some(htlc_deployment.location), - unlock_script: Some(vec![vec![]]), - }; - let transaction = matching_transaction(connector, pattern, start_of_swap) - .await - .context("failed to find transaction to refund from htlc")?; + ) -> anyhow::Result> { + let (transaction, _) = + watch_for_spent_outpoint(self, start_of_swap, htlc_deployment.location, vec![vec![]]) + .instrument(tracing::info_span!("htlc_refunded")) + .await?; Ok(Refunded { transaction }) } diff --git a/cnd/src/swap_protocols/rfc003/bitcoin/mod.rs b/cnd/src/swap_protocols/rfc003/bitcoin/mod.rs index 19d1af8f1b..4525cce7fa 100644 --- a/cnd/src/swap_protocols/rfc003/bitcoin/mod.rs +++ b/cnd/src/swap_protocols/rfc003/bitcoin/mod.rs @@ -1,27 +1,23 @@ mod extract_secret; mod htlc_events; -use crate::swap_protocols::{ - ledger, - rfc003::{create_swap::HtlcParams, Ledger}, +use crate::{ + asset, identity, + swap_protocols::{ledger, rfc003::create_swap::HtlcParams}, }; use ::bitcoin::{ hashes::{hash160, Hash}, - Address, OutPoint, Transaction, + Address, }; use blockchain_contracts::bitcoin::rfc003::bitcoin_htlc::BitcoinHtlc; pub use self::htlc_events::*; -use crate::{asset, bitcoin::PublicKey}; -impl Ledger for B { - type HtlcLocation = OutPoint; - type Identity = PublicKey; - type Transaction = Transaction; -} - -impl From> for BitcoinHtlc { - fn from(htlc_params: HtlcParams) -> Self { +impl From> for BitcoinHtlc +where + B: ledger::Bitcoin, +{ + fn from(htlc_params: HtlcParams) -> Self { let refund_public_key = ::bitcoin::PublicKey::from(htlc_params.refund_identity); let redeem_public_key = ::bitcoin::PublicKey::from(htlc_params.redeem_identity); @@ -37,7 +33,10 @@ impl From> for BitcoinHtlc { } } -impl HtlcParams { +impl HtlcParams +where + B: ledger::Bitcoin + ledger::bitcoin::Network, +{ pub fn compute_address(&self) -> Address { BitcoinHtlc::from(*self).compute_address(B::network()) } diff --git a/cnd/src/swap_protocols/rfc003/bob/actions/erc20.rs b/cnd/src/swap_protocols/rfc003/bob/actions/erc20.rs index b483cbfd35..b6db96b1d5 100644 --- a/cnd/src/swap_protocols/rfc003/bob/actions/erc20.rs +++ b/cnd/src/swap_protocols/rfc003/bob/actions/erc20.rs @@ -1,5 +1,6 @@ use crate::{ - asset::{self, Asset}, + asset::{self}, + htlc_location, identity, swap_protocols::{ actions::{ethereum, Actions}, ledger::Ethereum, @@ -7,17 +8,33 @@ use crate::{ actions::{erc20, Accept, Action, Decline, FundAction, RedeemAction, RefundAction}, bob, create_swap::HtlcParams, - Ledger, LedgerState, SwapCommunication, + LedgerState, SwapCommunication, }, }, + transaction, }; use std::convert::Infallible; -impl Actions for bob::State +impl Actions + for bob::State< + AL, + Ethereum, + AA, + asset::Erc20, + AH, + htlc_location::Ethereum, + AI, + identity::Ethereum, + AT, + transaction::Ethereum, + > where - AL: Ledger, - AA: Asset, - (AL, AA): RedeemAction, + AL: Clone, + AA: Clone, + AH: Clone, + AI: Clone, + AT: Clone, + (AL, AA): RedeemAction, HtlcLocation = AH>, { #[allow(clippy::type_complexity)] type ActionKind = Action< @@ -25,7 +42,7 @@ where Decline, ethereum::DeployContract, ethereum::CallContract, - <(AL, AA) as RedeemAction>::RedeemActionOutput, + <(AL, AA) as RedeemAction>::Output, ethereum::CallContract, >; @@ -55,7 +72,7 @@ where HtlcParams::new_alpha_params(request, response), htlc_location.clone(), &*self.secret_source, // Derive identities with this. - *secret, /* Bob uses the secret learned from Alice's redeem + *secret, /* Bob uses the secret learned from Aliceredeem * action. */ ))] } @@ -83,20 +100,36 @@ where } } -impl Actions for bob::State +impl Actions + for bob::State< + Ethereum, + BL, + asset::Erc20, + BA, + htlc_location::Ethereum, + BH, + identity::Ethereum, + BI, + transaction::Ethereum, + BT, + > where - BL: Ledger, - BA: Asset, - (BL, BA): FundAction + RefundAction, + BL: Clone, + BA: Clone, + BH: Clone, + BI: Clone, + BT: Clone, + (BL, BA): FundAction> + + RefundAction, HtlcLocation = BH, FundTransaction = BT>, { #[allow(clippy::type_complexity)] type ActionKind = Action< Accept, Decline, Infallible, - <(BL, BA) as FundAction>::FundActionOutput, + <(BL, BA) as FundAction>::Output, ethereum::CallContract, - <(BL, BA) as RefundAction>::RefundActionOutput, + <(BL, BA) as RefundAction>::Output, >; fn actions(&self) -> Vec { diff --git a/cnd/src/swap_protocols/rfc003/bob/actions/generic_impl.rs b/cnd/src/swap_protocols/rfc003/bob/actions/generic_impl.rs index 6e366c4948..1942232c5b 100644 --- a/cnd/src/swap_protocols/rfc003/bob/actions/generic_impl.rs +++ b/cnd/src/swap_protocols/rfc003/bob/actions/generic_impl.rs @@ -1,34 +1,39 @@ -use crate::{ - asset::Asset, - swap_protocols::{ - actions::Actions, - rfc003::{ - actions::{Accept, Action, Decline, FundAction, RedeemAction, RefundAction}, - bob, - create_swap::HtlcParams, - Ledger, LedgerState, SwapCommunication, - }, +use crate::swap_protocols::{ + actions::Actions, + rfc003::{ + actions::{Accept, Action, Decline, FundAction, RedeemAction, RefundAction}, + bob, + create_swap::HtlcParams, + LedgerState, SwapCommunication, }, }; use std::convert::Infallible; -impl Actions for bob::State +impl Actions + for bob::State where - AL: Ledger, - BL: Ledger, - AA: Asset, - BA: Asset, - (BL, BA): FundAction + RefundAction, - (AL, AA): RedeemAction, + AL: Clone, + BL: Clone, + AA: Clone, + BA: Clone, + AH: Clone, + BH: Clone, + AI: Clone, + BI: Clone, + AT: Clone, + BT: Clone, + (BL, BA): FundAction> + + RefundAction, HtlcLocation = BH, FundTransaction = BT>, + (AL, AA): RedeemAction, HtlcLocation = AH>, { #[allow(clippy::type_complexity)] type ActionKind = Action< Accept, Decline, Infallible, - <(BL, BA) as FundAction>::FundActionOutput, - <(AL, AA) as RedeemAction>::RedeemActionOutput, - <(BL, BA) as RefundAction>::RefundActionOutput, + <(BL, BA) as FundAction>::Output, + <(AL, AA) as RedeemAction>::Output, + <(BL, BA) as RefundAction>::Output, >; fn actions(&self) -> Vec { @@ -56,7 +61,7 @@ where HtlcParams::new_alpha_params(request, response), htlc_location.clone(), &*self.secret_source, // Derive identities with this. - *secret, /* Bob uses the secret learned from Alice's redeem + *secret, /* Bob uses the secret learned from Alice redeem * action. */ ))] } diff --git a/cnd/src/swap_protocols/rfc003/bob/mod.rs b/cnd/src/swap_protocols/rfc003/bob/mod.rs index c202cab1c9..2d38e4cd3b 100644 --- a/cnd/src/swap_protocols/rfc003/bob/mod.rs +++ b/cnd/src/swap_protocols/rfc003/bob/mod.rs @@ -1,34 +1,26 @@ pub mod actions; -use crate::{ - asset::Asset, - swap_protocols::rfc003::{ - self, ledger::Ledger, ledger_state::LedgerState, messages::Request, Accept, ActorState, - Decline, DeriveIdentities, SwapCommunication, - }, +use crate::swap_protocols::rfc003::{ + ledger_state::LedgerState, messages::Request, Accept, ActorState, Decline, DeriveIdentities, + SwapCommunication, }; use derivative::Derivative; -use futures::sync::oneshot; -use std::sync::{Arc, Mutex}; - -#[allow(type_alias_bounds)] -pub type ResponseSender = - Arc>>>>; +use std::sync::Arc; #[derive(Clone, Derivative)] #[derivative(Debug)] -pub struct State { - pub swap_communication: SwapCommunication, - pub alpha_ledger_state: LedgerState, - pub beta_ledger_state: LedgerState, +pub struct State { + pub swap_communication: SwapCommunication, + pub alpha_ledger_state: LedgerState, + pub beta_ledger_state: LedgerState, #[derivative(Debug = "ignore")] pub secret_source: Arc, pub failed: bool, // Gets set on any error during the execution of a swap. } -impl State { +impl State { pub fn proposed( - request: Request, + request: Request, secret_source: impl DeriveIdentities, ) -> Self { Self { @@ -41,8 +33,8 @@ impl State { } pub fn accepted( - request: Request, - response: Accept, + request: Request, + response: Accept, secret_source: impl DeriveIdentities, ) -> Self { Self { @@ -55,7 +47,7 @@ impl State { } pub fn declined( - request: Request, + request: Request, response: Decline, secret_source: impl DeriveIdentities, ) -> Self { @@ -68,34 +60,51 @@ impl State { } } - pub fn request(&self) -> Request { + pub fn request(&self) -> &Request { match &self.swap_communication { SwapCommunication::Accepted { request, .. } | SwapCommunication::Proposed { request, .. } - | SwapCommunication::Declined { request, .. } => request.clone(), + | SwapCommunication::Declined { request, .. } => request, } } } -impl ActorState for State { +impl ActorState + for State +where + AL: 'static, + BL: 'static, + AA: 'static, + BA: 'static, + AH: 'static, + BH: 'static, + AI: 'static, + BI: 'static, + AT: 'static, + BT: 'static, +{ type AL = AL; type BL = BL; type AA = AA; type BA = BA; + type AH = AH; + type BH = BH; + type AT = AT; + type BT = BT; - fn expected_alpha_asset(&self) -> Self::AA { - self.swap_communication.request().alpha_asset.clone() + fn expected_alpha_asset(&self) -> &Self::AA { + &self.swap_communication.request().alpha_asset } - fn expected_beta_asset(&self) -> Self::BA { - self.swap_communication.request().beta_asset.clone() + fn expected_beta_asset(&self) -> &Self::BA { + &self.swap_communication.request().beta_asset } - fn alpha_ledger_mut(&mut self) -> &mut LedgerState { + fn alpha_ledger_mut(&mut self) -> &mut LedgerState { &mut self.alpha_ledger_state } - fn beta_ledger_mut(&mut self) -> &mut LedgerState { + fn beta_ledger_mut(&mut self) -> &mut LedgerState { &mut self.beta_ledger_state } diff --git a/cnd/src/swap_protocols/rfc003/create_swap.rs b/cnd/src/swap_protocols/rfc003/create_swap.rs index 6fe2ac02f8..6a89cda145 100644 --- a/cnd/src/swap_protocols/rfc003/create_swap.rs +++ b/cnd/src/swap_protocols/rfc003/create_swap.rs @@ -1,5 +1,4 @@ use crate::{ - asset::Asset, db::AcceptedSwap, swap_protocols::{ rfc003::{ @@ -8,23 +7,22 @@ use crate::{ Deployed, Funded, HtlcDeployed, HtlcFunded, HtlcRedeemed, HtlcRefunded, Redeemed, Refunded, }, - ledger::Ledger, - state_store::StateStore, Accept, ActorState, Request, SecretHash, }, + state_store::Update, HashFunction, }, timestamp::Timestamp, }; use chrono::NaiveDateTime; -use futures_core::future::{self, Either}; +use futures::future::{self, Either}; use genawaiter::{ sync::{Co, Gen}, GeneratorState, }; /// Returns a future that tracks the swap negotiated from the given request and -/// accept response on both ledgers. +/// accept response on alpha ledger. /// /// The current implementation is naive in the sense that it does not take into /// account situations where it is clear that no more events will happen even @@ -34,20 +32,108 @@ use genawaiter::{ /// /// It is highly unlikely for Bob to fund the HTLC now, yet the current /// implementation is still waiting for that. -pub async fn create_swap( +pub async fn create_alpha_watcher( dependencies: D, - accepted: AcceptedSwap, + accepted: AcceptedSwap, ) where - D: StateStore - + HtlcFunded - + HtlcFunded - + HtlcDeployed - + HtlcDeployed - + HtlcRedeemed - + HtlcRedeemed - + HtlcRefunded - + HtlcRefunded - + Clone, + D: Update> + + HtlcFunded + + HtlcFunded + + HtlcDeployed + + HtlcDeployed + + HtlcRedeemed + + HtlcRedeemed + + HtlcRefunded + + HtlcRefunded, + A::AL: Clone, + A::BL: Clone, + A::AA: Ord + Clone, + A::BA: Ord + Clone, + A::AH: Clone, + A::BH: Clone, + AI: Clone, + BI: Clone, + A::AT: Clone, + A::BT: Clone, + A: ActorState + Clone + Send, + AcceptedSwap: Clone, +{ + let (request, accept, at) = accepted; + + let id = request.swap_id; + let swap = OngoingSwap::new(request, accept); + + // construct a generator that watches alpha and beta ledger concurrently + let mut generator = Gen::new({ + |co| async { + watch_alpha_ledger::<_, A::AL, A::BL, A::AA, A::BA, A::AH, A::BH, AI, BI, A::AT, A::BT>( + &dependencies, + co, + swap.alpha_htlc_params(), + at, + ) + .await + } + }); + + loop { + // wait for events to be emitted as the generator executes + match generator.async_resume().await { + // every event that is yielded is passed on + GeneratorState::Yielded(event) => { + tracing::info!("swap {} yielded event {}", id, event); + dependencies.update(&id, event); + } + // the generator stopped executing, this means there are no more events that can be + // watched. + GeneratorState::Complete(Ok(_)) => { + tracing::info!("swap {} finished", id); + return; + } + GeneratorState::Complete(Err(e)) => { + tracing::error!("swap {} failed with {:?}", id, e); + return; + } + } + } +} + +/// Returns a future that tracks the swap negotiated from the given request and +/// accept response on beta ledger. +/// +/// The current implementation is naive in the sense that it does not take into +/// account situations where it is clear that no more events will happen even +/// though in theory, there could. For example: +/// - alpha funded +/// - alpha refunded +/// +/// It is highly unlikely for Bob to fund the HTLC now, yet the current +/// implementation is still waiting for that. +pub async fn create_beta_watcher( + dependencies: D, + accepted: AcceptedSwap, +) where + D: Update> + + HtlcFunded + + HtlcFunded + + HtlcDeployed + + HtlcDeployed + + HtlcRedeemed + + HtlcRedeemed + + HtlcRefunded + + HtlcRefunded, + A::AL: Clone, + A::BL: Clone, + A::AA: Ord + Clone, + A::BA: Ord + Clone, + A::AH: Clone, + A::BH: Clone, + A::AT: Clone, + A::BT: Clone, + AI: Clone, + BI: Clone, + A: ActorState + Clone + Send, + AcceptedSwap: Clone, { let (request, accept, at) = accepted.clone(); @@ -56,11 +142,12 @@ pub async fn create_swap( // construct a generator that watches alpha and beta ledger concurrently let mut generator = Gen::new({ - let dependencies = dependencies.clone(); - |co| async move { - future::try_join( - watch_alpha_ledger(&dependencies, &co, swap.alpha_htlc_params(), at), - watch_beta_ledger(&dependencies, &co, swap.beta_htlc_params(), at), + |co| async { + watch_beta_ledger::<_, A::AL, A::BL, A::AA, A::BA, A::AH, A::BH, AI, BI, A::AT, A::BT>( + &dependencies, + co, + swap.beta_htlc_params(), + at, ) .await } @@ -72,7 +159,7 @@ pub async fn create_swap( // every event that is yielded is passed on GeneratorState::Yielded(event) => { tracing::info!("swap {} yielded event {}", id, event); - dependencies.update::(&id, event); + dependencies.update(&id, event); } // the generator stopped executing, this means there are no more events that can be // watched. @@ -91,32 +178,34 @@ pub async fn create_swap( /// Returns a future that waits for events on alpha ledger to happen. /// /// Each event is yielded through the controller handle (co) of the coroutine. -async fn watch_alpha_ledger( +async fn watch_alpha_ledger( dependencies: &D, - co: &Co>, - htlc_params: HtlcParams, + co: Co>, + htlc_params: HtlcParams, start_of_swap: NaiveDateTime, ) -> anyhow::Result<()> where - AL: Ledger, - BL: Ledger, - AA: Asset, - BA: Asset, - D: HtlcFunded + HtlcDeployed + HtlcRedeemed + HtlcRefunded, + D: HtlcFunded + + HtlcDeployed + + HtlcRedeemed + + HtlcRefunded, + Deployed: Clone, + Redeemed: Clone, + Refunded: Clone, { let deployed = dependencies - .htlc_deployed(htlc_params.clone(), start_of_swap) + .htlc_deployed(&htlc_params, start_of_swap) .await?; co.yield_(SwapEvent::AlphaDeployed(deployed.clone())).await; let funded = dependencies - .htlc_funded(htlc_params.clone(), &deployed, start_of_swap) + .htlc_funded(&htlc_params, &deployed, start_of_swap) .await?; - co.yield_(SwapEvent::AlphaFunded(funded.clone())).await; + co.yield_(SwapEvent::AlphaFunded(funded)).await; - let redeemed = dependencies.htlc_redeemed(htlc_params.clone(), &deployed, start_of_swap); + let redeemed = dependencies.htlc_redeemed(&htlc_params, &deployed, start_of_swap); - let refunded = dependencies.htlc_refunded(htlc_params, &deployed, start_of_swap); + let refunded = dependencies.htlc_refunded(&htlc_params, &deployed, start_of_swap); match future::try_select(redeemed, refunded).await { Ok(Either::Left((redeemed, _))) => { @@ -138,32 +227,34 @@ where /// Returns a future that waits for events on beta ledger to happen. /// /// Each event is yielded through the controller handle (co) of the coroutine. -async fn watch_beta_ledger( +async fn watch_beta_ledger( dependencies: &D, - co: &Co>, - htlc_params: HtlcParams, + co: Co>, + htlc_params: HtlcParams, start_of_swap: NaiveDateTime, ) -> anyhow::Result<()> where - AL: Ledger, - BL: Ledger, - AA: Asset, - BA: Asset, - D: HtlcFunded + HtlcDeployed + HtlcRedeemed + HtlcRefunded, + D: HtlcFunded + + HtlcDeployed + + HtlcRedeemed + + HtlcRefunded, + Deployed: Clone, + Redeemed: Clone, + Refunded: Clone, { let deployed = dependencies - .htlc_deployed(htlc_params.clone(), start_of_swap) + .htlc_deployed(&htlc_params, start_of_swap) .await?; co.yield_(SwapEvent::BetaDeployed(deployed.clone())).await; let funded = dependencies - .htlc_funded(htlc_params.clone(), &deployed, start_of_swap) + .htlc_funded(&htlc_params, &deployed, start_of_swap) .await?; - co.yield_(SwapEvent::BetaFunded(funded.clone())).await; + co.yield_(SwapEvent::BetaFunded(funded)).await; - let redeemed = dependencies.htlc_redeemed(htlc_params.clone(), &deployed, start_of_swap); + let redeemed = dependencies.htlc_redeemed(&htlc_params, &deployed, start_of_swap); - let refunded = dependencies.htlc_refunded(htlc_params, &deployed, start_of_swap); + let refunded = dependencies.htlc_refunded(&htlc_params, &deployed, start_of_swap); match future::try_select(redeemed, refunded).await { Ok(Either::Left((redeemed, _))) => { @@ -183,39 +274,44 @@ where } #[derive(Clone, Copy, Debug)] -pub struct HtlcParams { +pub struct HtlcParams { pub asset: A, pub ledger: L, - pub redeem_identity: L::Identity, - pub refund_identity: L::Identity, + pub redeem_identity: I, + pub refund_identity: I, pub expiry: Timestamp, pub secret_hash: SecretHash, } -impl HtlcParams { - pub fn new_alpha_params( - request: &rfc003::Request, - accept_response: &rfc003::Accept, +impl HtlcParams +where + L: Clone, + A: Clone, + I: Clone, +{ + pub fn new_alpha_params( + request: &rfc003::Request, + accept_response: &rfc003::Accept, ) -> Self { HtlcParams { asset: request.alpha_asset.clone(), - ledger: request.alpha_ledger, - redeem_identity: accept_response.alpha_ledger_redeem_identity, - refund_identity: request.alpha_ledger_refund_identity, + ledger: request.alpha_ledger.clone(), + redeem_identity: accept_response.alpha_ledger_redeem_identity.clone(), + refund_identity: request.alpha_ledger_refund_identity.clone(), expiry: request.alpha_expiry, secret_hash: request.secret_hash, } } - pub fn new_beta_params( - request: &rfc003::Request, - accept_response: &rfc003::Accept, + pub fn new_beta_params( + request: &rfc003::Request, + accept_response: &rfc003::Accept, ) -> Self { HtlcParams { asset: request.beta_asset.clone(), - ledger: request.beta_ledger, - redeem_identity: request.beta_ledger_redeem_identity, - refund_identity: accept_response.beta_ledger_refund_identity, + ledger: request.beta_ledger.clone(), + redeem_identity: request.beta_ledger_redeem_identity.clone(), + refund_identity: accept_response.beta_ledger_refund_identity.clone(), expiry: request.beta_expiry, secret_hash: request.secret_hash, } @@ -223,29 +319,23 @@ impl HtlcParams { } #[derive(Debug, Clone, PartialEq)] -pub struct OngoingSwap -where - AL: Ledger, - BL: Ledger, - AA: Asset, - BA: Asset, -{ +pub struct OngoingSwap { pub alpha_ledger: AL, pub beta_ledger: BL, pub alpha_asset: AA, pub beta_asset: BA, pub hash_function: HashFunction, - pub alpha_ledger_redeem_identity: AL::Identity, - pub alpha_ledger_refund_identity: AL::Identity, - pub beta_ledger_redeem_identity: BL::Identity, - pub beta_ledger_refund_identity: BL::Identity, + pub alpha_ledger_redeem_identity: AI, + pub alpha_ledger_refund_identity: AI, + pub beta_ledger_redeem_identity: BI, + pub beta_ledger_refund_identity: BI, pub alpha_expiry: Timestamp, pub beta_expiry: Timestamp, pub secret_hash: SecretHash, } -impl OngoingSwap { - pub fn new(request: Request, accept: Accept) -> Self { +impl OngoingSwap { + pub fn new(request: Request, accept: Accept) -> Self { OngoingSwap { alpha_ledger: request.alpha_ledger, beta_ledger: request.beta_ledger, @@ -261,24 +351,38 @@ impl OngoingSwap { secret_hash: request.secret_hash, } } +} - pub fn alpha_htlc_params(&self) -> HtlcParams { +impl OngoingSwap +where + AL: Clone, + AA: Clone, + AI: Clone, +{ + pub fn alpha_htlc_params(&self) -> HtlcParams { HtlcParams { asset: self.alpha_asset.clone(), - ledger: self.alpha_ledger, - redeem_identity: self.alpha_ledger_redeem_identity, - refund_identity: self.alpha_ledger_refund_identity, + ledger: self.alpha_ledger.clone(), + redeem_identity: self.alpha_ledger_redeem_identity.clone(), + refund_identity: self.alpha_ledger_refund_identity.clone(), expiry: self.alpha_expiry, secret_hash: self.secret_hash, } } +} - pub fn beta_htlc_params(&self) -> HtlcParams { +impl OngoingSwap +where + BL: Clone, + BA: Clone, + BI: Clone, +{ + pub fn beta_htlc_params(&self) -> HtlcParams { HtlcParams { asset: self.beta_asset.clone(), - ledger: self.beta_ledger, - redeem_identity: self.beta_ledger_redeem_identity, - refund_identity: self.beta_ledger_refund_identity, + ledger: self.beta_ledger.clone(), + redeem_identity: self.beta_ledger_redeem_identity.clone(), + refund_identity: self.beta_ledger_refund_identity.clone(), expiry: self.beta_expiry, secret_hash: self.secret_hash, } @@ -286,39 +390,35 @@ impl OngoingSwap { } #[derive(Debug, Clone, PartialEq, strum_macros::Display)] -pub enum SwapEvent -where - AL: Ledger, - BL: Ledger, - AA: Asset, - BA: Asset, -{ - AlphaDeployed(Deployed), - AlphaFunded(Funded), - AlphaRedeemed(Redeemed), - AlphaRefunded(Refunded), - - BetaDeployed(Deployed), - BetaFunded(Funded), - BetaRedeemed(Redeemed), - BetaRefunded(Refunded), +pub enum SwapEvent { + AlphaDeployed(Deployed), + AlphaFunded(Funded), + AlphaRedeemed(Redeemed), + AlphaRefunded(Refunded), + + BetaDeployed(Deployed), + BetaFunded(Funded), + BetaRedeemed(Redeemed), + BetaRefunded(Refunded), } #[cfg(test)] mod tests { use super::*; - use crate::{asset, ethereum, swap_protocols::ledger}; + use crate::{asset, htlc_location, transaction}; #[test] fn swap_event_should_render_to_nice_string() { let event = SwapEvent::< - ledger::bitcoin::Mainnet, - ledger::Ethereum, asset::Bitcoin, asset::Ether, + htlc_location::Bitcoin, + htlc_location::Ethereum, + transaction::Bitcoin, + transaction::Ethereum, >::BetaDeployed(Deployed { - transaction: ethereum::Transaction::default(), - location: ethereum::Address::default(), + location: htlc_location::Ethereum::default(), + transaction: transaction::Ethereum::default(), }); let formatted = format!("{}", event); diff --git a/cnd/src/swap_protocols/rfc003/ethereum/htlc_events.rs b/cnd/src/swap_protocols/rfc003/ethereum/htlc_events.rs index 8b4c153cfc..ab0c0cac6c 100644 --- a/cnd/src/swap_protocols/rfc003/ethereum/htlc_events.rs +++ b/cnd/src/swap_protocols/rfc003/ethereum/htlc_events.rs @@ -1,9 +1,11 @@ use crate::{ - asset::{self, Asset}, + asset, + asset::{ethereum::FromWei, Erc20, Erc20Quantity, Ether}, btsieve::ethereum::{ - matching_transaction, Cache, Event, Topic, TransactionPattern, Web3Connector, + watch_for_contract_creation, watch_for_event, Cache, Event, Topic, Web3Connector, }, - ethereum::{Address, CalculateContractAddress, Transaction, TransactionAndReceipt, H256}, + ethereum::{H256, U256}, + htlc_location, identity, swap_protocols::{ ledger::Ethereum, rfc003::{ @@ -15,259 +17,257 @@ use crate::{ Secret, }, }, + transaction, }; -use anyhow::Context; -use asset::ethereum::FromWei; use chrono::NaiveDateTime; +use tracing_futures::Instrument; lazy_static::lazy_static! { - pub static ref REDEEM_LOG_MSG: H256 = blockchain_contracts::ethereum::rfc003::REDEEMED_LOG_MSG.parse().expect("to be valid hex"); - pub static ref REFUND_LOG_MSG: H256 = blockchain_contracts::ethereum::rfc003::REFUNDED_LOG_MSG.parse().expect("to be valid hex"); + static ref REDEEM_LOG_MSG: H256 = blockchain_contracts::ethereum::rfc003::REDEEMED_LOG_MSG.parse().expect("to be valid hex"); + static ref REFUND_LOG_MSG: H256 = blockchain_contracts::ethereum::rfc003::REFUNDED_LOG_MSG.parse().expect("to be valid hex"); /// keccak('Transfer(address,address,uint256)') - pub static ref TRANSFER_LOG_MSG: H256 = "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef".parse().expect("to be valid hex"); + static ref TRANSFER_LOG_MSG: H256 = "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef".parse().expect("to be valid hex"); } #[async_trait::async_trait] -impl HtlcFunded for Cache { +impl + HtlcFunded< + Ethereum, + asset::Ether, + htlc_location::Ethereum, + identity::Ethereum, + transaction::Ethereum, + > for Cache +{ async fn htlc_funded( &self, - _htlc_params: HtlcParams, - deploy_transaction: &Deployed, + _htlc_params: &HtlcParams, + deploy_transaction: &Deployed, _start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { + ) -> anyhow::Result> { Ok(Funded { + asset: Ether::from_wei(deploy_transaction.transaction.value), transaction: deploy_transaction.transaction.clone(), - asset: asset::Ether::from_wei(deploy_transaction.transaction.value), }) } } #[async_trait::async_trait] -impl HtlcDeployed for Cache { +impl + HtlcDeployed< + Ethereum, + asset::Ether, + htlc_location::Ethereum, + identity::Ethereum, + transaction::Ethereum, + > for Cache +{ async fn htlc_deployed( &self, - htlc_params: HtlcParams, + htlc_params: &HtlcParams, start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { - let connector = self.clone(); - let pattern = TransactionPattern { - from_address: None, - to_address: None, - is_contract_creation: Some(true), - transaction_data: Some(htlc_params.bytecode()), - transaction_data_length: None, - events: None, - }; - let TransactionAndReceipt { transaction, .. } = - matching_transaction(connector, pattern, start_of_swap) - .await - .context("failed to find transaction for htlc deployment")?; + ) -> anyhow::Result> { + let (transaction, location) = + watch_for_contract_creation(self, start_of_swap, htlc_params.bytecode()) + .instrument(tracing::info_span!("htlc_deployed")) + .await?; Ok(Deployed { - location: calculate_contract_address_from_deployment_transaction(&transaction), transaction, + location, }) } } #[async_trait::async_trait] -impl HtlcRedeemed for Cache { +impl + HtlcRedeemed< + Ethereum, + asset::Ether, + htlc_location::Ethereum, + identity::Ethereum, + transaction::Ethereum, + > for Cache +{ async fn htlc_redeemed( &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, + _htlc_params: &HtlcParams, + htlc_deployment: &Deployed, start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { - htlc_redeemed(self.clone(), htlc_params, htlc_deployment, start_of_swap).await + ) -> anyhow::Result> { + let event = Event { + address: htlc_deployment.location, + topics: vec![Some(Topic(*REDEEM_LOG_MSG))], + }; + + let (transaction, log) = watch_for_event(self, start_of_swap, event) + .instrument(tracing::info_span!("htlc_redeemed")) + .await?; + + let log_data = log.data.0.as_ref(); + let secret = + Secret::from_vec(log_data).expect("Must be able to construct secret from log data"); + + Ok(Redeemed { + transaction, + secret, + }) } } #[async_trait::async_trait] -impl HtlcRefunded for Cache { +impl + HtlcRefunded< + Ethereum, + asset::Ether, + htlc_location::Ethereum, + identity::Ethereum, + transaction::Ethereum, + > for Cache +{ async fn htlc_refunded( &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, + _htlc_params: &HtlcParams, + htlc_deployment: &Deployed, start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { - htlc_refunded(self.clone(), htlc_params, htlc_deployment, start_of_swap).await + ) -> anyhow::Result> { + let event = Event { + address: htlc_deployment.location, + topics: vec![Some(Topic(*REFUND_LOG_MSG))], + }; + + let (transaction, _) = watch_for_event(self, start_of_swap, event) + .instrument(tracing::info_span!("htlc_refunded")) + .await?; + + Ok(Refunded { transaction }) } } -fn calculate_contract_address_from_deployment_transaction(tx: &Transaction) -> Address { - tx.from.calculate_contract_address(&tx.nonce) -} +#[async_trait::async_trait] +impl + HtlcFunded< + Ethereum, + asset::Erc20, + htlc_location::Ethereum, + identity::Ethereum, + transaction::Ethereum, + > for Cache +{ + async fn htlc_funded( + &self, + htlc_params: &HtlcParams, + htlc_deployment: &Deployed, + start_of_swap: NaiveDateTime, + ) -> anyhow::Result> { + let event = Event { + address: htlc_params.asset.token_contract, + topics: vec![ + Some(Topic(*TRANSFER_LOG_MSG)), + None, + Some(Topic(htlc_deployment.location.into())), + ], + }; -async fn htlc_redeemed( - connector: Cache, - _htlc_params: HtlcParams, - htlc_deployment: &Deployed, - start_of_swap: NaiveDateTime, -) -> anyhow::Result> { - let pattern = TransactionPattern { - from_address: None, - to_address: None, - is_contract_creation: None, - transaction_data: None, - transaction_data_length: None, - events: Some(vec![Event { - address: Some(htlc_deployment.location), - data: None, - topics: vec![Some(Topic(*REDEEM_LOG_MSG))], - }]), - }; + let (transaction, log) = watch_for_event(self, start_of_swap, event) + .instrument(tracing::info_span!("htlc_funded")) + .await?; - let TransactionAndReceipt { - transaction, - receipt, - } = matching_transaction(connector, pattern, start_of_swap) - .await - .context("failed to find transaction to redeem from htlc")?; - let log = receipt - .logs - .into_iter() - .find(|log| log.topics.contains(&*REDEEM_LOG_MSG)) - .expect("Redeem transaction receipt must contain redeem logs"); - let log_data = log.data.0.as_ref(); - let secret = - Secret::from_vec(log_data).expect("Must be able to construct secret from log data"); + let quantity = Erc20Quantity::from_wei(U256::from_big_endian(log.data.0.as_ref())); + let asset = Erc20::new(log.address, quantity); - Ok(Redeemed { - transaction, - secret, - }) + Ok(Funded { asset, transaction }) + } } -async fn htlc_refunded( - connector: Cache, - _htlc_params: HtlcParams, - htlc_deployment: &Deployed, - start_of_swap: NaiveDateTime, -) -> anyhow::Result> { - let pattern = TransactionPattern { - from_address: None, - to_address: None, - is_contract_creation: None, - transaction_data: None, - transaction_data_length: None, - events: Some(vec![Event { - address: Some(htlc_deployment.location), - data: None, - topics: vec![Some(Topic(*REFUND_LOG_MSG))], - }]), - }; - - let TransactionAndReceipt { transaction, .. } = - matching_transaction(connector, pattern, start_of_swap) - .await - .context("failed to find transaction to refund from htlc")?; +#[async_trait::async_trait] +impl + HtlcDeployed< + Ethereum, + asset::Erc20, + htlc_location::Ethereum, + identity::Ethereum, + transaction::Ethereum, + > for Cache +{ + async fn htlc_deployed( + &self, + htlc_params: &HtlcParams, + start_of_swap: NaiveDateTime, + ) -> anyhow::Result> { + let (transaction, location) = + watch_for_contract_creation(self, start_of_swap, htlc_params.clone().bytecode()) + .instrument(tracing::info_span!("htlc_deployed")) + .await?; - Ok(Refunded { transaction }) + Ok(Deployed { + transaction, + location, + }) + } } -mod erc20 { - use super::*; - use crate::ethereum::U256; - use asset::ethereum::FromWei; +#[async_trait::async_trait] +impl + HtlcRedeemed< + Ethereum, + asset::Erc20, + htlc_location::Ethereum, + identity::Ethereum, + transaction::Ethereum, + > for Cache +{ + async fn htlc_redeemed( + &self, + _htlc_params: &HtlcParams, + htlc_deployment: &Deployed, + start_of_swap: NaiveDateTime, + ) -> anyhow::Result> { + let event = Event { + address: htlc_deployment.location, + topics: vec![Some(Topic(*REDEEM_LOG_MSG))], + }; - #[async_trait::async_trait] - impl HtlcFunded for Cache { - async fn htlc_funded( - &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, - start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { - let connector = self.clone(); - let events = Some(vec![Event { - address: Some(htlc_params.asset.token_contract), - data: None, - topics: vec![ - Some(Topic(*super::TRANSFER_LOG_MSG)), - None, - Some(Topic(htlc_deployment.location.into())), - ], - }]); - let TransactionAndReceipt { - transaction, - receipt, - } = matching_transaction( - connector, - TransactionPattern { - from_address: None, - to_address: None, - is_contract_creation: None, - transaction_data: None, - transaction_data_length: None, - events, - }, - start_of_swap, - ) - .await - .context("failed to find transaction to fund htlc")?; - let log = receipt - .logs - .into_iter() - .find(|log| log.topics.contains(&*super::TRANSFER_LOG_MSG)) - .expect("Fund transaction receipt must contain transfer events"); + let (transaction, log) = watch_for_event(self, start_of_swap, event) + .instrument(tracing::info_span!("htlc_redeemed")) + .await?; - let quantity = - asset::Erc20Quantity::from_wei(U256::from_big_endian(log.data.0.as_ref())); - let asset = asset::Erc20::new(log.address, quantity); + let log_data = log.data.0.as_ref(); + let secret = + Secret::from_vec(log_data).expect("Must be able to construct secret from log data"); - Ok(Funded { transaction, asset }) - } + Ok(Redeemed { + transaction, + secret, + }) } +} - #[async_trait::async_trait] - impl HtlcDeployed for Cache { - async fn htlc_deployed( - &self, - htlc_params: HtlcParams, - start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { - let connector = self.clone(); - let pattern = TransactionPattern { - from_address: None, - to_address: None, - is_contract_creation: Some(true), - transaction_data: Some(htlc_params.bytecode()), - transaction_data_length: None, - events: None, - }; - let TransactionAndReceipt { transaction, .. } = - matching_transaction(connector, pattern, start_of_swap) - .await - .context("failed to find transaction to deploy htlc")?; - - Ok(Deployed { - location: calculate_contract_address_from_deployment_transaction(&transaction), - transaction, - }) - } - } +#[async_trait::async_trait] +impl + HtlcRefunded< + Ethereum, + Erc20, + htlc_location::Ethereum, + identity::Ethereum, + transaction::Ethereum, + > for Cache +{ + async fn htlc_refunded( + &self, + _htlc_params: &HtlcParams, + htlc_deployment: &Deployed, + start_of_swap: NaiveDateTime, + ) -> anyhow::Result> { + let event = Event { + address: htlc_deployment.location, + topics: vec![Some(Topic(*REFUND_LOG_MSG))], + }; - #[async_trait::async_trait] - impl HtlcRedeemed for Cache { - async fn htlc_redeemed( - &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, - start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { - htlc_redeemed(self.clone(), htlc_params, htlc_deployment, start_of_swap).await - } - } + let (transaction, _) = watch_for_event(self, start_of_swap, event) + .instrument(tracing::info_span!("htlc_refunded")) + .await?; - #[async_trait::async_trait] - impl HtlcRefunded for Cache { - async fn htlc_refunded( - &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, - start_of_swap: NaiveDateTime, - ) -> anyhow::Result> { - htlc_refunded(self.clone(), htlc_params, htlc_deployment, start_of_swap).await - } + Ok(Refunded { transaction }) } } diff --git a/cnd/src/swap_protocols/rfc003/ethereum/mod.rs b/cnd/src/swap_protocols/rfc003/ethereum/mod.rs index 7c81ce3f2c..c53c9e9fb1 100644 --- a/cnd/src/swap_protocols/rfc003/ethereum/mod.rs +++ b/cnd/src/swap_protocols/rfc003/ethereum/mod.rs @@ -2,56 +2,16 @@ pub mod htlc_events; use crate::{ asset, - ethereum::{Address, Bytes, Transaction}, - swap_protocols::{ - ledger::Ethereum, - rfc003::{create_swap::HtlcParams, Ledger}, - }, + ethereum::Bytes, + identity, + swap_protocols::{ledger::Ethereum, rfc003::create_swap::HtlcParams}, }; use blockchain_contracts::ethereum::rfc003::{erc20_htlc::Erc20Htlc, ether_htlc::EtherHtlc}; -use serde::{Deserialize, Serialize}; -use std::time::Duration; -#[derive(Deserialize, Serialize, Debug)] -pub struct ByteCode(pub String); - -impl Into for ByteCode { - fn into(self) -> Bytes { - Bytes(hex::decode(self.0).unwrap()) - } -} - -pub trait Htlc { - fn compile_to_hex(&self) -> ByteCode; -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct Seconds(pub u64); - -impl From for Seconds { - fn from(duration: Duration) -> Self { - Seconds(duration.as_secs()) - } -} - -impl From for Duration { - fn from(seconds: Seconds) -> Duration { - Duration::from_secs(seconds.0) - } -} - -impl Ledger for Ethereum { - type HtlcLocation = Address; - type Identity = Address; - type Transaction = Transaction; -} - -impl From> for EtherHtlc { - fn from(htlc_params: HtlcParams) -> Self { - let refund_address = - blockchain_contracts::ethereum::Address(htlc_params.refund_identity.into()); - let redeem_address = - blockchain_contracts::ethereum::Address(htlc_params.redeem_identity.into()); +impl From> for EtherHtlc { + fn from(htlc_params: HtlcParams) -> Self { + let refund_address = blockchain_contracts::ethereum::Address(htlc_params.refund_identity.0); + let redeem_address = blockchain_contracts::ethereum::Address(htlc_params.redeem_identity.0); EtherHtlc::new( htlc_params.expiry.into(), @@ -62,18 +22,16 @@ impl From> for EtherHtlc { } } -impl HtlcParams { +impl HtlcParams { pub fn bytecode(&self) -> Bytes { EtherHtlc::from(self.clone()).into() } } -impl From> for Erc20Htlc { - fn from(htlc_params: HtlcParams) -> Self { - let refund_address = - blockchain_contracts::ethereum::Address(htlc_params.refund_identity.into()); - let redeem_address = - blockchain_contracts::ethereum::Address(htlc_params.redeem_identity.into()); +impl From> for Erc20Htlc { + fn from(htlc_params: HtlcParams) -> Self { + let refund_address = blockchain_contracts::ethereum::Address(htlc_params.refund_identity.0); + let redeem_address = blockchain_contracts::ethereum::Address(htlc_params.redeem_identity.0); let token_contract_address = blockchain_contracts::ethereum::Address(htlc_params.asset.token_contract.into()); @@ -88,7 +46,7 @@ impl From> for Erc20Htlc { } } -impl HtlcParams { +impl HtlcParams { pub fn bytecode(self) -> Bytes { Erc20Htlc::from(self).into() } diff --git a/cnd/src/swap_protocols/rfc003/events/mod.rs b/cnd/src/swap_protocols/rfc003/events/mod.rs index bf9c5bc6f8..30967e9b52 100644 --- a/cnd/src/swap_protocols/rfc003/events/mod.rs +++ b/cnd/src/swap_protocols/rfc003/events/mod.rs @@ -1,72 +1,65 @@ -// This is fine because we're using associated types -// see: https://github.com/rust-lang/rust/issues/21903 -#![allow(type_alias_bounds)] - -use crate::{ - asset::Asset, - swap_protocols::rfc003::{create_swap::HtlcParams, ledger::Ledger, Secret}, -}; +use crate::swap_protocols::rfc003::{create_swap::HtlcParams, Secret}; use chrono::NaiveDateTime; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub struct Funded { - pub transaction: L::Transaction, +pub struct Funded { pub asset: A, + pub transaction: T, } #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub struct Redeemed { - pub transaction: L::Transaction, +pub struct Redeemed { + pub transaction: T, pub secret: Secret, } #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub struct Deployed { - pub transaction: L::Transaction, - pub location: L::HtlcLocation, +pub struct Deployed { + pub location: H, + pub transaction: T, } #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub struct Refunded { - pub transaction: L::Transaction, +pub struct Refunded { + pub transaction: T, } #[async_trait::async_trait] -pub trait HtlcFunded: Send + Sync + Sized + 'static { +pub trait HtlcFunded: Send + Sync + Sized + 'static { async fn htlc_funded( &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, + htlc_params: &HtlcParams, + htlc_deployment: &Deployed, start_of_swap: NaiveDateTime, - ) -> anyhow::Result>; + ) -> anyhow::Result>; } #[async_trait::async_trait] -pub trait HtlcDeployed: Send + Sync + Sized + 'static { +pub trait HtlcDeployed: Send + Sync + Sized + 'static { async fn htlc_deployed( &self, - htlc_params: HtlcParams, + htlc_params: &HtlcParams, start_of_swap: NaiveDateTime, - ) -> anyhow::Result>; + ) -> anyhow::Result>; } #[async_trait::async_trait] -pub trait HtlcRedeemed: Send + Sync + Sized + 'static { +pub trait HtlcRedeemed: Send + Sync + Sized + 'static { async fn htlc_redeemed( &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, + htlc_params: &HtlcParams, + htlc_deployment: &Deployed, start_of_swap: NaiveDateTime, - ) -> anyhow::Result>; + ) -> anyhow::Result>; } #[async_trait::async_trait] -pub trait HtlcRefunded: Send + Sync + Sized + 'static { +pub trait HtlcRefunded: Send + Sync + Sized + 'static { async fn htlc_refunded( &self, - htlc_params: HtlcParams, - htlc_deployment: &Deployed, + htlc_params: &HtlcParams, + htlc_deployment: &Deployed, start_of_swap: NaiveDateTime, - ) -> anyhow::Result>; + ) -> anyhow::Result>; } diff --git a/cnd/src/swap_protocols/rfc003/ledger.rs b/cnd/src/swap_protocols/rfc003/ledger.rs deleted file mode 100644 index f8a088ed8f..0000000000 --- a/cnd/src/swap_protocols/rfc003/ledger.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::swap_protocols::LedgerKind; -use serde::{de::DeserializeOwned, Serialize}; -use std::{fmt::Debug, hash::Hash}; - -pub trait Ledger: - Clone + Copy + Debug + Send + Sync + 'static + PartialEq + Eq + Hash + Into + Sized -{ - type HtlcLocation: PartialEq + Debug + Clone + DeserializeOwned + Serialize + Send + Sync; - type Identity: Clone - + Copy - + Debug - + Send - + Sync - + PartialEq - + Eq - + Hash - + 'static - + Serialize - + DeserializeOwned; - type Transaction: Debug - + Clone - + DeserializeOwned - + Serialize - + Send - + Sync - + PartialEq - + 'static; -} diff --git a/cnd/src/swap_protocols/rfc003/ledger_state.rs b/cnd/src/swap_protocols/rfc003/ledger_state.rs index 07750a25b1..5efcd02619 100644 --- a/cnd/src/swap_protocols/rfc003/ledger_state.rs +++ b/cnd/src/swap_protocols/rfc003/ledger_state.rs @@ -1,10 +1,6 @@ -use crate::{ - asset::Asset, - swap_protocols::rfc003::{ - events::{Deployed, Funded, Redeemed, Refunded}, - ledger::Ledger, - Secret, - }, +use crate::swap_protocols::rfc003::{ + events::{Deployed, Funded, Redeemed, Refunded}, + Secret, }; use serde::Serialize; use strum_macros::EnumDiscriminants; @@ -15,43 +11,43 @@ use strum_macros::EnumDiscriminants; derive(Serialize, Display), serde(rename_all = "SCREAMING_SNAKE_CASE") )] -pub enum LedgerState { +pub enum LedgerState { NotDeployed, Deployed { - htlc_location: L::HtlcLocation, - deploy_transaction: L::Transaction, + htlc_location: H, + deploy_transaction: T, }, Funded { - htlc_location: L::HtlcLocation, - deploy_transaction: L::Transaction, - fund_transaction: L::Transaction, + htlc_location: H, + deploy_transaction: T, + fund_transaction: T, asset: A, }, Redeemed { - htlc_location: L::HtlcLocation, - deploy_transaction: L::Transaction, - fund_transaction: L::Transaction, - redeem_transaction: L::Transaction, + htlc_location: H, + deploy_transaction: T, + fund_transaction: T, + redeem_transaction: T, asset: A, secret: Secret, }, Refunded { - htlc_location: L::HtlcLocation, - deploy_transaction: L::Transaction, - fund_transaction: L::Transaction, - refund_transaction: L::Transaction, + htlc_location: H, + deploy_transaction: T, + fund_transaction: T, + refund_transaction: T, asset: A, }, IncorrectlyFunded { - htlc_location: L::HtlcLocation, - deploy_transaction: L::Transaction, - fund_transaction: L::Transaction, + htlc_location: H, + deploy_transaction: T, + fund_transaction: T, asset: A, }, } -impl LedgerState { - pub fn transition_to_deployed(&mut self, deployed: Deployed) { +impl LedgerState { + pub fn transition_to_deployed(&mut self, deployed: Deployed) { let Deployed { transaction, location, @@ -68,7 +64,7 @@ impl LedgerState { } } - pub fn transition_to_funded(&mut self, funded: Funded) { + pub fn transition_to_funded(&mut self, funded: Funded) { let Funded { transaction, asset } = funded; match std::mem::replace(self, LedgerState::NotDeployed) { @@ -87,7 +83,7 @@ impl LedgerState { } } - pub fn transition_to_incorrectly_funded(&mut self, funded: Funded) { + pub fn transition_to_incorrectly_funded(&mut self, funded: Funded) { let Funded { transaction, asset } = funded; match std::mem::replace(self, LedgerState::NotDeployed) { @@ -106,7 +102,7 @@ impl LedgerState { } } - pub fn transition_to_redeemed(&mut self, redeemed: Redeemed) { + pub fn transition_to_redeemed(&mut self, redeemed: Redeemed) { let Redeemed { transaction, secret, @@ -132,7 +128,7 @@ impl LedgerState { } } - pub fn transition_to_refunded(&mut self, refunded: Refunded) { + pub fn transition_to_refunded(&mut self, refunded: Refunded) { let Refunded { transaction } = refunded; match std::mem::replace(self, LedgerState::NotDeployed) { diff --git a/cnd/src/swap_protocols/rfc003/messages.rs b/cnd/src/swap_protocols/rfc003/messages.rs index 4ec91e1d1d..187f1d690a 100644 --- a/cnd/src/swap_protocols/rfc003/messages.rs +++ b/cnd/src/swap_protocols/rfc003/messages.rs @@ -1,7 +1,6 @@ use crate::{ - asset::Asset, swap_protocols::{ - rfc003::{DeriveIdentities, Ledger, SecretHash}, + rfc003::{DeriveIdentities, SecretHash}, HashFunction, SwapId, }, timestamp::Timestamp, @@ -13,15 +12,15 @@ use serde::{Deserialize, Serialize}; /// This does _not_ represent the actual network message, that is why it also /// does not implement Serialize. #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct Request { +pub struct Request { pub swap_id: SwapId, pub alpha_ledger: AL, pub beta_ledger: BL, pub alpha_asset: AA, pub beta_asset: BA, pub hash_function: HashFunction, - pub alpha_ledger_refund_identity: AL::Identity, - pub beta_ledger_redeem_identity: BL::Identity, + pub alpha_ledger_refund_identity: AI, + pub beta_ledger_redeem_identity: BI, pub alpha_expiry: Timestamp, pub beta_expiry: Timestamp, pub secret_hash: SecretHash, @@ -32,10 +31,10 @@ pub struct Request { /// This does _not_ represent the actual network message, that is why it also /// does not implement Serialize. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct Accept { +pub struct Accept { pub swap_id: SwapId, - pub beta_ledger_refund_identity: BL::Identity, - pub alpha_ledger_redeem_identity: AL::Identity, + pub beta_ledger_refund_identity: BI, + pub alpha_ledger_redeem_identity: AI, } /// High-level message that represents declining a Swap request @@ -50,9 +49,9 @@ pub struct Decline { /// Body of the rfc003 request message #[derive(Debug, Deserialize, PartialEq, Serialize)] -pub struct RequestBody { - pub alpha_ledger_refund_identity: AL::Identity, - pub beta_ledger_redeem_identity: BL::Identity, +pub struct RequestBody { + pub alpha_ledger_refund_identity: AI, + pub beta_ledger_redeem_identity: BI, pub alpha_expiry: Timestamp, pub beta_expiry: Timestamp, pub secret_hash: SecretHash, @@ -66,9 +65,9 @@ pub enum Decision { /// Body of the rfc003 accept message #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct AcceptResponseBody { - pub beta_ledger_refund_identity: BL::Identity, - pub alpha_ledger_redeem_identity: AL::Identity, +pub struct AcceptResponseBody { + pub beta_ledger_refund_identity: BI, + pub alpha_ledger_redeem_identity: AI, } /// Body of the rfc003 decline message @@ -88,12 +87,12 @@ pub enum SwapDeclineReason { BadJsonField, } -pub trait IntoAcceptMessage { +pub trait IntoAcceptMessage { fn into_accept_message( self, id: SwapId, secret_source: &dyn DeriveIdentities, - ) -> Accept; + ) -> Accept; } #[cfg(test)] diff --git a/cnd/src/swap_protocols/rfc003/mod.rs b/cnd/src/swap_protocols/rfc003/mod.rs index c38f0a657f..8a4e926539 100644 --- a/cnd/src/swap_protocols/rfc003/mod.rs +++ b/cnd/src/swap_protocols/rfc003/mod.rs @@ -1,6 +1,3 @@ -#[macro_use] -mod transition_save; - pub mod alice; pub mod bitcoin; pub mod bob; @@ -9,46 +6,43 @@ pub mod ethereum; pub mod events; pub mod ledger_state; pub mod messages; -pub mod state_store; pub mod actions; mod actor_state; -mod ledger; mod secret; pub use self::{ actor_state::ActorState, - create_swap::create_swap, - ledger::Ledger, + create_swap::create_alpha_watcher, ledger_state::{HtlcState, LedgerState}, secret::{FromErr, Secret, SecretHash}, }; pub use self::messages::{Accept, Decline, Request}; -use crate::{asset::Asset, seed::SwapSeed}; +use crate::seed::SwapSeed; use ::bitcoin::secp256k1::SecretKey; /// Swap request response as received from peer node acting as Bob. -pub type Response = Result, Decline>; +pub type Response = Result, Decline>; #[derive(Clone, Debug, PartialEq)] -pub enum SwapCommunication { +pub enum SwapCommunication { Proposed { - request: Request, + request: Request, }, Accepted { - request: Request, - response: Accept, + request: Request, + response: Accept, }, Declined { - request: Request, + request: Request, response: Decline, }, } -impl SwapCommunication { - pub fn request(&self) -> &Request { +impl SwapCommunication { + pub fn request(&self) -> &Request { match self { SwapCommunication::Accepted { request, .. } => request, SwapCommunication::Proposed { request } => request, diff --git a/cnd/src/swap_protocols/rfc003/transition_save.rs b/cnd/src/swap_protocols/rfc003/transition_save.rs deleted file mode 100644 index 6e1be7f3cc..0000000000 --- a/cnd/src/swap_protocols/rfc003/transition_save.rs +++ /dev/null @@ -1,11 +0,0 @@ -#[macro_export] -macro_rules! transition_save { - ($repo:expr, $new_state:expr) => {{ - let save_state = $new_state; - $repo.save(save_state.clone().into()); - - tracing::debug!("Transitioning to {}", save_state); - - return Ok(::futures::Async::Ready(save_state.into())); - }}; -} diff --git a/cnd/src/swap_protocols/rfc003/state_store.rs b/cnd/src/swap_protocols/state_store.rs similarity index 61% rename from cnd/src/swap_protocols/rfc003/state_store.rs rename to cnd/src/swap_protocols/state_store.rs index 06fdca6251..60dc361872 100644 --- a/cnd/src/swap_protocols/rfc003/state_store.rs +++ b/cnd/src/swap_protocols/state_store.rs @@ -10,45 +10,70 @@ pub enum Error { InvalidType, } -pub trait StateStore: Send + Sync + 'static { - fn insert(&self, key: SwapId, value: A); - fn get(&self, key: &SwapId) -> Result, Error>; - fn update(&self, key: &SwapId, update: SwapEvent); +#[allow(clippy::type_complexity)] +pub trait Insert: Send + Sync + 'static { + fn insert(&self, key: SwapId, value: S); +} + +#[allow(clippy::type_complexity)] +pub trait Get: Send + Sync + 'static { + fn get(&self, key: &SwapId) -> Result, Error>; +} + +#[allow(clippy::type_complexity)] +pub trait Update: Send + Sync + 'static { + fn update(&self, key: &SwapId, update: E); } #[derive(Default, Debug)] pub struct InMemoryStateStore { - states: Mutex>>, + states: Mutex>>, } -impl StateStore for InMemoryStateStore { - fn insert(&self, key: SwapId, value: A) { +impl Insert for InMemoryStateStore +where + S: Send + 'static, +{ + fn insert(&self, key: SwapId, value: S) { let mut states = self.states.lock().unwrap(); states.insert(key, Box::new(value)); } +} - fn get(&self, key: &SwapId) -> Result, Error> { +impl Get for InMemoryStateStore +where + S: Clone + Send + 'static, +{ + fn get(&self, key: &SwapId) -> Result, Error> { let states = self.states.lock().unwrap(); match states.get(key) { - Some(state) => match state.downcast_ref::() { + Some(state) => match state.downcast_ref::() { Some(state) => Ok(Some(state.clone())), None => Err(Error::InvalidType), }, None => Ok(None), } } +} - fn update(&self, key: &SwapId, event: SwapEvent) { - let mut actor_state = match self.get::(key) { - Ok(Some(actor_state)) => actor_state, - Ok(None) => { +impl Update> for InMemoryStateStore +where + S: ActorState + Send, + S::AA: Ord, + S::BA: Ord, +{ + #[allow(clippy::type_complexity)] + fn update(&self, key: &SwapId, event: SwapEvent) { + let mut states = self.states.lock().unwrap(); + let actor_state = match states + .get_mut(key) + .and_then(|state| state.downcast_mut::()) + { + Some(state) => state, + None => { tracing::warn!("Value not found for key {}", key); return; } - Err(_invalid_type) => { - tracing::warn!("Attempted to get state with wrong type for key {}", key); - return; - } }; match event { @@ -97,8 +122,6 @@ impl StateStore for InMemoryStateStore { .beta_ledger_mut() .transition_to_refunded(refunded), } - - self.insert(key.clone(), actor_state) } } @@ -106,9 +129,9 @@ impl StateStore for InMemoryStateStore { mod tests { use super::*; use crate::{ - asset, - asset::ethereum::FromWei, + asset::{self, ethereum::FromWei}, ethereum::Address, + htlc_location, identity, seed::{DeriveSwapSeed, RootSeed}, swap_protocols::{ ledger::{bitcoin, Ethereum}, @@ -116,17 +139,21 @@ mod tests { HashFunction, }, timestamp::Timestamp, + transaction, }; use spectral::prelude::*; + use std::str::FromStr; #[test] fn insert_and_get_state() { let state_store = InMemoryStateStore::default(); - let bitcoin_pub_key = "02c2a8efce029526d364c2cf39d89e3cdda05e5df7b2cbfc098b4e3d02b70b5275" - .parse() - .unwrap(); - let ethereum_address: Address = "8457037fcd80a8650c4692d7fcfc1d0a96b92867".parse().unwrap(); + let bitcoin_pub_key = identity::Bitcoin::from_str( + "02c2a8efce029526d364c2cf39d89e3cdda05e5df7b2cbfc098b4e3d02b70b5275", + ) + .unwrap(); + let ethereum_address = + Address::from_str("8457037fcd80a8650c4692d7fcfc1d0a96b92867").unwrap(); let request = Request { swap_id: SwapId::default(), @@ -150,17 +177,36 @@ mod tests { let id = SwapId::default(); let seed = RootSeed::from(*b"hello world, you are beautiful!!"); let secret_source = seed.derive_swap_seed(id); - let state = alice::State::accepted(request, accept, secret_source); - - state_store - .insert::>( - id, - state.clone(), - ); - - let res = state_store - .get::>(&id) - .unwrap(); - assert_that(&res).contains_value(state); + let state: alice::State< + bitcoin::Regtest, + Ethereum, + asset::Bitcoin, + asset::Ether, + htlc_location::Bitcoin, + htlc_location::Ethereum, + identity::Bitcoin, + identity::Ethereum, + transaction::Bitcoin, + transaction::Ethereum, + > = alice::State::accepted(request, accept, secret_source); + + state_store.insert(id, state.clone()); + + #[allow(clippy::type_complexity)] + let res: Option< + alice::State< + bitcoin::Regtest, + Ethereum, + asset::Bitcoin, + asset::Ether, + htlc_location::Bitcoin, + htlc_location::Ethereum, + identity::Bitcoin, + identity::Ethereum, + transaction::Bitcoin, + transaction::Ethereum, + >, + > = state_store.get(&id).unwrap(); + assert_that(&res).contains_value(&state); } } diff --git a/cnd/tests/bitcoin_go_back_into_the_past.rs b/cnd/tests/bitcoin_go_back_into_the_past.rs index 5d019ea814..dff8bb1d89 100644 --- a/cnd/tests/bitcoin_go_back_into_the_past.rs +++ b/cnd/tests/bitcoin_go_back_into_the_past.rs @@ -3,7 +3,7 @@ pub mod bitcoin_helper; use bitcoin::Address; use bitcoin_helper::BitcoinConnectorMock; use chrono::NaiveDateTime; -use cnd::btsieve::bitcoin::{matching_transaction, TransactionPattern}; +use cnd::btsieve::bitcoin::watch_for_created_outpoint; use std::str::FromStr; #[tokio::test] @@ -26,22 +26,18 @@ async fn find_transaction_go_back_into_the_past() { ], ); - let pattern = TransactionPattern { - to_address: Some( - Address::from_str( - include_str!("test_data/bitcoin/find_transaction_go_back_into_the_past/address") - .trim(), - ) - .unwrap(), - ), - from_outpoint: None, - unlock_script: None, - }; let start_of_swap = NaiveDateTime::from_timestamp(block1_with_transaction.header.time as i64, 0); - let expected_transaction = matching_transaction(connector, pattern, start_of_swap) - .await - .unwrap(); + let (expected_transaction, _out_point) = watch_for_created_outpoint( + &connector, + start_of_swap, + Address::from_str( + include_str!("test_data/bitcoin/find_transaction_go_back_into_the_past/address").trim(), + ) + .unwrap(), + ) + .await + .unwrap(); assert_eq!( expected_transaction, diff --git a/cnd/tests/bitcoin_helper/connector_mock.rs b/cnd/tests/bitcoin_helper/connector_mock.rs index 0374aca326..d2fdacfe70 100644 --- a/cnd/tests/bitcoin_helper/connector_mock.rs +++ b/cnd/tests/bitcoin_helper/connector_mock.rs @@ -1,24 +1,18 @@ -use bitcoin::{hashes::sha256d, util::hash::BitcoinHash, BlockHash}; +use anyhow::Context; +use async_trait::async_trait; +use bitcoin::{util::hash::BitcoinHash, BlockHash}; use cnd::btsieve::{BlockByHash, LatestBlock}; -use futures::{future::IntoFuture, Future}; -use std::{ - collections::HashMap, - time::{Duration, Instant}, -}; +use futures::{stream::BoxStream, StreamExt}; +use std::{collections::HashMap, time::Duration}; +use tokio::{stream, sync::Mutex, time::throttle}; -#[derive(Clone)] pub struct BitcoinConnectorMock { all_blocks: HashMap, - latest_blocks: Vec, - latest_time_return_block: Instant, - current_latest_block_index: usize, + latest_blocks: Mutex>, } impl BitcoinConnectorMock { - pub fn new( - latest_blocks: impl IntoIterator, - all_blocks: impl IntoIterator, - ) -> Self { + pub fn new(latest_blocks: Vec, all_blocks: Vec) -> Self { BitcoinConnectorMock { all_blocks: all_blocks .into_iter() @@ -26,54 +20,44 @@ impl BitcoinConnectorMock { hm.insert(block.bitcoin_hash(), block); hm }), - latest_blocks: latest_blocks.into_iter().collect(), - latest_time_return_block: Instant::now(), - current_latest_block_index: 0, + latest_blocks: Mutex::new( + throttle(Duration::from_secs(1), stream::iter(latest_blocks)).boxed(), + ), } } } +#[derive(Debug, thiserror::Error)] +#[error("there are no more blocks in this blockchain, either your implementation is buggy or you need a better test setup")] +pub struct OutOfBlocks; + +#[async_trait] impl LatestBlock for BitcoinConnectorMock { type Block = bitcoin::Block; - type BlockHash = sha256d::Hash; - fn latest_block( - &mut self, - ) -> Box + Send + 'static> { - if self.latest_blocks.is_empty() { - return Box::new(Err(anyhow::Error::from(Error::NoMoreBlocks)).into_future()); - } + async fn latest_block(&self) -> anyhow::Result { + let block = self + .latest_blocks + .lock() + .await + .next() + .await + .ok_or(OutOfBlocks)?; - let latest_block = self.latest_blocks[self.current_latest_block_index].clone(); - if self.latest_time_return_block.elapsed() >= Duration::from_secs(1) { - self.latest_time_return_block = Instant::now(); - if self - .latest_blocks - .get(self.current_latest_block_index + 1) - .is_some() - { - self.current_latest_block_index += 1; - } - } - Box::new(Ok(latest_block).into_future()) + Ok(block) } } +#[async_trait] impl BlockByHash for BitcoinConnectorMock { type Block = bitcoin::Block; type BlockHash = bitcoin::BlockHash; - fn block_by_hash( - &self, - block_hash: Self::BlockHash, - ) -> Box + Send + 'static> { - Box::new( - self.all_blocks - .get(&block_hash) - .cloned() - .ok_or_else(|| anyhow::Error::from(Error::UnknownHash(block_hash))) - .into_future(), - ) + async fn block_by_hash(&self, block_hash: Self::BlockHash) -> anyhow::Result { + self.all_blocks + .get(&block_hash) + .cloned() + .with_context(|| format!("could not find block with hash {}", block_hash)) } } @@ -81,6 +65,4 @@ impl BlockByHash for BitcoinConnectorMock { pub enum Error { #[error("ran out of blocks in chain")] NoMoreBlocks, - #[error("could not find block with hash {0}")] - UnknownHash(BlockHash), } diff --git a/cnd/tests/bitcoin_missed_previous_latest_block.rs b/cnd/tests/bitcoin_missed_previous_latest_block.rs index cb420f7442..bb999dcca6 100644 --- a/cnd/tests/bitcoin_missed_previous_latest_block.rs +++ b/cnd/tests/bitcoin_missed_previous_latest_block.rs @@ -2,8 +2,8 @@ pub mod bitcoin_helper; use bitcoin::Address; use bitcoin_helper::BitcoinConnectorMock; -use chrono::offset::Utc; -use cnd::btsieve::bitcoin::{matching_transaction, TransactionPattern}; +use chrono::{offset::Utc, NaiveDateTime}; +use cnd::btsieve::bitcoin::watch_for_created_outpoint; use std::str::FromStr; #[tokio::test] @@ -22,23 +22,25 @@ async fn find_transaction_missed_previous_latest_block() { ], ); - let pattern = TransactionPattern { - to_address: Some( - Address::from_str( - include_str!( - "test_data/bitcoin/find_transaction_missed_previous_latest_block/address" - ) + let block1: bitcoin::Block = include_hex!( + "./test_data/bitcoin/find_transaction_missed_previous_latest_block/block1.hex" + ); + + // set the start of the swap to one second after the first block, + // otherwise we run into the problem, that we try to fetch blocks prior to the + // first one + let start_of_swap = NaiveDateTime::from_timestamp((block1.header.time as i64) + 1, 0); + let (expected_transaction, _out_point) = watch_for_created_outpoint( + &connector, + start_of_swap, + Address::from_str( + include_str!("test_data/bitcoin/find_transaction_missed_previous_latest_block/address") .trim(), - ) - .unwrap(), - ), - from_outpoint: None, - unlock_script: None, - }; - let start_of_swap = Utc::now().naive_local(); - let expected_transaction = matching_transaction(connector, pattern, start_of_swap) - .await - .unwrap(); + ) + .unwrap(), + ) + .await + .unwrap(); assert_eq!( expected_transaction, @@ -67,23 +69,27 @@ async fn find_transaction_missed_previous_latest_block_with_big_gap() { ], ); - let pattern = TransactionPattern { - to_address: Some( - Address::from_str( - include_str!( - "test_data/bitcoin/find_transaction_missed_previous_latest_block_with_big_gap/address" - ) - .trim(), - ) - .unwrap(), - ), - from_outpoint: None, - unlock_script: None, - }; - let start_of_swap = Utc::now().naive_local(); - let expected_transaction = matching_transaction(connector, pattern, start_of_swap) - .await - .unwrap(); + let block1: bitcoin::Block = include_hex!( + "./test_data/bitcoin/find_transaction_missed_previous_latest_block_with_big_gap/block1.hex" + ); + + // set the start of the swap to one second after the first block, + // otherwise we run into the problem, that we try to fetch blocks prior to the + // first one + let start_of_swap = NaiveDateTime::from_timestamp((block1.header.time as i64) + 1, 0); + let (expected_transaction, _out_point) = watch_for_created_outpoint( + &connector, + start_of_swap, + Address::from_str( + include_str!( + "test_data/bitcoin/find_transaction_missed_previous_latest_block_with_big_gap/address" + ) + .trim(), + ) + .unwrap(), + ) + .await + .unwrap(); assert_eq!( expected_transaction, @@ -108,23 +114,18 @@ async fn find_transaction_if_blockchain_reorganisation() { ], ); - let pattern = TransactionPattern { - to_address: Some( - Address::from_str( - include_str!( - "test_data/bitcoin/find_transaction_if_blockchain_reorganisation/address" - ) - .trim(), - ) - .unwrap(), - ), - from_outpoint: None, - unlock_script: None, - }; let start_of_swap = Utc::now().naive_local(); - let expected_transaction = matching_transaction(connector, pattern, start_of_swap) - .await - .unwrap(); + let (expected_transaction, _out_point) = watch_for_created_outpoint( + &connector, + start_of_swap, + Address::from_str( + include_str!("test_data/bitcoin/find_transaction_if_blockchain_reorganisation/address") + .trim(), + ) + .unwrap(), + ) + .await + .unwrap(); assert_eq!( expected_transaction, @@ -152,21 +153,14 @@ async fn find_transaction_if_blockchain_reorganisation_with_long_chain() { ], ); - let pattern = TransactionPattern { - to_address: Some( - Address::from_str( - include_str!( - "test_data/bitcoin/find_transaction_if_blockchain_reorganisation_with_long_chain/address" - ).trim() - , - ) - .unwrap(), - ), - from_outpoint: None, - unlock_script: None, - }; let start_of_swap = Utc::now().naive_local(); - let expected_transaction = matching_transaction(connector, pattern, start_of_swap) + let (expected_transaction, _out_point) = watch_for_created_outpoint(&connector, start_of_swap, Address::from_str( + include_str!( + "test_data/bitcoin/find_transaction_if_blockchain_reorganisation_with_long_chain/address" + ).trim() + , + ) + .unwrap(),) .await .unwrap(); diff --git a/cnd/tests/bitcoin_transaction_pattern_e2e.rs b/cnd/tests/bitcoin_transaction_pattern_e2e.rs index f744ebda76..891bca06cf 100644 --- a/cnd/tests/bitcoin_transaction_pattern_e2e.rs +++ b/cnd/tests/bitcoin_transaction_pattern_e2e.rs @@ -1,8 +1,7 @@ use bitcoin::{Amount, Network}; use bitcoincore_rpc::RpcApi; use chrono::offset::Utc; -use cnd::btsieve::bitcoin::{matching_transaction, BitcoindConnector, TransactionPattern}; -use futures_core::future; +use cnd::btsieve::bitcoin::{watch_for_created_outpoint, BitcoindConnector}; use images::coblox_bitcoincore::BitcoinCore; use reqwest::Url; use std::time::Duration; @@ -31,52 +30,51 @@ async fn bitcoin_transaction_pattern_e2e_test() { // make sure we have money client.generate(101, None).unwrap(); - let pattern = TransactionPattern { - to_address: Some(target_address.clone()), - from_outpoint: None, - unlock_script: None, - }; - let start_of_swap = Utc::now().naive_local(); - let funding_transaction = matching_transaction(connector, pattern, start_of_swap); + let send_money_to_address = async { tokio::time::delay_for(Duration::from_secs(2)).await; - tokio::task::spawn_blocking(move || { - let transaction_hash = client - .send_to_address( - &target_address, - Amount::from_sat(100_000_000), - None, - None, - None, - None, - None, - None, - ) - .unwrap(); - client.generate(1, None).unwrap(); + tokio::task::spawn_blocking({ + let target_address = target_address.clone(); + move || { + let transaction_hash = client + .send_to_address( + &target_address, + Amount::from_sat(100_000_000), + None, + None, + None, + None, + None, + None, + ) + .unwrap(); + client.generate(1, None).unwrap(); - transaction_hash + transaction_hash + } }) .await }; - let future = future::join(send_money_to_address, funding_transaction); + let actual_transaction = tokio::time::timeout(Duration::from_secs(5), send_money_to_address) + .await + .expect("failed to send money to address"); - let (actual_transaction, funding_transaction) = - tokio::time::timeout(Duration::from_secs(5), future) + let (funding_transaction, _out_point) = + watch_for_created_outpoint(&connector, start_of_swap, target_address) .await .unwrap(); - assert_eq!( - funding_transaction.unwrap().txid(), - actual_transaction.unwrap() - ) + assert_eq!(funding_transaction.txid(), actual_transaction.unwrap()) } -pub fn new_bitcoincore_client( +pub fn new_bitcoincore_client( container: &Container<'_, D, BitcoinCore>, -) -> bitcoincore_rpc::Client { +) -> bitcoincore_rpc::Client +where + D: Docker, +{ let port = container.get_host_port(18443).unwrap(); let auth = container.image().auth(); diff --git a/cnd/tests/ethereum_go_back_into_the_past.rs b/cnd/tests/ethereum_go_back_into_the_past.rs index e07e2589ef..ad325c4cd9 100644 --- a/cnd/tests/ethereum_go_back_into_the_past.rs +++ b/cnd/tests/ethereum_go_back_into_the_past.rs @@ -2,20 +2,20 @@ pub mod ethereum_helper; use chrono::NaiveDateTime; use cnd::{ - btsieve::ethereum::{matching_transaction, TransactionPattern}, - ethereum::{Block, Transaction, TransactionAndReceipt, TransactionReceipt}, + btsieve::ethereum::matching_transaction_and_receipt, + ethereum::{Block, Transaction, TransactionReceipt}, }; use ethereum_helper::EthereumConnectorMock; #[tokio::test] async fn find_transaction_go_back_into_the_past() { - let block1_with_transaction: Block = include_json_test_data!( + let block1_with_transaction: Block = include_json_test_data!( "./test_data/ethereum/find_transaction_go_back_into_the_past/block1_with_transaction.json" ); - let transaction: Transaction = include_json_test_data!( + let want_transaction: Transaction = include_json_test_data!( "./test_data/ethereum/find_transaction_go_back_into_the_past/transaction.json" ); - let receipt: TransactionReceipt = include_json_test_data!( + let want_receipt: TransactionReceipt = include_json_test_data!( "./test_data/ethereum/find_transaction_go_back_into_the_past/receipt.json" ); let connector = EthereumConnectorMock::new( @@ -42,26 +42,21 @@ async fn find_transaction_go_back_into_the_past() { "./test_data/ethereum/find_transaction_go_back_into_the_past/block5.json" ), ], - vec![(transaction.hash, receipt.clone())], + vec![(want_transaction.hash, want_receipt.clone())], ); - let pattern = TransactionPattern { - from_address: None, - to_address: Some(transaction.to.unwrap()), - is_contract_creation: None, - transaction_data: None, - transaction_data_length: None, - events: None, - }; - let start_of_swap = NaiveDateTime::from_timestamp(block1_with_transaction.timestamp.low_u32() as i64, 0); - let expected_transaction_and_receipt = matching_transaction(connector, pattern, start_of_swap) + + let (got_transaction, got_receipt) = + matching_transaction_and_receipt(&connector, start_of_swap, { + |transaction| transaction.to == want_transaction.to + }) .await - .unwrap(); + .expect("failed to get the transaction and receipt"); - assert_eq!(expected_transaction_and_receipt, TransactionAndReceipt { - transaction, - receipt - }); + assert_eq!( + (got_transaction, got_receipt), + (want_transaction, want_receipt) + ); } diff --git a/cnd/tests/ethereum_helper/connector_mock.rs b/cnd/tests/ethereum_helper/connector_mock.rs index ce10dcaf42..f47a4daa2d 100644 --- a/cnd/tests/ethereum_helper/connector_mock.rs +++ b/cnd/tests/ethereum_helper/connector_mock.rs @@ -1,26 +1,23 @@ +use anyhow::Context; +use async_trait::async_trait; use cnd::{ - btsieve::{BlockByHash, LatestBlock, ReceiptByHash}, - ethereum::{Block, Transaction, TransactionReceipt, H256}, -}; -use futures::{future::IntoFuture, Future}; -use std::{ - collections::HashMap, - time::{Duration, Instant}, + btsieve::{ethereum::ReceiptByHash, BlockByHash, LatestBlock}, + ethereum::{Block, TransactionReceipt, H256}, }; +use futures::{stream::BoxStream, StreamExt}; +use std::{collections::HashMap, time::Duration}; +use tokio::{stream, sync::Mutex, time::throttle}; -#[derive(Clone)] pub struct EthereumConnectorMock { - all_blocks: HashMap>, - latest_blocks: Vec>, + all_blocks: HashMap, receipts: HashMap, - latest_time_return_block: Instant, - current_latest_block_index: usize, + latest_blocks: Mutex>, } impl EthereumConnectorMock { pub fn new( - latest_blocks: impl IntoIterator>, - all_blocks: impl IntoIterator>, + latest_blocks: Vec, + all_blocks: Vec, receipts: Vec<(H256, TransactionReceipt)>, ) -> Self { let all_blocks = all_blocks @@ -30,70 +27,56 @@ impl EthereumConnectorMock { hm }); - let latest_blocks = latest_blocks.into_iter().collect(); - EthereumConnectorMock { all_blocks, - latest_blocks, - latest_time_return_block: Instant::now(), - current_latest_block_index: 0, receipts: receipts.into_iter().collect(), + latest_blocks: Mutex::new( + throttle(Duration::from_secs(1), stream::iter(latest_blocks)).boxed(), + ), } } } +#[derive(Debug, thiserror::Error)] +#[error("there are no more blocks in this blockchain, either your implementation is buggy or you need a better test setup")] +pub struct OutOfBlocks; + +#[async_trait] impl LatestBlock for EthereumConnectorMock { - type Block = Option>; - type BlockHash = H256; + type Block = Block; - fn latest_block( - &mut self, - ) -> Box + Send + 'static> { - if self.latest_blocks.is_empty() { - return Box::new(Err(anyhow::Error::from(Error::NoMoreBlocks)).into_future()); - } + async fn latest_block(&self) -> anyhow::Result { + let block = self + .latest_blocks + .lock() + .await + .next() + .await + .ok_or(OutOfBlocks)?; - let latest_block = self.latest_blocks[self.current_latest_block_index].clone(); - if self.latest_time_return_block.elapsed() >= Duration::from_secs(1) { - self.latest_time_return_block = Instant::now(); - if self - .latest_blocks - .get(self.current_latest_block_index + 1) - .is_some() - { - self.current_latest_block_index += 1; - } - } - Box::new(Ok(Some(latest_block)).into_future()) + Ok(block) } } +#[async_trait] impl BlockByHash for EthereumConnectorMock { - type Block = Option>; + type Block = Block; type BlockHash = H256; - fn block_by_hash( - &self, - block_hash: Self::BlockHash, - ) -> Box + Send + 'static> { - Box::new(Ok(self.all_blocks.get(&block_hash).cloned()).into_future()) + async fn block_by_hash(&self, block_hash: Self::BlockHash) -> anyhow::Result { + self.all_blocks + .get(&block_hash) + .cloned() + .with_context(|| format!("could not find block with hash {}", block_hash)) } } +#[async_trait] impl ReceiptByHash for EthereumConnectorMock { - type Receipt = Option; - type TransactionHash = H256; - - fn receipt_by_hash( - &self, - transaction_hash: Self::TransactionHash, - ) -> Box + Send + 'static> { - Box::new(Ok(self.receipts.get(&transaction_hash).cloned()).into_future()) + async fn receipt_by_hash(&self, transaction_hash: H256) -> anyhow::Result { + self.receipts + .get(&transaction_hash) + .cloned() + .with_context(|| format!("could not find block with hash {}", transaction_hash)) } } - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("ran out of blocks in chain")] - NoMoreBlocks, -} diff --git a/cnd/tests/ethereum_missed_previous_latest_block.rs b/cnd/tests/ethereum_missed_previous_latest_block.rs index b72b5d9256..710783a8c9 100644 --- a/cnd/tests/ethereum_missed_previous_latest_block.rs +++ b/cnd/tests/ethereum_missed_previous_latest_block.rs @@ -2,17 +2,17 @@ pub mod ethereum_helper; use chrono::NaiveDateTime; use cnd::{ - btsieve::ethereum::{matching_transaction, TransactionPattern}, - ethereum::{Block, Transaction, TransactionAndReceipt, TransactionReceipt}, + btsieve::ethereum::matching_transaction_and_receipt, + ethereum::{Block, Transaction, TransactionReceipt}, }; use ethereum_helper::EthereumConnectorMock; #[tokio::test] async fn find_transaction_missed_previous_latest_block_single_block_gap() { - let transaction: Transaction = include_json_test_data!( + let want_transaction: Transaction = include_json_test_data!( "./test_data/ethereum/find_transaction_missed_previous_latest_block/transaction.json" ); - let receipt: TransactionReceipt = include_json_test_data!( + let want_receipt: TransactionReceipt = include_json_test_data!( "./test_data/ethereum/find_transaction_missed_previous_latest_block/receipt.json" ); let connector = EthereumConnectorMock::new( @@ -38,38 +38,32 @@ async fn find_transaction_missed_previous_latest_block_single_block_gap() { "./test_data/ethereum/find_transaction_missed_previous_latest_block/block4.json" ), ], - vec![(transaction.hash, receipt.clone())], + vec![(want_transaction.hash, want_receipt.clone())], ); - let block2: Block = include_json_test_data!( + let block2: Block = include_json_test_data!( "./test_data/ethereum/find_transaction_missed_previous_latest_block/block2.json" ); let start_of_swap = NaiveDateTime::from_timestamp(block2.timestamp.as_u32() as i64, 0); - let pattern = TransactionPattern { - from_address: None, - to_address: Some(transaction.to.unwrap()), - is_contract_creation: None, - transaction_data: None, - transaction_data_length: None, - events: None, - }; - - let expected_transaction_and_receipt = matching_transaction(connector, pattern, start_of_swap) + let (got_transaction, got_receipt) = + matching_transaction_and_receipt(&connector, start_of_swap, { + |transaction| transaction.to == want_transaction.to + }) .await - .unwrap(); + .expect("failed to get the transaction and receipt"); - assert_eq!(expected_transaction_and_receipt, TransactionAndReceipt { - transaction, - receipt - }); + assert_eq!( + (got_transaction, got_receipt), + (want_transaction, want_receipt) + ); } #[tokio::test] async fn find_transaction_missed_previous_latest_block_two_block_gap() { - let transaction: Transaction = include_json_test_data!( + let want_transaction: Transaction = include_json_test_data!( "./test_data/ethereum/find_transaction_missed_previous_latest_block/transaction.json" ); - let receipt: TransactionReceipt = include_json_test_data!( + let want_receipt: TransactionReceipt = include_json_test_data!( "./test_data/ethereum/find_transaction_missed_previous_latest_block/receipt.json" ); let connector = EthereumConnectorMock::new( @@ -98,28 +92,22 @@ async fn find_transaction_missed_previous_latest_block_two_block_gap() { "./test_data/ethereum/find_transaction_missed_previous_latest_block/block5.json" ), ], - vec![(transaction.hash, receipt.clone())], + vec![(want_transaction.hash, want_receipt.clone())], ); - let block2: Block = include_json_test_data!( + let block2: Block = include_json_test_data!( "./test_data/ethereum/find_transaction_missed_previous_latest_block/block2.json" ); let start_of_swap = NaiveDateTime::from_timestamp(block2.timestamp.as_u32() as i64, 0); - let pattern = TransactionPattern { - from_address: None, - to_address: Some(transaction.to.unwrap()), - is_contract_creation: None, - transaction_data: None, - transaction_data_length: None, - events: None, - }; - - let expected_transaction_and_receipt = matching_transaction(connector, pattern, start_of_swap) + let (got_transaction, got_receipt) = + matching_transaction_and_receipt(&connector, start_of_swap, { + |transaction| transaction.to == want_transaction.to + }) .await - .unwrap(); + .expect("failed to get the transaction and receipt"); - assert_eq!(expected_transaction_and_receipt, TransactionAndReceipt { - transaction, - receipt - }); + assert_eq!( + (got_transaction, got_receipt), + (want_transaction, want_receipt) + ); } diff --git a/cnd/tests/ethereum_pattern.rs b/cnd/tests/ethereum_pattern.rs deleted file mode 100644 index e876c47688..0000000000 --- a/cnd/tests/ethereum_pattern.rs +++ /dev/null @@ -1,169 +0,0 @@ -pub mod ethereum_helper; - -use cnd::{ - btsieve::ethereum::{Event, Topic, TransactionPattern, TRANSACTION_STATUS_OK}, - ethereum::{Address, Block, Bytes, Transaction, TransactionReceipt}, -}; -use spectral::prelude::*; -use std::str::FromStr; - -#[test] -fn cannot_skip_block_containing_transaction_with_event() { - let block: Block = include_json_test_data!("./test_data/ethereum/block.json"); - let receipt: TransactionReceipt = include_json_test_data!("./test_data/ethereum/receipt.json"); - - let pattern = TransactionPattern { - from_address: None, - to_address: None, - is_contract_creation: None, - transaction_data: None, - transaction_data_length: None, - events: Some(vec![Event { - address: Some(receipt.logs[0].address), - data: None, - topics: vec![ - Some(Topic(receipt.logs[0].topics[0])), - Some(Topic(receipt.logs[0].topics[1])), - Some(Topic(receipt.logs[0].topics[2])), - ], - }]), - }; - - assert_that!(pattern.needs_receipts(&block)).is_false(); -} - -fn pattern_matches_block(pattern: TransactionPattern) -> bool { - let block: Block = include_json_test_data!("./test_data/ethereum/block.json"); - - let mut receipt = TransactionReceipt::default(); - receipt.status = Some(TRANSACTION_STATUS_OK.into()); - - for transaction in block.transactions.into_iter() { - if pattern.matches(&transaction, &receipt) { - return true; - } - } - false -} - -// `matches()` method is a filter which returns `true` if things match and -// `false` otherwise. In order to _really_ test that the we successfully match -// we first do a negative test then do an identical positive test. - -#[test] -fn invalid_from_address_does_not_match_transaction_pattern() { - let invalid_from_address = Address::from_str("fffffa1fba5b4804863131145bc27256d3abffff") - .expect("failed to construct from_address"); - - let pattern = TransactionPattern { - from_address: Some(invalid_from_address), - ..TransactionPattern::default() - }; - - let result = pattern_matches_block(pattern); - assert_that!(&result).is_false(); -} - -#[test] -fn valid_from_address_does_match_transaction_pattern() { - let valid_from_address = Address::from_str("fb303a1fba5b4804863131145bc27256d3ab6692") - .expect("failed to construct from_address"); - - let pattern = TransactionPattern { - from_address: Some(valid_from_address), - ..TransactionPattern::default() - }; - - let result = pattern_matches_block(pattern); - assert_that!(&result).is_true(); -} - -#[test] -fn invalid_to_address_does_not_match_transaction_pattern() { - let invalid_to_address = Address::from_str("fffffe335b2786520f4c5d706c76c9ee69d0ffff") - .expect("failed to construct to_address"); - - let pattern = TransactionPattern { - to_address: Some(invalid_to_address), - ..TransactionPattern::default() - }; - - let result = pattern_matches_block(pattern); - assert_that!(&result).is_false(); -} - -#[test] -fn valid_to_address_does_match_transaction_pattern() { - let valid_to_address = Address::from_str("c5549e335b2786520f4c5d706c76c9ee69d0a028") - .expect("failed to construct to_address"); - - let pattern = TransactionPattern { - to_address: Some(valid_to_address), - ..TransactionPattern::default() - }; - - let result = pattern_matches_block(pattern); - assert_that!(&result).is_true(); -} - -#[test] -fn invalid_transaction_data_does_not_match_transaction_pattern() { - let invalid_transaction_data = "ffff9cbb000000000000000000000000d50fb7d948426633ec126aeea140ce4dd09796820000000000000000000000000000000000000000000000000000000ba43bffff"; - let invalid_transaction_data = - hex::decode(invalid_transaction_data).expect("failed to decode hex data"); - let invalid_transaction_data = Bytes::from(invalid_transaction_data); - - let pattern = TransactionPattern { - transaction_data: Some(invalid_transaction_data), - ..TransactionPattern::default() - }; - - let result = pattern_matches_block(pattern); - assert_that!(&result).is_false(); -} - -#[test] -fn valid_transaction_data_does_match_transaction_pattern() { - let valid_transaction_data = "a9059cbb000000000000000000000000d50fb7d948426633ec126aeea140ce4dd09796820000000000000000000000000000000000000000000000000000000ba43b7400"; - let valid_transaction_data = - hex::decode(valid_transaction_data).expect("failed to decode hex data"); - let valid_transaction_data = Bytes::from(valid_transaction_data); - - let pattern = TransactionPattern { - transaction_data: Some(valid_transaction_data), - ..TransactionPattern::default() - }; - - let result = pattern_matches_block(pattern); - assert_that!(&result).is_true(); -} - -#[test] -fn invalid_transaction_data_length_does_not_match_transaction_pattern() { - let invalid_transaction_data_length = 999_999; - - let pattern = TransactionPattern { - transaction_data_length: Some(invalid_transaction_data_length), - ..TransactionPattern::default() - }; - - let result = pattern_matches_block(pattern); - assert_that!(&result).is_false(); -} - -#[test] -fn valid_transaction_data_length_does_match_transaction_pattern() { - let valid_transaction_data = "a9059cbb000000000000000000000000d50fb7d948426633ec126aeea140ce4dd09796820000000000000000000000000000000000000000000000000000000ba43b7400"; - let valid_transaction_data = - hex::decode(valid_transaction_data).expect("failed to decode hex data"); - - let invalid_transaction_data_length = valid_transaction_data.len(); - - let pattern = TransactionPattern { - transaction_data_length: Some(invalid_transaction_data_length), - ..TransactionPattern::default() - }; - - let result = pattern_matches_block(pattern); - assert_that!(&result).is_true(); -} diff --git a/cnd/tests/ethereum_transaction_matching_smoke_test.rs b/cnd/tests/ethereum_transaction_matching_smoke_test.rs new file mode 100644 index 0000000000..9d3aa95e2d --- /dev/null +++ b/cnd/tests/ethereum_transaction_matching_smoke_test.rs @@ -0,0 +1,87 @@ +use chrono::offset::Utc; +use cnd::{ + btsieve::ethereum::{matching_transaction_and_receipt, Web3Connector}, + ethereum::{Address, U256}, + jsonrpc, +}; +use reqwest::Url; +use std::time::Duration; +use testcontainers::*; + +/// A very basic e2e test that verifies that we glued all our code together +/// correctly for ethereum transaction pattern matching. +/// +/// We get the default account from the node and send some money to it +/// from the parity dev account. Afterwards we verify that the tx hash of +/// the sent tx equals the one that we found through btsieve. +#[tokio::test] +async fn ethereum_transaction_matching_smoke_test() { + let cli = clients::Cli::default(); + let container = cli.run(images::parity_parity::ParityEthereum::default()); + let start_of_swap = Utc::now().naive_local(); + + let url = connection_url(&container).unwrap(); + + let client = jsonrpc::Client::new(url.clone()); + let connector = Web3Connector::new(url); + + let accounts: Vec
= client + .send(jsonrpc::Request::new("eth_accounts", Vec::::new())) + .await + .expect("failed to get accounts"); + + let target_address = accounts[0]; + + let send_money_to_address = async { + tokio::time::delay_for(Duration::from_secs(2)).await; + client + .send(jsonrpc::Request::new("personal_sendTransaction", vec![ + jsonrpc::serialize(TransactionRequest { + from: "00a329c0648769a73afac7f9381e08fb43dbea72" + .parse() + .expect("failed to parse static string"), + to: target_address, + value: U256::from(1_000_000_000u64), + }) + .unwrap(), + jsonrpc::serialize("").unwrap(), + ])) + .await + .expect("failed to send transaction") + }; + let transaction = tokio::time::timeout(Duration::from_secs(5), send_money_to_address) + .await + .expect("failed to send money to address"); + + let (matched_transaction, _receipt) = tokio::time::timeout( + Duration::from_secs(5), + matching_transaction_and_receipt(&connector, start_of_swap, |transaction| { + transaction.to == Some(target_address) + }), + ) + .await + .expect("failed to timeout") + .expect("failed to get the actual transaction and receipt"); + + assert_eq!(matched_transaction.hash, transaction) +} + +fn connection_url(container: &Container<'_, D, E>) -> anyhow::Result +where + D: Docker, + E: Image, +{ + let port = container.get_host_port(8545).unwrap(); + let endpoint = format!("http://localhost:{}", port); + + let url = Url::parse(&endpoint)?; + + Ok(url) +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize)] +struct TransactionRequest { + pub from: Address, + pub to: Address, + pub value: U256, +} diff --git a/cnd/tests/ethereum_transaction_pattern_e2e.rs b/cnd/tests/ethereum_transaction_pattern_e2e.rs deleted file mode 100644 index 8731566893..0000000000 --- a/cnd/tests/ethereum_transaction_pattern_e2e.rs +++ /dev/null @@ -1,110 +0,0 @@ -use chrono::offset::Utc; -use cnd::{ - btsieve::ethereum::{matching_transaction, TransactionPattern, Web3Connector}, - ethereum::{TransactionRequest, U256}, -}; -use futures_core::compat::Future01CompatExt; -use reqwest::Url; -use std::time::Duration; -use testcontainers::*; -use web3::{ - transports::{EventLoopHandle, Http}, - Web3, -}; - -/// A very basic e2e test that verifies that we glued all our code together -/// correctly for ethereum transaction pattern matching. -/// -/// We get the default account from the node and send some money to it -/// from the parity dev account. Afterwards we verify that the tx hash of -/// the sent tx equals the one that we found through btsieve. -#[tokio::test] -async fn ethereum_transaction_pattern_e2e_test() { - let cli = clients::Cli::default(); - let container = cli.run(images::parity_parity::ParityEthereum::default()); - let start_of_swap = Utc::now().naive_local(); - - let (_handle, client) = new_web3_client(&container); - - let mut url = Url::parse("http://localhost").expect("failed to parse static URL"); - #[allow(clippy::cast_possible_truncation)] - url.set_port(Some( - container - .get_host_port(8545) - .expect("failed to get host port") as u16, - )) - .unwrap(); - - let connector = Web3Connector::new(url); - - let accounts = client - .eth() - .accounts() - .compat() - .await - .expect("failed to get accounts"); - - let target_address = accounts[0]; - - let send_money_to_address = async { - tokio::time::delay_for(Duration::from_secs(2)).await; - client - .personal() - .send_transaction( - TransactionRequest { - from: "00a329c0648769a73afac7f9381e08fb43dbea72" - .parse() - .expect("failed to parse static string"), - to: Some(target_address), - gas: None, - gas_price: None, - value: Some(U256::from(1_000_000_000u64)), - data: None, - nonce: None, - condition: None, - }, - "", - ) - .compat() - .await - .expect("failed to send transaction") - }; - let transaction = tokio::time::timeout(Duration::from_secs(5), send_money_to_address) - .await - .expect("failed to send money to address"); - - let pattern = TransactionPattern { - from_address: None, - to_address: Some(target_address), - is_contract_creation: None, - transaction_data: None, - transaction_data_length: None, - events: None, - }; - let matched_transaction = tokio::time::timeout( - Duration::from_secs(5), - matching_transaction(connector, pattern, start_of_swap), - ) - .await - .expect("failed to timeout"); - - assert_eq!( - matched_transaction - .expect("failed to get funding transaction") - .transaction - .hash, - transaction - ) -} - -pub fn new_web3_client( - container: &Container<'_, D, E>, -) -> (EventLoopHandle, Web3) { - let port = container.get_host_port(8545).unwrap(); - let endpoint = format!("http://localhost:{}", port); - - let (event_loop, transport) = Http::new(&endpoint).unwrap(); - let web3 = Web3::new(transport); - - (event_loop, web3) -} diff --git a/libp2p-comit/Cargo.toml b/libp2p-comit/Cargo.toml index 2208947fc1..2214bccef9 100644 --- a/libp2p-comit/Cargo.toml +++ b/libp2p-comit/Cargo.toml @@ -6,22 +6,16 @@ edition = "2018" description = "Implementation of the COMIT messaging protocol" [dependencies] -bytes = "0.4" -derivative = "1.0.3" -futures = "0.1" -libp2p-core = "0.13" -libp2p-swarm = "0.3" -log = "0.4" +bytes = "0.5" +derivative = "2" +futures = { version = "0.3", features = ["alloc", "io-compat"] } +futures_codec = "0.4" +libp2p = { version = "0.16", default-features = false } serde = { version = "1", features = ["derive"] } serde_json = "1.0" -strum_macros = "0.17" +strum_macros = "0.18" thiserror = "1" -tokio = "0.1" -tokio-codec = "0.1" tracing = "0.1" [dev-dependencies] -matches = "0.1.8" -multistream-select = "0.6.0" spectral = "0.6" - diff --git a/libp2p-comit/src/behaviour.rs b/libp2p-comit/src/behaviour.rs index da547f6dd6..86f77aeab7 100644 --- a/libp2p-comit/src/behaviour.rs +++ b/libp2p-comit/src/behaviour.rs @@ -1,24 +1,22 @@ use crate::{ frame::{OutboundRequest, Response}, handler::{ - self, InboundMessage, OutboundMessage, PendingInboundResponse, ProtocolInEvent, - ProtocolOutEvent, + InboundMessage, OutboundMessage, PendingInboundResponse, ProtocolInEvent, ProtocolOutEvent, }, ComitHandler, PendingInboundRequest, PendingOutboundRequest, }; use futures::{ - stream::Stream, - sync::mpsc::{self, UnboundedReceiver, UnboundedSender}, - Async, Future, + channel::mpsc::{self, UnboundedReceiver, UnboundedSender}, + Future, StreamExt, TryFutureExt, +}; +use libp2p::{ + core::{ConnectedPoint, Multiaddr, PeerId}, + swarm::{NetworkBehaviour, NetworkBehaviourAction, PollParameters}, }; -use handler::Error; -use libp2p_core::{ConnectedPoint, Multiaddr, PeerId}; -use libp2p_swarm::{NetworkBehaviour, NetworkBehaviourAction, PollParameters}; use std::{ collections::{hash_map::Entry, HashMap, HashSet}, - marker::PhantomData, + task::{Context, Poll}, }; -use tokio::prelude::{AsyncRead, AsyncWrite}; #[derive(Debug)] enum ConnectionState { @@ -43,9 +41,7 @@ pub enum BehaviourOutEvent { /// Network behaviour that handles the COMIT messaging protocol. #[derive(Debug)] -pub struct Comit { - marker: PhantomData, - +pub struct Comit { events_sender: UnboundedSender>, events: UnboundedReceiver>, @@ -53,14 +49,13 @@ pub struct Comit { connections: HashMap, } -impl Comit { +impl Comit { pub fn new(known_request_headers: HashMap>) -> Self { - let (sender, receiver) = mpsc::unbounded(); + let (events_sender, events) = mpsc::unbounded(); Self { - marker: PhantomData, - events_sender: sender, - events: receiver, + events_sender, + events, known_request_headers, connections: HashMap::new(), } @@ -70,9 +65,9 @@ impl Comit { &mut self, dial_information: (PeerId, Option), request: OutboundRequest, - ) -> Box + Send> { + ) -> impl Future> + Send + 'static + Unpin { let (peer_id, address_hint) = dial_information; - let (sender, receiver) = futures::oneshot(); + let (sender, receiver) = futures::channel::oneshot::channel(); let request = PendingOutboundRequest { request, @@ -127,11 +122,11 @@ impl Comit { } } - Box::new(receiver.map_err(|_| { + receiver.map_err(|_| { tracing::warn!( "Sender of response future was unexpectedly dropped before response was received." ) - })) + }) } pub fn connected_peers(&mut self) -> impl Iterator)> { @@ -150,11 +145,8 @@ impl Comit { } } -impl NetworkBehaviour for Comit -where - TSubstream: AsyncRead + AsyncWrite, -{ - type ProtocolsHandler = ComitHandler; +impl NetworkBehaviour for Comit { + type ProtocolsHandler = ComitHandler; type OutEvent = BehaviourOutEvent; fn new_handler(&mut self) -> Self::ProtocolsHandler { @@ -262,70 +254,16 @@ where })) => { let _ = channel.send(response); } - ProtocolOutEvent::Error(e) => log_error(e, peer), } } fn poll( &mut self, + cx: &mut Context<'_>, _params: &mut impl PollParameters, - ) -> Async> { + ) -> Poll> { self.events - .poll() - .expect("unbounded channel can never fail") + .poll_next_unpin(cx) .map(|item| item.expect("unbounded channel never ends")) } } - -// tracing trippers clippy warning, issue reported: https://github.com/tokio-rs/tracing/issues/553 -#[allow(clippy::cognitive_complexity)] -fn log_error(err: Error, peer: PeerId) { - use tracing::error; - - match err { - Error::MalformedJson(error) => { - error!("failure in communication with {}: {:?}", peer, error); - } - Error::DroppedResponseSender(_) => { - // The `oneshot::Sender` is the only way to send a RESPONSE as an answer to the - // SWAP REQUEST. A dropped `Sender` therefore is either a bug in - // the application or the application consciously does not want to answer the - // SWAP REQUEST. In either way, we should signal this to the remote peer by - // closing the substream. - error!( - "user dropped `oneshot::Sender` for response, closing substream with peer {}", - peer - ); - } - Error::UnknownMandatoryHeader(error) => { - error!( - "received frame with unexpected mandatory header from {}, {:?}", - peer, error - ); - } - Error::UnknownRequestType(error) => { - error!( - "received frame with unknown request type from {}, {:?}", - peer, error - ); - } - Error::UnknownFrameType => { - error!("received frame with unknown type from {}", peer); - } - Error::UnexpectedFrame(frame) => { - error!( - "received unexpected frame of type from {}, {:?}", - peer, frame - ); - } - Error::MalformedFrame(error) => { - error!("received malformed frame from {}, {:?}", peer, error); - } - Error::UnexpectedEOF => { - error!( - "substream with {} unexpectedly ended while waiting for messages", - peer - ); - } - } -} diff --git a/libp2p-comit/src/frame/codec.rs b/libp2p-comit/src/frame/codec.rs index e2d61763b2..fd370f3ecd 100644 --- a/libp2p-comit/src/frame/codec.rs +++ b/libp2p-comit/src/frame/codec.rs @@ -1,7 +1,7 @@ use crate::Frame; use bytes::BytesMut; +use futures_codec::{Decoder, Encoder}; use std::io; -use tokio_codec::{Decoder, Encoder}; #[derive(Debug, thiserror::Error)] pub enum CodecError { @@ -54,12 +54,12 @@ impl Decoder for JsonFrameCodec { mod tests { use super::*; - use crate::FrameType; + use crate::FrameKind; use spectral::prelude::*; #[test] fn should_encode_frame_to_bytes() { - let frame = Frame::new(FrameType::Request, serde_json::Value::Null); + let frame = Frame::new(FrameKind::Request, serde_json::Value::Null); let mut codec = JsonFrameCodec::default(); @@ -85,7 +85,7 @@ mod tests { let mut bytes = BytesMut::new(); bytes.extend([frame_bytes, newline].concat()); - let expected_frame = Frame::new(FrameType::Response, serde_json::Value::Null); + let expected_frame = Frame::new(FrameKind::Response, serde_json::Value::Null); assert_that(&codec.decode(&mut bytes)) .is_ok() @@ -124,7 +124,7 @@ mod tests { let first = codec.decode(&mut bytes); let second = codec.decode(&mut bytes); - let expected_frame = Frame::new(FrameType::Response, serde_json::Value::Null); + let expected_frame = Frame::new(FrameKind::Response, serde_json::Value::Null); assert_that(&first) .is_ok() diff --git a/libp2p-comit/src/frame/header.rs b/libp2p-comit/src/frame/header.rs index fee0fb20e6..bb047ccf43 100644 --- a/libp2p-comit/src/frame/header.rs +++ b/libp2p-comit/src/frame/header.rs @@ -68,7 +68,10 @@ pub struct Header { } impl Header { - pub fn value(&self) -> Result { + pub fn value(&self) -> Result + where + V: DeserializeOwned, + { serde_json::from_value(self.inner.value()) } @@ -79,10 +82,10 @@ impl Header { /// the `value` being null. This allows to change the otherwise cumbersome /// return type of `Option>` to `Result`. The caller has to handle conversion errors anyway. - pub fn take_parameter( - &mut self, - key: &'static str, - ) -> Result { + pub fn take_parameter

(&mut self, key: &'static str) -> Result + where + P: DeserializeOwned, + { let parameter = self .inner .take_parameter(key) @@ -93,10 +96,13 @@ impl Header { /// Returns the parameter with the provided key converted into the type `P` /// or `P::default()` if the parameter is not present. - pub fn take_parameter_or_default( + pub fn take_parameter_or_default

( &mut self, key: &'static str, - ) -> Result { + ) -> Result + where + P: DeserializeOwned + Default, + { self.inner .take_parameter(key) .map(serde_json::from_value) @@ -115,17 +121,23 @@ impl Header { } } - pub fn with_value(value: V) -> Result { + pub fn with_value(value: V) -> Result + where + V: Serialize, + { Ok(Header { inner: CompactOrExtended::Compact(serde_json::to_value(value)?), }) } - pub fn with_parameter( + pub fn with_parameter

( self, key: &'static str, parameter: P, - ) -> Result { + ) -> Result + where + P: Serialize, + { let (value, mut parameters) = match self.inner { CompactOrExtended::Compact(value) => (value, BTreeMap::new()), CompactOrExtended::Extended { value, parameters } => (value, parameters), diff --git a/libp2p-comit/src/frame/request.rs b/libp2p-comit/src/frame/request.rs index bffe227fae..b3f0dd7403 100644 --- a/libp2p-comit/src/frame/request.rs +++ b/libp2p-comit/src/frame/request.rs @@ -1,6 +1,6 @@ use crate::{ frame::header::{Header, Headers}, - Frame, FrameType, IntoFrame, + Frame, FrameKind, }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::{self, Value as JsonValue}; @@ -37,13 +37,19 @@ impl ValidatedInboundRequest { self.inner.headers.take(key) } - pub fn take_body_as(self) -> Result { + pub fn take_body_as(self) -> Result + where + B: DeserializeOwned, + { self.inner.take_body_as() } } impl OutboundRequest { - pub fn new>(request_type: T) -> Self { + pub fn new(request_type: T) -> Self + where + T: Into, + { Self { inner: Request { request_type: request_type.into(), @@ -135,17 +141,23 @@ struct Request { } impl Request { - pub fn take_body_as(self) -> Result { + pub fn take_body_as(self) -> Result + where + B: DeserializeOwned, + { B::deserialize(self.body) } } -impl IntoFrame for OutboundRequest { - fn into_frame(self) -> Frame { - // Serializing Request should never fail because its members are just Strings - // and JsonValues - let payload = serde_json::to_value(self).unwrap(); - - Frame::new(FrameType::Request, payload) +impl From for Frame { + fn from(r: OutboundRequest) -> Frame { + let payload = serialize(r); + Frame::new(FrameKind::Request, payload) } } + +fn serialize(r: OutboundRequest) -> JsonValue { + // Serializing and OutboundRequest should never fail because its + // members are just Strings and JsonValues. + serde_json::to_value(r).unwrap() +} diff --git a/libp2p-comit/src/frame/response.rs b/libp2p-comit/src/frame/response.rs index 096e59d536..6f2526dab0 100644 --- a/libp2p-comit/src/frame/response.rs +++ b/libp2p-comit/src/frame/response.rs @@ -1,6 +1,6 @@ use crate::{ frame::header::{Header, Headers}, - Frame, FrameType, IntoFrame, + Frame, FrameKind, }; use serde::{Deserialize, Serialize}; use serde_json::{self, Value as JsonValue}; @@ -47,12 +47,15 @@ impl Response { } } -impl IntoFrame for Response { - fn into_frame(self) -> Frame { - // Serializing Response should never fail because its members are just Strings - // and JsonValues - let payload = serde_json::to_value(self).unwrap(); - - Frame::new(FrameType::Response, payload) +impl From for Frame { + fn from(r: Response) -> Frame { + let payload = serialize(r); + Frame::new(FrameKind::Response, payload) } } + +fn serialize(r: Response) -> JsonValue { + // Serializing and Response should never fail because its + // members are just Strings and JsonValues. + serde_json::to_value(r).unwrap() +} diff --git a/libp2p-comit/src/handler.rs b/libp2p-comit/src/handler.rs index a1b261f930..ab8793f005 100644 --- a/libp2p-comit/src/handler.rs +++ b/libp2p-comit/src/handler.rs @@ -1,43 +1,35 @@ use crate::{ - frame::{ - self, JsonFrameCodec, OutboundRequest, Response, UnknownMandatoryHeaders, - ValidatedInboundRequest, - }, - protocol::ComitProtocolConfig, + frame::{self, OutboundRequest, Response, UnknownMandatoryHeaders, ValidatedInboundRequest}, + protocol::Config, substream::{self, Advance, Advanced}, - ComitHandlerEvent, Frame, IntoFrame, + ComitHandlerEvent, Frame, Frames, }; use futures::{ - sync::oneshot::{self, Canceled}, - task::Task, - Async, Poll, + channel::oneshot::{self, Canceled}, + task::{Poll, Waker}, }; -use libp2p_core::Negotiated; -use libp2p_swarm::{ +use libp2p::swarm::{ KeepAlive, ProtocolsHandler, ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr, SubstreamProtocol, }; use std::{ collections::{HashMap, HashSet}, convert::Infallible, fmt::Display, -}; -use tokio::{ - codec::Framed, - prelude::{AsyncRead, AsyncWrite}, + task::Context, }; #[derive(derivative::Derivative)] #[derivative(Debug)] -pub struct ComitHandler { +pub struct ComitHandler { #[derivative(Debug = "ignore")] - inbound_substreams: Vec>, + inbound_substreams: Vec, #[derivative(Debug = "ignore")] - outbound_substreams: Vec>, + outbound_substreams: Vec, to_send: Vec, #[derivative(Debug = "ignore")] - current_task: Option, + current_task: Option, known_headers: HashMap>, } @@ -47,13 +39,13 @@ pub enum Error { #[error("malformed frame: ")] MalformedJson(#[from] frame::CodecError), #[error("dropped response: {0}")] - DroppedResponseSender(Canceled), + DroppedResponseSender(#[from] Canceled), #[error("unknown mandatory header: {0:?}")] UnknownMandatoryHeader(UnknownMandatoryHeaders), #[error("unknown request type: {0}")] UnknownRequestType(String), #[error("unknown frame type")] - UnknownFrameType, + UnknownFrameKind, #[error("unexpected frame")] UnexpectedFrame(Frame), #[error("malformed frame")] @@ -62,13 +54,7 @@ pub enum Error { UnexpectedEOF, } -impl From for Error { - fn from(e: Canceled) -> Self { - Error::DroppedResponseSender(e) - } -} - -impl ComitHandler { +impl ComitHandler { pub fn new(known_headers: HashMap>) -> Self { Self { known_headers, @@ -116,7 +102,6 @@ pub enum ProtocolOutboundOpenInfo { #[derive(Debug)] pub enum ProtocolOutEvent { Message(InboundMessage), - Error(Error), } #[derive(Debug)] @@ -130,34 +115,32 @@ pub enum OutboundMessage { Request(PendingOutboundRequest), } -impl ProtocolsHandler for ComitHandler { +impl ProtocolsHandler for ComitHandler { type InEvent = ProtocolInEvent; type OutEvent = ProtocolOutEvent; - type Error = frame::CodecError; - type Substream = TSubstream; - type InboundProtocol = ComitProtocolConfig; - type OutboundProtocol = ComitProtocolConfig; + type Error = Error; + type InboundProtocol = Config; + type OutboundProtocol = Config; type OutboundOpenInfo = ProtocolOutboundOpenInfo; fn listen_protocol(&self) -> SubstreamProtocol { - SubstreamProtocol::new(ComitProtocolConfig {}) + SubstreamProtocol::new(Config {}) } - fn inject_fully_negotiated_inbound( - &mut self, - stream: Framed, JsonFrameCodec>, - ) { + fn inject_fully_negotiated_inbound(&mut self, stream: Frames) { self.inbound_substreams - .push(substream::inbound::State::WaitingMessage { stream }); + .push(substream::inbound::State::WaitingMessage { + stream: Box::pin(stream), + }); - if let Some(task) = &self.current_task { - task.notify() + if let Some(waker) = self.current_task.take() { + waker.wake() } } fn inject_fully_negotiated_outbound( &mut self, - stream: Framed, JsonFrameCodec>, + stream: Frames, outbound_open_info: Self::OutboundOpenInfo, ) { match outbound_open_info { @@ -166,15 +149,15 @@ impl ProtocolsHandler for ComitHandler { self.outbound_substreams .push(substream::outbound::State::WaitingSend { - frame: request.into_frame(), + frame: request.into(), response_sender: channel, - stream, + stream: Box::pin(stream), }); } } - if let Some(task) = &self.current_task { - task.notify() + if let Some(waker) = self.current_task.take() { + waker.wake() } } @@ -185,8 +168,8 @@ impl ProtocolsHandler for ComitHandler ProtocolsHandler for ComitHandler Poll { + fn poll(&mut self, cx: &mut Context<'_>) -> Poll { if let Some(request) = self.to_send.pop() { - return Ok(Async::Ready( - ProtocolsHandlerEvent::OutboundSubstreamRequest { - protocol: SubstreamProtocol::new(ComitProtocolConfig {}), - info: ProtocolOutboundOpenInfo::Message(OutboundMessage::Request(request)), - }, - )); + return Poll::Ready(ProtocolsHandlerEvent::OutboundSubstreamRequest { + protocol: SubstreamProtocol::new(Config {}), + info: ProtocolOutboundOpenInfo::Message(OutboundMessage::Request(request)), + }); } - if let Some(result) = poll_substreams(&mut self.outbound_substreams, &self.known_headers) { + if let Some(result) = + poll_substreams(&mut self.outbound_substreams, &self.known_headers, cx) + { return result; } - if let Some(result) = poll_substreams(&mut self.inbound_substreams, &self.known_headers) { + if let Some(result) = poll_substreams(&mut self.inbound_substreams, &self.known_headers, cx) + { return result; } - self.current_task = Some(futures::task::current()); + self.current_task = Some(cx.waker().clone()); - Ok(Async::NotReady) + Poll::Pending } } -fn poll_substreams( +fn poll_substreams( substreams: &mut Vec, known_headers: &HashMap>, -) -> Option> { + cx: &mut Context<'_>, +) -> Option> +where + S: Display + Advance, +{ // We remove each element from `substreams` one by one and add them back. for n in (0..substreams.len()).rev() { let substream_state = substreams.swap_remove(n); let log_message = format!("transition from {}", substream_state); - let Advanced { new_state, event } = substream_state.advance(known_headers); + let Advanced { new_state, event } = substream_state.advance(known_headers, cx); if let Some(new_state) = new_state { tracing::trace!("{} to {}", log_message, new_state); @@ -243,400 +231,8 @@ fn poll_substreams( } if let Some(event) = event { - return Some(Ok(Async::Ready(event))); + return Some(Poll::Ready(event)); } } None } - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - frame::{Header, JsonFrameCodec, OutboundRequest, Response}, - test_harness::{ - request_with_no_headers, setup_substream, setup_substream_with_json_codec, - IntoEventStream, IntoFutureWithResponse, WaitForFrame, - }, - }; - use futures::{Future, Sink, Stream}; - use libp2p_swarm::ProtocolsHandlerEvent; - use spectral::prelude::*; - use tokio::codec::LinesCodec; - - #[test] - fn given_an_inbound_request_handler_sends_response() { - let mut runtime = tokio::runtime::Runtime::new().unwrap(); - let (dialer, listener) = runtime.block_on(setup_substream_with_json_codec()).unwrap(); - let mut handler = ComitHandler::new(request_with_no_headers("PING")); - - // given an inbound substream - handler.inject_fully_negotiated_inbound(listener); - - // and we receive a request - let send = dialer.send(OutboundRequest::new("PING").into_frame()); - let dialer = runtime.block_on(send).unwrap(); - - // and we provide an answer - let future = handler.into_future_with_response( - Response::empty().with_header("decision", Header::with_str_value("declined")), - ); - runtime.spawn(future); - - // then we send the response back to the dialer - let response = runtime.block_on(dialer.wait_for_frame()); - - assert_that(&response).is_ok().is_some().is_equal_to( - Response::empty() - .with_header("decision", Header::with_str_value("declined")) - .into_frame(), - ); - } - - #[test] - fn given_inbound_substream_when_unknown_request_should_emit_unknown_request_type() { - let mut runtime = tokio::runtime::Runtime::new().unwrap(); - let (dialer, listener) = runtime.block_on(setup_substream_with_json_codec()).unwrap(); - let mut handler = ComitHandler::new(HashMap::new()); - - // given a substream - handler.inject_fully_negotiated_inbound(listener); - - // when receiving a request - let send = dialer.send(OutboundRequest::new("PING").into_frame()); - let _ = runtime.block_on(send).unwrap(); - - let events = runtime - .block_on(handler.into_event_stream().take(1).collect()) - .unwrap(); - - // then - matches::assert_matches!( - events.get(0), - Some( - ProtocolsHandlerEvent::Custom(ProtocolOutEvent::Error(Error::UnknownRequestType(_))), - ) - ) - } - - #[test] - fn given_inbound_substream_when_request_with_unknown_headers_should_emit_unknown_mandatory_headers( - ) { - let mut runtime = tokio::runtime::Runtime::new().unwrap(); - let (dialer, listener) = runtime.block_on(setup_substream_with_json_codec()).unwrap(); - let mut handler = ComitHandler::new(request_with_no_headers("PING")); - - // given a substream - handler.inject_fully_negotiated_inbound(listener); - - // when receiving a request - let send = dialer.send( - OutboundRequest::new("PING") - .with_header( - "foo", - Header::with_str_value("foo") - .with_parameter("bar", "foobar") - .unwrap(), - ) - .into_frame(), - ); - let _ = runtime.block_on(send).unwrap(); - - let events = runtime - .block_on(handler.into_event_stream().take(1).collect()) - .unwrap(); - - // then - matches::assert_matches!( - events.get(0), - Some( - ProtocolsHandlerEvent::Custom( - ProtocolOutEvent::Error(Error::UnknownMandatoryHeader(_)), - ), - ) - ) - } - - #[test] - fn given_inbound_substream_when_request_without_type_should_emit_malformed_frame() { - let mut runtime = tokio::runtime::Runtime::new().unwrap(); - let (dialer, listener) = runtime - .block_on(setup_substream( - LinesCodec::new(), - JsonFrameCodec::default(), - )) - .unwrap(); - let mut handler = ComitHandler::new(request_with_no_headers("PING")); - - // given a substream - handler.inject_fully_negotiated_inbound(listener); - - // when receiving a request - let send = dialer - .send(r#"{"type": "REQUEST", "payload":{}}"#.to_owned()) - .map(|_| ()) - .map_err(|_| ()); - runtime.block_on(send).unwrap(); - - let events = runtime - .block_on(handler.into_event_stream().take(1).collect()) - .unwrap(); - - // then - matches::assert_matches!( - events.get(0), - Some(ProtocolsHandlerEvent::Custom(ProtocolOutEvent::Error(Error::MalformedFrame(_)))) - ) - } - - #[test] - fn given_inbound_substream_when_request_without_header_and_body_should_emit_request() { - let mut runtime = tokio::runtime::Runtime::new().unwrap(); - let (dialer, listener) = runtime - .block_on(setup_substream( - LinesCodec::new(), - JsonFrameCodec::default(), - )) - .unwrap(); - let mut handler = ComitHandler::new(request_with_no_headers("PING")); - - // given a substream - handler.inject_fully_negotiated_inbound(listener); - - // when receiving a request - let send = dialer - .send(r#"{"type": "REQUEST", "payload":{"type": "PING", "foo": "bar"}}"#.to_owned()) - .map(|_| ()) - .map_err(|_| ()); - runtime.block_on(send).unwrap(); - - let events = runtime - .block_on(handler.into_event_stream().take(1).collect()) - .unwrap(); - - // then - matches::assert_matches!( - events.get(0), - Some( - ProtocolsHandlerEvent::Custom(ProtocolOutEvent::Message(InboundMessage::Request(_))), - ) - ) - } - - #[test] - fn given_inbound_substream_when_response_should_emit_unexpected_frame() { - let mut runtime = tokio::runtime::Runtime::new().unwrap(); - let (dialer, listener) = runtime - .block_on(setup_substream( - LinesCodec::new(), - JsonFrameCodec::default(), - )) - .unwrap(); - let mut handler = ComitHandler::new(request_with_no_headers("PING")); - - // given a substream - handler.inject_fully_negotiated_inbound(listener); - - // when receiving a request - let send = dialer - .send(r#"{"type": "RESPONSE", "payload":{}}"#.to_owned()) - .map(|_| ()) - .map_err(|_| ()); - runtime.block_on(send).unwrap(); - - let events = runtime - .block_on(handler.into_event_stream().take(1).collect()) - .unwrap(); - - // then - matches::assert_matches!( - events.get(0), - Some(ProtocolsHandlerEvent::Custom(ProtocolOutEvent::Error(Error::UnexpectedFrame(_)))) - ) - } - - #[test] - fn given_inbound_substream_when_frame_with_unknown_type_should_emit_unknown_frame_type() { - let mut runtime = tokio::runtime::Runtime::new().unwrap(); - let (dialer, listener) = runtime - .block_on(setup_substream( - LinesCodec::new(), - JsonFrameCodec::default(), - )) - .unwrap(); - let mut handler = ComitHandler::new(request_with_no_headers("PING")); - - // given a substream - handler.inject_fully_negotiated_inbound(listener); - - // when receiving a request - let send = dialer - .send(r#"{"type": "FOOBAR", "payload":{}}"#.to_owned()) - .map(|_| ()) - .map_err(|_| ()); - runtime.block_on(send).unwrap(); - - let events = runtime - .block_on(handler.into_event_stream().take(1).collect()) - .unwrap(); - - // then - matches::assert_matches!( - events.get(0), - Some(ProtocolsHandlerEvent::Custom(ProtocolOutEvent::Error( - Error::UnknownFrameType - ))) - ) - } - - #[test] - fn given_inbound_substream_when_invalid_json_should_emit_malformed_json() { - let mut runtime = tokio::runtime::Runtime::new().unwrap(); - let (dialer, listener) = runtime - .block_on(setup_substream( - LinesCodec::new(), - JsonFrameCodec::default(), - )) - .unwrap(); - let mut handler = ComitHandler::new(request_with_no_headers("PING")); - - // given a substream - handler.inject_fully_negotiated_inbound(listener); - - // when receiving a request - let send = dialer - .send(r#"invlid json"#.to_owned()) - .map(|_| ()) - .map_err(|_| ()); - runtime.block_on(send).unwrap(); - - let events = runtime - .block_on(handler.into_event_stream().take(1).collect()) - .unwrap(); - - // then - matches::assert_matches!( - events.get(0), - Some(ProtocolsHandlerEvent::Custom(ProtocolOutEvent::Error(Error::MalformedJson(_)))) - ) - } - - #[test] - fn given_an_outbound_request_when_frame_with_unknown_type_should_emit_unknown_frame_type() { - let mut runtime = tokio::runtime::Runtime::new().unwrap(); - let (dialer, listener) = runtime - .block_on(setup_substream( - JsonFrameCodec::default(), - LinesCodec::new(), - )) - .unwrap(); - let mut handler = ComitHandler::new(request_with_no_headers("PING")); - - // given an outbound substream - let (sender, _receiver) = oneshot::channel(); - handler.inject_fully_negotiated_outbound( - dialer, - ProtocolOutboundOpenInfo::Message(OutboundMessage::Request(PendingOutboundRequest { - request: OutboundRequest::new("PING"), - channel: sender, - })), - ); - - // when receiving a frame with unknown type - let send = listener - .send(r#"{"type": "FOOBAR", "payload":{}}"#.to_owned()) - .map(|_| ()) - .map_err(|_| ()); - let _ = runtime.spawn(send); - - let events = runtime - .block_on(handler.into_event_stream().take(1).collect()) - .unwrap(); - - // then - matches::assert_matches!( - events.get(0), - Some(ProtocolsHandlerEvent::Custom(ProtocolOutEvent::Error( - Error::UnknownFrameType - ))) - ) - } - - #[test] - fn given_an_outbound_request_when_request_should_emit_unexpected_frame() { - let mut runtime = tokio::runtime::Runtime::new().unwrap(); - let (dialer, listener) = runtime - .block_on(setup_substream( - JsonFrameCodec::default(), - LinesCodec::new(), - )) - .unwrap(); - let mut handler = ComitHandler::new(request_with_no_headers("PING")); - - // given an outbound substream - let (sender, _receiver) = oneshot::channel(); - handler.inject_fully_negotiated_outbound( - dialer, - ProtocolOutboundOpenInfo::Message(OutboundMessage::Request(PendingOutboundRequest { - request: OutboundRequest::new("PING"), - channel: sender, - })), - ); - - // when receiving a known type (REQUEST) that is unexpected - let send = listener - .send(r#"{"type": "REQUEST", "payload":{}}"#.to_owned()) - .map(|_| ()) - .map_err(|_| ()); - let _ = runtime.spawn(send); - - let events = runtime - .block_on(handler.into_event_stream().take(1).collect()) - .unwrap(); - - // then - matches::assert_matches!( - events.get(0), - Some(ProtocolsHandlerEvent::Custom(ProtocolOutEvent::Error(Error::UnexpectedFrame(_)))) - ) - } - - #[test] - fn given_an_outbound_request_when_invalid_json_should_emit_malformed_json() { - let mut runtime = tokio::runtime::Runtime::new().unwrap(); - let (dialer, listener) = runtime - .block_on(setup_substream( - JsonFrameCodec::default(), - LinesCodec::new(), - )) - .unwrap(); - let mut handler = ComitHandler::new(request_with_no_headers("PING")); - - // given an outbound substream - let (sender, _receiver) = oneshot::channel(); - handler.inject_fully_negotiated_outbound( - dialer, - ProtocolOutboundOpenInfo::Message(OutboundMessage::Request(PendingOutboundRequest { - request: OutboundRequest::new("PING"), - channel: sender, - })), - ); - - // when receiving invalid json - let send = listener - .send(r#"invalid json"#.to_owned()) - .map(|_| ()) - .map_err(|_| ()); - let _ = runtime.spawn(send); - - let events = runtime - .block_on(handler.into_event_stream().take(1).collect()) - .unwrap(); - - // then - matches::assert_matches!( - events.get(0), - Some(ProtocolsHandlerEvent::Custom(ProtocolOutEvent::Error(Error::MalformedJson(_)))) - ) - } -} diff --git a/libp2p-comit/src/lib.rs b/libp2p-comit/src/lib.rs index 2571c650e9..20567b48f9 100644 --- a/libp2p-comit/src/lib.rs +++ b/libp2p-comit/src/lib.rs @@ -19,8 +19,6 @@ mod behaviour; pub mod handler; mod protocol; mod substream; -#[cfg(test)] -pub mod test_harness; use serde::{Deserialize, Serialize}; use serde_json::{self, Value as JsonValue}; @@ -28,28 +26,24 @@ use serde_json::{self, Value as JsonValue}; pub use self::{ behaviour::{BehaviourOutEvent, Comit}, handler::{ComitHandler, PendingInboundRequest, PendingOutboundRequest}, - protocol::{ComitProtocolConfig, Frames}, + protocol::{Config, Frames}, }; use crate::handler::{ProtocolOutEvent, ProtocolOutboundOpenInfo}; -use libp2p_swarm::ProtocolsHandlerEvent; +use libp2p::swarm::ProtocolsHandlerEvent; pub type ComitHandlerEvent = - ProtocolsHandlerEvent; - -pub trait IntoFrame { - fn into_frame(self) -> F; -} + ProtocolsHandlerEvent; #[derive(Deserialize, Serialize, PartialEq, Debug)] pub struct Frame { #[serde(rename = "type")] - pub frame_type: FrameType, + pub kind: FrameKind, pub payload: JsonValue, } #[derive(Deserialize, Serialize, PartialEq, Debug, Clone, Copy)] #[serde(rename_all = "UPPERCASE")] -pub enum FrameType { +pub enum FrameKind { Request, Response, @@ -60,10 +54,7 @@ pub enum FrameType { } impl Frame { - pub fn new(frame_type: FrameType, payload: JsonValue) -> Self { - Self { - frame_type, - payload, - } + pub fn new(kind: FrameKind, payload: JsonValue) -> Self { + Self { kind, payload } } } diff --git a/libp2p-comit/src/protocol.rs b/libp2p-comit/src/protocol.rs index 93fed8d7bf..242110e1ce 100644 --- a/libp2p-comit/src/protocol.rs +++ b/libp2p-comit/src/protocol.rs @@ -1,18 +1,18 @@ use crate::frame::{self, JsonFrameCodec}; -use futures::future::FutureResult; -use libp2p_core::{InboundUpgrade, Negotiated, OutboundUpgrade, UpgradeInfo}; -use std::{convert::Infallible, iter}; -use tokio::{ - codec::{Decoder, Framed}, - prelude::*, +use futures::future; +use futures_codec::Framed; +use libp2p::{ + core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}, + swarm::NegotiatedSubstream, }; +use std::{convert::Infallible, iter}; -pub type Frames = Framed, JsonFrameCodec>; +pub type Frames = Framed; #[derive(Clone, Copy, Debug)] -pub struct ComitProtocolConfig {} +pub struct Config {} -impl UpgradeInfo for ComitProtocolConfig { +impl UpgradeInfo for Config { type Info = &'static [u8]; type InfoIter = iter::Once; @@ -21,32 +21,30 @@ impl UpgradeInfo for ComitProtocolConfig { } } -impl InboundUpgrade for ComitProtocolConfig -where - TSubstream: AsyncRead + AsyncWrite, -{ - type Output = Frames; +impl InboundUpgrade for Config { + type Output = Frames; type Error = Infallible; - type Future = FutureResult; + type Future = future::Ready>; #[inline] - fn upgrade_inbound(self, socket: Negotiated, _: Self::Info) -> Self::Future { + fn upgrade_inbound(self, socket: NegotiatedSubstream, _: Self::Info) -> Self::Future { let codec = frame::JsonFrameCodec::default(); - futures::future::ok(codec.framed(socket)) + let framed = Framed::new(socket, codec); + + future::ok(framed) } } -impl OutboundUpgrade for ComitProtocolConfig -where - TSubstream: AsyncRead + AsyncWrite, -{ - type Output = Frames; +impl OutboundUpgrade for Config { + type Output = Frames; type Error = Infallible; - type Future = FutureResult; + type Future = future::Ready>; #[inline] - fn upgrade_outbound(self, socket: Negotiated, _: Self::Info) -> Self::Future { + fn upgrade_outbound(self, socket: NegotiatedSubstream, _: Self::Info) -> Self::Future { let codec = frame::JsonFrameCodec::default(); - futures::future::ok(codec.framed(socket)) + let framed = Framed::new(socket, codec); + + future::ok(framed) } } diff --git a/libp2p-comit/src/substream/inbound.rs b/libp2p-comit/src/substream/inbound.rs index 94dade7d4b..c3600a8089 100644 --- a/libp2p-comit/src/substream/inbound.rs +++ b/libp2p-comit/src/substream/inbound.rs @@ -3,53 +3,55 @@ use crate::{ handler::{self, InboundMessage, PendingInboundRequest, ProtocolOutEvent}, protocol::Frames, substream::{Advance, Advanced, CloseStream}, - Frame, FrameType, IntoFrame, + Frame, FrameKind, +}; +use futures::{channel::oneshot, task::Poll, Future, Sink, Stream}; +use libp2p::swarm::ProtocolsHandlerEvent; +use std::{ + collections::{HashMap, HashSet}, + pin::Pin, + task::Context, }; -use futures::sync::oneshot; -use libp2p_swarm::ProtocolsHandlerEvent; -use std::collections::{HashMap, HashSet}; -use tokio::prelude::*; #[derive(strum_macros::Display)] #[allow(missing_debug_implementations)] /// States of an inbound substream i.e. from peer node to us. -pub enum State { +pub enum State { /// Waiting for a request from the remote. - WaitingMessage { stream: Frames }, + WaitingMessage { stream: Pin> }, /// Waiting for the user to send the response back to us. WaitingUser { - receiver: oneshot::Receiver, - stream: Frames, + receiver: Pin>>, + stream: Pin>, }, /// Waiting to send an answer back to the remote. WaitingSend { msg: Frame, - stream: Frames, + stream: Pin>, }, /// Waiting to flush an answer back to the remote. - WaitingFlush { stream: Frames }, + WaitingFlush { stream: Pin> }, /// The substream is being closed. - WaitingClose { stream: Frames }, + WaitingClose { stream: Pin> }, } -impl CloseStream for State { - type TSubstream = TSubstream; - - fn close(stream: Frames) -> Self { +impl CloseStream for State { + fn close(stream: Pin>) -> Self { State::WaitingClose { stream } } } -impl Advance for State { +impl Advance for State { fn advance( self, known_headers: &HashMap>, - ) -> Advanced> { + cx: &mut Context<'_>, + ) -> Advanced { use self::State::*; match self { - WaitingMessage { mut stream } => match stream.poll() { - Ok(Async::Ready(Some(frame))) => match frame.frame_type { - FrameType::Request => { + WaitingMessage { mut stream } => match stream.as_mut().poll_next(cx) { + Poll::Ready(Some(Ok(frame))) => match frame.kind { + FrameKind::Request => { let request = serde_json::from_value::(frame.payload) .map_err(handler::Error::MalformedFrame) @@ -72,7 +74,10 @@ impl Advance for State { Ok(request) => { let (sender, receiver) = oneshot::channel(); Advanced { - new_state: Some(WaitingUser { receiver, stream }), + new_state: Some(WaitingUser { + receiver: Box::pin(receiver), + stream, + }), event: Some(ProtocolsHandlerEvent::Custom( ProtocolOutEvent::Message(InboundMessage::Request( PendingInboundRequest { @@ -86,43 +91,42 @@ impl Advance for State { Err(error) => Advanced::error(stream, error), } } - FrameType::Response => { + FrameKind::Response => { Advanced::error(stream, handler::Error::UnexpectedFrame(frame)) } - FrameType::Unknown => Advanced::error(stream, handler::Error::UnknownFrameType), + FrameKind::Unknown => Advanced::error(stream, handler::Error::UnknownFrameKind), }, - Ok(Async::NotReady) => Advanced::transition_to(WaitingMessage { stream }), - Ok(Async::Ready(None)) => Advanced::error(stream, handler::Error::UnexpectedEOF), - Err(error) => Advanced::error(stream, error), + Poll::Ready(Some(Err(error))) => { + Advanced::error(stream, handler::Error::MalformedJson(error)) + } + Poll::Pending => Advanced::transition_to(WaitingMessage { stream }), + Poll::Ready(None) => Advanced::error(stream, handler::Error::UnexpectedEOF), }, WaitingUser { mut receiver, stream, - } => match receiver.poll() { - Ok(Async::Ready(response)) => WaitingSend { - msg: response.into_frame(), + } => match receiver.as_mut().poll(cx) { + Poll::Ready(Ok(response)) => WaitingSend { + msg: response.into(), stream, } - .advance(known_headers), - Ok(Async::NotReady) => Advanced::transition_to(WaitingUser { receiver, stream }), - Err(error) => Advanced::error(stream, error), + .advance(known_headers, cx), + Poll::Pending => Advanced::transition_to(WaitingUser { receiver, stream }), + Poll::Ready(Err(error)) => Advanced::error(stream, error), }, - WaitingSend { msg, mut stream } => match stream.start_send(msg) { - Ok(AsyncSink::Ready) => WaitingFlush { stream }.advance(known_headers), - Ok(AsyncSink::NotReady(msg)) => { - Advanced::transition_to(WaitingSend { msg, stream }) - } + WaitingSend { msg, mut stream } => match stream.as_mut().start_send(msg) { + Ok(()) => WaitingFlush { stream }.advance(known_headers, cx), Err(error) => Advanced::error(stream, error), }, - WaitingFlush { mut stream } => match stream.poll_complete() { - Ok(Async::Ready(_)) => Advanced::transition_to(WaitingClose { stream }), - Ok(Async::NotReady) => Advanced::transition_to(WaitingFlush { stream }), - Err(error) => Advanced::error(stream, error), + WaitingFlush { mut stream } => match stream.as_mut().poll_flush(cx) { + Poll::Ready(Ok(())) => Advanced::transition_to(WaitingClose { stream }), + Poll::Ready(Err(error)) => Advanced::error(stream, error), + Poll::Pending => Advanced::transition_to(WaitingFlush { stream }), }, - WaitingClose { mut stream } => match stream.close() { - Ok(Async::Ready(_)) => Advanced::end(), - Ok(Async::NotReady) => Advanced::transition_to(WaitingClose { stream }), - Err(error) => Advanced::error(stream, error), + WaitingClose { mut stream } => match stream.as_mut().poll_close(cx) { + Poll::Ready(Ok(())) => Advanced::end(), + Poll::Pending => Advanced::transition_to(WaitingClose { stream }), + Poll::Ready(Err(error)) => Advanced::error(stream, error), }, } } diff --git a/libp2p-comit/src/substream/mod.rs b/libp2p-comit/src/substream/mod.rs index 303f9bef03..58ace4e52a 100644 --- a/libp2p-comit/src/substream/mod.rs +++ b/libp2p-comit/src/substream/mod.rs @@ -1,10 +1,10 @@ -use crate::{ - handler::{Error, ProtocolOutEvent}, - protocol::Frames, - ComitHandlerEvent, +use crate::{handler::Error, protocol::Frames, ComitHandlerEvent}; +use libp2p::swarm::ProtocolsHandlerEvent; +use std::{ + collections::{HashMap, HashSet}, + pin::Pin, + task::Context, }; -use libp2p_swarm::ProtocolsHandlerEvent; -use std::collections::{HashMap, HashSet}; pub mod inbound; pub mod outbound; @@ -18,7 +18,11 @@ pub struct Advanced { } pub trait Advance: Sized { - fn advance(self, known_headers: &HashMap>) -> Advanced; + fn advance( + self, + known_headers: &HashMap>, + cx: &mut Context<'_>, + ) -> Advanced; } impl Advanced { @@ -37,21 +41,23 @@ impl Advanced { } } -impl Advanced { - fn error>(stream: Frames, error: E) -> Self { +impl Advanced +where + S: CloseStream, +{ + fn error(stream: Pin>, error: E) -> Self + where + E: Into, + { let error = error.into(); Self { new_state: Some(S::close(stream)), - event: Some(ProtocolsHandlerEvent::Custom(ProtocolOutEvent::Error( - error, - ))), + event: Some(ProtocolsHandlerEvent::Close(error)), } } } pub trait CloseStream: Sized { - type TSubstream; - - fn close(stream: Frames) -> Self; + fn close(stream: Pin>) -> Self; } diff --git a/libp2p-comit/src/substream/outbound.rs b/libp2p-comit/src/substream/outbound.rs index d23c7d1845..f8164a316c 100644 --- a/libp2p-comit/src/substream/outbound.rs +++ b/libp2p-comit/src/substream/outbound.rs @@ -3,124 +3,130 @@ use crate::{ handler::{self, InboundMessage, PendingInboundResponse, ProtocolOutEvent}, protocol::Frames, substream::{Advance, Advanced, CloseStream}, - Frame, FrameType, + Frame, FrameKind, +}; +use futures::{channel::oneshot, Sink, Stream}; +use libp2p::swarm::ProtocolsHandlerEvent; +use std::{ + collections::{HashMap, HashSet}, + pin::Pin, + task::{Context, Poll}, }; -use futures::sync::oneshot; -use libp2p_swarm::ProtocolsHandlerEvent; -use std::collections::{HashMap, HashSet}; -use tokio::prelude::*; #[derive(strum_macros::Display)] #[allow(missing_debug_implementations)] /// States of an outbound substream i.e. from us to peer node. -pub enum State { +pub enum State { /// Waiting to send a message to the remote. WaitingSend { frame: Frame, response_sender: oneshot::Sender, - stream: Frames, + stream: Pin>, }, /// Waiting to flush the substream so that the data arrives at the remote. WaitingFlush { response_sender: oneshot::Sender, - stream: Frames, + stream: Pin>, }, /// Waiting for the answer to our message. WaitingAnswer { response_sender: oneshot::Sender, - stream: Frames, + stream: Pin>, }, /// The substream is being closed. - WaitingClose { stream: Frames }, + WaitingClose { stream: Pin> }, } -impl CloseStream for State { - type TSubstream = TSubstream; - - fn close(stream: Frames) -> Self { +impl CloseStream for State { + fn close(stream: Pin>) -> Self { State::WaitingClose { stream } } } -impl Advance for State { +impl Advance for State { fn advance( self, known_headers: &HashMap>, - ) -> Advanced> { + cx: &mut Context<'_>, + ) -> Advanced { use self::State::*; + match self { WaitingSend { frame, response_sender, mut stream, - } => match stream.start_send(frame) { - Ok(AsyncSink::Ready) => WaitingFlush { + } => match stream.as_mut().start_send(frame) { + Ok(()) => WaitingFlush { response_sender, stream, } - .advance(known_headers), - Ok(AsyncSink::NotReady(frame)) => Advanced::transition_to(WaitingSend { - frame, - response_sender, - stream, - }), + .advance(known_headers, cx), Err(error) => Advanced::error(stream, error), }, WaitingFlush { response_sender, mut stream, - } => match stream.poll_complete() { - Ok(Async::Ready(_)) => WaitingAnswer { + } => match stream.as_mut().poll_flush(cx) { + Poll::Ready(Ok(())) => WaitingAnswer { response_sender, stream, } - .advance(&known_headers), - Ok(Async::NotReady) => Advanced::transition_to(WaitingFlush { + .advance(&known_headers, cx), + Poll::Pending => Advanced::transition_to(WaitingFlush { response_sender, stream, }), - Err(error) => Advanced::error(stream, error), + Poll::Ready(Err(error)) => Advanced::error(stream, error), }, WaitingAnswer { response_sender, mut stream, - } => match stream.poll() { - Ok(Async::Ready(Some(frame))) => match frame.frame_type { - FrameType::Response => { - let event = serde_json::from_value(frame.payload) - .map(|response| { - ProtocolOutEvent::Message(InboundMessage::Response( - PendingInboundResponse { - response, - channel: response_sender, - }, - )) - }) - .map_err(handler::Error::MalformedFrame) - .unwrap_or_else(ProtocolOutEvent::Error); + } => match stream.as_mut().poll_next(cx) { + Poll::Ready(Some(Ok(frame))) => { + let response = match frame.kind { + FrameKind::Response => serde_json::from_value(frame.payload), + FrameKind::Request => { + return Advanced::error(stream, handler::Error::UnexpectedFrame(frame)) + } + FrameKind::Unknown => { + return Advanced::error(stream, handler::Error::UnknownFrameKind) + } + }; + + match response { + Ok(response) => { + let event = ProtocolOutEvent::Message(InboundMessage::Response( + PendingInboundResponse { + response, + channel: response_sender, + }, + )); - Advanced { - new_state: Some(WaitingClose { stream }), - event: Some(ProtocolsHandlerEvent::Custom(event)), + Advanced { + new_state: Some(WaitingClose { stream }), + event: Some(ProtocolsHandlerEvent::Custom(event)), + } + } + Err(error) => { + Advanced::error(stream, handler::Error::MalformedFrame(error)) } } - FrameType::Request => { - Advanced::error(stream, handler::Error::UnexpectedFrame(frame)) - } - FrameType::Unknown => Advanced::error(stream, handler::Error::UnknownFrameType), - }, - Ok(Async::NotReady) => Advanced::transition_to(WaitingAnswer { + } + Poll::Ready(Some(Err(error))) => { + Advanced::error(stream, handler::Error::MalformedJson(error)) + } + Poll::Pending => Advanced::transition_to(WaitingAnswer { response_sender, stream, }), - Ok(Async::Ready(None)) => Advanced::error(stream, handler::Error::UnexpectedEOF), - Err(error) => Advanced::error(stream, error), + Poll::Ready(None) => Advanced::error(stream, handler::Error::UnexpectedEOF), }, - WaitingClose { mut stream } => match stream.close() { - Ok(Async::Ready(_)) => Advanced::end(), - Ok(Async::NotReady) => Advanced::transition_to(WaitingClose { stream }), - Err(error) => Advanced::error(stream, error), + WaitingClose { mut stream } => match stream.as_mut().poll_close(cx) { + Poll::Ready(Ok(())) => Advanced::end(), + Poll::Pending => Advanced::transition_to(WaitingClose { stream }), + Poll::Ready(Err(error)) => Advanced::error(stream, error), }, } } diff --git a/libp2p-comit/src/test_harness.rs b/libp2p-comit/src/test_harness.rs deleted file mode 100644 index edc6ef34b8..0000000000 --- a/libp2p-comit/src/test_harness.rs +++ /dev/null @@ -1,133 +0,0 @@ -use crate::{ - frame::{self, JsonFrameCodec, Response}, - handler::{InboundMessage, ProtocolOutEvent}, - ComitHandler, ComitHandlerEvent, Frame, PendingInboundRequest, -}; -use futures::{Future, Stream}; -use libp2p_swarm::{ProtocolsHandler, ProtocolsHandlerEvent}; -use multistream_select::{Negotiated, Version}; -use std::collections::{HashMap, HashSet}; -use tokio::{ - codec::{Decoder, Encoder, Framed}, - net::{TcpListener, TcpStream}, - prelude::{AsyncRead, AsyncWrite}, -}; - -pub fn setup_substream( - codec_dialer: CD, - codec_listener: CL, -) -> impl Future< - Item = ( - Framed, CD>, - Framed, CL>, - ), - Error = multistream_select::NegotiationError, -> { - let listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); - let listener_addr = listener.local_addr().unwrap(); - - let listener = listener - .incoming() - .into_future() - .map(|(connection, _stream)| connection.unwrap()) - .map_err(|(stream_error, _stream)| stream_error) - .from_err() - .and_then(move |connection| { - multistream_select::listener_select_proto(connection, vec![b"/proto1"]) - }) - .and_then(|(_proto, substream)| substream.complete()) - .map(move |substream| codec_listener.framed(substream)); - - let dialer = TcpStream::connect(&listener_addr) - .from_err() - .and_then(move |connection| { - multistream_select::dialer_select_proto(connection, vec![b"/proto1"], Version::V1) - }) - .and_then(|(_proto, substream)| substream.complete()) - .map(move |substream| codec_dialer.framed(substream)); - - dialer.join(listener) -} - -pub fn setup_substream_with_json_codec() -> impl Future< - Item = ( - Framed, JsonFrameCodec>, - Framed, JsonFrameCodec>, - ), - Error = multistream_select::NegotiationError, -> { - setup_substream(JsonFrameCodec::default(), JsonFrameCodec::default()) -} - -pub fn request_with_no_headers>( - request_type: S, -) -> HashMap> { - let mut headers = HashMap::new(); - headers.insert(request_type.into(), HashSet::new()); - headers -} - -pub trait IntoFutureWithResponse { - fn into_future_with_response( - self, - response: Response, - ) -> Box + Send>; -} - -impl IntoFutureWithResponse - for ComitHandler -{ - fn into_future_with_response( - self, - response: Response, - ) -> Box + Send> { - let future = self.into_event_stream().for_each(move |event| { - // assume we only want to handle requests - match event { - ProtocolsHandlerEvent::Custom(ProtocolOutEvent::Message( - InboundMessage::Request(PendingInboundRequest { channel, .. }), - )) => { - channel.send(response.clone()).unwrap(); - } - _ => panic!("expected event to be a PendingInboundRequest"), - } - Ok(()) - }); - - Box::new(future) - } -} - -pub trait IntoEventStream { - fn into_event_stream(self) -> Box + Send>; -} - -impl IntoEventStream - for ComitHandler -{ - fn into_event_stream(mut self) -> Box + Send> { - let stream = futures::stream::poll_fn(move || self.poll().map(|ok| ok.map(Some))) - // ignore all errors - .map_err(|e| panic!("{:?}", e)); - - Box::new(stream) - } -} - -pub trait WaitForFrame { - fn wait_for_frame( - self, - ) -> Box, Error = frame::CodecError> + Send>; -} - -impl WaitForFrame for Framed, JsonFrameCodec> { - fn wait_for_frame( - self, - ) -> Box, Error = frame::CodecError> + Send> { - Box::new( - self.into_future() - .map(|(item, _stream)| item) - .map_err(|(error, _stream)| error), - ) - } -} diff --git a/logo.svg b/logo.svg index b52bf84044..0938df2228 100644 --- a/logo.svg +++ b/logo.svg @@ -1 +1,40 @@ -COMITspellout_screen \ No newline at end of file + + + + + + + + + + COMITspellout_screenPLAIN + + + + + + + + + + + + + + + + + + + diff --git a/rust-toolchain b/rust-toolchain index 7d47e59980..f86fb9cbcf 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.41.0 +1.41.1