diff --git a/.github/workflows/base_node_binaries.json b/.github/workflows/base_node_binaries.json index 19c6becfe8..22a4dc1965 100644 --- a/.github/workflows/base_node_binaries.json +++ b/.github/workflows/base_node_binaries.json @@ -2,7 +2,7 @@ { "name": "linux-x86_64", "runs-on": "ubuntu-20.04", - "rust": "nightly-2022-05-01", + "rust": "nightly-2022-11-03", "target": "x86_64-unknown-linux-gnu", "cross": false, "target_cpu": "x86-64", diff --git a/.github/workflows/base_node_binaries.yml b/.github/workflows/base_node_binaries.yml index 33bf334514..7760aa89ad 100644 --- a/.github/workflows/base_node_binaries.yml +++ b/.github/workflows/base_node_binaries.yml @@ -23,7 +23,7 @@ concurrency: env: TBN_FILENAME: "tari_suite" TBN_BUNDLEID_BASE: "com.tarilabs.pkg" - toolchain: nightly-2022-05-01 + toolchain: nightly-2022-11-03 matrix-json-file: ".github/workflows/base_node_binaries.json" CARGO_HTTP_MULTIPLEXING: false diff --git a/.github/workflows/build_dockers.yml b/.github/workflows/build_dockers.yml index 2e8f5be49f..e573d0765f 100644 --- a/.github/workflows/build_dockers.yml +++ b/.github/workflows/build_dockers.yml @@ -47,7 +47,7 @@ name: Build docker images - xmrig env: - toolchain_default: nightly-2022-05-01 + toolchain_default: nightly-2022-11-03 jobs: builds_envs_setup: diff --git a/.github/workflows/build_dockers_workflow.yml b/.github/workflows/build_dockers_workflow.yml index 7ecc5b5450..24102988aa 100644 --- a/.github/workflows/build_dockers_workflow.yml +++ b/.github/workflows/build_dockers_workflow.yml @@ -14,7 +14,7 @@ name: Build docker images - workflow_call/on-demand toolchain: type: string description: 'Rust toolchain' - default: nightly-2022-05-01 + default: nightly-2022-11-03 arch: type: string default: x86-64 diff --git a/.github/workflows/build_libwallets.yml b/.github/workflows/build_libwallets.yml index 71ad35ba47..e61d682382 100644 --- a/.github/workflows/build_libwallets.yml +++ b/.github/workflows/build_libwallets.yml @@ -22,7 +22,7 @@ name: Build libwallets description: 'Rust toolchain' env: - toolchain_default: nightly-2022-05-01 + toolchain_default: nightly-2022-11-03 jobs: builds_envs_setup: diff --git a/.github/workflows/build_libwallets_workflow.yml b/.github/workflows/build_libwallets_workflow.yml index 374f222341..f809c88421 100644 --- a/.github/workflows/build_libwallets_workflow.yml +++ b/.github/workflows/build_libwallets_workflow.yml @@ -13,7 +13,7 @@ name: Build libwallet - workflow_call/on-demand toolchain: type: string description: 'Rust toolchain' - default: 'nightly-2022-05-01' + default: 'nightly-2022-11-03' jobs: android_build: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6fc20d9e67..c777fdee3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ --- name: CI -'on': +"on": workflow_dispatch: push: branches: @@ -15,7 +15,7 @@ name: CI - synchronize env: - toolchain: nightly-2022-05-01 + toolchain: nightly-2022-11-03 CARGO_HTTP_MULTIPLEXING: false CARGO_TERM_COLOR: always PROTOC: protoc @@ -25,7 +25,7 @@ jobs: clippy: name: clippy #runs-on: [ self-hosted, ubuntu18.04-high-cpu ] - runs-on: [ ubuntu-20.04 ] + runs-on: [ubuntu-20.04] steps: - name: checkout uses: actions/checkout@v3 @@ -34,7 +34,6 @@ jobs: with: toolchain: ${{ env.toolchain }} components: clippy, rustfmt - override: true - name: ubuntu dependencies run: | sudo apt-get update @@ -55,8 +54,8 @@ jobs: command: lints args: clippy --all-targets --all-features build: - name: check nightly - runs-on: [ self-hosted, ubuntu18.04-high-cpu ] + name: cargo check + runs-on: [self-hosted, ubuntu18.04-high-cpu] steps: - name: checkout uses: actions/checkout@v3 @@ -81,8 +80,8 @@ jobs: command: check args: --release --package tari_wallet_ffi build-stable: - name: check stable - runs-on: [ self-hosted, ubuntu18.04-high-cpu ] + name: cargo check stable + runs-on: [self-hosted, ubuntu18.04-high-cpu] steps: - name: checkout uses: actions/checkout@v3 @@ -104,7 +103,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: check - args: --release --all-targets --locked + args: --release --all-targets --workspace --exclude tari_integration_tests --locked - name: cargo check ffi separately uses: actions-rs/cargo@v1 with: @@ -112,7 +111,7 @@ jobs: args: --release --package tari_wallet_ffi licenses: name: file licenses - runs-on: [ ubuntu-20.04 ] + runs-on: [ubuntu-20.04] steps: - name: checkout uses: actions/checkout@v3 @@ -125,7 +124,7 @@ jobs: run: ./scripts/file_license_check.sh test: name: test - runs-on: [ self-hosted, ubuntu18.04-high-cpu ] + runs-on: [self-hosted, ubuntu18.04-high-cpu] steps: - name: checkout uses: actions/checkout@v3 @@ -152,12 +151,12 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: -v --all-features --release + args: -v --all-features --release --workspace --exclude tari_integration_tests # Allows other workflows to know the PR number artifacts: name: pr_2_artifact - runs-on: [ ubuntu-20.04 ] + runs-on: [ubuntu-20.04] steps: - name: Save the PR number in an artifact shell: bash diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 7b8fb38bff..092a5d41fd 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -8,7 +8,7 @@ name: Source Coverage - ci-coverage-* env: - toolchain: nightly-2022-05-01 + toolchain: nightly-2022-11-03 jobs: coverage: diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 39780df4a4..3a9a8a50a7 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -2,6 +2,11 @@ name: Integration tests 'on': + pull_request: + types: + - opened + - reopened + - synchronize push: paths-ignore: - '**/*.md' @@ -24,54 +29,39 @@ name: Integration tests type: string env: - toolchain: nightly-2022-05-01 - # space seperated string list - build_binaries: "tari_base_node tari_console_wallet tari_merge_mining_proxy tari_miner" + toolchain: nightly-2022-11-03 + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - cucumber_tests: - name: Cucumber tests - runs-on: ubuntu-20.04 + base_layer: + name: Cucumber tests / Base Layer + runs-on: [self-hosted, ubuntu18.04-high-cpu] steps: - - name: Checkout source code + - name: checkout uses: actions/checkout@v3 - name: Envs setup id: envs_setup shell: bash run: | - VAPPS_STRING="${{ env.build_binaries }}" - VAPPS_ARRAY=(${VAPPS_STRING}) - for i in "${!VAPPS_ARRAY[@]}"; do - if [ "${VAPPS_ARRAY[$i]:0:5}" = "tari_" ] ; then - VAPPS_TARGET_BINS="${VAPPS_TARGET_BINS} --bin ${VAPPS_ARRAY[$i]}" - fi - done - echo "TARGET_BINS=${VAPPS_TARGET_BINS}" >> $GITHUB_ENV if [ "${{ github.event_name }}" == "schedule" ] ; then echo "CI_FFI=false" >> $GITHUB_ENV if [ "${{ github.event.schedule }}" == "0 2 * * *" ] ; then - echo "CI_PROFILE=non-critical" >> $GITHUB_ENV + echo "CI_PROFILE=(not @long-running)" >> $GITHUB_ENV elif [ "${{ github.event.schedule }}" == "0 12 * * 6" ] ; then - echo "CI_PROFILE=long-running" >> $GITHUB_ENV + echo "CI_PROFILE=@long-running" >> $GITHUB_ENV fi else echo "CI ..." - echo "CI_PROFILE=ci" >> $GITHUB_ENV + echo "CI_PROFILE=@critical and (not @long-running)" >> $GITHUB_ENV CI_BINS=${{ inputs.ci_bins }} echo "Run binary - ${CI_BINS}" echo "CI_BINS=${CI_BINS:-true}" >> $GITHUB_ENV - CI_FFI=${{ inputs.ci_ffi }} - echo "Run FFI - ${CI_FFI}" - echo "CI_FFI=${CI_FFI:-true}" >> $GITHUB_ENV fi - - name: Install ubuntu dependencies - shell: bash - run: | - sudo apt-get update - sudo bash scripts/install_ubuntu_dependencies.sh - - name: Setup rust toolchain uses: actions-rs/toolchain@v1 with: @@ -80,107 +70,102 @@ jobs: toolchain: ${{ env.toolchain }} override: true + - name: Install ubuntu dependencies + shell: bash + run: | + sudo apt-get update + sudo bash scripts/install_ubuntu_dependencies.sh + - name: Cache cargo files and outputs uses: Swatinem/rust-cache@v2 - - name: Build binaries + - name: cargo test compile uses: actions-rs/cargo@v1 with: - use-cross: false - command: build - args: > - --release - --locked - ${{ env.TARGET_BINS }} + command: test + args: --no-run --locked --all-features --release ${{ env.TARGET_BINS }} - - name: Build ffi + - name: Run ${{ env.CI_PROFILE }} integration tests for binaries + if: ${{ env.CI_BINS == 'true' }} + timeout-minutes: 90 uses: actions-rs/cargo@v1 with: - use-cross: false - command: build + command: test args: > + --test cucumber + -v + --all-features --release - --locked - --package tari_wallet_ffi + --package tari_integration_tests + -- -t "${{ env.CI_PROFILE }} and (not @wallet-ffi) and (not @broken)" + -c 5 + --retry 2 + + ffi: + name: Cucumber tests / FFI + runs-on: [self-hosted, ubuntu18.04-high-cpu] + steps: + - name: checkout + uses: actions/checkout@v3 - - name: CI folder prep + - name: Envs setup + id: envs_setup shell: bash - working-directory: integration_tests run: | - mkdir -p cucumber_output - mkdir -p temp/reports - mkdir -p temp/out - cd ../target/release/ - cp -v ${{ env.build_binaries }} "$GITHUB_WORKSPACE/integration_tests/temp/out" - cd $GITHUB_WORKSPACE/integration_tests/temp/out - shasum -a 256 ${{ env.build_binaries }} > integration_tests.sha256sums - cat integration_tests.sha256sums - ls -alht - - - name: Setup Node.js - uses: actions/setup-node@v3 + if [ "${{ github.event_name }}" == "schedule" ] ; then + echo "CI_FFI=false" >> $GITHUB_ENV + if [ "${{ github.event.schedule }}" == "0 2 * * *" ] ; then + echo "CI_PROFILE=(not @long-running)" >> $GITHUB_ENV + elif [ "${{ github.event.schedule }}" == "0 12 * * 6" ] ; then + echo "CI_PROFILE=@long-running" >> $GITHUB_ENV + fi + else + echo "CI ..." + echo "CI_PROFILE=@critical and (not @long-running)" >> $GITHUB_ENV + CI_FFI=${{ inputs.ci_ffi }} + echo "Run FFI - ${CI_FFI}" + echo "CI_FFI=${CI_FFI:-true}" >> $GITHUB_ENV + fi + + - name: Setup rust toolchain + if: ${{ env.CI_FFI == 'true' }} + uses: actions-rs/toolchain@v1 with: - node-version: lts/erbium - cache: 'npm' - cache-dependency-path: integration_tests/package-lock.json + profile: minimal + components: rustfmt, clippy + toolchain: ${{ env.toolchain }} + override: true - - name: Run npm ci and lint + - name: Install ubuntu dependencies + if: ${{ env.CI_FFI == 'true' }} shell: bash - working-directory: integration_tests run: | - node -v - npm install - npm run check-fmt - npm run lint - npm ci - cd ../clients/nodejs/base_node_grpc_client - npm install - cd ../wallet_grpc_client - npm install - npm ci + sudo apt-get update + sudo bash scripts/install_ubuntu_dependencies.sh - - name: Run ${{ env.CI_PROFILE }} integration tests for binaries - if: ${{ env.CI_BINS == 'true' }} - continue-on-error: true - timeout-minutes: 90 - shell: bash - working-directory: integration_tests - run: | - node_modules/.bin/cucumber-js --publish-quiet \ - --profile "${{ env.CI_PROFILE }}" \ - --tags "not @wallet-ffi" --format json:cucumber_output/tests.cucumber \ - --exit --retry 2 --retry-tag-filter "@flaky and not @broken" + - name: Cache cargo files and outputs + if: ${{ env.CI_FFI == 'true' }} + uses: Swatinem/rust-cache@v2 + + - name: cargo test compile + if: ${{ env.CI_FFI == 'true' }} + uses: actions-rs/cargo@v1 + with: + command: test + args: --no-run --locked --all-features --release ${{ env.TARGET_BINS }} - name: Run ${{ env.CI_PROFILE }} integration tests for ffi if: ${{ env.CI_FFI == 'true' }} - continue-on-error: true timeout-minutes: 90 - shell: bash - working-directory: integration_tests - run: | - node_modules/.bin/cucumber-js --publish-quiet \ - --profile "${{ env.CI_PROFILE }}" \ - --tags "@wallet-ffi" --format json:cucumber_output/tests_ffi.cucumber \ - --exit --retry 2 --retry-tag-filter "@flaky and not @broken" - - - name: Generate report - continue-on-error: true - if: always() - shell: bash - working-directory: integration_tests - run: | - node ./generate_report.js - # Empty file check - if [ -s cucumber_output/tests_ffi.cucumber ] ; then - node ./generate_report.js "cucumber_output/tests_ffi.cucumber" "temp/reports/cucumber_ffi_report.html" - fi - - - name: Store ${{ env.CI_PROFILE }} test results - uses: actions/upload-artifact@v3 - if: always() + uses: actions-rs/cargo@v1 with: - name: ${{ env.CI_PROFILE }} test results - path: | - integration_tests/cucumber_output - integration_tests/temp/reports - integration_tests/temp/out + command: test + args: > + --test cucumber + -v + --all-features + --release + --package tari_integration_tests + -- -t "@wallet-ffi and ${{ env.CI_PROFILE }} and (not @broken)" + -c 1 + --retry 2 diff --git a/.license.ignore b/.license.ignore index ec1271b664..11b90032d8 100644 --- a/.license.ignore +++ b/.license.ignore @@ -12,14 +12,10 @@ ./applications/tari_base_node/osx-pkg/scripts/preinstall ./applications/tari_console_wallet/linux/start_tari_console_wallet ./base_layer/key_manager/Makefile -./base_layer/wallet/src/schema.rs ./base_layer/p2p/src/dns/roots/tls.rs +./base_layer/wallet/src/schema.rs ./buildtools/docker/torrc ./buildtools/vagrant/Vagrantfile ./docs/src/theme/book.js ./docs/src/theme/highlight.js -./integration_tests/Makefile -./integration_tests/cucumber.js -./integration_tests/environment -./integration_tests/generate_report.js -./scripts/env_sample +./scripts/env_sample \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index bcd244b2d4..00bb6c53b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -190,6 +190,70 @@ dependencies = [ "cc", ] +[[package]] +name = "axum" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08b108ad2665fa3f6e6a517c3d80ec3e77d224c47d605167aefaa5d7ef97fa48" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes 1.3.0", + "futures-util", + "http", + "http-body", + "hyper", + "itoa 1.0.5", + "matchit", + "memchr", + "mime", + "percent-encoding 2.2.0", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b8558f5a0581152dc94dcd289132a1d377494bdeafcd41869b3258e3e2ad92" +dependencies = [ + "async-trait", + "bytes 1.3.0", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-jrpc" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "163372a0c1cb179c5b3a9ac5edbb01501b4d4ad247b971712d0559e832819e3a" +dependencies = [ + "anyhow", + "async-trait", + "axum", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "base58-monero" version = "0.3.2" @@ -467,6 +531,12 @@ version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "bytecount" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" + [[package]] name = "bytemuck" version = "1.12.3" @@ -773,8 +843,8 @@ checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ "atty", "bitflags 1.3.2", - "clap_derive", - "clap_lex", + "clap_derive 3.2.18", + "clap_lex 0.2.4", "indexmap", "once_cell", "strsim 0.10.0", @@ -782,6 +852,22 @@ dependencies = [ "textwrap 0.16.0", ] +[[package]] +name = "clap" +version = "4.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d" +dependencies = [ + "bitflags 1.3.2", + "clap_derive 4.0.21", + "clap_lex 0.3.0", + "is-terminal", + "once_cell", + "strsim 0.10.0", + "termcolor", + "terminal_size 0.2.3", +] + [[package]] name = "clap_derive" version = "3.2.18" @@ -795,6 +881,19 @@ dependencies = [ "syn", ] +[[package]] +name = "clap_derive" +version = "4.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +dependencies = [ + "heck 0.4.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "clap_lex" version = "0.2.4" @@ -804,6 +903,15 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "clear_on_drop" version = "0.2.5" @@ -868,6 +976,20 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "console" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "terminal_size 0.1.17", + "unicode-width", + "winapi", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -1228,6 +1350,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "ctr" version = "0.8.0" @@ -1237,6 +1369,66 @@ dependencies = [ "cipher 0.3.0", ] +[[package]] +name = "cucumber" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b2b97b1b5763e57aeddbfc796f726daa8e561922e6438daffc5087b7bd03834" +dependencies = [ + "anyhow", + "async-trait", + "atty", + "clap 4.0.29", + "console", + "cucumber-codegen", + "cucumber-expressions", + "derive_more", + "drain_filter_polyfill", + "either", + "futures 0.3.25", + "gherkin", + "globwalk", + "humantime 2.1.0", + "inventory", + "itertools 0.10.5", + "linked-hash-map", + "once_cell", + "regex", + "sealed 0.4.0", + "serde", + "serde_json", +] + +[[package]] +name = "cucumber-codegen" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "755eae3e762505d670bbf02db048d512e79118f93f8383ae1bf6235506ed3c94" +dependencies = [ + "cucumber-expressions", + "inflections", + "itertools 0.10.5", + "proc-macro2", + "quote", + "regex", + "syn", + "synthez", +] + +[[package]] +name = "cucumber-expressions" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40d2fdf5e1bb4ae7e6b25c97bf9b9d249a02243fc0fbd91075592b5f00a3bc1" +dependencies = [ + "derive_more", + "either", + "nom 7.1.2", + "nom_locate", + "regex", + "regex-syntax", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -1622,6 +1814,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" +[[package]] +name = "drain_filter_polyfill" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca9f76bdd86dfc8d64eecb0484d02ad4cf0e6767d4682f686d1b0580b6c27f82" + [[package]] name = "ed25519" version = "1.5.2" @@ -1651,6 +1849,12 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.31" @@ -2015,6 +2219,34 @@ dependencies = [ "polyval", ] +[[package]] +name = "gherkin" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8f8f49b2b547ec22cc4d99f3bf30d4889ef0dbaa231c0736eeaf20efb5a38e" +dependencies = [ + "heck 0.4.0", + "peg", + "quote", + "serde", + "serde_json", + "syn", + "textwrap 0.16.0", + "thiserror", + "typed-builder", +] + +[[package]] +name = "ghost" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41973d4c45f7a35af8753ba3457cc99d406d863941fd7f52663cff54a5ab99b3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "git2" version = "0.8.0" @@ -2034,6 +2266,30 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "globset" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags 1.3.2", + "ignore", + "walkdir", +] + [[package]] name = "h2" version = "0.3.15" @@ -2179,6 +2435,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + [[package]] name = "httparse" version = "1.8.0" @@ -2317,6 +2579,24 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "ignore" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +dependencies = [ + "crossbeam-utils", + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + [[package]] name = "image" version = "0.23.14" @@ -2331,6 +2611,25 @@ dependencies = [ "num-traits", ] +[[package]] +name = "include_dir" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "indexmap" version = "1.9.2" @@ -2341,6 +2640,12 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "inflections" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" + [[package]] name = "inout" version = "0.1.3" @@ -2365,6 +2670,16 @@ version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" +[[package]] +name = "inventory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16fe3b35d64bd1f72917f06425e7573a2f63f74f42c8f56e53ea6826dde3a2b5" +dependencies = [ + "ctor", + "ghost", +] + [[package]] name = "io-lifetimes" version = "1.0.3" @@ -2381,6 +2696,18 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e" +[[package]] +name = "is-terminal" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +dependencies = [ + "hermit-abi 0.2.6", + "io-lifetimes", + "rustix", + "windows-sys 0.42.0", +] + [[package]] name = "itertools" version = "0.8.2" @@ -2678,6 +3005,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + [[package]] name = "md-5" version = "0.9.1" @@ -2833,7 +3166,7 @@ dependencies = [ "fixed-hash", "hex", "hex-literal", - "sealed", + "sealed 0.4.0", "serde", "serde-big-array", "thiserror", @@ -2971,6 +3304,17 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom_locate" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37794436ca3029a3089e0b95d42da1f0b565ad271e4d3bb4bad0c7bb70b10605" +dependencies = [ + "bytecount", + "memchr", + "nom 7.1.2", +] + [[package]] name = "ntapi" version = "0.3.7" @@ -3267,6 +3611,33 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +[[package]] +name = "peg" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f76678828272f177ac33b7e2ac2e3e73cc6c1cd1e3e387928aa69562fa51367" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "636d60acf97633e48d266d7415a9355d4389cea327a193f87df395d88cd2b14d" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555b1514d2d99d78150d3c799d4c357a3e2c2a8062cd108e93a06d9057629c5" + [[package]] name = "pem-rfc7468" version = "0.3.1" @@ -4115,6 +4486,12 @@ dependencies = [ "base64 0.13.1", ] +[[package]] +name = "rustversion" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" + [[package]] name = "rustyline" version = "9.1.2" @@ -4217,6 +4594,18 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sealed" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "636b9882a0f4cc2039488df89a10eb4b7976d4b6c1917fc0518f3f0f5e2c72ca" +dependencies = [ + "heck 0.3.3", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sealed" version = "0.4.0" @@ -4319,6 +4708,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b04f22b563c91331a10074bda3dd5492e3cc39d56bd557e91c0af42b6c7341" +dependencies = [ + "serde", +] + [[package]] name = "serde_repr" version = "0.1.10" @@ -4478,6 +4876,12 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +[[package]] +name = "smawk" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + [[package]] name = "snow" version = "0.9.0" @@ -4624,6 +5028,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" + [[package]] name = "synstructure" version = "0.12.6" @@ -4636,6 +5046,39 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "synthez" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "033178d0acccffc5490021657006e6a8dd586ee9dc6f7c24e7608b125e568cb1" +dependencies = [ + "syn", + "synthez-codegen", + "synthez-core", +] + +[[package]] +name = "synthez-codegen" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69263462a40e46960f070618e20094ce69e783a41f86e54bc75545136afd597a" +dependencies = [ + "syn", + "synthez-core", +] + +[[package]] +name = "synthez-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb8b5a4089fe1723279f06302afda64a5dacaa11a82bcbb4d08759590d4389d9" +dependencies = [ + "proc-macro2", + "quote", + "sealed 0.3.0", + "syn", +] + [[package]] name = "tari_app_grpc" version = "0.44.1" @@ -5093,6 +5536,71 @@ dependencies = [ "zeroize", ] +[[package]] +name = "tari_integration_tests" +version = "0.35.1" +dependencies = [ + "anyhow", + "async-trait", + "axum", + "axum-jrpc", + "bincode", + "blake2 0.9.2", + "borsh", + "bytes 1.3.0", + "chrono", + "clap 3.2.23", + "config", + "csv", + "cucumber", + "diesel", + "digest 0.9.0", + "futures 0.3.25", + "include_dir", + "indexmap", + "json5 0.2.8", + "libc", + "libsqlite3-sys", + "lmdb-zero", + "log", + "log4rs", + "prost", + "rand 0.7.3", + "reqwest", + "serde", + "serde_json", + "tari_app_grpc", + "tari_app_utilities", + "tari_base_node", + "tari_base_node_grpc_client", + "tari_common", + "tari_common_types", + "tari_comms", + "tari_comms_dht", + "tari_console_wallet", + "tari_core", + "tari_crypto", + "tari_merge_mining_proxy", + "tari_miner", + "tari_p2p", + "tari_script", + "tari_shutdown", + "tari_test_utils", + "tari_utilities", + "tari_wallet", + "tari_wallet_ffi", + "tari_wallet_grpc_client", + "tempfile", + "thiserror", + "time 0.3.17", + "tokio", + "tokio-stream", + "tonic", + "tower", + "tower-http", + "tower-layer", +] + [[package]] name = "tari_key_manager" version = "0.44.1" @@ -5518,6 +6026,26 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "terminal_size" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb20089a8ba2b69debd491f8d2d023761cbf196e999218c591fa1e7e15a21907" +dependencies = [ + "rustix", + "windows-sys 0.42.0", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -5532,6 +6060,11 @@ name = "textwrap" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] [[package]] name = "thiserror" @@ -5564,6 +6097,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + [[package]] name = "time" version = "0.1.45" @@ -5627,9 +6169,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.23.1" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38a54aca0c15d014013256222ba0ebed095673f89345dd79119d912eb561b7a8" +checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" dependencies = [ "autocfg", "bytes 1.3.0", @@ -5813,6 +6355,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +dependencies = [ + "bitflags 1.3.2", + "bytes 1.3.0", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.2" @@ -5951,6 +6512,17 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "typed-builder" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "typemap-ors" version = "1.0.0" @@ -6005,6 +6577,16 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +[[package]] +name = "unicode-linebreak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137" +dependencies = [ + "hashbrown 0.12.3", + "regex", +] + [[package]] name = "unicode-normalization" version = "0.1.22" diff --git a/Cargo.toml b/Cargo.toml index 9badd81c6c..399a5821c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ members = [ "applications/tari_app_utilities", "applications/tari_merge_mining_proxy", "applications/tari_miner", + "integration_tests" ] # Add here until we move to edition=2021 diff --git a/applications/tari_app_grpc/proto/wallet.proto b/applications/tari_app_grpc/proto/wallet.proto index f962aeb6c0..7da3b69062 100644 --- a/applications/tari_app_grpc/proto/wallet.proto +++ b/applications/tari_app_grpc/proto/wallet.proto @@ -39,6 +39,8 @@ service Wallet { rpc CheckForUpdates (Empty) returns (SoftwareUpdate); // This returns the identity information rpc Identify (GetIdentityRequest) returns (GetIdentityResponse); + // This returns the tari address + rpc GetAddress (Empty) returns (GetAddressResponse); // This returns a coinbase transaction rpc GetCoinbase (GetCoinbaseRequest) returns (GetCoinbaseResponse); // Send Tari to a number of recipients @@ -87,6 +89,10 @@ message GetVersionResponse { string version = 1; } +message GetAddressResponse { + bytes address = 1; +} + message TransferRequest { repeated PaymentRecipient recipients = 1; } diff --git a/applications/tari_base_node/src/commands/cli_loop.rs b/applications/tari_base_node/src/commands/cli_loop.rs index 58f89e0b7b..8225b730cb 100644 --- a/applications/tari_base_node/src/commands/cli_loop.rs +++ b/applications/tari_base_node/src/commands/cli_loop.rs @@ -177,7 +177,7 @@ impl CliLoop { if let Err(err) = self.context.handle_command_str(line).await { println!("Wrong command to watch `{}`. Failed with: {}", line, err); } else { - loop { + while !self.done { let interval = time::sleep(interval); tokio::select! { _ = interval => { @@ -188,6 +188,9 @@ impl CliLoop { }, _ = &mut interrupt => { break; + }, + _ = self.shutdown_signal.wait() => { + self.done = true; } } } diff --git a/applications/tari_base_node/src/grpc/base_node_grpc_server.rs b/applications/tari_base_node/src/grpc/base_node_grpc_server.rs index 0eda7719db..504631c75c 100644 --- a/applications/tari_base_node/src/grpc/base_node_grpc_server.rs +++ b/applications/tari_base_node/src/grpc/base_node_grpc_server.rs @@ -501,7 +501,7 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { .map_err(|e| obscure_error_if_true(report_error_flag, Status::internal(e)))?, ), - initial_sync_achieved: (*status_watch.borrow()).bootstrapped, + initial_sync_achieved: status_watch.borrow().bootstrapped, }; debug!(target: LOG_TARGET, "Sending GetNewBlockTemplate response to client"); @@ -912,10 +912,10 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { // Determine if we are bootstrapped let status_watch = self.state_machine_handle.get_status_info_watch(); - let state: tari_rpc::BaseNodeState = (&(*status_watch.borrow()).state_info).into(); + let state: tari_rpc::BaseNodeState = (&status_watch.borrow().state_info).into(); let response = tari_rpc::TipInfoResponse { metadata: Some(meta.into()), - initial_sync_achieved: (*status_watch.borrow()).bootstrapped, + initial_sync_achieved: status_watch.borrow().bootstrapped, base_node_state: state.into(), }; @@ -1440,8 +1440,8 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { })?; let response = tari_rpc::MempoolStatsResponse { - unconfirmed_txs: mempool_stats.unconfirmed_txs as u64, - reorg_txs: mempool_stats.reorg_txs as u64, + unconfirmed_txs: mempool_stats.unconfirmed_txs, + reorg_txs: mempool_stats.reorg_txs, unconfirmed_weight: mempool_stats.unconfirmed_weight, }; diff --git a/applications/tari_base_node/src/lib.rs b/applications/tari_base_node/src/lib.rs index 5c62b17cab..6ad1d8bf15 100644 --- a/applications/tari_base_node/src/lib.rs +++ b/applications/tari_base_node/src/lib.rs @@ -57,9 +57,11 @@ pub use crate::{ const LOG_TARGET: &str = "tari::base_node::app"; -pub async fn run_base_node(node_identity: Arc, config: Arc) -> Result<(), ExitError> { - let shutdown = Shutdown::new(); - +pub async fn run_base_node( + shutdown: Shutdown, + node_identity: Arc, + config: Arc, +) -> Result<(), ExitError> { let data_dir = config.base_node.data_dir.clone(); let data_dir_str = data_dir.clone().into_os_string().into_string().unwrap(); diff --git a/applications/tari_console_wallet/src/automation/commands.rs b/applications/tari_console_wallet/src/automation/commands.rs index e10468eaae..d3d446ff6b 100644 --- a/applications/tari_console_wallet/src/automation/commands.rs +++ b/applications/tari_console_wallet/src/automation/commands.rs @@ -266,7 +266,7 @@ pub async fn coin_split( transaction_service: &mut TransactionServiceHandle, ) -> Result { let (tx_id, tx, amount) = output_service - .create_coin_split(vec![], amount_per_split, num_splits as usize, fee_per_gram) + .create_coin_split(vec![], amount_per_split, num_splits, fee_per_gram) .await?; transaction_service .submit_transaction(tx_id, tx, amount, message) @@ -1038,14 +1038,15 @@ fn write_utxos_to_csv_file(utxos: Vec, file_path: PathBuf) -> R let mut csv_file = LineWriter::new(file); writeln!( csv_file, - r##""index","value","spending_key","commitment","flags","maturity","coinbase_extra","script","covenant","input_data","script_private_key","sender_offset_public_key","ephemeral_commitment","ephemeral_nonce","signature_u_x","signature_u_a","signature_u_y","script_lock_height","encrypted_value","minimum_value_promise""## + r##""index","version","value","spending_key","commitment","flags","maturity","coinbase_extra","script","covenant","input_data","script_private_key","sender_offset_public_key","ephemeral_commitment","ephemeral_nonce","signature_u_x","signature_u_a","signature_u_y","script_lock_height","encrypted_value","minimum_value_promise""## ) .map_err(|e| CommandError::CSVFile(e.to_string()))?; for (i, utxo) in utxos.iter().enumerate() { writeln!( csv_file, - r##""{}","{}","{}","{}","{:?}","{}","{}","{}","{}","{}","{},"{}","{}","{}","{}","{}","{}","{}","{}","{}""##, + r##""{}","V{}","{}","{}","{}","{:?}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}""##, i + 1, + utxo.version.as_u8(), utxo.value.0, utxo.spending_key.to_hex(), utxo.as_transaction_input(&factory)? diff --git a/applications/tari_console_wallet/src/automation/error.rs b/applications/tari_console_wallet/src/automation/error.rs index b2172d78cd..90f54a4d62 100644 --- a/applications/tari_console_wallet/src/automation/error.rs +++ b/applications/tari_console_wallet/src/automation/error.rs @@ -123,6 +123,6 @@ impl From for ExitError { fn from(err: ParseError) -> Self { error!(target: LOG_TARGET, "{}", err); let msg = format!("Failed to parse input file commands! {}", err); - Self::new(ExitCode::InputError, &msg) + Self::new(ExitCode::InputError, msg) } } diff --git a/applications/tari_console_wallet/src/config.rs b/applications/tari_console_wallet/src/config.rs index f1ac8d2a9d..e98dc3407e 100644 --- a/applications/tari_console_wallet/src/config.rs +++ b/applications/tari_console_wallet/src/config.rs @@ -28,7 +28,7 @@ use tari_common::{configuration::CommonConfig, ConfigurationError, DefaultConfig use tari_p2p::{auto_update::AutoUpdateConfig, PeerSeedsConfig}; use tari_wallet::WalletConfig; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ApplicationConfig { pub common: CommonConfig, pub auto_update: AutoUpdateConfig, diff --git a/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs b/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs index ce9427df1c..7eea2467b2 100644 --- a/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs +++ b/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs @@ -45,6 +45,7 @@ use tari_app_grpc::{ CreateBurnTransactionResponse, CreateTemplateRegistrationRequest, CreateTemplateRegistrationResponse, + GetAddressResponse, GetBalanceRequest, GetBalanceResponse, GetCoinbaseRequest, @@ -226,6 +227,15 @@ impl wallet_server::Wallet for WalletGrpcServer { })) } + async fn get_address(&self, _: Request) -> Result, Status> { + let network = self.wallet.network.as_network(); + let pk = self.wallet.comms.node_identity().public_key().clone(); + let address = TariAddress::new(pk, network); + Ok(Response::new(GetAddressResponse { + address: address.to_bytes().to_vec(), + })) + } + async fn set_base_node( &self, request: Request, diff --git a/applications/tari_console_wallet/src/init/mod.rs b/applications/tari_console_wallet/src/init/mod.rs index 90201e67b7..09b8b05179 100644 --- a/applications/tari_console_wallet/src/init/mod.rs +++ b/applications/tari_console_wallet/src/init/mod.rs @@ -197,7 +197,7 @@ pub async fn get_base_node_peer_config( let mut selected_base_node = match config.wallet.custom_base_node { Some(ref custom) => SeedPeer::from_str(custom) .map(|node| Some(Peer::from(node))) - .map_err(|err| ExitError::new(ExitCode::ConfigError, &format!("Malformed custom base node: {}", err)))?, + .map_err(|err| ExitError::new(ExitCode::ConfigError, format!("Malformed custom base node: {}", err)))?, None => get_custom_base_node_peer_from_db(wallet), }; @@ -291,7 +291,7 @@ pub async fn init_wallet( non_interactive_mode: bool, ) -> Result { fs::create_dir_all( - &config + config .wallet .db_file .parent() @@ -368,10 +368,7 @@ pub async fn init_wallet( .await .map_err(|e| match e { WalletError::CommsInitializationError(cie) => cie.to_exit_error(), - e => ExitError::new( - ExitCode::WalletError, - &format!("Error creating Wallet Container: {}", e), - ), + e => ExitError::new(ExitCode::WalletError, format!("Error creating Wallet Container: {}", e)), })?; if let Some(hs) = wallet.comms.hidden_service() { wallet @@ -385,7 +382,7 @@ pub async fn init_wallet( let _result = fs::write(file_name, seed_words.reveal()).map_err(|e| { ExitError::new( ExitCode::WalletError, - &format!("Problem writing seed words to file: {}", e), + format!("Problem writing seed words to file: {}", e), ) }); }; @@ -482,7 +479,7 @@ pub async fn start_wallet( .map_err(|e| { ExitError::new( ExitCode::WalletError, - &format!("Error setting wallet base node peer. {}", e), + format!("Error setting wallet base node peer. {}", e), ) })?; diff --git a/applications/tari_console_wallet/src/lib.rs b/applications/tari_console_wallet/src/lib.rs index 74225744e6..a09fac944b 100644 --- a/applications/tari_console_wallet/src/lib.rs +++ b/applications/tari_console_wallet/src/lib.rs @@ -31,7 +31,18 @@ mod ui; mod utils; mod wallet_modes; -pub use cli::Cli; +pub use cli::{ + BurnTariArgs, + Cli, + CliCommands, + CoinSplitArgs, + DiscoverPeerArgs, + ExportUtxosArgs, + MakeItRainArgs, + SendTariArgs, + SetBaseNodeArgs, + WhoisArgs, +}; use init::{change_password, get_base_node_peer_config, init_wallet, start_wallet, tari_splash_screen, WalletBoot}; use log::*; use recovery::{get_seed_from_seed_words, prompt_private_key_from_seed_words}; @@ -53,7 +64,7 @@ use crate::init::{boot_with_password, confirm_seed_words, wallet_mode}; pub const LOG_TARGET: &str = "wallet::console_wallet::main"; -pub fn run_wallet(runtime: Runtime, config: &mut ApplicationConfig) -> Result<(), ExitError> { +pub fn run_wallet(shutdown: &mut Shutdown, runtime: Runtime, config: &mut ApplicationConfig) -> Result<(), ExitError> { let data_dir = config.wallet.data_dir.clone(); let data_dir_str = data_dir.clone().into_os_string().into_string().unwrap(); @@ -84,10 +95,15 @@ pub fn run_wallet(runtime: Runtime, config: &mut ApplicationConfig) -> Result<() command2: None, }; - run_wallet_with_cli(runtime, config, cli) + run_wallet_with_cli(shutdown, runtime, config, cli) } -pub fn run_wallet_with_cli(runtime: Runtime, config: &mut ApplicationConfig, cli: Cli) -> Result<(), ExitError> { +pub fn run_wallet_with_cli( + shutdown: &mut Shutdown, + runtime: Runtime, + config: &mut ApplicationConfig, + cli: Cli, +) -> Result<(), ExitError> { info!( target: LOG_TARGET, "== {} ({}) ==", @@ -109,7 +125,6 @@ pub fn run_wallet_with_cli(runtime: Runtime, config: &mut ApplicationConfig, cli // get command line password if provided let seed_words_file_name = cli.seed_words_file_name.clone(); - let mut shutdown = Shutdown::new(); let shutdown_signal = shutdown.to_signal(); if cli.change_password { diff --git a/applications/tari_console_wallet/src/main.rs b/applications/tari_console_wallet/src/main.rs index d00e8d11d2..8903809c2b 100644 --- a/applications/tari_console_wallet/src/main.rs +++ b/applications/tari_console_wallet/src/main.rs @@ -31,6 +31,7 @@ use tari_common::{ load_configuration, }; use tari_console_wallet::{run_wallet_with_cli, ApplicationConfig, Cli}; +use tari_shutdown::Shutdown; pub const LOG_TARGET: &str = "wallet::console_wallet::main"; @@ -87,7 +88,8 @@ fn main_inner() -> Result<(), ExitError> { .build() .expect("Failed to build a runtime!"); - run_wallet_with_cli(runtime, &mut config, cli) + let mut shutdown = Shutdown::new(); + run_wallet_with_cli(&mut shutdown, runtime, &mut config, cli) } fn setup_grpc_config(config: &mut ApplicationConfig) { diff --git a/applications/tari_console_wallet/src/ui/state/app_state.rs b/applications/tari_console_wallet/src/ui/state/app_state.rs index b591635890..dbb7bb271d 100644 --- a/applications/tari_console_wallet/src/ui/state/app_state.rs +++ b/applications/tari_console_wallet/src/ui/state/app_state.rs @@ -441,7 +441,7 @@ impl AppState { } pub fn get_confirmations(&self, tx_id: TxId) -> Option<&u64> { - (&self.cached_data.confirmations).get(&tx_id) + self.cached_data.confirmations.get(&tx_id) } pub fn get_completed_tx(&self, index: usize) -> Option<&CompletedTransactionInfo> { diff --git a/applications/tari_console_wallet/src/ui/state/wallet_event_monitor.rs b/applications/tari_console_wallet/src/ui/state/wallet_event_monitor.rs index afc63e751d..16cf266df1 100644 --- a/applications/tari_console_wallet/src/ui/state/wallet_event_monitor.rs +++ b/applications/tari_console_wallet/src/ui/state/wallet_event_monitor.rs @@ -97,7 +97,7 @@ impl WalletEventMonitor { ); self.app_state_inner.write().await.add_event(EventListItem{ event_type: "TransactionEvent".to_string(), - desc: (&*msg).to_string() + desc: (*msg).to_string() }); match (*msg).clone() { TransactionEvent::ReceivedFinalizedTransaction(tx_id) => { diff --git a/applications/tari_console_wallet/src/ui/widgets/utilities.rs b/applications/tari_console_wallet/src/ui/widgets/utilities.rs index 4d1a3e0914..b15e463d12 100644 --- a/applications/tari_console_wallet/src/ui/widgets/utilities.rs +++ b/applications/tari_console_wallet/src/ui/widgets/utilities.rs @@ -90,7 +90,7 @@ pub fn draw_dialog( } let center_area = centered_rect_absolute( width.min(full_area.width).saturating_sub(2), - ((spans.len()) as u16).max(2).min(full_area.height), + ((spans.len()) as u16).clamp(2, full_area.height), full_area, ); diff --git a/applications/tari_console_wallet/src/wallet_modes.rs b/applications/tari_console_wallet/src/wallet_modes.rs index ac0efa707f..c6f0dbf2e3 100644 --- a/applications/tari_console_wallet/src/wallet_modes.rs +++ b/applications/tari_console_wallet/src/wallet_modes.rs @@ -110,7 +110,7 @@ impl PeerConfig { } else { Err(ExitError::new( ExitCode::ConfigError, - &"No peer seeds or base node peer defined in config!", + "No peer seeds or base node peer defined in config!", )) } } @@ -171,7 +171,7 @@ pub(crate) fn parse_command_file(script: String) -> Result, Exi if !command.trim().is_empty() && !command.trim().starts_with('#') { let command_trimmed = cli_parse_prefix.to_owned() + " " + command.trim(); let parse_vec: Vec<&str> = command_trimmed.split(' ').collect(); - let cli_parsed = Cli::try_parse_from(&parse_vec); + let cli_parsed = Cli::try_parse_from(parse_vec); match cli_parsed { Ok(result) => { if let Some(sub_command) = result.command2 { @@ -271,7 +271,12 @@ pub fn tui_mode( if config.grpc_enabled { if let Some(address) = config.grpc_address.clone() { let grpc = WalletGrpcServer::new(wallet.clone()); - handle.spawn(run_grpc(grpc, address, config.grpc_authentication.clone())); + handle.spawn(run_grpc( + grpc, + address, + config.grpc_authentication.clone(), + wallet.clone(), + )); } } @@ -361,7 +366,7 @@ pub fn recovery_mode( WalletMode::RecoveryTui => tui_mode(handle, wallet_config, base_node_config, wallet), _ => Err(ExitError::new( ExitCode::RecoveryError, - &"Unsupported post recovery mode", + "Unsupported post recovery mode", )), } } @@ -369,10 +374,10 @@ pub fn recovery_mode( pub fn grpc_mode(handle: Handle, config: &WalletConfig, wallet: WalletSqlite) -> Result<(), ExitError> { info!(target: LOG_TARGET, "Starting grpc server"); if let Some(address) = config.grpc_address.as_ref().filter(|_| config.grpc_enabled).cloned() { - let grpc = WalletGrpcServer::new(wallet); + let grpc = WalletGrpcServer::new(wallet.clone()); let auth = config.grpc_authentication.clone(); handle - .block_on(run_grpc(grpc, address, auth)) + .block_on(run_grpc(grpc, address, auth, wallet)) .map_err(|e| ExitError::new(ExitCode::GrpcError, e))?; } else { println!("GRPC server is disabled"); @@ -385,6 +390,7 @@ async fn run_grpc( grpc: WalletGrpcServer, grpc_listener_addr: Multiaddr, auth_config: GrpcAuthentication, + wallet: WalletSqlite, ) -> Result<(), String> { // Do not remove this println! const CUCUMBER_TEST_MARKER_A: &str = "Tari Console Wallet running... (gRPC mode started)"; @@ -397,7 +403,7 @@ async fn run_grpc( Server::builder() .add_service(service) - .serve(address) + .serve_with_shutdown(address, wallet.wait_until_shutdown()) .await .map_err(|e| format!("GRPC server returned error:{}", e))?; diff --git a/applications/tari_merge_mining_proxy/src/cli.rs b/applications/tari_merge_mining_proxy/src/cli.rs index 83f9646cb1..fbf7650f33 100644 --- a/applications/tari_merge_mining_proxy/src/cli.rs +++ b/applications/tari_merge_mining_proxy/src/cli.rs @@ -27,7 +27,7 @@ use tari_common::configuration::{ConfigOverrideProvider, Network}; #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] #[clap(propagate_version = true)] -pub(crate) struct Cli { +pub struct Cli { #[clap(flatten)] pub common: CommonCliArgs, /// Supply a network (overrides existing configuration) diff --git a/applications/tari_merge_mining_proxy/src/lib.rs b/applications/tari_merge_mining_proxy/src/lib.rs new file mode 100644 index 0000000000..8c5291ffdd --- /dev/null +++ b/applications/tari_merge_mining_proxy/src/lib.rs @@ -0,0 +1,36 @@ +// Copyright 2020. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod block_template_data; +mod block_template_protocol; +mod cli; +pub use cli::Cli; +mod common; +mod config; +mod error; +mod proxy; +mod run_merge_miner; +use run_merge_miner::start_merge_miner; + +pub async fn merge_miner(cli: Cli) -> Result<(), anyhow::Error> { + start_merge_miner(cli).await +} diff --git a/applications/tari_merge_mining_proxy/src/main.rs b/applications/tari_merge_mining_proxy/src/main.rs index 2ff1a9e209..4e9513be48 100644 --- a/applications/tari_merge_mining_proxy/src/main.rs +++ b/applications/tari_merge_mining_proxy/src/main.rs @@ -20,6 +20,8 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use tari_merge_mining_proxy::Cli; + mod block_template_data; mod block_template_protocol; mod cli; @@ -27,33 +29,17 @@ mod common; mod config; mod error; mod proxy; +mod run_merge_miner; #[cfg(test)] mod test; -use std::{convert::Infallible, io::stdout}; +use std::io::stdout; use clap::Parser; use crossterm::{execute, terminal::SetTitle}; -use futures::future; -use hyper::{service::make_service_fn, Server}; -use log::*; -use proxy::MergeMiningProxyService; use tari_app_utilities::consts; -use tari_base_node_grpc_client::BaseNodeGrpcClient; -use tari_common::{initialize_logging, load_configuration, DefaultConfigLoader}; -use tari_comms::utils::multiaddr::multiaddr_to_socketaddr; -use tari_core::proof_of_work::randomx_factory::RandomXFactory; -use tari_wallet_grpc_client::WalletGrpcClient; -use tokio::time::Duration; - -use crate::{ - block_template_data::BlockTemplateRepository, - cli::Cli, - config::MergeMiningProxyConfig, - error::MmProxyError, -}; -const LOG_TARGET: &str = "tari_mm_proxy::proxy"; +use tari_common::initialize_logging; #[tokio::main] async fn main() -> Result<(), anyhow::Error> { @@ -64,60 +50,9 @@ async fn main() -> Result<(), anyhow::Error> { let cli = Cli::parse(); - let config_path = cli.common.config_path(); - let cfg = load_configuration(&config_path, true, &cli)?; initialize_logging( &cli.common.log_config_path("proxy"), include_str!("../log4rs_sample.yml"), )?; - - let config = MergeMiningProxyConfig::load_from(&cfg)?; - - info!(target: LOG_TARGET, "Configuration: {:?}", config); - let client = reqwest::Client::builder() - .connect_timeout(Duration::from_secs(5)) - .timeout(Duration::from_secs(10)) - .pool_max_idle_per_host(25) - .build() - .map_err(MmProxyError::ReqwestError)?; - - let base_node = multiaddr_to_socketaddr(&config.base_node_grpc_address)?; - info!(target: LOG_TARGET, "Connecting to base node at {}", base_node); - println!("Connecting to base node at {}", base_node); - let base_node_client = BaseNodeGrpcClient::connect(format!("http://{}", base_node)).await?; - let wallet_addr = multiaddr_to_socketaddr(&config.console_wallet_grpc_address)?; - info!(target: LOG_TARGET, "Connecting to wallet at {}", wallet_addr); - let wallet_addr = format!("http://{}", wallet_addr); - let wallet_client = - WalletGrpcClient::connect_with_auth(&wallet_addr, &config.console_wallet_grpc_authentication).await?; - let listen_addr = multiaddr_to_socketaddr(&config.listener_address)?; - let randomx_factory = RandomXFactory::new(config.max_randomx_vms); - let xmrig_service = MergeMiningProxyService::new( - config, - client, - base_node_client, - wallet_client, - BlockTemplateRepository::new(), - randomx_factory, - ); - let service = make_service_fn(|_conn| future::ready(Result::<_, Infallible>::Ok(xmrig_service.clone()))); - - match Server::try_bind(&listen_addr) { - Ok(builder) => { - info!(target: LOG_TARGET, "Listening on {}...", listen_addr); - println!("Listening on {}...", listen_addr); - builder.serve(service).await?; - Ok(()) - }, - Err(err) => { - error!(target: LOG_TARGET, "Fatal: Cannot bind to '{}'.", listen_addr); - println!("Fatal: Cannot bind to '{}'.", listen_addr); - println!("It may be part of a Port Exclusion Range. Please try to use another port for the"); - println!("'proxy_host_address' in 'config/config.toml' and for the applicable XMRig '[pools][url]' or"); - println!("[pools][self-select]' config setting that can be found in 'config/xmrig_config_***.json' or"); - println!("'/config.json'."); - println!(); - Err(err.into()) - }, - } + run_merge_miner::start_merge_miner(cli).await } diff --git a/applications/tari_merge_mining_proxy/src/proxy.rs b/applications/tari_merge_mining_proxy/src/proxy.rs index efaabc1a36..2844597f2c 100644 --- a/applications/tari_merge_mining_proxy/src/proxy.rs +++ b/applications/tari_merge_mining_proxy/src/proxy.rs @@ -250,7 +250,7 @@ impl InnerService { debug!( target: LOG_TARGET, "Tari Hash found in Monero block: {}", - hex::encode(&hash) + hex::encode(hash) ); let mut block_data = match self.block_templates.get(&hash).await { @@ -259,7 +259,7 @@ impl InnerService { info!( target: LOG_TARGET, "Block `{}` submitted but no matching block template was found, possible duplicate submission", - hex::encode(&hash) + hex::encode(hash) ); continue; }, diff --git a/applications/tari_merge_mining_proxy/src/run_merge_miner.rs b/applications/tari_merge_mining_proxy/src/run_merge_miner.rs new file mode 100644 index 0000000000..154ebf3c97 --- /dev/null +++ b/applications/tari_merge_mining_proxy/src/run_merge_miner.rs @@ -0,0 +1,96 @@ +// Copyright 2020. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::convert::Infallible; + +use futures::future; +use hyper::{service::make_service_fn, Server}; +use log::*; +use tari_base_node_grpc_client::BaseNodeGrpcClient; +use tari_common::{load_configuration, DefaultConfigLoader}; +use tari_comms::utils::multiaddr::multiaddr_to_socketaddr; +use tari_core::proof_of_work::randomx_factory::RandomXFactory; +use tari_wallet_grpc_client::WalletGrpcClient; +use tokio::time::Duration; + +use crate::{ + block_template_data::BlockTemplateRepository, + config::MergeMiningProxyConfig, + error::MmProxyError, + proxy::MergeMiningProxyService, + Cli, +}; +const LOG_TARGET: &str = "tari_mm_proxy::proxy"; + +pub async fn start_merge_miner(cli: Cli) -> Result<(), anyhow::Error> { + let config_path = cli.common.config_path(); + let cfg = load_configuration(&config_path, true, &cli)?; + let config = MergeMiningProxyConfig::load_from(&cfg)?; + + info!(target: LOG_TARGET, "Configuration: {:?}", config); + let client = reqwest::Client::builder() + .connect_timeout(Duration::from_secs(5)) + .timeout(Duration::from_secs(10)) + .pool_max_idle_per_host(25) + .build() + .map_err(MmProxyError::ReqwestError)?; + + let base_node = multiaddr_to_socketaddr(&config.base_node_grpc_address)?; + info!(target: LOG_TARGET, "Connecting to base node at {}", base_node); + println!("Connecting to base node at {}", base_node); + let base_node_client = BaseNodeGrpcClient::connect(format!("http://{}", base_node)).await?; + let wallet_addr = multiaddr_to_socketaddr(&config.console_wallet_grpc_address)?; + info!(target: LOG_TARGET, "Connecting to wallet at {}", wallet_addr); + let wallet_addr = format!("http://{}", wallet_addr); + let wallet_client = + WalletGrpcClient::connect_with_auth(&wallet_addr, &config.console_wallet_grpc_authentication).await?; + let listen_addr = multiaddr_to_socketaddr(&config.listener_address)?; + let randomx_factory = RandomXFactory::new(config.max_randomx_vms); + let xmrig_service = MergeMiningProxyService::new( + config, + client, + base_node_client, + wallet_client, + BlockTemplateRepository::new(), + randomx_factory, + ); + let service = make_service_fn(|_conn| future::ready(Result::<_, Infallible>::Ok(xmrig_service.clone()))); + + match Server::try_bind(&listen_addr) { + Ok(builder) => { + info!(target: LOG_TARGET, "Listening on {}...", listen_addr); + println!("Listening on {}...", listen_addr); + builder.serve(service).await?; + Ok(()) + }, + Err(err) => { + error!(target: LOG_TARGET, "Fatal: Cannot bind to '{}'.", listen_addr); + println!("Fatal: Cannot bind to '{}'.", listen_addr); + println!("It may be part of a Port Exclusion Range. Please try to use another port for the"); + println!("'proxy_host_address' in 'config/config.toml' and for the applicable XMRig '[pools][url]' or"); + println!("[pools][self-select]' config setting that can be found in 'config/xmrig_config_***.json' or"); + println!("'/config.json'."); + println!(); + Err(err.into()) + }, + } +} diff --git a/applications/tari_miner/src/cli.rs b/applications/tari_miner/src/cli.rs index d6462160bc..fe17d88595 100644 --- a/applications/tari_miner/src/cli.rs +++ b/applications/tari_miner/src/cli.rs @@ -25,7 +25,7 @@ use tari_app_utilities::common_cli_args::CommonCliArgs; #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] #[clap(propagate_version = true)] -pub(crate) struct Cli { +pub struct Cli { #[clap(flatten)] pub common: CommonCliArgs, #[clap(long, alias = "mine-until-height")] diff --git a/applications/tari_miner/src/config.rs b/applications/tari_miner/src/config.rs index 392e4dad6f..fe56c31729 100644 --- a/applications/tari_miner/src/config.rs +++ b/applications/tari_miner/src/config.rs @@ -129,7 +129,7 @@ impl MinerConfig { mod test { use tari_common::DefaultConfigLoader; - use crate::MinerConfig; + use crate::config::MinerConfig; #[test] fn miner_configuration() { diff --git a/applications/tari_miner/src/lib.rs b/applications/tari_miner/src/lib.rs new file mode 100644 index 0000000000..7e6aee674b --- /dev/null +++ b/applications/tari_miner/src/lib.rs @@ -0,0 +1,37 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod cli; +pub use cli::Cli; +use tari_common::exit_codes::ExitError; +mod run_miner; +use run_miner::start_miner; +mod config; +mod difficulty; +mod errors; +mod miner; +mod stratum; +mod utils; + +pub async fn run_miner(cli: Cli) -> Result<(), ExitError> { + start_miner(cli).await +} diff --git a/applications/tari_miner/src/main.rs b/applications/tari_miner/src/main.rs index f4c4694ec1..6ff6709961 100644 --- a/applications/tari_miner/src/main.rs +++ b/applications/tari_miner/src/main.rs @@ -20,37 +20,17 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::{convert::TryFrom, io::stdout, str::FromStr, thread, time::Instant}; +use std::io::stdout; use clap::Parser; use crossterm::{execute, terminal::SetTitle}; -use errors::{err_empty, MinerError}; -use futures::stream::StreamExt; use log::*; -use miner::Miner; -use tari_app_grpc::{ - authentication::ClientAuthenticationInterceptor, - tari_rpc::{base_node_client::BaseNodeClient, wallet_client::WalletClient}, -}; +use run_miner::start_miner; use tari_app_utilities::consts; -use tari_common::{ - exit_codes::{ExitCode, ExitError}, - initialize_logging, - load_configuration, - DefaultConfigLoader, -}; -use tari_comms::utils::multiaddr::multiaddr_to_socketaddr; -use tari_core::blocks::BlockHeader; -use tari_crypto::ristretto::RistrettoPublicKey; -use tari_utilities::hex::Hex; -use tokio::{runtime::Runtime, time::sleep}; -use tonic::{ - codegen::InterceptedService, - transport::{Channel, Endpoint}, -}; -use utils::{coinbase_request, extract_outputs_and_kernels}; +use tari_common::{exit_codes::ExitError, initialize_logging}; +use tokio::runtime::Runtime; -use crate::{cli::Cli, config::MinerConfig, miner::MiningReport, stratum::stratum_controller::controller::Controller}; +use crate::cli::Cli; pub const LOG_TARGET: &str = "tari_miner::miner::main"; pub const LOG_TARGET_FILE: &str = "tari_miner::logging::miner::main"; @@ -60,11 +40,10 @@ mod config; mod difficulty; mod errors; mod miner; +mod run_miner; mod stratum; mod utils; -type WalletGrpcClient = WalletClient>; - /// Application entry point fn main() { let rt = Runtime::new().expect("Failed to start tokio runtime"); @@ -86,281 +65,9 @@ fn main() { #[allow(clippy::too_many_lines)] async fn main_inner() -> Result<(), ExitError> { let cli = Cli::parse(); - - let config_path = cli.common.config_path(); - let cfg = load_configuration(config_path.as_path(), true, &cli.common)?; initialize_logging( &cli.common.log_config_path("miner"), include_str!("../log4rs_sample.yml"), )?; - let config = MinerConfig::load_from(&cfg).expect("Failed to load config"); - debug!(target: LOG_TARGET_FILE, "{:?}", config); - - if !config.mining_wallet_address.is_empty() && !config.mining_pool_address.is_empty() { - let url = config.mining_pool_address.clone(); - let mut miner_address = config.mining_wallet_address.clone(); - let _ = RistrettoPublicKey::from_hex(&miner_address).map_err(|_| { - ExitError::new( - ExitCode::ConfigError, - "Miner is not configured with a valid wallet address.", - ) - })?; - if !config.mining_worker_name.is_empty() { - miner_address += &format!("{}{}", ".", config.mining_worker_name); - } - let mut mc = Controller::new(config.num_mining_threads).unwrap_or_else(|e| { - debug!(target: LOG_TARGET_FILE, "Error loading mining controller: {}", e); - panic!("Error loading mining controller: {}", e); - }); - let cc = stratum::controller::Controller::new(&url, Some(miner_address), None, None, mc.tx.clone()) - .unwrap_or_else(|e| { - debug!( - target: LOG_TARGET_FILE, - "Error loading stratum client controller: {:?}", e - ); - panic!("Error loading stratum client controller: {:?}", e); - }); - mc.set_client_tx(cc.tx.clone()); - - let _join_handle = thread::Builder::new() - .name("client_controller".to_string()) - .spawn(move || { - cc.run(); - }); - - mc.run() - .await - .map_err(|err| ExitError::new(ExitCode::UnknownError, format!("Stratum error: {:?}", err)))?; - - Ok(()) - } else { - let (mut node_conn, mut wallet_conn) = connect(&config).await.map_err(|e| { - ExitError::new( - ExitCode::GrpcError, - format!("Could not connect to wallet or base node: {}", e), - ) - })?; - - let mut blocks_found: u64 = 0; - loop { - debug!(target: LOG_TARGET, "Starting new mining cycle"); - match mining_cycle(&mut node_conn, &mut wallet_conn, &config, &cli).await { - err @ Err(MinerError::GrpcConnection(_)) | err @ Err(MinerError::GrpcStatus(_)) => { - // Any GRPC error we will try to reconnect with a standard delay - error!(target: LOG_TARGET, "Connection error: {:?}", err); - loop { - info!(target: LOG_TARGET, "Holding for {:?}", config.wait_timeout()); - sleep(config.wait_timeout()).await; - match connect(&config).await { - Ok((nc, wc)) => { - node_conn = nc; - wallet_conn = wc; - break; - }, - Err(err) => { - error!(target: LOG_TARGET, "Connection error: {:?}", err); - continue; - }, - } - } - }, - Err(MinerError::MineUntilHeightReached(h)) => { - warn!( - target: LOG_TARGET, - "Prescribed blockchain height {} reached. Aborting ...", h - ); - return Ok(()); - }, - Err(MinerError::MinerLostBlock(h)) => { - warn!( - target: LOG_TARGET, - "Height {} already mined by other node. Restarting ...", h - ); - }, - Err(err) => { - error!(target: LOG_TARGET, "Error: {:?}", err); - sleep(config.wait_timeout()).await; - }, - Ok(submitted) => { - info!(target: LOG_TARGET, "💰 Found block"); - if submitted { - blocks_found += 1; - } - if let Some(max_blocks) = cli.miner_max_blocks { - if blocks_found >= max_blocks { - return Ok(()); - } - } - }, - } - } - } -} - -async fn connect(config: &MinerConfig) -> Result<(BaseNodeClient, WalletGrpcClient), MinerError> { - let base_node_addr = multiaddr_to_socketaddr(&config.base_node_grpc_address)?; - info!(target: LOG_TARGET, "🔗 Connecting to base node at {}", base_node_addr); - let node_conn = BaseNodeClient::connect(format!("http://{}", base_node_addr)).await?; - let wallet_conn = connect_wallet(config).await?; - - Ok((node_conn, wallet_conn)) -} - -async fn connect_wallet(config: &MinerConfig) -> Result { - let wallet_addr = format!("http://{}", multiaddr_to_socketaddr(&config.wallet_grpc_address)?); - info!(target: LOG_TARGET, "👛 Connecting to wallet at {}", wallet_addr); - let channel = Endpoint::from_str(&wallet_addr)?.connect().await?; - let wallet_conn = WalletClient::with_interceptor( - channel, - ClientAuthenticationInterceptor::create(&config.wallet_grpc_authentication)?, - ); - - Ok(wallet_conn) -} - -async fn mining_cycle( - node_conn: &mut BaseNodeClient, - wallet_conn: &mut WalletGrpcClient, - config: &MinerConfig, - cli: &Cli, -) -> Result { - debug!(target: LOG_TARGET, "Getting new block template"); - let template = node_conn - .get_new_block_template(config.pow_algo_request()) - .await? - .into_inner(); - let mut block_template = template - .new_block_template - .clone() - .ok_or_else(|| err_empty("new_block_template"))?; - - if config.mine_on_tip_only { - debug!( - target: LOG_TARGET, - "Checking if base node is synced, because mine_on_tip_only is true" - ); - let height = block_template - .header - .as_ref() - .ok_or_else(|| err_empty("header"))? - .height; - validate_tip(node_conn, height, cli.mine_until_height).await?; - } - - debug!(target: LOG_TARGET, "Getting coinbase"); - let request = coinbase_request(&template, config.coinbase_extra.as_bytes().to_vec())?; - let coinbase = wallet_conn.get_coinbase(request).await?.into_inner(); - let (output, kernel) = extract_outputs_and_kernels(coinbase)?; - let body = block_template - .body - .as_mut() - .ok_or_else(|| err_empty("new_block_template.body"))?; - body.outputs.push(output); - body.kernels.push(kernel); - let target_difficulty = template - .miner_data - .ok_or_else(|| err_empty("miner_data"))? - .target_difficulty; - - debug!(target: LOG_TARGET, "Asking base node to assemble the MMR roots"); - let block_result = node_conn.get_new_block(block_template).await?.into_inner(); - let block = block_result.block.ok_or_else(|| err_empty("block"))?; - let header = block.clone().header.ok_or_else(|| err_empty("block.header"))?; - - debug!(target: LOG_TARGET, "Initializing miner"); - let mut reports = Miner::init_mining(header.clone(), target_difficulty, config.num_mining_threads, false); - let mut reporting_timeout = Instant::now(); - let mut block_submitted = false; - while let Some(report) = reports.next().await { - if let Some(header) = report.header.clone() { - let mut submit = true; - if let Some(min_diff) = cli.miner_min_diff { - if report.difficulty < min_diff { - submit = false; - debug!( - target: LOG_TARGET_FILE, - "Mined difficulty {} below minimum difficulty {}. Not submitting.", report.difficulty, min_diff - ); - } - } - if let Some(max_diff) = cli.miner_max_diff { - if report.difficulty > max_diff { - submit = false; - debug!( - target: LOG_TARGET_FILE, - "Mined difficulty {} greater than maximum difficulty {}. Not submitting.", - report.difficulty, - max_diff - ); - } - } - if submit { - // Mined a block fitting the difficulty - let block_header = BlockHeader::try_from(header.clone()).map_err(MinerError::Conversion)?; - debug!( - target: LOG_TARGET, - "Miner found block header {} with difficulty {:?}", block_header, report.difficulty, - ); - let mut mined_block = block.clone(); - mined_block.header = Some(header); - // 5. Sending block to the node - node_conn.submit_block(mined_block).await?; - block_submitted = true; - break; - } else { - display_report(&report, config.num_mining_threads).await; - } - } else { - display_report(&report, config.num_mining_threads).await; - } - if config.mine_on_tip_only && reporting_timeout.elapsed() > config.validate_tip_interval() { - validate_tip(node_conn, report.height, cli.mine_until_height).await?; - reporting_timeout = Instant::now(); - } - } - - // Not waiting for threads to stop, they should stop in a short while after `reports` dropped - Ok(block_submitted) -} - -async fn display_report(report: &MiningReport, num_mining_threads: usize) { - let hashrate = report.hashes as f64 / report.elapsed.as_micros() as f64; - info!( - target: LOG_TARGET, - "⛏ Miner {:0>2} reported {:.2}MH/s with total {:.2}MH/s over {} threads. Height: {}. Target: {})", - report.miner, - hashrate, - hashrate * num_mining_threads as f64, - num_mining_threads, - report.height, - report.target_difficulty, - ); -} - -/// If config -async fn validate_tip( - node_conn: &mut BaseNodeClient, - height: u64, - mine_until_height: Option, -) -> Result<(), MinerError> { - let tip = node_conn - .get_tip_info(tari_app_grpc::tari_rpc::Empty {}) - .await? - .into_inner(); - let longest_height = tip.clone().metadata.unwrap().height_of_longest_chain; - if let Some(height) = mine_until_height { - if longest_height >= height { - return Err(MinerError::MineUntilHeightReached(height)); - } - } - if height <= longest_height { - return Err(MinerError::MinerLostBlock(height)); - } - if !tip.initial_sync_achieved || tip.metadata.is_none() { - return Err(MinerError::NodeNotReady); - } - if height <= longest_height { - return Err(MinerError::MinerLostBlock(height)); - } - Ok(()) + start_miner(cli).await } diff --git a/applications/tari_miner/src/miner.rs b/applications/tari_miner/src/miner.rs index d744256491..aadfc91700 100644 --- a/applications/tari_miner/src/miner.rs +++ b/applications/tari_miner/src/miner.rs @@ -42,7 +42,7 @@ pub const LOG_TARGET: &str = "tari_miner::miner::standalone"; const REPORTING_FREQUENCY: u64 = 3_000_000; // Thread's stack size, ideally we would fit all thread's data in the CPU L1 cache -const STACK_SIZE: usize = 32_000; +const STACK_SIZE: usize = 320_000; /// Miner will send regular reports from every mining threads #[derive(Debug)] diff --git a/applications/tari_miner/src/run_miner.rs b/applications/tari_miner/src/run_miner.rs new file mode 100644 index 0000000000..fc293cfb85 --- /dev/null +++ b/applications/tari_miner/src/run_miner.rs @@ -0,0 +1,332 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{convert::TryFrom, str::FromStr, thread, time::Instant}; + +use futures::stream::StreamExt; +use log::*; +use tari_app_grpc::{ + authentication::ClientAuthenticationInterceptor, + tari_rpc::{base_node_client::BaseNodeClient, wallet_client::WalletClient}, +}; +use tari_common::{ + exit_codes::{ExitCode, ExitError}, + load_configuration, + DefaultConfigLoader, +}; +use tari_comms::utils::multiaddr::multiaddr_to_socketaddr; +use tari_core::blocks::BlockHeader; +use tari_crypto::ristretto::RistrettoPublicKey; +use tari_utilities::hex::Hex; +use tokio::time::sleep; +use tonic::{ + codegen::InterceptedService, + transport::{Channel, Endpoint}, +}; + +use crate::{ + cli::Cli, + config::MinerConfig, + errors::{err_empty, MinerError}, + miner::{Miner, MiningReport}, + stratum::stratum_controller::controller::Controller, + utils::{coinbase_request, extract_outputs_and_kernels}, +}; + +pub const LOG_TARGET: &str = "tari_miner::miner::main"; +pub const LOG_TARGET_FILE: &str = "tari_miner::logging::miner::main"; + +type WalletGrpcClient = WalletClient>; + +pub async fn start_miner(cli: Cli) -> Result<(), ExitError> { + let config_path = cli.common.config_path(); + let cfg = load_configuration(config_path.as_path(), true, &cli.common)?; + let config = MinerConfig::load_from(&cfg).expect("Failed to load config"); + debug!(target: LOG_TARGET_FILE, "{:?}", config); + if !config.mining_wallet_address.is_empty() && !config.mining_pool_address.is_empty() { + let url = config.mining_pool_address.clone(); + let mut miner_address = config.mining_wallet_address.clone(); + let _ = RistrettoPublicKey::from_hex(&miner_address).map_err(|_| { + ExitError::new( + ExitCode::ConfigError, + "Miner is not configured with a valid wallet address.", + ) + })?; + if !config.mining_worker_name.is_empty() { + miner_address += &format!("{}{}", ".", config.mining_worker_name); + } + let mut mc = Controller::new(config.num_mining_threads).unwrap_or_else(|e| { + debug!(target: LOG_TARGET_FILE, "Error loading mining controller: {}", e); + panic!("Error loading mining controller: {}", e); + }); + let cc = crate::stratum::controller::Controller::new(&url, Some(miner_address), None, None, mc.tx.clone()) + .unwrap_or_else(|e| { + debug!( + target: LOG_TARGET_FILE, + "Error loading stratum client controller: {:?}", e + ); + panic!("Error loading stratum client controller: {:?}", e); + }); + mc.set_client_tx(cc.tx.clone()); + + let _join_handle = thread::Builder::new() + .name("client_controller".to_string()) + .spawn(move || { + cc.run(); + }); + + mc.run() + .await + .map_err(|err| ExitError::new(ExitCode::UnknownError, format!("Stratum error: {:?}", err)))?; + + Ok(()) + } else { + let (mut node_conn, mut wallet_conn) = connect(&config).await.map_err(|e| { + ExitError::new( + ExitCode::GrpcError, + format!("Could not connect to wallet or base node: {}", e), + ) + })?; + + let mut blocks_found: u64 = 0; + loop { + debug!(target: LOG_TARGET, "Starting new mining cycle"); + match mining_cycle(&mut node_conn, &mut wallet_conn, &config, &cli).await { + err @ Err(MinerError::GrpcConnection(_)) | err @ Err(MinerError::GrpcStatus(_)) => { + // Any GRPC error we will try to reconnect with a standard delay + error!(target: LOG_TARGET, "Connection error: {:?}", err); + loop { + info!(target: LOG_TARGET, "Holding for {:?}", config.wait_timeout()); + sleep(config.wait_timeout()).await; + match connect(&config).await { + Ok((nc, wc)) => { + node_conn = nc; + wallet_conn = wc; + break; + }, + Err(err) => { + error!(target: LOG_TARGET, "Connection error: {:?}", err); + continue; + }, + } + } + }, + Err(MinerError::MineUntilHeightReached(h)) => { + warn!( + target: LOG_TARGET, + "Prescribed blockchain height {} reached. Aborting ...", h + ); + return Ok(()); + }, + Err(MinerError::MinerLostBlock(h)) => { + warn!( + target: LOG_TARGET, + "Height {} already mined by other node. Restarting ...", h + ); + }, + Err(err) => { + error!(target: LOG_TARGET, "Error: {:?}", err); + sleep(config.wait_timeout()).await; + }, + Ok(submitted) => { + info!(target: LOG_TARGET, "💰 Found block"); + if submitted { + blocks_found += 1; + } + if let Some(max_blocks) = cli.miner_max_blocks { + if blocks_found >= max_blocks { + return Ok(()); + } + } + }, + } + } + } +} + +async fn connect(config: &MinerConfig) -> Result<(BaseNodeClient, WalletGrpcClient), MinerError> { + let base_node_addr = multiaddr_to_socketaddr(&config.base_node_grpc_address)?; + info!(target: LOG_TARGET, "🔗 Connecting to base node at {}", base_node_addr); + let node_conn = BaseNodeClient::connect(format!("http://{}", base_node_addr)).await?; + let wallet_conn = connect_wallet(config).await?; + + Ok((node_conn, wallet_conn)) +} + +async fn connect_wallet(config: &MinerConfig) -> Result { + let wallet_addr = format!("http://{}", multiaddr_to_socketaddr(&config.wallet_grpc_address)?); + info!(target: LOG_TARGET, "👛 Connecting to wallet at {}", wallet_addr); + let channel = Endpoint::from_str(&wallet_addr)?.connect().await?; + let wallet_conn = WalletClient::with_interceptor( + channel, + ClientAuthenticationInterceptor::create(&config.wallet_grpc_authentication)?, + ); + + Ok(wallet_conn) +} + +async fn mining_cycle( + node_conn: &mut BaseNodeClient, + wallet_conn: &mut WalletGrpcClient, + config: &MinerConfig, + cli: &Cli, +) -> Result { + debug!(target: LOG_TARGET, "Getting new block template"); + let template = node_conn + .get_new_block_template(config.pow_algo_request()) + .await? + .into_inner(); + let mut block_template = template + .new_block_template + .clone() + .ok_or_else(|| err_empty("new_block_template"))?; + + if config.mine_on_tip_only { + debug!( + target: LOG_TARGET, + "Checking if base node is synced, because mine_on_tip_only is true" + ); + let height = block_template + .header + .as_ref() + .ok_or_else(|| err_empty("header"))? + .height; + validate_tip(node_conn, height, cli.mine_until_height).await?; + } + + debug!(target: LOG_TARGET, "Getting coinbase"); + let request = coinbase_request(&template, config.coinbase_extra.as_bytes().to_vec())?; + let coinbase = wallet_conn.get_coinbase(request).await?.into_inner(); + let (output, kernel) = extract_outputs_and_kernels(coinbase)?; + let body = block_template + .body + .as_mut() + .ok_or_else(|| err_empty("new_block_template.body"))?; + body.outputs.push(output); + body.kernels.push(kernel); + let target_difficulty = template + .miner_data + .ok_or_else(|| err_empty("miner_data"))? + .target_difficulty; + + debug!(target: LOG_TARGET, "Asking base node to assemble the MMR roots"); + let block_result = node_conn.get_new_block(block_template).await?.into_inner(); + let block = block_result.block.ok_or_else(|| err_empty("block"))?; + let header = block.clone().header.ok_or_else(|| err_empty("block.header"))?; + + debug!(target: LOG_TARGET, "Initializing miner"); + let mut reports = Miner::init_mining(header.clone(), target_difficulty, config.num_mining_threads, false); + let mut reporting_timeout = Instant::now(); + let mut block_submitted = false; + while let Some(report) = reports.next().await { + if let Some(header) = report.header.clone() { + let mut submit = true; + if let Some(min_diff) = cli.miner_min_diff { + if report.difficulty < min_diff { + submit = false; + debug!( + target: LOG_TARGET_FILE, + "Mined difficulty {} below minimum difficulty {}. Not submitting.", report.difficulty, min_diff + ); + } + } + if let Some(max_diff) = cli.miner_max_diff { + if report.difficulty > max_diff { + submit = false; + debug!( + target: LOG_TARGET_FILE, + "Mined difficulty {} greater than maximum difficulty {}. Not submitting.", + report.difficulty, + max_diff + ); + } + } + if submit { + // Mined a block fitting the difficulty + let block_header = BlockHeader::try_from(header.clone()).map_err(MinerError::Conversion)?; + debug!( + target: LOG_TARGET, + "Miner found block header {} with difficulty {:?}", block_header, report.difficulty, + ); + let mut mined_block = block.clone(); + mined_block.header = Some(header); + // 5. Sending block to the node + node_conn.submit_block(mined_block).await?; + block_submitted = true; + break; + } else { + display_report(&report, config.num_mining_threads).await; + } + } else { + display_report(&report, config.num_mining_threads).await; + } + if config.mine_on_tip_only && reporting_timeout.elapsed() > config.validate_tip_interval() { + validate_tip(node_conn, report.height, cli.mine_until_height).await?; + reporting_timeout = Instant::now(); + } + } + + // Not waiting for threads to stop, they should stop in a short while after `reports` dropped + Ok(block_submitted) +} + +pub async fn display_report(report: &MiningReport, num_mining_threads: usize) { + let hashrate = report.hashes as f64 / report.elapsed.as_micros() as f64; + info!( + target: LOG_TARGET, + "⛏ Miner {:0>2} reported {:.2}MH/s with total {:.2}MH/s over {} threads. Height: {}. Target: {})", + report.miner, + hashrate, + hashrate * num_mining_threads as f64, + num_mining_threads, + report.height, + report.target_difficulty, + ); +} + +/// If config +async fn validate_tip( + node_conn: &mut BaseNodeClient, + height: u64, + mine_until_height: Option, +) -> Result<(), MinerError> { + let tip = node_conn + .get_tip_info(tari_app_grpc::tari_rpc::Empty {}) + .await? + .into_inner(); + let longest_height = tip.clone().metadata.unwrap().height_of_longest_chain; + if let Some(height) = mine_until_height { + if longest_height >= height { + return Err(MinerError::MineUntilHeightReached(height)); + } + } + if height <= longest_height { + return Err(MinerError::MinerLostBlock(height)); + } + if !tip.initial_sync_achieved || tip.metadata.is_none() { + return Err(MinerError::NodeNotReady); + } + if height <= longest_height { + return Err(MinerError::MinerLostBlock(height)); + } + Ok(()) +} diff --git a/applications/tari_miner/src/stratum/stratum_controller/controller.rs b/applications/tari_miner/src/stratum/stratum_controller/controller.rs index 51318d671a..732ef0bbd8 100644 --- a/applications/tari_miner/src/stratum/stratum_controller/controller.rs +++ b/applications/tari_miner/src/stratum/stratum_controller/controller.rs @@ -29,8 +29,8 @@ use tari_app_grpc::tari_rpc::BlockHeader; use tari_utilities::hex::Hex; use crate::{ - display_report, miner::Miner, + run_miner::display_report, stratum::{error::Error, stratum_types as types}, }; diff --git a/base_layer/core/src/base_node/chain_metadata_service/service.rs b/base_layer/core/src/base_node/chain_metadata_service/service.rs index f68e01e0d3..2645c0e59f 100644 --- a/base_layer/core/src/base_node/chain_metadata_service/service.rs +++ b/base_layer/core/src/base_node/chain_metadata_service/service.rs @@ -95,7 +95,7 @@ impl ChainMetadataService { log_if_error!( target: LOG_TARGET, "Failed to handle liveness event because '{}'", - self.handle_liveness_event(&*event).await + self.handle_liveness_event(&event).await ); }, diff --git a/base_layer/core/src/base_node/comms_interface/comms_response.rs b/base_layer/core/src/base_node/comms_interface/comms_response.rs index 028cbb709b..ddeaf7238f 100644 --- a/base_layer/core/src/base_node/comms_interface/comms_response.rs +++ b/base_layer/core/src/base_node/comms_interface/comms_response.rs @@ -38,6 +38,7 @@ use crate::{ }; /// API Response enum +#[allow(clippy::large_enum_variant)] #[derive(Debug, Clone)] pub enum NodeCommsResponse { ChainMetadata(ChainMetadata), diff --git a/base_layer/core/src/base_node/rpc/service.rs b/base_layer/core/src/base_node/rpc/service.rs index c5fbf37c10..ade78356a8 100644 --- a/base_layer/core/src/base_node/rpc/service.rs +++ b/base_layer/core/src/base_node/rpc/service.rs @@ -177,7 +177,7 @@ impl BaseNodeWalletService for BaseNodeWalletRpc // Determine if we are synced let status_watch = state_machine.get_status_info_watch(); - let is_synced = match (*status_watch.borrow()).state_info { + let is_synced = match (status_watch.borrow()).state_info { StateInfo::Listening(li) => li.is_synced(), _ => false, }; @@ -274,7 +274,7 @@ impl BaseNodeWalletService for BaseNodeWalletRpc // Determine if we are synced let status_watch = state_machine.get_status_info_watch(); - let is_synced = match (*status_watch.borrow()).state_info { + let is_synced = match (status_watch.borrow()).state_info { StateInfo::Listening(li) => li.is_synced(), _ => false, }; @@ -319,7 +319,7 @@ impl BaseNodeWalletService for BaseNodeWalletRpc let state_machine = self.state_machine(); // Determine if we are synced let status_watch = state_machine.get_status_info_watch(); - let is_synced = match (*status_watch.borrow()).state_info { + let is_synced = match (status_watch.borrow()).state_info { StateInfo::Listening(li) => li.is_synced(), _ => false, }; diff --git a/base_layer/core/src/base_node/service/service.rs b/base_layer/core/src/base_node/service/service.rs index 60516c2262..5622e6a5f4 100644 --- a/base_layer/core/src/base_node/service/service.rs +++ b/base_layer/core/src/base_node/service/service.rs @@ -287,7 +287,7 @@ where B: BlockchainBackend + 'static // Determine if we are bootstrapped let status_watch = self.state_machine_handle.get_status_info_watch(); - if !(*status_watch.borrow()).bootstrapped { + if !(status_watch.borrow()).bootstrapped { debug!( target: LOG_TARGET, "Propagated block `{}` from peer `{}` not processed while busy with initial sync.", @@ -373,7 +373,7 @@ async fn handle_incoming_request( // Determine if we are synced let status_watch = state_machine_handle.get_status_info_watch(); - let is_synced = match (*status_watch.borrow()).state_info { + let is_synced = match (status_watch.borrow()).state_info { StateInfo::Listening(li) => li.is_synced(), _ => false, }; diff --git a/base_layer/core/src/base_node/sync/header_sync/synchronizer.rs b/base_layer/core/src/base_node/sync/header_sync/synchronizer.rs index 1128bed396..0eac69f32b 100644 --- a/base_layer/core/src/base_node/sync/header_sync/synchronizer.rs +++ b/base_layer/core/src/base_node/sync/header_sync/synchronizer.rs @@ -421,7 +421,7 @@ impl<'a, B: BlockchainBackend + 'static> HeaderSynchronizer<'a, B> { }, }; - let steps_back = resp.fork_hash_index as u64 + offset as u64; + let steps_back = resp.fork_hash_index + offset as u64; return Ok((resp, block_hashes, steps_back)); } } diff --git a/base_layer/core/src/chain_storage/blockchain_database.rs b/base_layer/core/src/chain_storage/blockchain_database.rs index 2e74dcd509..90e91720b8 100644 --- a/base_layer/core/src/chain_storage/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/blockchain_database.rs @@ -1569,7 +1569,7 @@ pub fn fetch_target_difficulty_for_next_block( fn fetch_block(db: &T, height: u64, compact: bool) -> Result { let mark = Instant::now(); - let (tip_height, is_pruned) = check_for_valid_height(&*db, height)?; + let (tip_height, is_pruned) = check_for_valid_height(db, height)?; let chain_header = db.fetch_chain_header_by_height(height)?; let (header, accumulated_data) = chain_header.into_parts(); let kernels = db.fetch_kernels_in_block(&accumulated_data.hash)?; @@ -3010,7 +3010,7 @@ mod test { let tip = access.fetch_last_header().unwrap(); assert_eq!(&tip, reorg_chain.get("E2").unwrap().header()); - check_whole_chain(&mut *access); + check_whole_chain(&mut access); } #[test] @@ -3060,7 +3060,7 @@ mod test { let tip = access.fetch_last_header().unwrap(); assert_eq!(&tip, mainchain.get("D").unwrap().header()); - check_whole_chain(&mut *access); + check_whole_chain(&mut access); } #[test] diff --git a/base_layer/core/src/chain_storage/lmdb_db/lmdb.rs b/base_layer/core/src/chain_storage/lmdb_db/lmdb.rs index dffda61a1c..cca6cb8fdf 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/lmdb.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/lmdb.rs @@ -302,7 +302,7 @@ where { let access = txn.access(); - let cursor = txn.cursor(&*db).map_err(|e| { + let cursor = txn.cursor(db).map_err(|e| { error!(target: LOG_TARGET, "Could not get read cursor from lmdb: {:?}", e); ChainStorageError::AccessError(e.to_string()) })?; diff --git a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs index b3db780554..0279f23976 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs @@ -343,7 +343,7 @@ impl LMDBDatabase { kernel, mmr_position, } => { - self.insert_kernel(&write_txn, header_hash, &**kernel, *mmr_position)?; + self.insert_kernel(&write_txn, header_hash, kernel, *mmr_position)?; }, InsertOutput { header_hash, @@ -356,7 +356,7 @@ impl LMDBDatabase { &write_txn, header_hash, *header_height, - &*output, + output, *mmr_position, *timestamp, )?; @@ -584,7 +584,7 @@ impl LMDBDatabase { lmdb_insert( txn, - &*self.utxo_commitment_index, + &self.utxo_commitment_index, output.commitment.as_bytes(), &output_hash, "utxo_commitment_index", @@ -592,14 +592,14 @@ impl LMDBDatabase { lmdb_insert( txn, - &*self.txos_hash_to_index_db, + &self.txos_hash_to_index_db, output_hash.as_slice(), &(mmr_position, output_key.to_vec()), "txos_hash_to_index_db", )?; lmdb_insert( txn, - &*self.utxos_db, + &self.utxos_db, &output_key, &TransactionOutputRowData { output: Some(output.clone()), @@ -635,14 +635,14 @@ impl LMDBDatabase { let key = OutputKey::try_from_parts(&[header_hash.as_slice(), mmr_position.to_be_bytes().as_slice()])?; lmdb_insert( txn, - &*self.txos_hash_to_index_db, + &self.txos_hash_to_index_db, output_hash.as_slice(), &(mmr_position, key.to_vec()), "txos_hash_to_index_db", )?; lmdb_insert( txn, - &*self.utxos_db, + &self.utxos_db, &key, &TransactionOutputRowData { output: None, @@ -673,7 +673,7 @@ impl LMDBDatabase { lmdb_insert( txn, - &*self.kernel_excess_index, + &self.kernel_excess_index, kernel.excess.as_bytes(), &(*header_hash, mmr_position, hash), "kernel_excess_index", @@ -684,7 +684,7 @@ impl LMDBDatabase { excess_sig_key.extend(kernel.excess_sig.get_signature().as_bytes()); lmdb_insert( txn, - &*self.kernel_excess_sig_index, + &self.kernel_excess_sig_index, excess_sig_key.as_slice(), &(*header_hash, mmr_position, hash), "kernel_excess_sig_index", @@ -692,7 +692,7 @@ impl LMDBDatabase { lmdb_insert( txn, - &*self.kernels_db, + &self.kernels_db, &key, &TransactionKernelRowData { kernel: kernel.clone(), @@ -739,7 +739,7 @@ impl LMDBDatabase { ])?; lmdb_insert( txn, - &*self.inputs_db, + &self.inputs_db, &key, &TransactionInputRowDataRef { input: &input.to_compact(), @@ -1007,7 +1007,7 @@ impl LMDBDatabase { } lmdb_delete( txn, - &*self.utxo_commitment_index, + &self.utxo_commitment_index, output.commitment.as_bytes(), "utxo_commitment_index", )?; @@ -1063,7 +1063,7 @@ impl LMDBDatabase { trace!(target: LOG_TARGET, "Input moved to UTXO set: {}", input); lmdb_insert( txn, - &*self.utxo_commitment_index, + &self.utxo_commitment_index, input.commitment()?.as_bytes(), &input.output_hash(), "utxo_commitment_index", @@ -1198,7 +1198,7 @@ impl LMDBDatabase { let data = if header.height == 0 { BlockAccumulatedData::default() } else { - self.fetch_block_accumulated_data(&*txn, header.height - 1)? + self.fetch_block_accumulated_data(txn, header.height - 1)? .ok_or_else(|| ChainStorageError::ValueNotFound { entity: "BlockAccumulatedData", field: "height", @@ -1272,7 +1272,7 @@ impl LMDBDatabase { // unique_id_index expects inputs to be inserted before outputs for input in &inputs { let output_hash = input.output_hash(); - let index = match self.fetch_mmr_leaf_index(&**txn, MmrTree::Utxo, &output_hash)? { + let index = match self.fetch_mmr_leaf_index(txn, MmrTree::Utxo, &output_hash)? { Some(index) => index, None => match output_mmr.find_leaf_index(output_hash.as_slice())? { Some(index) => { @@ -1469,7 +1469,7 @@ impl LMDBDatabase { )?; let mut block_accum_data = self - .fetch_block_accumulated_data(&*write_txn, height)? + .fetch_block_accumulated_data(write_txn, height)? .unwrap_or_default(); if let Some(deleted_diff) = values.deleted_diff { @@ -1612,7 +1612,7 @@ impl LMDBDatabase { lmdb_replace(txn, &self.bad_blocks, hash.deref(), &height)?; // Clean up bad blocks that are far from the tip - let metadata = fetch_metadata(&*txn, &self.metadata_db)?; + let metadata = fetch_metadata(txn, &self.metadata_db)?; let deleted_before_height = metadata .height_of_longest_chain() .saturating_sub(CLEAN_BAD_BLOCKS_BEFORE_REL_HEIGHT); @@ -2094,7 +2094,7 @@ impl BlockchainBackend for LMDBDatabase { fn fetch_output(&self, output_hash: &HashOutput) -> Result, ChainStorageError> { debug!(target: LOG_TARGET, "Fetch output: {}", output_hash.to_hex()); let txn = self.read_transaction()?; - self.fetch_output_in_txn(&*txn, output_hash.as_slice()) + self.fetch_output_in_txn(&txn, output_hash.as_slice()) } fn fetch_unspent_output_hash_by_commitment( @@ -2102,7 +2102,7 @@ impl BlockchainBackend for LMDBDatabase { commitment: &Commitment, ) -> Result, ChainStorageError> { let txn = self.read_transaction()?; - lmdb_get::<_, HashOutput>(&*txn, &*self.utxo_commitment_index, commitment.as_bytes()) + lmdb_get::<_, HashOutput>(&txn, &self.utxo_commitment_index, commitment.as_bytes()) } fn fetch_outputs_in_block(&self, header_hash: &HashOutput) -> Result, ChainStorageError> { @@ -2139,7 +2139,7 @@ impl BlockchainBackend for LMDBDatabase { fn fetch_mmr_leaf_index(&self, tree: MmrTree, hash: &HashOutput) -> Result, ChainStorageError> { let txn = self.read_transaction()?; - self.fetch_mmr_leaf_index(&*txn, tree, hash) + self.fetch_mmr_leaf_index(&txn, tree, hash) } /// Returns the number of blocks in the block orphan pool. @@ -2786,7 +2786,7 @@ fn run_migrations(db: &LMDBDatabase) -> Result<(), ChainStorageError> { let txn = db.read_transaction()?; let k = MetadataKey::MigrationVersion; - let val = lmdb_get::<_, MetadataValue>(&*txn, &db.metadata_db, &k.as_u32())?; + let val = lmdb_get::<_, MetadataValue>(&txn, &db.metadata_db, &k.as_u32())?; let n = match val { Some(MetadataValue::MigrationVersion(n)) => n, Some(_) | None => 0, diff --git a/base_layer/core/src/chain_storage/tests/blockchain_database.rs b/base_layer/core/src/chain_storage/tests/blockchain_database.rs index 88358bcfcd..9e10771618 100644 --- a/base_layer/core/src/chain_storage/tests/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/tests/blockchain_database.rs @@ -53,7 +53,7 @@ fn create_next_block( rules, prev_block, BlockSpec::new() - .with_transactions(transactions.into_iter().map(|t| (&*t).clone()).collect()) + .with_transactions(transactions.into_iter().map(|t| (*t).clone()).collect()) .finish(), ); let block = apply_mmr_to_block(db, block); diff --git a/base_layer/core/src/consensus/chain_strength_comparer.rs b/base_layer/core/src/consensus/chain_strength_comparer.rs index 0a20e1c8ff..1695c35cec 100644 --- a/base_layer/core/src/consensus/chain_strength_comparer.rs +++ b/base_layer/core/src/consensus/chain_strength_comparer.rs @@ -94,19 +94,19 @@ impl ChainStrengthComparerBuilder { } pub fn by_accumulated_difficulty(self) -> Self { - self.add_comparer_as_then(Box::new(AccumulatedDifficultySquaredComparer::default())) + self.add_comparer_as_then(Box::::default()) } pub fn by_monero_difficulty(self) -> Self { - self.add_comparer_as_then(Box::new(MoneroDifficultyComparer::default())) + self.add_comparer_as_then(Box::::default()) } pub fn by_sha3_difficulty(self) -> Self { - self.add_comparer_as_then(Box::new(Sha3DifficultyComparer::default())) + self.add_comparer_as_then(Box::::default()) } pub fn by_height(self) -> Self { - self.add_comparer_as_then(Box::new(HeightComparer::default())) + self.add_comparer_as_then(Box::::default()) } pub fn then(self) -> Self { diff --git a/base_layer/core/src/consensus/consensus_constants.rs b/base_layer/core/src/consensus/consensus_constants.rs index ff91dad971..54724bfef6 100644 --- a/base_layer/core/src/consensus/consensus_constants.rs +++ b/base_layer/core/src/consensus/consensus_constants.rs @@ -363,7 +363,7 @@ impl ConsensusConstants { emission_tail: 800 * T, max_randomx_seed_height: u64::MAX, proof_of_work: algos, - faucet_value: (10 * 4000) * T, + faucet_value: MicroTari(0), transaction_weight: TransactionWeight::latest(), max_script_byte_size: 2048, input_version_range, diff --git a/base_layer/core/src/consensus/consensus_encoding/string.rs b/base_layer/core/src/consensus/consensus_encoding/string.rs index d2bcda6979..2df0a718b3 100644 --- a/base_layer/core/src/consensus/consensus_encoding/string.rs +++ b/base_layer/core/src/consensus/consensus_encoding/string.rs @@ -156,7 +156,7 @@ mod tests { #[test] fn it_returns_none_if_size_exceeded() { - let s = MaxSizeString::<10>::from_utf8_bytes_checked(&[0u8; 11]); + let s = MaxSizeString::<10>::from_utf8_bytes_checked([0u8; 11]); assert_eq!(s, None); } @@ -169,7 +169,7 @@ mod tests { #[test] fn it_returns_none_if_invalid_utf8() { - let s = MaxSizeString::<10>::from_utf8_bytes_checked(&[255u8; 10]); + let s = MaxSizeString::<10>::from_utf8_bytes_checked([255u8; 10]); assert_eq!(s, None); } } diff --git a/base_layer/core/src/consensus/emission.rs b/base_layer/core/src/consensus/emission.rs index f8a149b832..7eee227652 100644 --- a/base_layer/core/src/consensus/emission.rs +++ b/base_layer/core/src/consensus/emission.rs @@ -136,7 +136,7 @@ impl EmissionSchedule { let mut carry_last = 0u8; for i in 0..len { let index = len - 1 - i; - let carry = if num[index] >= 5 { 1 } else { 0 }; + let carry = (num[index] >= 5).into(); num[index] = (2 * num[index]) % 10 + carry_last; carry_last = carry; } diff --git a/base_layer/core/src/covenants/covenant.rs b/base_layer/core/src/covenants/covenant.rs index ebc86a7732..c9d4a036ef 100644 --- a/base_layer/core/src/covenants/covenant.rs +++ b/base_layer/core/src/covenants/covenant.rs @@ -61,7 +61,7 @@ impl BorshSerialize for Covenant { } } -impl<'a> BorshDeserialize for Covenant { +impl BorshDeserialize for Covenant { fn deserialize(buf: &mut &[u8]) -> io::Result { let len = buf.read_varint()?; let mut data = Vec::with_capacity(len); diff --git a/base_layer/core/src/covenants/fields.rs b/base_layer/core/src/covenants/fields.rs index 12cbbfe1b4..342cb8f586 100644 --- a/base_layer/core/src/covenants/fields.rs +++ b/base_layer/core/src/covenants/fields.rs @@ -300,7 +300,7 @@ impl OutputFields { let mut challenge = Blake256::new(); BaseLayerCovenantsDomain::add_domain_separation_tag(&mut challenge, COVENANTS_FIELD_HASHER_LABEL); for field in &self.fields { - challenge.update(&field.get_field_value_bytes(output).as_slice()); + challenge.update(field.get_field_value_bytes(output).as_slice()); } challenge } diff --git a/base_layer/core/src/covenants/filters/fields_hashed_eq.rs b/base_layer/core/src/covenants/filters/fields_hashed_eq.rs index 4adfcf04d6..c8bd229bd0 100644 --- a/base_layer/core/src/covenants/filters/fields_hashed_eq.rs +++ b/base_layer/core/src/covenants/filters/fields_hashed_eq.rs @@ -65,7 +65,7 @@ mod test { }; let mut hasher = Challenge::new(); BaseLayerCovenantsDomain::add_domain_separation_tag(&mut hasher, COVENANTS_FIELD_HASHER_LABEL); - let hash = hasher.chain(&features.try_to_vec().unwrap()).finalize(); + let hash = hasher.chain(features.try_to_vec().unwrap()).finalize(); let covenant = covenant!(fields_hashed_eq(@fields(@field::features), @hash(hash.into()))); let input = create_input(); let (mut context, outputs) = setup_filter_test(&covenant, &input, 0, |outputs| { diff --git a/base_layer/core/src/covenants/output_set.rs b/base_layer/core/src/covenants/output_set.rs index 8ab2c076c5..8192418f04 100644 --- a/base_layer/core/src/covenants/output_set.rs +++ b/base_layer/core/src/covenants/output_set.rs @@ -54,7 +54,7 @@ impl<'a> OutputSet<'a> { pub fn retain(&mut self, mut f: F) -> Result<(), CovenantError> where F: FnMut(&'a TransactionOutput) -> Result { let mut err = None; - self.0.retain(|output| match f(**output) { + self.0.retain(|output| match f(output) { Ok(b) => b, Err(e) => { // Theres no way to stop retain early, so keep the error for when this completes @@ -82,7 +82,7 @@ impl<'a> OutputSet<'a> { pub fn find_inplace(&mut self, mut pred: F) where F: FnMut(&TransactionOutput) -> bool { - match self.0.iter().find(|indexed| pred(&**indexed)) { + match self.0.iter().find(|indexed| pred(indexed)) { Some(output) => { let output = *output; self.clear(); diff --git a/base_layer/core/src/mempool/mempool.rs b/base_layer/core/src/mempool/mempool.rs index 72553e07dd..ad4b33bda1 100644 --- a/base_layer/core/src/mempool/mempool.rs +++ b/base_layer/core/src/mempool/mempool.rs @@ -160,7 +160,7 @@ impl Mempool { let storage = self.pool_storage.clone(); task::spawn_blocking(move || { let lock = storage.read().map_err(|_| MempoolError::RwLockPoisonError)?; - callback(&*lock) + callback(&lock) }) .await? } @@ -173,7 +173,7 @@ impl Mempool { let storage = self.pool_storage.clone(); task::spawn_blocking(move || { let mut lock = storage.write().map_err(|_| MempoolError::RwLockPoisonError)?; - callback(&mut *lock) + callback(&mut lock) }) .await? } diff --git a/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs b/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs index afb839c0c6..33910354b0 100644 --- a/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs +++ b/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs @@ -265,7 +265,7 @@ impl UnconfirmedPool { transactions_to_recheck: &mut Vec<(TransactionKey, Arc)>, selected_txs: &HashMap>, total_weight: &mut u64, - unique_ids: &mut HashSet<[u8; 32]>, + _unique_ids: &mut HashSet<[u8; 32]>, ) -> Result<(), UnconfirmedPoolError> { for dependent_output in &transaction.dependent_output_hashes { match self.txs_by_output.get(dependent_output) { @@ -278,7 +278,7 @@ impl UnconfirmedPool { transactions_to_recheck, selected_txs, total_weight, - unique_ids, + _unique_ids, )?; if !transactions_to_recheck.is_empty() { @@ -517,7 +517,7 @@ impl UnconfirmedPool { /// Returns all transaction stored in the UnconfirmedPool. pub fn snapshot(&self) -> Vec> { - self.tx_by_key.iter().map(|(_, ptx)| ptx.transaction.clone()).collect() + self.tx_by_key.values().map(|ptx| ptx.transaction.clone()).collect() } /// Returns the total weight of all transactions stored in the pool. diff --git a/base_layer/core/src/proof_of_work/monero_rx/helpers.rs b/base_layer/core/src/proof_of_work/monero_rx/helpers.rs index 7f343ee202..f44c539d07 100644 --- a/base_layer/core/src/proof_of_work/monero_rx/helpers.rs +++ b/base_layer/core/src/proof_of_work/monero_rx/helpers.rs @@ -138,7 +138,7 @@ pub fn create_blockhashing_blob_from_block(block: &monero::Block) -> Result Vec { @@ -252,7 +252,7 @@ mod test { hex::encode(transaction.hash().0.to_vec()).as_bytes().to_vec() ); let hex = hex::encode(consensus::serialize::(&transaction)); - deserialize::(&hex::decode(&hex).unwrap()).unwrap(); + deserialize::(&hex::decode(hex).unwrap()).unwrap(); } // This tests checks the blockhashing blob of monero-rs diff --git a/base_layer/core/src/proof_of_work/monero_rx/merkle_tree.rs b/base_layer/core/src/proof_of_work/monero_rx/merkle_tree.rs index b35000e8cf..ae46eecf48 100644 --- a/base_layer/core/src/proof_of_work/monero_rx/merkle_tree.rs +++ b/base_layer/core/src/proof_of_work/monero_rx/merkle_tree.rs @@ -224,8 +224,8 @@ pub fn create_merkle_proof(hashes: &[Hash], hash: &Hash) -> Option if h != hash { return None; } - let i = if pos == 0 { 1 } else { 0 }; - MerkleProof::try_construct(vec![hashes[i]], 1, if pos == 0 { 0 } else { 1 }) + let i = usize::from(pos == 0); + MerkleProof::try_construct(vec![hashes[i]], 1, u32::from(pos != 0)) }), len => { let mut idx = hashes.iter().position(|node| node == hash)?; @@ -246,7 +246,7 @@ pub fn create_merkle_proof(hashes: &[Hash], hash: &Hash) -> Option let ii = if idx == i { i + 1 } else { i }; branch.push(hashes[ii]); depth += 1; - path = (path << 1) | (if idx == i { 0 } else { 1 }); + path = (path << 1) | u32::from(idx != i); idx = j; } *val = cn_fast_hash2(&hashes[i], &hashes[i + 1]); @@ -263,7 +263,7 @@ pub fn create_merkle_proof(hashes: &[Hash], hash: &Hash) -> Option let ii = if idx == i { i + 1 } else { i }; branch.push(ints[ii]); depth += 1; - path = (path << 1) | (if idx == i { 0 } else { 1 }); + path = (path << 1) | u32::from(idx != i); idx = j; } ints[j] = cn_fast_hash2(&ints[i], &ints[i + 1]); @@ -272,10 +272,10 @@ pub fn create_merkle_proof(hashes: &[Hash], hash: &Hash) -> Option } if idx == 0 || idx == 1 { - let ii = if idx == 0 { 1 } else { 0 }; + let ii = usize::from(idx == 0); branch.push(ints[ii]); depth += 1; - path = (path << 1) | (if idx == 0 { 0 } else { 1 }); + path = (path << 1) | u32::from(idx != 0); } MerkleProof::try_construct(branch, depth, path) diff --git a/base_layer/core/src/test_helpers/mod.rs b/base_layer/core/src/test_helpers/mod.rs index c6a5bd8bc3..c119cdd8d8 100644 --- a/base_layer/core/src/test_helpers/mod.rs +++ b/base_layer/core/src/test_helpers/mod.rs @@ -124,7 +124,7 @@ pub fn create_peer_manager>(data_path: P) -> Arc { let peer_database_name = { let mut rng = rand::thread_rng(); iter::repeat(()) - .map(|_| rng.sample(Alphanumeric) as char) + .map(|_| rng.sample(Alphanumeric)) .take(8) .collect::() }; diff --git a/base_layer/core/src/transactions/tari_amount.rs b/base_layer/core/src/transactions/tari_amount.rs index ab4387eb4d..affb87baf3 100644 --- a/base_layer/core/src/transactions/tari_amount.rs +++ b/base_layer/core/src/transactions/tari_amount.rs @@ -161,7 +161,7 @@ impl std::str::FromStr for MicroTari { type Err = MicroTariError; fn from_str(s: &str) -> Result { - let processed = s.replace(',', "").replace(' ', "").to_ascii_lowercase(); + let processed = s.replace([',', ' '], "").to_ascii_lowercase(); // Is this Tari or MicroTari let is_micro_tari = if processed.ends_with("ut") || processed.ends_with("µt") { true diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/validator_node_registration.rs b/base_layer/core/src/transactions/transaction_components/side_chain/validator_node_registration.rs index 8917a57beb..a5e9fec208 100644 --- a/base_layer/core/src/transactions/transaction_components/side_chain/validator_node_registration.rs +++ b/base_layer/core/src/transactions/transaction_components/side_chain/validator_node_registration.rs @@ -58,12 +58,12 @@ impl ValidatorNodeRegistration { match prev_shard_key { Some(prev) => { if does_require_new_shard_key(self.public_key(), epoch, interval) { - generate_shard_key(self.public_key(), &**block_hash) + generate_shard_key(self.public_key(), block_hash) } else { prev } }, - None => generate_shard_key(self.public_key(), &**block_hash), + None => generate_shard_key(self.public_key(), block_hash), } } diff --git a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs index 828fc6e487..3735e680fa 100644 --- a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs +++ b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs @@ -497,19 +497,19 @@ impl SenderTransactionInitializer { } if !self.amounts.is_full() { let size = self.amounts.size(); - return self.build_err(&*format!("Missing all {} amounts", size)); + return self.build_err(&format!("Missing all {} amounts", size)); } if !self.recipient_sender_offset_private_keys.is_full() { let size = self.recipient_sender_offset_private_keys.size(); - return self.build_err(&*format!("Missing {} recipient script offset private key/s", size)); + return self.build_err(&format!("Missing {} recipient script offset private key/s", size)); } if !self.private_commitment_nonces.is_full() { let size = self.private_commitment_nonces.size(); - return self.build_err(&*format!("Missing {} private commitment nonce/s", size)); + return self.build_err(&format!("Missing {} private commitment nonce/s", size)); } if !self.recipient_scripts.is_full() { let size = self.recipient_scripts.size(); - return self.build_err(&*format!("Missing all {} recipient scripts", size)); + return self.build_err(&format!("Missing all {} recipient scripts", size)); } if self.inputs.is_empty() { return self.build_err("A transaction cannot have zero inputs"); diff --git a/base_layer/core/src/validation/aggregate_body/aggregate_body_internal_validator.rs b/base_layer/core/src/validation/aggregate_body/aggregate_body_internal_validator.rs index 49c232eb6a..4814fc4fc2 100644 --- a/base_layer/core/src/validation/aggregate_body/aggregate_body_internal_validator.rs +++ b/base_layer/core/src/validation/aggregate_body/aggregate_body_internal_validator.rs @@ -307,7 +307,7 @@ fn check_kernel_lock_height(height: u64, kernels: &[TransactionKernel]) -> Resul /// Checks that all inputs have matured at the given height fn check_maturity(height: u64, inputs: &[TransactionInput]) -> Result<(), TransactionError> { - if let Err(e) = inputs + inputs .iter() .map(|input| match input.is_mature_at(height) { Ok(mature) => { @@ -323,10 +323,8 @@ fn check_maturity(height: u64, inputs: &[TransactionInput]) -> Result<(), Transa }, Err(e) => Err(e), }) - .sum::>() - { - return Err(e); - } + .sum::>()?; + Ok(()) } diff --git a/base_layer/core/src/validation/header/header_full_validator.rs b/base_layer/core/src/validation/header/header_full_validator.rs index 425fd87f5d..9a2cfc50d1 100644 --- a/base_layer/core/src/validation/header/header_full_validator.rs +++ b/base_layer/core/src/validation/header/header_full_validator.rs @@ -109,7 +109,7 @@ fn check_timestamp_count( let timestamps: Vec = prev_timestamps.iter().take(expected_timestamp_count).copied().collect(); if timestamps.len() < expected_timestamp_count { return Err(ValidationError::NotEnoughTimestamps { - actual: timestamps.len() as usize, + actual: timestamps.len(), expected: expected_timestamp_count, }); } diff --git a/base_layer/core/src/validation/header_iter.rs b/base_layer/core/src/validation/header_iter.rs new file mode 100644 index 0000000000..8dde41bf21 --- /dev/null +++ b/base_layer/core/src/validation/header_iter.rs @@ -0,0 +1,21 @@ +// Copyright 2023. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/base_layer/core/src/validation/test.rs b/base_layer/core/src/validation/test.rs index 6da06ffdb1..8f016b55dc 100644 --- a/base_layer/core/src/validation/test.rs +++ b/base_layer/core/src/validation/test.rs @@ -471,7 +471,7 @@ impl<'a, B> HeaderIter<'a, B> { } } - fn next_chunk(&self) -> (u64, u64) { + fn get_next_chunk(&self) -> (u64, u64) { #[allow(clippy::cast_possible_truncation)] let upper_bound = cmp::min(self.cursor + self.chunk_size, self.height as usize); (self.cursor as u64, upper_bound as u64) @@ -487,7 +487,7 @@ impl Iterator for HeaderIter<'_, B> { } if self.chunk.is_empty() { - let (start, end) = self.next_chunk(); + let (start, end) = self.get_next_chunk(); // We're done: No more block headers to fetch if start > end { return None; diff --git a/base_layer/core/tests/mempool.rs b/base_layer/core/tests/mempool.rs index 5c47b40400..70266646f7 100644 --- a/base_layer/core/tests/mempool.rs +++ b/base_layer/core/tests/mempool.rs @@ -1224,7 +1224,6 @@ async fn consensus_validation_unique_excess_sig() { // trying to submit a transaction with an existing excess signature already in the chain is an error let tx = Arc::new(tx1); let response = mempool.insert(tx).await.unwrap(); - dbg!(&response); assert!(matches!(response, TxStorageResponse::NotStoredAlreadyMined)); } diff --git a/base_layer/key_manager/Makefile b/base_layer/key_manager/Makefile index bb448eb691..79b2f8894c 100644 --- a/base_layer/key_manager/Makefile +++ b/base_layer/key_manager/Makefile @@ -1,4 +1,4 @@ -toolchain=nightly-2022-05-01 +toolchain=nightly-2022-11-03 .phony: test test: diff --git a/base_layer/key_manager/src/cipher_seed.rs b/base_layer/key_manager/src/cipher_seed.rs index 2aec050c81..2eef504793 100644 --- a/base_layer/key_manager/src/cipher_seed.rs +++ b/base_layer/key_manager/src/cipher_seed.rs @@ -190,7 +190,7 @@ impl CipherSeed { let mut secret_data = Zeroizing::new(Vec::::with_capacity( CIPHER_SEED_BIRTHDAY_BYTES + CIPHER_SEED_ENTROPY_BYTES + CIPHER_SEED_MAC_BYTES, )); - secret_data.extend(&self.birthday.to_le_bytes()); + secret_data.extend(self.birthday.to_le_bytes()); secret_data.extend(self.entropy.iter()); secret_data.extend(&mac); @@ -207,7 +207,7 @@ impl CipherSeed { let mut crc_hasher = CrcHasher::new(); crc_hasher.update(encrypted_seed.as_slice()); let checksum = crc_hasher.finalize().to_le_bytes(); - encrypted_seed.extend(&checksum); + encrypted_seed.extend(checksum); Ok(encrypted_seed) } @@ -348,7 +348,7 @@ impl CipherSeed { Ok(mac_domain_hasher::(LABEL_MAC_GENERATION) .chain(birthday) .chain(entropy) - .chain(&[cipher_seed_version]) + .chain([cipher_seed_version]) .chain(salt) .chain(mac_key.reveal()) .finalize() diff --git a/base_layer/mmr/src/merkle_mountain_range.rs b/base_layer/mmr/src/merkle_mountain_range.rs index e400b529c8..8a239945e0 100644 --- a/base_layer/mmr/src/merkle_mountain_range.rs +++ b/base_layer/mmr/src/merkle_mountain_range.rs @@ -123,7 +123,7 @@ where } let count = max(1, count); let last_leaf_index = min(leaf_index + count - 1, leaf_count); - let mut leaf_hashes = Vec::with_capacity((last_leaf_index - leaf_index + 1) as usize); + let mut leaf_hashes = Vec::with_capacity(last_leaf_index - leaf_index + 1); for leaf_index in leaf_index..=last_leaf_index { if let Some(hash) = self.get_leaf_hash(leaf_index)? { leaf_hashes.push(hash); diff --git a/base_layer/mmr/tests/mutable_mmr.rs b/base_layer/mmr/tests/mutable_mmr.rs index f7a893021f..0c4f549241 100644 --- a/base_layer/mmr/tests/mutable_mmr.rs +++ b/base_layer/mmr/tests/mutable_mmr.rs @@ -34,7 +34,7 @@ fn hash_with_bitmap(hash: &HashSlice, bitmap: &mut Bitmap) -> Hash { let hasher = MmrTestHasherBlake256::new(); hasher .chain(hash) - .chain(&bitmap.serialize()) + .chain(bitmap.serialize()) .finalize() .as_ref() .to_vec() diff --git a/base_layer/p2p/src/initialization.rs b/base_layer/p2p/src/initialization.rs index 43c6218018..64f190b9d9 100644 --- a/base_layer/p2p/src/initialization.rs +++ b/base_layer/p2p/src/initialization.rs @@ -135,7 +135,7 @@ pub async fn initialize_local_test_comms>( let peer_database_name = { let mut rng = thread_rng(); iter::repeat(()) - .map(|_| rng.sample(Alphanumeric) as char) + .map(|_| rng.sample(Alphanumeric)) .take(8) .collect::() }; diff --git a/base_layer/wallet/src/base_node_service/monitor.rs b/base_layer/wallet/src/base_node_service/monitor.rs index 1504797205..e573fe1f89 100644 --- a/base_layer/wallet/src/base_node_service/monitor.rs +++ b/base_layer/wallet/src/base_node_service/monitor.rs @@ -198,7 +198,7 @@ where async fn update_state(&self, new_state: BaseNodeState) { let mut lock = self.state.write().await; - let (new_block_detected, height) = match (new_state.chain_metadata.clone(), (*lock).chain_metadata.clone()) { + let (new_block_detected, height) = match (new_state.chain_metadata.clone(), lock.chain_metadata.clone()) { (Some(new_metadata), Some(old_metadata)) => ( new_metadata.height_of_longest_chain() != old_metadata.height_of_longest_chain(), new_metadata.height_of_longest_chain(), diff --git a/base_layer/wallet/src/contacts_service/service.rs b/base_layer/wallet/src/contacts_service/service.rs index af5a1248c1..72bf78c3bb 100644 --- a/base_layer/wallet/src/contacts_service/service.rs +++ b/base_layer/wallet/src/contacts_service/service.rs @@ -169,7 +169,7 @@ where T: ContactsBackend + 'static }, Ok(event) = liveness_event_stream.recv() => { - let _result = self.handle_liveness_event(&*event).await.map_err(|e| { + let _result = self.handle_liveness_event(&event).await.map_err(|e| { error!(target: LOG_TARGET, "Failed to handle contact status liveness event: {:?}", e); e }); @@ -306,18 +306,15 @@ where T: ContactsBackend + 'static async fn get_online_status(&self, contact: &Contact) -> Result { let mut online_status = ContactOnlineStatus::NeverSeen; - match self.connectivity.get_peer_info(contact.node_id.clone()).await? { - Some(peer_data) => { - if let Some(banned_until) = peer_data.banned_until() { - let msg = format!( - "Until {} ({})", - banned_until.format("%m-%d %H:%M"), - peer_data.banned_reason - ); - return Ok(ContactOnlineStatus::Banned(msg)); - } - }, - None => {}, + if let Some(peer_data) = self.connectivity.get_peer_info(contact.node_id.clone()).await? { + if let Some(banned_until) = peer_data.banned_until() { + let msg = format!( + "Until {} ({})", + banned_until.format("%m-%d %H:%M"), + peer_data.banned_reason + ); + return Ok(ContactOnlineStatus::Banned(msg)); + } }; if let Some(time) = contact.last_seen { if self.is_online(time) { diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs index 1d75bed0af..6d3b739335 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/mod.rs @@ -160,7 +160,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { }, }, DbKey::UnspentOutputHash(hash) => { - match OutputSql::find_by_hash(hash.as_slice(), OutputStatus::Unspent, &(*conn)) { + match OutputSql::find_by_hash(hash.as_slice(), OutputStatus::Unspent, &conn) { Ok(o) => Some(DbValue::UnspentOutput(Box::new(o.to_db_unblinded_output(&cipher)?))), Err(e) => { match e { diff --git a/base_layer/wallet/src/storage/database.rs b/base_layer/wallet/src/storage/database.rs index c3727bffee..0e19128114 100644 --- a/base_layer/wallet/src/storage/database.rs +++ b/base_layer/wallet/src/storage/database.rs @@ -378,7 +378,7 @@ mod test { fn test_database_crud() { let db_name = format!("{}.sqlite3", string(8).as_str()); let db_folder = tempdir().unwrap().path().to_str().unwrap().to_string(); - let connection = run_migration_and_create_sqlite_connection(&format!("{}{}", db_folder, db_name), 16).unwrap(); + let connection = run_migration_and_create_sqlite_connection(format!("{}{}", db_folder, db_name), 16).unwrap(); let passphrase = SafePassword::from("my secret lovely passphrase"); let db = WalletDatabase::new(WalletSqliteDatabase::new(connection, passphrase).unwrap()); diff --git a/base_layer/wallet/src/storage/sqlite_db/wallet.rs b/base_layer/wallet/src/storage/sqlite_db/wallet.rs index 805878814d..4f1e15917a 100644 --- a/base_layer/wallet/src/storage/sqlite_db/wallet.rs +++ b/base_layer/wallet/src/storage/sqlite_db/wallet.rs @@ -213,15 +213,15 @@ impl WalletSqliteDatabase { match kvp { DbKeyValuePair::MasterSeed(seed) => { kvp_text = "MasterSeed"; - self.set_master_seed(&seed, &(*conn))?; + self.set_master_seed(&seed, &conn)?; }, DbKeyValuePair::TorId(node_id) => { kvp_text = "TorId"; - self.set_tor_id(node_id, &(*conn))?; + self.set_tor_id(node_id, &conn)?; }, DbKeyValuePair::BaseNodeChainMetadata(metadata) => { kvp_text = "BaseNodeChainMetadata"; - self.set_chain_metadata(metadata, &(*conn))?; + self.set_chain_metadata(metadata, &conn)?; }, DbKeyValuePair::ClientKeyValue(k, v) => { // First see if we will overwrite a value so we can return the old value @@ -720,7 +720,7 @@ mod test { let db_name = format!("{}.sqlite3", string(8).as_str()); let tempdir = tempdir().unwrap(); let db_folder = tempdir.path().to_str().unwrap().to_string(); - let connection = run_migration_and_create_sqlite_connection(&format!("{}{}", db_folder, db_name), 16).unwrap(); + let connection = run_migration_and_create_sqlite_connection(format!("{}{}", db_folder, db_name), 16).unwrap(); let secret_seed1 = CipherSeed::new(); { @@ -743,7 +743,7 @@ mod test { let db_name = format!("{}.sqlite3", string(8).as_str()); let db_tempdir = tempdir().unwrap(); let db_folder = db_tempdir.path().to_str().unwrap().to_string(); - let connection = run_migration_and_create_sqlite_connection(&format!("{}{}", db_folder, db_name), 16).unwrap(); + let connection = run_migration_and_create_sqlite_connection(format!("{}{}", db_folder, db_name), 16).unwrap(); let passphrase = SafePassword::from("an example very very secret key.".to_string()); @@ -775,7 +775,7 @@ mod test { let db_tempdir = tempdir().unwrap(); let db_folder = db_tempdir.path().to_str().unwrap().to_string(); let db_path = format!("{}/{}", db_folder, db_name); - let connection = run_migration_and_create_sqlite_connection(&db_path, 16).unwrap(); + let connection = run_migration_and_create_sqlite_connection(db_path, 16).unwrap(); let seed = CipherSeed::new(); let passphrase = "a very very secret key example.".to_string().into(); @@ -857,7 +857,7 @@ mod test { let db_name = format!("{}.sqlite3", string(8).as_str()); let db_tempdir = tempdir().unwrap(); let db_folder = db_tempdir.path().to_str().unwrap().to_string(); - let connection = run_migration_and_create_sqlite_connection(&format!("{}{}", db_folder, db_name), 16).unwrap(); + let connection = run_migration_and_create_sqlite_connection(format!("{}{}", db_folder, db_name), 16).unwrap(); let conn = connection.get_pooled_connection().unwrap(); let key1 = "key1".to_string(); @@ -909,7 +909,7 @@ mod test { let db_name = format!("{}.sqlite3", string(8).as_str()); let db_tempdir = tempdir().unwrap(); let db_folder = db_tempdir.path().to_str().unwrap().to_string(); - let connection = run_migration_and_create_sqlite_connection(&format!("{}{}", db_folder, db_name), 16).unwrap(); + let connection = run_migration_and_create_sqlite_connection(format!("{}{}", db_folder, db_name), 16).unwrap(); let passphrase = SafePassword::from("an example very very secret key.".to_string()); diff --git a/base_layer/wallet/src/test_utils.rs b/base_layer/wallet/src/test_utils.rs index d54e665df4..4b580acc42 100644 --- a/base_layer/wallet/src/test_utils.rs +++ b/base_layer/wallet/src/test_utils.rs @@ -34,10 +34,7 @@ use crate::storage::sqlite_utilities::{ }; pub fn random_string(len: usize) -> String { - iter::repeat(()) - .map(|_| OsRng.sample(Alphanumeric) as char) - .take(len) - .collect() + iter::repeat(()).map(|_| OsRng.sample(Alphanumeric)).take(len).collect() } /// A test helper to create a temporary wallet service databases @@ -54,8 +51,7 @@ pub fn make_wallet_database_connection(path: Option) -> (WalletDbConnect let db_path = Path::new(&path_string).join(db_name); let connection = - run_migration_and_create_sqlite_connection(&db_path.to_str().expect("Should be able to make path"), 16) - .unwrap(); + run_migration_and_create_sqlite_connection(db_path.to_str().expect("Should be able to make path"), 16).unwrap(); (connection, temp_dir) } diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_broadcast_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_broadcast_protocol.rs index a3c9509728..7d1310cb51 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_broadcast_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_broadcast_protocol.rs @@ -323,7 +323,7 @@ where if !(response.is_synced || (response.location == TxLocation::Mined && - response.confirmations >= self.resources.config.num_confirmations_required as u64)) + response.confirmations >= self.resources.config.num_confirmations_required)) { info!( target: LOG_TARGET, diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_validation_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_validation_protocol.rs index e6bbf2a64b..235f778e90 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_validation_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_validation_protocol.rs @@ -101,7 +101,7 @@ where .ok_or(TransactionServiceError::Shutdown) .for_protocol(self.operation_id)?; - self.check_for_reorgs(&mut *base_node_wallet_client).await?; + self.check_for_reorgs(&mut base_node_wallet_client).await?; debug!( target: LOG_TARGET, "Checking if transactions have been mined since last we checked (Operation ID: {})", self.operation_id @@ -116,7 +116,7 @@ where let mut state_changed = false; for batch in unconfirmed_transactions.chunks(self.config.max_tx_query_batch_size) { let (mined, unmined, tip_info) = self - .query_base_node_for_transactions(batch, &mut *base_node_wallet_client) + .query_base_node_for_transactions(batch, &mut base_node_wallet_client) .await .for_protocol(self.operation_id)?; debug!( diff --git a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs index 7239e044fd..7ff7f8a493 100644 --- a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs +++ b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs @@ -922,7 +922,7 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { let conn = self.database_connection.get_pooled_connection()?; let acquire_lock = start.elapsed(); let mut tx_info: Vec = vec![]; - match UnconfirmedTransactionInfoSql::fetch_unconfirmed_transactions_info(&*conn) { + match UnconfirmedTransactionInfoSql::fetch_unconfirmed_transactions_info(&conn) { Ok(info) => { for item in info { tx_info.push(UnconfirmedTransactionInfo::try_from(item)?); @@ -1130,7 +1130,7 @@ impl TryFrom for InboundTransactionSenderInfo { fn try_from(i: InboundTransactionSenderInfoSql) -> Result { Ok(Self { tx_id: TxId::from(i.tx_id as u64), - source_address: TariAddress::from_bytes(&*i.source_address) + source_address: TariAddress::from_bytes(&i.source_address) .map_err(TransactionStorageError::TariAddressError)?, }) } @@ -2227,7 +2227,7 @@ impl UnconfirmedTransactionInfoSql { ) .filter(completed_transactions::cancelled.is_null()) .order_by(completed_transactions::tx_id) - .load::(&*conn)?; + .load::(conn)?; Ok(query_result) } } @@ -3149,7 +3149,7 @@ mod test { for txn in &txn_list { assert!(txn.status == TransactionStatus::Completed || txn.status == TransactionStatus::Broadcast); assert!(txn.cancelled.is_none()); - assert!(txn.coinbase_block_height == None || txn.coinbase_block_height == Some(0)); + assert!(txn.coinbase_block_height.is_none() || txn.coinbase_block_height == Some(0)); } let info_list = db1.get_pending_inbound_transaction_sender_info().unwrap(); diff --git a/base_layer/wallet/src/util/encryption.rs b/base_layer/wallet/src/util/encryption.rs index 0d711fc1cf..d7c40afeb6 100644 --- a/base_layer/wallet/src/util/encryption.rs +++ b/base_layer/wallet/src/util/encryption.rs @@ -181,7 +181,7 @@ mod test { assert!(decrypt_bytes_integral_nonce( &cipher, b"correct_domain".to_vec(), - &ciphertext[0..(size_of::() + size_of::() - 1)].to_vec() + &ciphertext[0..(size_of::() + size_of::() - 1)] ) .is_err()); } diff --git a/base_layer/wallet/tests/support/data.rs b/base_layer/wallet/tests/support/data.rs index a63eeec4fd..86d3909c69 100644 --- a/base_layer/wallet/tests/support/data.rs +++ b/base_layer/wallet/tests/support/data.rs @@ -42,7 +42,7 @@ pub fn clean_up_sql_database(name: &str) { pub fn init_sql_database(name: &str) { clean_up_sql_database(name); let path = get_path(None); - std::fs::create_dir_all(&path).unwrap_or_default(); + std::fs::create_dir_all(path).unwrap_or_default(); } pub fn get_temp_sqlite_database_connection() -> (WalletDbConnection, TempDir) { @@ -51,7 +51,7 @@ pub fn get_temp_sqlite_database_connection() -> (WalletDbConnection, TempDir) { let db_folder = db_tempdir.path().to_str().unwrap().to_string(); let db_path = format!("{}/{}", db_folder, db_name); // let db_path = "/tmp/test.sqlite3".to_string(); - let connection = run_migration_and_create_sqlite_connection(&db_path, 16).unwrap(); + let connection = run_migration_and_create_sqlite_connection(db_path, 16).unwrap(); (connection, db_tempdir) } diff --git a/base_layer/wallet/tests/transaction_service_tests/service.rs b/base_layer/wallet/tests/transaction_service_tests/service.rs index 5ca58a338c..54808ef0c1 100644 --- a/base_layer/wallet/tests/transaction_service_tests/service.rs +++ b/base_layer/wallet/tests/transaction_service_tests/service.rs @@ -576,7 +576,7 @@ async fn manage_single_transaction() { loop { tokio::select! { _event = alice_event_stream.recv() => { - println!("alice: {:?}", &*_event.as_ref().unwrap()); + println!("alice: {:?}", _event.as_ref().unwrap()); count+=1; if count>=2 { break; diff --git a/base_layer/wallet/tests/transaction_service_tests/storage.rs b/base_layer/wallet/tests/transaction_service_tests/storage.rs index 50d37f93a9..108fe7a59c 100644 --- a/base_layer/wallet/tests/transaction_service_tests/storage.rs +++ b/base_layer/wallet/tests/transaction_service_tests/storage.rs @@ -471,7 +471,7 @@ pub fn test_transaction_service_sqlite_db() { let db_tempdir = tempdir().unwrap(); let db_folder = db_tempdir.path().to_str().unwrap().to_string(); let db_path = format!("{}/{}", db_folder, db_name); - let connection = run_migration_and_create_sqlite_connection(&db_path, 16).unwrap(); + let connection = run_migration_and_create_sqlite_connection(db_path, 16).unwrap(); let mut key = [0u8; size_of::()]; OsRng.fill_bytes(&mut key); @@ -487,7 +487,7 @@ async fn import_tx_and_read_it_from_db() { let db_tempdir = tempdir().unwrap(); let db_folder = db_tempdir.path().to_str().unwrap().to_string(); let db_path = format!("{}/{}", db_folder, db_name); - let connection = run_migration_and_create_sqlite_connection(&db_path, 16).unwrap(); + let connection = run_migration_and_create_sqlite_connection(db_path, 16).unwrap(); let mut key = [0u8; size_of::()]; OsRng.fill_bytes(&mut key); diff --git a/base_layer/wallet/tests/transaction_service_tests/transaction_protocols.rs b/base_layer/wallet/tests/transaction_service_tests/transaction_protocols.rs index 83868aae16..8b5b3d4219 100644 --- a/base_layer/wallet/tests/transaction_service_tests/transaction_protocols.rs +++ b/base_layer/wallet/tests/transaction_service_tests/transaction_protocols.rs @@ -129,7 +129,7 @@ pub async fn setup() -> ( let db_name = format!("{}.sqlite3", random::string(8).as_str()); let temp_dir = tempdir().unwrap(); let db_folder = temp_dir.path().to_str().unwrap().to_string(); - let db_connection = run_migration_and_create_sqlite_connection(&format!("{}/{}", db_folder, db_name), 16).unwrap(); + let db_connection = run_migration_and_create_sqlite_connection(format!("{}/{}", db_folder, db_name), 16).unwrap(); let mut key = [0u8; size_of::()]; OsRng.fill_bytes(&mut key); diff --git a/base_layer/wallet/tests/utxo_scanner.rs b/base_layer/wallet/tests/utxo_scanner.rs index 05711a13d4..4b96dce0f6 100644 --- a/base_layer/wallet/tests/utxo_scanner.rs +++ b/base_layer/wallet/tests/utxo_scanner.rs @@ -158,7 +158,7 @@ async fn setup( let db_path = format!("{}/{}", path_string, db_name); // let db_path = "/tmp/test.sqlite3"; - let db_connection = run_migration_and_create_sqlite_connection(&db_path, 16).unwrap(); + let db_connection = run_migration_and_create_sqlite_connection(db_path, 16).unwrap(); let passphrase = SafePassword::from("my lovely secret passphrase"); WalletDatabase::new( diff --git a/base_layer/wallet_ffi/build.rs b/base_layer/wallet_ffi/build.rs index 3e0fe81495..1bf022af33 100644 --- a/base_layer/wallet_ffi/build.rs +++ b/base_layer/wallet_ffi/build.rs @@ -42,7 +42,7 @@ fn main() { cbindgen::generate_with_config(&crate_dir, config) .unwrap() - .write_to_file(&output_file); + .write_to_file(output_file); } // /// Find the location of the `target/` directory. Note that this may be diff --git a/base_layer/wallet_ffi/src/callback_handler_tests.rs b/base_layer/wallet_ffi/src/callback_handler_tests.rs index 8ac19dc8fc..e990387372 100644 --- a/base_layer/wallet_ffi/src/callback_handler_tests.rs +++ b/base_layer/wallet_ffi/src/callback_handler_tests.rs @@ -124,56 +124,56 @@ mod test { let mut lock = CALLBACK_STATE.lock().unwrap(); lock.received_tx_callback_called = true; drop(lock); - Box::from_raw(tx); + drop(Box::from_raw(tx)) } unsafe extern "C" fn received_tx_reply_callback(tx: *mut CompletedTransaction) { let mut lock = CALLBACK_STATE.lock().unwrap(); lock.received_tx_reply_callback_called = true; drop(lock); - Box::from_raw(tx); + drop(Box::from_raw(tx)) } unsafe extern "C" fn received_tx_finalized_callback(tx: *mut CompletedTransaction) { let mut lock = CALLBACK_STATE.lock().unwrap(); lock.received_finalized_tx_callback_called = true; drop(lock); - Box::from_raw(tx); + drop(Box::from_raw(tx)) } unsafe extern "C" fn broadcast_callback(tx: *mut CompletedTransaction) { let mut lock = CALLBACK_STATE.lock().unwrap(); lock.broadcast_tx_callback_called = true; drop(lock); - Box::from_raw(tx); + drop(Box::from_raw(tx)) } unsafe extern "C" fn mined_callback(tx: *mut CompletedTransaction) { let mut lock = CALLBACK_STATE.lock().unwrap(); lock.mined_tx_callback_called = true; drop(lock); - Box::from_raw(tx); + drop(Box::from_raw(tx)) } unsafe extern "C" fn mined_unconfirmed_callback(tx: *mut CompletedTransaction, confirmations: u64) { let mut lock = CALLBACK_STATE.lock().unwrap(); lock.mined_tx_unconfirmed_callback_called = confirmations; drop(lock); - Box::from_raw(tx); + drop(Box::from_raw(tx)) } unsafe extern "C" fn faux_confirmed_callback(tx: *mut CompletedTransaction) { let mut lock = CALLBACK_STATE.lock().unwrap(); lock.faux_tx_confirmed_callback_called = true; drop(lock); - Box::from_raw(tx); + drop(Box::from_raw(tx)) } unsafe extern "C" fn faux_unconfirmed_callback(tx: *mut CompletedTransaction, confirmations: u64) { let mut lock = CALLBACK_STATE.lock().unwrap(); lock.faux_tx_unconfirmed_callback_called = confirmations; drop(lock); - Box::from_raw(tx); + drop(Box::from_raw(tx)) } unsafe extern "C" fn transaction_send_result_callback(_tx_id: u64, status: *mut TransactionSendStatus) { @@ -205,7 +205,7 @@ mod test { _ => (), } drop(lock); - Box::from_raw(tx); + drop(Box::from_raw(tx)) } unsafe extern "C" fn txo_validation_complete_callback(_tx_id: u64, result: u64) { @@ -230,7 +230,7 @@ mod test { let mut lock = CALLBACK_STATE.lock().unwrap(); lock.callback_balance_updated += 1; drop(lock); - Box::from_raw(balance); + drop(Box::from_raw(balance)); } unsafe extern "C" fn transaction_validation_complete_callback(request_key: u64, result: u64) { diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index ba9d9c0e4f..ca6cbd10ce 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -731,7 +731,7 @@ pub unsafe extern "C" fn transaction_kernel_get_excess_signature_hex( #[no_mangle] pub unsafe extern "C" fn transaction_kernel_destroy(x: *mut TariTransactionKernel) { if !x.is_null() { - Box::from_raw(x); + drop(Box::from_raw(x)) } } @@ -792,7 +792,7 @@ pub unsafe extern "C" fn byte_vector_create( #[no_mangle] pub unsafe extern "C" fn byte_vector_destroy(bytes: *mut ByteVector) { if !bytes.is_null() { - Box::from_raw(bytes); + drop(Box::from_raw(bytes)) } } @@ -904,7 +904,7 @@ pub unsafe extern "C" fn public_key_create(bytes: *mut ByteVector, error_out: *m #[no_mangle] pub unsafe extern "C" fn public_key_destroy(pk: *mut TariPublicKey) { if !pk.is_null() { - Box::from_raw(pk); + drop(Box::from_raw(pk)) } } @@ -921,7 +921,7 @@ pub unsafe extern "C" fn public_key_destroy(pk: *mut TariPublicKey) { #[no_mangle] pub unsafe extern "C" fn public_keys_destroy(pks: *mut TariPublicKeys) { if !pks.is_null() { - Box::from_raw(pks); + drop(Box::from_raw(pks)) } } @@ -1078,7 +1078,7 @@ pub unsafe extern "C" fn tari_address_create(bytes: *mut ByteVector, error_out: #[no_mangle] pub unsafe extern "C" fn tari_address_destroy(address: *mut TariWalletAddress) { if !address.is_null() { - Box::from_raw(address); + drop(Box::from_raw(address)) } } @@ -1410,7 +1410,7 @@ pub unsafe extern "C" fn commitment_and_public_signature_create_from_bytes( #[no_mangle] pub unsafe extern "C" fn commitment_and_public_signature_destroy(compub_sig: *mut TariComAndPubSignature) { if !compub_sig.is_null() { - Box::from_raw(compub_sig); + drop(Box::from_raw(compub_sig)) } } @@ -1585,7 +1585,7 @@ pub unsafe extern "C" fn create_tari_unblinded_output( #[no_mangle] pub unsafe extern "C" fn tari_unblinded_output_destroy(output: *mut TariUnblindedOutput) { if !output.is_null() { - Box::from_raw(output); + drop(Box::from_raw(output)) } } @@ -1805,7 +1805,7 @@ pub unsafe extern "C" fn unblinded_outputs_received_tx_id_get_at( #[no_mangle] pub unsafe extern "C" fn unblinded_outputs_destroy(outputs: *mut TariUnblindedOutputs) { if !outputs.is_null() { - Box::from_raw(outputs); + drop(Box::from_raw(outputs)) } } @@ -1990,7 +1990,7 @@ pub unsafe extern "C" fn private_key_create(bytes: *mut ByteVector, error_out: * #[no_mangle] pub unsafe extern "C" fn private_key_destroy(pk: *mut TariPrivateKey) { if !pk.is_null() { - Box::from_raw(pk); + drop(Box::from_raw(pk)) } } @@ -2140,7 +2140,7 @@ pub unsafe extern "C" fn covenant_create_from_bytes( #[no_mangle] pub unsafe extern "C" fn covenant_destroy(covenant: *mut TariCovenant) { if !covenant.is_null() { - Box::from_raw(covenant); + drop(Box::from_raw(covenant)) } } @@ -2312,7 +2312,7 @@ pub unsafe extern "C" fn output_features_create_from_bytes( #[no_mangle] pub unsafe extern "C" fn output_features_destroy(output_features: *mut TariOutputFeatures) { if !output_features.is_null() { - Box::from_raw(output_features); + drop(Box::from_raw(output_features)) } } @@ -2625,7 +2625,7 @@ pub unsafe extern "C" fn seed_words_push_word( #[no_mangle] pub unsafe extern "C" fn seed_words_destroy(seed_words: *mut TariSeedWords) { if !seed_words.is_null() { - Box::from_raw(seed_words); + drop(Box::from_raw(seed_words)) } } @@ -2757,7 +2757,7 @@ pub unsafe extern "C" fn contact_get_tari_address( #[no_mangle] pub unsafe extern "C" fn contact_destroy(contact: *mut TariContact) { if !contact.is_null() { - Box::from_raw(contact); + drop(Box::from_raw(contact)) } } @@ -2840,7 +2840,7 @@ pub unsafe extern "C" fn contacts_get_at( #[no_mangle] pub unsafe extern "C" fn contacts_destroy(contacts: *mut TariContacts) { if !contacts.is_null() { - Box::from_raw(contacts); + drop(Box::from_raw(contacts)) } } @@ -3048,7 +3048,7 @@ pub unsafe extern "C" fn liveness_data_get_online_status( #[no_mangle] pub unsafe extern "C" fn liveness_data_destroy(liveness_data: *mut TariContactsLivenessData) { if !liveness_data.is_null() { - Box::from_raw(liveness_data); + drop(Box::from_raw(liveness_data)) } } /// -------------------------------------------------------------------------------------------- /// @@ -3135,7 +3135,7 @@ pub unsafe extern "C" fn completed_transactions_get_at( #[no_mangle] pub unsafe extern "C" fn completed_transactions_destroy(transactions: *mut TariCompletedTransactions) { if !transactions.is_null() { - Box::from_raw(transactions); + drop(Box::from_raw(transactions)) } } @@ -3224,7 +3224,7 @@ pub unsafe extern "C" fn pending_outbound_transactions_get_at( #[no_mangle] pub unsafe extern "C" fn pending_outbound_transactions_destroy(transactions: *mut TariPendingOutboundTransactions) { if !transactions.is_null() { - Box::from_raw(transactions); + drop(Box::from_raw(transactions)) } } @@ -3312,7 +3312,7 @@ pub unsafe extern "C" fn pending_inbound_transactions_get_at( #[no_mangle] pub unsafe extern "C" fn pending_inbound_transactions_destroy(transactions: *mut TariPendingInboundTransactions) { if !transactions.is_null() { - Box::from_raw(transactions); + drop(Box::from_raw(transactions)) } } @@ -3827,7 +3827,7 @@ pub unsafe extern "C" fn create_tari_completed_transaction_from_json( #[no_mangle] pub unsafe extern "C" fn completed_transaction_destroy(transaction: *mut TariCompletedTransaction) { if !transaction.is_null() { - Box::from_raw(transaction); + drop(Box::from_raw(transaction)) } } @@ -4061,7 +4061,7 @@ pub unsafe extern "C" fn pending_outbound_transaction_get_status( #[no_mangle] pub unsafe extern "C" fn pending_outbound_transaction_destroy(transaction: *mut TariPendingOutboundTransaction) { if !transaction.is_null() { - Box::from_raw(transaction); + drop(Box::from_raw(transaction)) } } @@ -4269,7 +4269,7 @@ pub unsafe extern "C" fn pending_inbound_transaction_get_status( #[no_mangle] pub unsafe extern "C" fn pending_inbound_transaction_destroy(transaction: *mut TariPendingInboundTransaction) { if !transaction.is_null() { - Box::from_raw(transaction); + drop(Box::from_raw(transaction)) } } @@ -4342,7 +4342,7 @@ pub unsafe extern "C" fn transaction_send_status_decode( #[no_mangle] pub unsafe extern "C" fn transaction_send_status_destroy(status: *mut TariTransactionSendStatus) { if !status.is_null() { - Box::from_raw(status); + drop(Box::from_raw(status)) } } @@ -4629,7 +4629,7 @@ pub unsafe extern "C" fn transport_type_destroy(transport: *mut TariTransportCon #[no_mangle] pub unsafe extern "C" fn transport_config_destroy(transport: *mut TariTransportConfig) { if !transport.is_null() { - Box::from_raw(transport); + drop(Box::from_raw(transport)) } } @@ -4801,7 +4801,7 @@ pub unsafe extern "C" fn comms_config_create( #[no_mangle] pub unsafe extern "C" fn comms_config_destroy(wc: *mut TariCommsConfig) { if !wc.is_null() { - Box::from_raw(wc); + drop(Box::from_raw(wc)) } } @@ -4852,6 +4852,65 @@ pub unsafe extern "C" fn comms_list_connected_public_keys( } } +/// Gets the length of the public keys vector +/// +/// ## Arguments +/// `public_keys` - Pointer to TariPublicKeys +/// +/// ## Returns +/// `c_uint` - Length of the TariPublicKeys vector, 0 if is null +/// +/// # Safety +/// None +#[no_mangle] +pub unsafe extern "C" fn public_keys_get_length(public_keys: *const TariPublicKeys, error_out: *mut c_int) -> c_uint { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + if public_keys.is_null() { + error = LibWalletError::from(InterfaceError::NullError("public_keys".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 0; + } + (*public_keys).0.len() as c_uint +} + +/// Gets a ByteVector at position in a EmojiSet +/// +/// ## Arguments +/// `public_keys` - The pointer to a TariPublicKeys +/// `position` - The integer position +/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions +/// as an out parameter. +/// +/// ## Returns +/// `ByteVector` - Returns a ByteVector. Note that the ByteVector will be null if ptr +/// is null or if the position is invalid +/// +/// # Safety +/// The ```byte_vector_destroy``` function must be called when finished with the ByteVector to prevent a memory leak. +#[no_mangle] +pub unsafe extern "C" fn public_keys_get_at( + public_keys: *const TariPublicKeys, + position: c_uint, + error_out: *mut c_int, +) -> *mut TariPublicKey { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + if public_keys.is_null() { + error = LibWalletError::from(InterfaceError::NullError("public_keys".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } + let last_index = public_keys_get_length(public_keys, error_out) - 1; + if position > last_index { + error = LibWalletError::from(InterfaceError::PositionInvalidError).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } + let result = (*public_keys).0[position as usize].clone(); + Box::into_raw(Box::new(result)) +} + /// ---------------------------------------------------------------------------------------------- /// /// ------------------------------------- Wallet -------------------------------------------------/// @@ -5132,7 +5191,7 @@ pub unsafe extern "C" fn wallet_create( .expect("A non-null network should be able to be converted to string"); info!(target: LOG_TARGET, "network set to {}", network); // eprintln!("network set to {}", network); - match Network::from_str(&*network) { + match Network::from_str(network) { Ok(n) => n, Err(_) => { error = LibWalletError::from(InterfaceError::InvalidArgument("network".to_string())).code; @@ -5975,7 +6034,7 @@ pub unsafe extern "C" fn wallet_verify_message_signature( return result; } - if let Some(key1) = hex_keys.get(0) { + if let Some(key1) = hex_keys.first() { if let Some(key2) = hex_keys.get(1) { let secret = TariPrivateKey::from_hex(key1); match secret { @@ -6292,7 +6351,7 @@ pub unsafe extern "C" fn balance_get_pending_outgoing(balance: *mut TariBalance, #[no_mangle] pub unsafe extern "C" fn balance_destroy(balance: *mut TariBalance) { if !balance.is_null() { - Box::from_raw(balance); + drop(Box::from_raw(balance)) } } @@ -7989,7 +8048,7 @@ pub unsafe extern "C" fn emoji_set_get_at( #[no_mangle] pub unsafe extern "C" fn emoji_set_destroy(emoji_set: *mut EmojiSet) { if !emoji_set.is_null() { - Box::from_raw(emoji_set); + drop(Box::from_raw(emoji_set)) } } @@ -8172,7 +8231,7 @@ pub unsafe extern "C" fn fee_per_gram_stats_get_at( #[no_mangle] pub unsafe extern "C" fn fee_per_gram_stats_destroy(fee_per_gram_stats: *mut TariFeePerGramStats) { if !fee_per_gram_stats.is_null() { - Box::from_raw(fee_per_gram_stats); + drop(Box::from_raw(fee_per_gram_stats)) } } @@ -8309,7 +8368,7 @@ pub unsafe extern "C" fn fee_per_gram_stat_get_max_fee_per_gram( #[no_mangle] pub unsafe extern "C" fn fee_per_gram_stat_destroy(fee_per_gram_stat: *mut TariFeePerGramStat) { if !fee_per_gram_stat.is_null() { - Box::from_raw(fee_per_gram_stat); + drop(Box::from_raw(fee_per_gram_stat)) } } @@ -8951,7 +9010,7 @@ mod test { assert_eq!((*output_features).version, OutputFeaturesVersion::V1); assert_eq!( (*output_features).output_type, - OutputType::from_byte(output_type as u8).unwrap() + OutputType::from_byte(output_type).unwrap() ); assert_eq!((*output_features).maturity, maturity); assert_eq!((*output_features).coinbase_extra, expected_metadata); @@ -9218,7 +9277,7 @@ mod test { let sql_database_path = alice_temp_dir.path().join("backup").with_extension("sqlite3"); let connection = - run_migration_and_create_sqlite_connection(&sql_database_path, 16).expect("Could not open Sqlite db"); + run_migration_and_create_sqlite_connection(sql_database_path, 16).expect("Could not open Sqlite db"); let wallet_backend = WalletDatabase::new(WalletSqliteDatabase::new(connection, "holiday".to_string().into()).unwrap()); @@ -10481,7 +10540,7 @@ mod test { let amount = utxo_1.value.as_u64(); let spending_key_ptr = Box::into_raw(Box::new(utxo_1.spending_key.clone())); let features_ptr = Box::into_raw(Box::new(utxo_1.features.clone())); - let source_address_ptr = Box::into_raw(Box::new(TariWalletAddress::default())); + let source_address_ptr = Box::into_raw(Box::default()); let metadata_signature_ptr = Box::into_raw(Box::new(utxo_1.metadata_signature.clone())); let sender_offset_public_key_ptr = Box::into_raw(Box::new(utxo_1.sender_offset_public_key.clone())); let script_private_key_ptr = Box::into_raw(Box::new(utxo_1.script_private_key.clone())); @@ -10565,7 +10624,7 @@ mod test { let amount = utxo_1.value.as_u64(); let spending_key_ptr = Box::into_raw(Box::new(utxo_1.spending_key.clone())); let features_ptr = Box::into_raw(Box::new(utxo_1.features.clone())); - let source_address_ptr = Box::into_raw(Box::new(TariWalletAddress::default())); + let source_address_ptr = Box::into_raw(Box::::default()); let metadata_signature_ptr = Box::into_raw(Box::new(utxo_1.metadata_signature.clone())); let sender_offset_public_key_ptr = Box::into_raw(Box::new(utxo_1.sender_offset_public_key.clone())); let script_private_key_ptr = Box::into_raw(Box::new(utxo_1.script_private_key.clone())); diff --git a/base_layer/wallet_ffi/wallet.h b/base_layer/wallet_ffi/wallet.h index 573ad59ba3..cbae1a0de6 100644 --- a/base_layer/wallet_ffi/wallet.h +++ b/base_layer/wallet_ffi/wallet.h @@ -2528,6 +2528,40 @@ void comms_config_destroy(TariCommsConfig *wc); struct TariPublicKeys *comms_list_connected_public_keys(struct TariWallet *wallet, int *error_out); +/** + * Gets the length of the public keys vector + * + * ## Arguments + * `public_keys` - Pointer to TariPublicKeys + * + * ## Returns + * `c_uint` - Length of the TariPublicKeys vector, 0 if is null + * + * # Safety + * None + */ +unsigned int public_keys_get_length(const struct TariPublicKeys *public_keys, int *error_out); + +/** + * Gets a ByteVector at position in a EmojiSet + * + * ## Arguments + * `public_keys` - The pointer to a TariPublicKeys + * `position` - The integer position + * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions + * as an out parameter. + * + * ## Returns + * `ByteVector` - Returns a ByteVector. Note that the ByteVector will be null if ptr + * is null or if the position is invalid + * + * # Safety + * The ```byte_vector_destroy``` function must be called when finished with the ByteVector to prevent a memory leak. + */ +TariPublicKey *public_keys_get_at(const struct TariPublicKeys *public_keys, + unsigned int position, + int *error_out); + /** * Creates a TariWallet * diff --git a/common/src/build/application.rs b/common/src/build/application.rs index 832daa65de..39bddc484e 100644 --- a/common/src/build/application.rs +++ b/common/src/build/application.rs @@ -125,7 +125,7 @@ fn find_git_root() -> Result { fn get_commit() -> Result { let git_root = find_git_root()?; - let repo = git2::Repository::open(&git_root)?; + let repo = git2::Repository::open(git_root)?; let head = repo.revparse_single("HEAD")?; let id = format!("{:?}", head.id()); id.split_at(7).0.to_string(); diff --git a/common/src/build/protobuf.rs b/common/src/build/protobuf.rs index 48d1ba381f..5d68b78746 100644 --- a/common/src/build/protobuf.rs +++ b/common/src/build/protobuf.rs @@ -152,7 +152,7 @@ impl ProtobufCompiler { self.include_paths .iter() .fold(Vec::with_capacity(self.include_paths.len()), |mut protos, path| { - protos.extend(walk_files(&path, "proto")); + protos.extend(walk_files(path, "proto")); protos }); @@ -160,7 +160,7 @@ impl ProtobufCompiler { .proto_paths .iter() .fold(Vec::with_capacity(self.proto_paths.len()), |mut protos, path| { - protos.extend(walk_files(&path, "proto")); + protos.extend(walk_files(path, "proto")); protos }); diff --git a/common/src/configuration/common_config.rs b/common/src/configuration/common_config.rs index 3e093d9ff6..df7291c8c7 100644 --- a/common/src/configuration/common_config.rs +++ b/common/src/configuration/common_config.rs @@ -30,7 +30,7 @@ use crate::SubConfigPath; #[serde(deny_unknown_fields)] pub struct CommonConfig { override_from: Option, - base_path: PathBuf, + pub base_path: PathBuf, } impl Default for CommonConfig { diff --git a/common/src/configuration/loader.rs b/common/src/configuration/loader.rs index 862a8b763f..3e82b495be 100644 --- a/common/src/configuration/loader.rs +++ b/common/src/configuration/loader.rs @@ -255,7 +255,10 @@ pub trait ConfigLoader: ConfigPath + Sized { /// config.set("my_node.goodbye_message", "see you later"); /// let my_config = MyNodeConfig::load_from(&config).unwrap(); /// assert_eq!(my_config.goodbye_message, "see you later".to_string()); -/// assert_eq!(my_config.welcome_message, MyNodeConfig::default().welcome_message); +/// assert_eq!( +/// my_config.welcome_message, +/// MyNodeConfig::default().welcome_message +/// ); /// ``` pub trait DefaultConfigLoader: ConfigPath + Sized { /// Try to load configuration from supplied Config by `main_key_prefix()` diff --git a/common/src/configuration/network.rs b/common/src/configuration/network.rs index b587dc440a..2b2e0aba6a 100644 --- a/common/src/configuration/network.rs +++ b/common/src/configuration/network.rs @@ -94,7 +94,7 @@ impl FromStr for Network { invalid => Err(ConfigurationError::new( "network", Some(value.to_string()), - &format!("Invalid network option: {}", invalid), + format!("Invalid network option: {}", invalid), )), } } @@ -129,7 +129,7 @@ impl TryFrom for Network { _ => Err(ConfigurationError::new( "network", Some(v.to_string()), - &format!("Invalid network option: {}", v), + format!("Invalid network option: {}", v), )), } } diff --git a/comms/core/src/connection_manager/tests/manager.rs b/comms/core/src/connection_manager/tests/manager.rs index c3b693d06b..8dc084ec24 100644 --- a/comms/core/src/connection_manager/tests/manager.rs +++ b/comms/core/src/connection_manager/tests/manager.rs @@ -445,6 +445,6 @@ async fn dial_cancelled() { assert_eq!(events1.len(), 1); unpack_enum!(ConnectionManagerEvent::PeerConnectFailed(node_id, err) = &*events1[0]); - assert_eq!(&*node_id, node_identity2.node_id()); + assert_eq!(node_id, node_identity2.node_id()); unpack_enum!(ConnectionManagerError::DialCancelled = err); } diff --git a/comms/core/src/connectivity/connection_pool.rs b/comms/core/src/connectivity/connection_pool.rs index 4ef6a53f82..fb8fe017c5 100644 --- a/comms/core/src/connectivity/connection_pool.rs +++ b/comms/core/src/connectivity/connection_pool.rs @@ -181,7 +181,7 @@ impl ConnectionPool { where P: FnMut(&PeerConnectionState) -> bool { self.connections .values() - .filter(|c| (predicate)(*c)) + .filter(|c| (predicate)(c)) .filter_map(|c| c.connection()) .collect() } @@ -191,7 +191,7 @@ impl ConnectionPool { self.connections .values_mut() .filter_map(|c| c.connection_mut()) - .filter(|c| (predicate)(*c)) + .filter(|c| (predicate)(c)) .collect() } @@ -246,6 +246,6 @@ impl ConnectionPool { pub(in crate::connectivity) fn count_filtered

(&self, mut predicate: P) -> usize where P: FnMut(&PeerConnectionState) -> bool { - self.connections.values().filter(|c| (predicate)(*c)).count() + self.connections.values().filter(|c| (predicate)(c)).count() } } diff --git a/comms/core/src/connectivity/manager.rs b/comms/core/src/connectivity/manager.rs index a9e916a842..b356fed12c 100644 --- a/comms/core/src/connectivity/manager.rs +++ b/comms/core/src/connectivity/manager.rs @@ -403,7 +403,7 @@ impl ConnectivityManagerActor { let mut connections = self .pool .get_inactive_outbound_connections_mut(self.config.reaper_min_inactive_age); - connections.truncate(excess_connections as usize); + connections.truncate(excess_connections); for conn in connections { if !conn.is_connected() { continue; @@ -572,7 +572,7 @@ impl ConnectivityManagerActor { } let (node_id, mut new_status, connection) = match event { - PeerDisconnected(_, node_id) => (&*node_id, ConnectionStatus::Disconnected, None), + PeerDisconnected(_, node_id) => (node_id, ConnectionStatus::Disconnected, None), PeerConnected(conn) => (conn.peer_node_id(), ConnectionStatus::Connected, Some(conn.clone())), PeerConnectFailed(node_id, ConnectionManagerError::DialCancelled) => { @@ -590,7 +590,7 @@ impl ConnectivityManagerActor { target: LOG_TARGET, "Dial was cancelled before connection completed to peer '{}'", node_id ); - (&*node_id, ConnectionStatus::Failed, None) + (node_id, ConnectionStatus::Failed, None) }, PeerConnectFailed(node_id, err) => { debug!( @@ -598,7 +598,7 @@ impl ConnectivityManagerActor { "Connection to peer '{}' failed because '{:?}'", node_id, err ); self.on_peer_connection_failure(node_id).await?; - (&*node_id, ConnectionStatus::Failed, None) + (node_id, ConnectionStatus::Failed, None) }, _ => return Ok(()), }; diff --git a/comms/core/src/memsocket/mod.rs b/comms/core/src/memsocket/mod.rs index cc05feb692..fae585a218 100644 --- a/comms/core/src/memsocket/mod.rs +++ b/comms/core/src/memsocket/mod.rs @@ -65,7 +65,7 @@ impl Slot { struct SwitchBoard(HashMap>>, u16); pub fn acquire_next_memsocket_port() -> NonZeroU16 { - let mut switchboard = (&*SWITCHBOARD).lock().unwrap(); + let mut switchboard = (*SWITCHBOARD).lock().unwrap(); let port = loop { let port = NonZeroU16::new(switchboard.1).unwrap_or_else(|| unreachable!()); @@ -93,7 +93,7 @@ pub fn acquire_next_memsocket_port() -> NonZeroU16 { } pub fn release_memsocket_port(port: NonZeroU16) { - let mut switchboard = (&*SWITCHBOARD).lock().unwrap(); + let mut switchboard = (*SWITCHBOARD).lock().unwrap(); if let Entry::Occupied(entry) = switchboard.0.entry(port) { match *entry.get() { Slot::Acquired => { @@ -149,7 +149,7 @@ pub struct MemoryListener { impl Drop for MemoryListener { fn drop(&mut self) { - let mut switchboard = (&*SWITCHBOARD).lock().unwrap(); + let mut switchboard = (*SWITCHBOARD).lock().unwrap(); // Remove the Sending side of the channel in the switchboard when // MemoryListener is dropped switchboard.0.remove(&self.port); @@ -179,7 +179,7 @@ impl MemoryListener { /// /// [`local_addr`]: #method.local_addr pub fn bind(port: u16) -> io::Result { - let mut switchboard = (&*SWITCHBOARD).lock().unwrap(); + let mut switchboard = (*SWITCHBOARD).lock().unwrap(); // Get the port we should bind to. If 0 was given, use a random port let port = if let Some(port) = NonZeroU16::new(port) { @@ -381,7 +381,7 @@ impl MemorySocket { /// # Ok(())} /// ``` pub fn connect(port: u16) -> io::Result { - let mut switchboard = (&*SWITCHBOARD).lock().unwrap(); + let mut switchboard = (*SWITCHBOARD).lock().unwrap(); // Find port to connect to let port = NonZeroU16::new(port).ok_or(ErrorKind::AddrNotAvailable)?; diff --git a/comms/core/src/noise/config.rs b/comms/core/src/noise/config.rs index 63e893725e..be7d65957e 100644 --- a/comms/core/src/noise/config.rs +++ b/comms/core/src/noise/config.rs @@ -72,9 +72,8 @@ impl NoiseConfig { TSocket: AsyncWrite + AsyncRead + Unpin, { let handshake_state = { - let builder = - snow::Builder::with_resolver(self.parameters.clone(), Box::new(TariCryptoResolver::default())) - .local_private_key(self.node_identity.secret_key().as_bytes()); + let builder = snow::Builder::with_resolver(self.parameters.clone(), Box::::default()) + .local_private_key(self.node_identity.secret_key().as_bytes()); match direction { ConnectionDirection::Outbound => { diff --git a/comms/core/src/noise/crypto_resolver.rs b/comms/core/src/noise/crypto_resolver.rs index d8df16b8a1..8f94543a4b 100644 --- a/comms/core/src/noise/crypto_resolver.rs +++ b/comms/core/src/noise/crypto_resolver.rs @@ -54,7 +54,7 @@ impl CryptoResolver for TariCryptoResolver { fn resolve_dh(&self, choice: &DHChoice) -> Option> { match *choice { - DHChoice::Curve25519 => Some(Box::new(CommsDiffieHellman::default())), + DHChoice::Curve25519 => Some(Box::::default()), _ => None, } } @@ -137,7 +137,7 @@ mod test { fn build_keypair() -> Keypair { snow::Builder::with_resolver( NOISE_IX_PARAMETER.parse().unwrap(), - Box::new(TariCryptoResolver::default()), + Box::::default(), ) .generate_keypair() .unwrap() diff --git a/comms/core/src/noise/socket.rs b/comms/core/src/noise/socket.rs index 62155f5c2d..11875503e6 100644 --- a/comms/core/src/noise/socket.rs +++ b/comms/core/src/noise/socket.rs @@ -322,7 +322,7 @@ where TSocket: AsyncRead + Unpin decrypted_len, ref mut offset, } => { - let bytes_to_copy = cmp::min(decrypted_len as usize - *offset, buf.len()); + let bytes_to_copy = cmp::min(decrypted_len - *offset, buf.len()); buf[..bytes_to_copy] .copy_from_slice(&self.buffers.read_decrypted[*offset..(*offset + bytes_to_copy)]); trace!( @@ -332,7 +332,7 @@ where TSocket: AsyncRead + Unpin decrypted_len ); *offset += bytes_to_copy; - if *offset == decrypted_len as usize { + if *offset == decrypted_len { self.read_state = ReadState::Init; } return Poll::Ready(Ok(bytes_to_copy)); diff --git a/comms/core/src/peer_manager/identity_signature.rs b/comms/core/src/peer_manager/identity_signature.rs index 512116e301..de867f9b17 100644 --- a/comms/core/src/peer_manager/identity_signature.rs +++ b/comms/core/src/peer_manager/identity_signature.rs @@ -148,7 +148,7 @@ impl IdentitySignature { .chain(features.bits().to_le_bytes()); addresses .into_iter() - .fold(challenge, |challenge, addr| challenge.chain(&addr)) + .fold(challenge, |challenge, addr| challenge.chain(addr)) } pub fn to_bytes(&self) -> Vec { diff --git a/comms/core/src/protocol/identity.rs b/comms/core/src/protocol/identity.rs index 582d723894..cf03fcfcba 100644 --- a/comms/core/src/protocol/identity.rs +++ b/comms/core/src/protocol/identity.rs @@ -73,7 +73,7 @@ where } .to_encoded_bytes(); - write_protocol_frame(socket, network_info.major_version as u8, &msg_bytes).await?; + write_protocol_frame(socket, network_info.major_version, &msg_bytes).await?; socket.flush().await?; // Receive the connecting node's identity diff --git a/comms/core/src/protocol/protocols.rs b/comms/core/src/protocol/protocols.rs index e6271a4d31..29a74f97f4 100644 --- a/comms/core/src/protocol/protocols.rs +++ b/comms/core/src/protocol/protocols.rs @@ -128,7 +128,7 @@ impl Protocols { /// Returns an iterator of currently registered [ProtocolId](self::ProtocolId) pub fn iter(&self) -> impl Iterator { - self.protocols.iter().map(|(protocol_id, _)| protocol_id) + self.protocols.keys() } } diff --git a/comms/core/src/socks/client.rs b/comms/core/src/socks/client.rs index 776db37f40..d2c728c2ef 100644 --- a/comms/core/src/socks/client.rs +++ b/comms/core/src/socks/client.rs @@ -325,7 +325,7 @@ where TSocket: AsyncRead + AsyncWrite + Unpin }, // Domain 0x03 => { - let domain_bytes = (&self.buf[5..(self.len - 2)]).to_vec(); + let domain_bytes = (self.buf[5..(self.len - 2)]).to_vec(); let domain = String::from_utf8(domain_bytes) .map_err(|_| SocksError::InvalidTargetAddress("domain bytes are not a valid UTF-8 string"))?; let mut addr: Multiaddr = Protocol::Dns4(Cow::Owned(domain)).into(); diff --git a/comms/core/src/test_utils/mocks/connectivity_manager.rs b/comms/core/src/test_utils/mocks/connectivity_manager.rs index 9f89e6a34e..052d0497a8 100644 --- a/comms/core/src/test_utils/mocks/connectivity_manager.rs +++ b/comms/core/src/test_utils/mocks/connectivity_manager.rs @@ -194,7 +194,7 @@ impl ConnectivityManagerMockState { pub(self) async fn with_state(&self, f: F) -> R where F: FnOnce(&mut State) -> R { let mut lock = self.inner.lock().await; - (f)(&mut *lock) + (f)(&mut lock) } } diff --git a/comms/core/src/test_utils/peer_manager.rs b/comms/core/src/test_utils/peer_manager.rs index 3c5d67bea2..62514e25af 100644 --- a/comms/core/src/test_utils/peer_manager.rs +++ b/comms/core/src/test_utils/peer_manager.rs @@ -45,7 +45,7 @@ mod not_test { let peer_database_name = { let mut rng = rand::thread_rng(); iter::repeat(()) - .map(|_| rng.sample(Alphanumeric) as char) + .map(|_| rng.sample(Alphanumeric)) .take(8) .collect::() }; diff --git a/comms/core/src/utils/cidr.rs b/comms/core/src/utils/cidr.rs index d51971b3ed..e0469b106c 100644 --- a/comms/core/src/utils/cidr.rs +++ b/comms/core/src/utils/cidr.rs @@ -42,10 +42,10 @@ mod test { #[test] fn parse() { let cidrs = ["127.0.0.1/32", "2620:0:0:0::0/16"]; - let cidrs = parse_cidrs(&cidrs).unwrap(); + let cidrs = parse_cidrs(cidrs).unwrap(); assert_eq!(cidrs[0].network_length(), Some(32)); assert_eq!(cidrs[1].network_length(), Some(16)); let cidrs = ["127.0.0.1/32", "127.0-0.1/32", "127.0.0.1?32", "2620:0:2d0:200::7/32"]; - parse_cidrs(&cidrs).unwrap_err(); + parse_cidrs(cidrs).unwrap_err(); } } diff --git a/comms/dht/examples/memory_net/utilities.rs b/comms/dht/examples/memory_net/utilities.rs index 4156abd79e..2fbe0d1745 100644 --- a/comms/dht/examples/memory_net/utilities.rs +++ b/comms/dht/examples/memory_net/utilities.rs @@ -740,7 +740,7 @@ impl TestNode { match event.as_deref() { Ok(MessageReceived(peer_node_id, _)) => { messaging_events_tx - .send((Clone::clone(&*peer_node_id), node_id.clone())) + .send((Clone::clone(peer_node_id), node_id.clone())) .unwrap(); }, Err(broadcast::error::RecvError::Closed) => { @@ -918,7 +918,7 @@ async fn setup_comms_dht( } let db_name = iter::repeat(()) - .map(|_| OsRng.sample(distributions::Alphanumeric) as char) + .map(|_| OsRng.sample(distributions::Alphanumeric)) .take(8) .collect::(); diff --git a/comms/dht/src/broadcast_strategy.rs b/comms/dht/src/broadcast_strategy.rs index a4dc21f361..b843511606 100644 --- a/comms/dht/src/broadcast_strategy.rs +++ b/comms/dht/src/broadcast_strategy.rs @@ -150,8 +150,8 @@ mod test { #[test] fn is_direct() { - assert!(BroadcastStrategy::DirectPublicKey(Box::new(CommsPublicKey::default())).is_direct()); - assert!(BroadcastStrategy::DirectNodeId(Box::new(NodeId::default())).is_direct()); + assert!(BroadcastStrategy::DirectPublicKey(Box::default()).is_direct()); + assert!(BroadcastStrategy::DirectNodeId(Box::default()).is_direct()); assert!(!BroadcastStrategy::Broadcast(Default::default()).is_direct()); assert!(!BroadcastStrategy::Propagate(Default::default(), Default::default()).is_direct(),); assert!(!BroadcastStrategy::Flood(Default::default()).is_direct()); @@ -166,10 +166,10 @@ mod test { #[test] fn direct_public_key() { - assert!(BroadcastStrategy::DirectPublicKey(Box::new(CommsPublicKey::default())) + assert!(BroadcastStrategy::DirectPublicKey(Box::default()) .direct_public_key() .is_some()); - assert!(BroadcastStrategy::DirectNodeId(Box::new(NodeId::default())) + assert!(BroadcastStrategy::DirectNodeId(Box::default()) .direct_public_key() .is_none()); assert!(BroadcastStrategy::Broadcast(Default::default(),) @@ -190,10 +190,10 @@ mod test { #[test] fn direct_node_id() { - assert!(BroadcastStrategy::DirectPublicKey(Box::new(CommsPublicKey::default())) + assert!(BroadcastStrategy::DirectPublicKey(Box::default()) .direct_node_id() .is_none()); - assert!(BroadcastStrategy::DirectNodeId(Box::new(NodeId::default())) + assert!(BroadcastStrategy::DirectNodeId(Box::default()) .direct_node_id() .is_some()); assert!(BroadcastStrategy::Broadcast(Default::default(),) diff --git a/comms/dht/src/builder.rs b/comms/dht/src/builder.rs index d9cb9aaa23..97ab371c39 100644 --- a/comms/dht/src/builder.rs +++ b/comms/dht/src/builder.rs @@ -41,7 +41,9 @@ use crate::{ /// /// ```rust /// # use tari_comms_dht::{DbConnectionUrl, Dht}; -/// let builder = Dht::builder().mainnet().with_database_url(DbConnectionUrl::Memory); +/// let builder = Dht::builder() +/// .mainnet() +/// .with_database_url(DbConnectionUrl::Memory); /// // let dht = builder.build(...).unwrap(); /// ``` #[derive(Debug, Clone, Default)] diff --git a/comms/dht/src/crypt.rs b/comms/dht/src/crypt.rs index 78574d8433..784e5046a3 100644 --- a/comms/dht/src/crypt.rs +++ b/comms/dht/src/crypt.rs @@ -328,7 +328,7 @@ mod test { fn sanity_check() { let mut key = CommsSignatureKey::from(SafeArray::default()); comms_dht_hash_domain_key_signature() - .chain(&[10, 12, 13, 82, 93, 101, 87, 28, 27, 17, 11, 35, 43]) + .chain([10, 12, 13, 82, 93, 101, 87, 28, 27, 17, 11, 35, 43]) .finalize_into(GenericArray::from_mut_slice(key.reveal_mut())); let signature = b"Top secret message, handle with care".as_slice(); diff --git a/comms/dht/src/dedup/mod.rs b/comms/dht/src/dedup/mod.rs index f49e48a4a4..da43f9e1af 100644 --- a/comms/dht/src/dedup/mod.rs +++ b/comms/dht/src/dedup/mod.rs @@ -63,7 +63,7 @@ pub fn hash_inbound_message(msg: &DhtInboundMessage) -> [u8; 32] { pub fn create_message_hash(message_signature: &[u8], body: &[u8]) -> [u8; 32] { let result = comms_dht_dedup_message_hash::(DEDUP_MESSAGE_HASH_LABEL) .chain(message_signature) - .chain(&body) + .chain(body) .finalize(); let mut out = [0u8; 32]; diff --git a/comms/dht/src/discovery/service.rs b/comms/dht/src/discovery/service.rs index b6aeef7d31..4730b57aa2 100644 --- a/comms/dht/src/discovery/service.rs +++ b/comms/dht/src/discovery/service.rs @@ -385,7 +385,7 @@ mod test { ) .spawn(); - let dest_public_key = Box::new(CommsPublicKey::default()); + let dest_public_key = Box::::default(); let result = requester .discover_peer( *dest_public_key.clone(), diff --git a/comms/dht/src/inbound/decryption.rs b/comms/dht/src/inbound/decryption.rs index 104c2b8634..ef6d10323c 100644 --- a/comms/dht/src/inbound/decryption.rs +++ b/comms/dht/src/inbound/decryption.rs @@ -618,7 +618,7 @@ mod test { .unwrap(); inbound_msg.body = Vec::new(); - let err = block_on(service.call(inbound_msg.clone())).unwrap_err(); + let err = block_on(service.call(inbound_msg)).unwrap_err(); let err = err.downcast::().unwrap(); unpack_enum!(DecryptionError::EncryptedMessageEmptyBody = err); } diff --git a/comms/dht/src/outbound/serialize.rs b/comms/dht/src/outbound/serialize.rs index ff0e8fe745..4ba404b28d 100644 --- a/comms/dht/src/outbound/serialize.rs +++ b/comms/dht/src/outbound/serialize.rs @@ -88,10 +88,10 @@ where destination_node_id.short_str() ); let dht_header = custom_header.map(DhtHeader::from).unwrap_or_else(|| DhtHeader { - major: protocol_version.as_major() as u32, + major: protocol_version.as_major(), message_signature: message_signature.map(|b| b.to_vec()).unwrap_or_else(Vec::new), ephemeral_public_key: ephemeral_public_key.map(|e| e.to_vec()).unwrap_or_else(Vec::new), - message_type: dht_message_type as i32, + message_type: dht_message_type.into(), flags: dht_flags.bits(), destination: Some(destination.into()), message_tag: tag.as_value(), diff --git a/comms/dht/src/rpc/service.rs b/comms/dht/src/rpc/service.rs index e984780898..77c75be981 100644 --- a/comms/dht/src/rpc/service.rs +++ b/comms/dht/src/rpc/service.rs @@ -58,7 +58,7 @@ impl DhtRpcServiceImpl { // A maximum buffer size of 10 is selected arbitrarily and is to allow the producer/consumer some room to // buffer. - let (tx, rx) = mpsc::channel(cmp::min(10, peers.len() as usize)); + let (tx, rx) = mpsc::channel(cmp::min(10, peers.len())); task::spawn(async move { let iter = peers .into_iter() diff --git a/comms/dht/tests/dht.rs b/comms/dht/tests/dht.rs index 55e9da8e72..188ca055f5 100644 --- a/comms/dht/tests/dht.rs +++ b/comms/dht/tests/dht.rs @@ -450,7 +450,7 @@ async fn dht_store_forward() { .unwrap(); // Wait for node C to and receive a response from the SAF request let event = collect_try_recv!(node_C_msg_events, take = 1, timeout = Duration::from_secs(20)); - unpack_enum!(MessagingEvent::MessageReceived(_node_id, _msg) = &*event.get(0).unwrap().as_ref()); + unpack_enum!(MessagingEvent::MessageReceived(_node_id, _msg) = event.get(0).unwrap().as_ref()); let msg = node_C.next_inbound_message(Duration::from_secs(5)).await.unwrap(); assert_eq!( @@ -479,7 +479,7 @@ async fn dht_store_forward() { // Check that Node C emitted the StoreAndForwardMessagesReceived event when it went Online let event = collect_try_recv!(node_C_dht_events, take = 1, timeout = Duration::from_secs(20)); - unpack_enum!(DhtEvent::StoreAndForwardMessagesReceived = &*event.get(0).unwrap().as_ref()); + unpack_enum!(DhtEvent::StoreAndForwardMessagesReceived = event.get(0).unwrap().as_ref()); node_A.shutdown().await; node_B.shutdown().await; @@ -1129,7 +1129,7 @@ fn count_messages_received(events: &[Arc], node_ids: &[&NodeId]) .iter() .filter(|event| { unpack_enum!(MessagingEvent::MessageReceived(recv_node_id, _tag) = &***event); - node_ids.iter().any(|n| &*recv_node_id == *n) + node_ids.iter().any(|n| recv_node_id == *n) }) .count() } diff --git a/comms/rpc_macros/src/generator.rs b/comms/rpc_macros/src/generator.rs index 5180e7ebed..6d8dd28a4e 100644 --- a/comms/rpc_macros/src/generator.rs +++ b/comms/rpc_macros/src/generator.rs @@ -164,7 +164,7 @@ impl RpcCodeGenerator { let method_num = m.method_num; let request_type = &m.request_type; let result_type = &m.return_type; - let is_unit = m.request_type.as_ref().filter(|ty| is_unit_type(*ty)).is_some(); + let is_unit = m.request_type.as_ref().filter(|ty| is_unit_type(ty)).is_some(); let var = if is_unit { quote!(()) } else { quote!(request) }; diff --git a/infrastructure/shutdown/src/lib.rs b/infrastructure/shutdown/src/lib.rs index d16eec666c..18226edcf1 100644 --- a/infrastructure/shutdown/src/lib.rs +++ b/infrastructure/shutdown/src/lib.rs @@ -39,6 +39,7 @@ use crate::oneshot_trigger::OneshotSignal; /// /// _Note_: This will trigger when dropped, so the `Shutdown` instance should be held as /// long as required by the application. +#[derive(Clone, Debug)] pub struct Shutdown(oneshot_trigger::OneshotTrigger<()>); impl Shutdown { pub fn new() -> Self { diff --git a/infrastructure/shutdown/src/oneshot_trigger.rs b/infrastructure/shutdown/src/oneshot_trigger.rs index 2cad4b5a18..4b47943e9c 100644 --- a/infrastructure/shutdown/src/oneshot_trigger.rs +++ b/infrastructure/shutdown/src/oneshot_trigger.rs @@ -23,6 +23,7 @@ use std::{ future::Future, pin::Pin, + sync::{Arc, Mutex}, task::{Context, Poll}, }; @@ -36,8 +37,9 @@ pub fn channel() -> OneshotTrigger { OneshotTrigger::new() } +#[derive(Clone, Debug)] pub struct OneshotTrigger { - sender: Option>, + sender: Arc>>>, signal: OneshotSignal, } @@ -45,7 +47,7 @@ impl OneshotTrigger { pub fn new() -> Self { let (tx, rx) = oneshot::channel(); Self { - sender: Some(tx), + sender: Arc::new(Mutex::new(Some(tx))), signal: rx.shared().into(), } } @@ -55,13 +57,14 @@ impl OneshotTrigger { } pub fn broadcast(&mut self, item: T) { - if let Some(tx) = self.sender.take() { + let mut x = self.sender.lock().unwrap(); + if let Some(tx) = (*x).take() { let _result = tx.send(item); } } pub fn is_used(&self) -> bool { - self.sender.is_none() + self.sender.lock().unwrap().is_none() } } diff --git a/infrastructure/storage/src/lmdb_store/store.rs b/infrastructure/storage/src/lmdb_store/store.rs index d4d0582eff..3caf90468d 100644 --- a/infrastructure/storage/src/lmdb_store/store.rs +++ b/infrastructure/storage/src/lmdb_store/store.rs @@ -518,7 +518,7 @@ impl LMDBDatabase { let tx = WriteTransaction::new(env)?; { let mut accessor = tx.access(); - accessor.put(&*self.db, key, value, put::Flags::empty())?; + accessor.put(&self.db, key, value, put::Flags::empty())?; } tx.commit()?; Ok(()) @@ -533,7 +533,7 @@ impl LMDBDatabase { K: AsLmdbBytes + ?Sized, for<'t> V: DeserializeOwned, // read this as, for *any* lifetime, t, we can convert a [u8] to V { - let env = &(*self.db.env()); + let env = self.db.env(); let txn = ReadTransaction::new(env)?; let accessor = txn.access(); let val = accessor.get(&self.db, key).to_opt(); @@ -542,7 +542,7 @@ impl LMDBDatabase { /// Return statistics about the database, See [Stat](lmdb_zero/struct.Stat.html) for more details. pub fn get_stats(&self) -> Result { - let env = &(*self.db.env()); + let env = self.db.env(); Ok(ReadTransaction::new(env).and_then(|txn| txn.db_stat(&self.db))?) } @@ -630,7 +630,7 @@ impl LMDBDatabase { /// Checks whether a key exists in this database pub fn contains_key(&self, key: &K) -> Result where K: AsLmdbBytes + ?Sized { - let txn = ReadTransaction::new(&(*self.db.env()))?; + let txn = ReadTransaction::new(self.db.env())?; let accessor = txn.access(); let res: error::Result<&Ignore> = accessor.get(&self.db, key); let res = res.to_opt()?.is_some(); @@ -640,7 +640,7 @@ impl LMDBDatabase { /// Delete a record associated with `key` from the database. If the key is not found, pub fn remove(&self, key: &K) -> Result<(), LMDBError> where K: AsLmdbBytes + ?Sized { - let tx = WriteTransaction::new(&(*self.db.env()))?; + let tx = WriteTransaction::new(self.db.env())?; { let mut accessor = tx.access(); accessor.del_key(&self.db, key)?; diff --git a/infrastructure/tari_script/src/op_codes.rs b/infrastructure/tari_script/src/op_codes.rs index 03791f976f..22a9291072 100644 --- a/infrastructure/tari_script/src/op_codes.rs +++ b/infrastructure/tari_script/src/op_codes.rs @@ -341,7 +341,7 @@ impl Opcode { /// Take a byte slice and read the next opcode from it, including any associated data. `read_next` returns a tuple /// of the deserialised opcode, and an updated slice that has the Opcode and data removed. fn read_next(bytes: &[u8]) -> Result<(Opcode, &[u8]), ScriptError> { - let code = bytes.get(0).ok_or(ScriptError::InvalidOpcode)?; + let code = bytes.first().ok_or(ScriptError::InvalidOpcode)?; #[allow(clippy::enum_glob_use)] use Opcode::*; match *code { diff --git a/infrastructure/tari_script/src/script.rs b/infrastructure/tari_script/src/script.rs index c97ab1bbdd..972c505e4b 100644 --- a/infrastructure/tari_script/src/script.rs +++ b/infrastructure/tari_script/src/script.rs @@ -204,7 +204,7 @@ impl TariScript { pub fn script_message(&self, pub_key: &RistrettoPublicKey) -> Result { let b = Blake256::new() .chain(pub_key.as_bytes()) - .chain(&self.to_bytes()) + .chain(self.to_bytes()) .finalize(); RistrettoSecretKey::from_bytes(b.as_slice()).map_err(|_| ScriptError::InvalidSignature) } diff --git a/infrastructure/tari_script/src/stack.rs b/infrastructure/tari_script/src/stack.rs index d2fe725670..78305eb055 100644 --- a/infrastructure/tari_script/src/stack.rs +++ b/infrastructure/tari_script/src/stack.rs @@ -107,7 +107,7 @@ impl StackItem { /// Take a byte slice and read the next stack item from it, including any associated data. `read_next` returns a /// tuple of the deserialised item, and an updated slice that has the Opcode and data removed. pub fn read_next(bytes: &[u8]) -> Option<(Self, &[u8])> { - let code = bytes.get(0)?; + let code = bytes.first()?; match *code { TYPE_NUMBER => StackItem::b_to_number(&bytes[1..]), TYPE_HASH => StackItem::b_to_hash(&bytes[1..]), diff --git a/infrastructure/test_utils/src/random/mod.rs b/infrastructure/test_utils/src/random/mod.rs index f2d5340665..4bd558a132 100644 --- a/infrastructure/test_utils/src/random/mod.rs +++ b/infrastructure/test_utils/src/random/mod.rs @@ -27,17 +27,14 @@ use rand::{distributions::Alphanumeric, thread_rng, Rng}; /// Generate a random alphanumeric string of the given size using the default `ThreadRng`. pub fn string(len: usize) -> String { let mut rng = thread_rng(); - iter::repeat(()) - .map(|_| rng.sample(Alphanumeric) as char) - .take(len) - .collect() + iter::repeat(()).map(|_| rng.sample(Alphanumeric)).take(len).collect() } /// Generate a random alphanumeric string of the given size using the default `ThreadRng`. pub fn prefixed_string(prefix: &str, len: usize) -> String { let mut rng = thread_rng(); let rand_str = iter::repeat(()) - .map(|_| rng.sample(Alphanumeric) as char) + .map(|_| rng.sample(Alphanumeric)) .take(len) .collect::(); format!("{}{}", prefix, rand_str) diff --git a/integration_tests/.eslintrc.json b/integration_tests/.eslintrc.json deleted file mode 100644 index a14b97b970..0000000000 --- a/integration_tests/.eslintrc.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "extends": ["eslint:recommended", "plugin:prettier/recommended"], - "plugins": ["prettier", "@babel"], - "env": { - "browser": true, - "commonjs": true, - "node": true, - "es2021": true - }, - "parser": "@babel/eslint-parser", // Tell ESLint that you want to use the @babel/eslint-parser. - "parserOptions": { - "ecmaVersion": 2021, - "sourceType": "module", - "allowImportExportEverywhere": false, - "ecmaFeatures": { - "globalReturn": false - }, - "requireConfigFile": false - }, - "rules": { - "no-undef": "error", - "no-unused-vars": [ - "error", - { - "argsIgnorePattern": "^_" - } - ], - "prettier/prettier": [ - "error", - { - "doubleQuote": true, - "endOfLine": "auto" - } - ] - } -} diff --git a/integration_tests/.husky/.gitignore b/integration_tests/.husky/.gitignore deleted file mode 100644 index 31354ec138..0000000000 --- a/integration_tests/.husky/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_ diff --git a/integration_tests/.husky/pre-commit b/integration_tests/.husky/pre-commit deleted file mode 100755 index f8bcd98ef7..0000000000 --- a/integration_tests/.husky/pre-commit +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -# run prettier and lint check before commit if any files in integration_tests have been staged -CHECK=$(git diff --name-only --staged | grep -i integration_tests || true) - -if [ -z "${CHECK}" ]; then - exit 0 -else - echo "Files staged for commit in integration_tests/" - echo "Running pre-commit hook" - # cd integration_tests && npm run check-fmt && npm run lint -fi diff --git a/integration_tests/.nvmrc b/integration_tests/.nvmrc deleted file mode 100644 index e1fcd1ea2f..0000000000 --- a/integration_tests/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -lts/erbium diff --git a/integration_tests/.prettierignore b/integration_tests/.prettierignore deleted file mode 100644 index 2b190e38aa..0000000000 --- a/integration_tests/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -temp/ -fixtures/ diff --git a/integration_tests/.prettierrc b/integration_tests/.prettierrc deleted file mode 100644 index 168d9d2a0c..0000000000 --- a/integration_tests/.prettierrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "endOfLine": "auto" -} diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml new file mode 100644 index 0000000000..38a72b6b1a --- /dev/null +++ b/integration_tests/Cargo.toml @@ -0,0 +1,79 @@ +[package] +name = "tari_integration_tests" +authors = ["The Tari Development Community"] +repository = "https://github.com/tari-project/tari" +license = "BSD-3-Clause" +version = "0.35.1" +edition = "2018" + +[dependencies] +tari_app_grpc = { path = "../applications/tari_app_grpc" } +tari_app_utilities = { path = "../applications/tari_app_utilities" } +tari_base_node = { path = "../applications/tari_base_node" } +tari_base_node_grpc_client = { path = "../clients/rust/base_node_grpc_client" } +tari_common = { path = "../common" } +tari_common_types = { path = "../base_layer/common_types" } +tari_comms = { path = "../comms/core" } +tari_comms_dht = { path = "../comms/dht" } +tari_console_wallet = { path = "../applications/tari_console_wallet" } +tari_core = { path = "../base_layer/core" } +tari_merge_mining_proxy = { path = "../applications/tari_merge_mining_proxy" } +tari_miner = { path = "../applications/tari_miner" } +tari_p2p = { path = "../base_layer/p2p" } +tari_script = { path = "../infrastructure/tari_script" } +tari_shutdown = { path = "../infrastructure/shutdown" } +tari_test_utils = { path = "../infrastructure/test_utils" } +tari_utilities = { git = "https://github.com/tari-project/tari_utilities.git", tag="v0.4.10"} +tari_wallet = { path = "../base_layer/wallet" } +tari_wallet_grpc_client = { path = "../clients/rust/wallet_grpc_client" } +tari_wallet_ffi = { path = "../base_layer/wallet_ffi" } + +anyhow = "1.0.53" +async-trait = "0.1.50" +axum = "0.6.0" +axum-jrpc = { version = "0.3.2", features = ["anyhow_error"] } +bincode = "1.3.3" +blake2 = "0.9.2" +borsh = { version = "0.9.3", default-features = false } +bytes = "1" +chrono = "0.4.22" +clap = { version = "3.2.5", features = ["env"] } +config = "0.13.0" +csv = "1.1" +digest = "0.9.0" +diesel = { version = "1.4.8", default-features = false, features = ["sqlite"] } +futures = { version = "^0.3.1" } +include_dir = "0.7.2" +json5 = "0.2.2" +libc = "0.2.65" +libsqlite3-sys = { version = "0.22.2", features = ["bundled"] } +lmdb-zero = "0.4.4" +log = { version = "0.4.8", features = ["std"] } +log4rs = { version = "1.1.1", features = ["rolling_file_appender", "compound_policy", "size_trigger", "fixed_window_roller"] } +prost = "0.9" +rand = "0.7" +reqwest = "0.11.11" +serde = { version = "1.0.126", features = ["derive"] } +serde_json = "1.0.64" +thiserror = "^1.0.20" +time = "0.3.15" +tokio = { version = "1.10", features = ["macros", "time", "sync", "rt-multi-thread"] } +tokio-stream = { version = "0.1.7", features = ["sync"] } +tonic = "0.6.2" +tower = "0.4" +tower-http = { version = "0.3.0", features = ["cors"] } +tower-layer = "0.3" + + +[dev-dependencies] +tari_crypto = { git = "https://github.com/tari-project/tari-crypto.git", tag = "v0.16.6" } + +# FIXME: the newest version failed compilation due to a missing "bool_to_option" unstable feature +#cucumber = { version = "0.13.0", features = ["default"] } +cucumber = { version = "0.18.0", features = ["default", "libtest"] } +tempfile = "3.3.0" +indexmap = "1.9.1" + +[[test]] +name = "cucumber" # this should be the same as the filename of your test target +harness = false # allows Cucumber to print output instead of libtest diff --git a/integration_tests/Makefile b/integration_tests/Makefile deleted file mode 100644 index b01f62b903..0000000000 --- a/integration_tests/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -.DEFAULT_GOAL := test - -.phony: build -build: - cargo build --release --bin tari_base_node && \ - cargo build --release --bin tari_console_wallet && \ - cargo build --release --bin tari_miner && \ - cargo build --release --bin tari_merge_mining_proxy && \ - cargo build --release --package tari_wallet_ffi - -.phony: test -test: build - npm test -- --profile "ci" --tags "@critical and not @long-running and not @broken and not @wallet-ffi" diff --git a/integration_tests/README.md b/integration_tests/README.md index 802720a3af..ca9b6543e2 100644 --- a/integration_tests/README.md +++ b/integration_tests/README.md @@ -1,106 +1,55 @@ # Tari integration cucumber tests -## Prerequisites - -- Install `Node.js` (_Currently, LTS version 12.22.X is recommended for wallet FFI compatibility_) - -- Open terminal in the `tari-project\integration_tests` folder and run - ``` - npm install - ``` -- Open terminal in the `tari-project\clients\base_node_grpc_client` folder and run - ``` - npm install - ``` - - Open terminal in the `tari-project\clients\validator_node_grpc_client` folder and run - ``` - npm install - ``` - - Open terminal in the `tari-project\clients\wallet_grpc_client` folder and run - ``` - npm install - ``` - ## Procedure to run -- Open terminal in the `tari-project\integration_tests` folder - -- Before running tests, you'll need to run `npm install` - - ``` - npm install - ``` - -- To run all tests: +In its simplest form you can run the tests from the project route with `cargo test --test cucumber` - ``` - npm test - ``` - -- To run all tests and generate the reports (\*nix): - - ``` - ./run-tests.sh - ``` - -- To run a specific test, add `-- --name ` to the command line: +- To run a specific test, add `-- --name ` to the command line ```shell # Runs a specific test - npm test -- --name "Simple block sync" + cargo test --test cucumber -- --name "Basic connectivity between 2 nodes" + + # You can also use the short option -n + cargo test --test cucumber -- -n "Basic connectivity between 2 nodes" ``` - To run tests with specific tags, e.g. `critical`, add `-- --tags @` to the command line. ```shell # Runs all critical tests - npm test -- --tags "@critical" - ./run-tests.sh --tags "@critical" + cargo test --test cucumber -- --tags "@critical" - # Runs all critical tests base node tests - npm test -- --tags "@critical AND @base-node" - ./run-tests.sh --tags "@critical AND @base-node" + # Runs all critical tests base node tests + cargo test --test cucumber -- --tags "@critical AND @base-node" - # Runs all critical tests, but not @long-running and not @tbroken - npm test -- --tags "@critical and not @long-running and not @broken" + # Runs all critical tests, but not @long-running and not @tbroken + cargo test --test cucumber -- --tags "@critical and not @long-running and not @broken" + + # You can also use the short option -t + cargo test --test cucumber -- -t "@critical" ``` -- To run the wallet FFI tests, add `--profile "ci"` or `--profile "none"` to the command line (_take note of the node - version requirements_) and take note of the profile definition in `cucumber.js`. +- To run a specific file or files add `-- --input glob` to the command line ```shell - # Runs a specific FFI test - npm test -- --profile "ci" --name "My wallet FFI test scenario name" - # Runs a complete set of FFI tests - npm test -- --profile "none" --tags "@wallet-ffi" - ``` + # Runs all files matching glob + cargo test --test cucumber -- --input "tests/features/Wallet*" -- Runs all @critical tests, but not @long-running + # If you want to run a single file make the glob specific to the filename + cargo test --test cucumber -- --input "tests/features/WalletTransactions*" + # You can also use the short option -i + cargo test --test cucumber -- -i "tests/features/WalletTransactions*" ``` - npm test -- --tags "@critical and not @long-running" - ``` - -- See `npm test -- --help` for more options. ## Notes -In Windows, running the Tari executables in debug mode fails with a stack overflow, thus Windows users must -run in release mode. See command line options in `baseNodeProcess.js`, `mergeMiningProxyProcess.js` -and `walletProcess.js`. - -## Code Contribution - -[Prettier](https://prettier.io/) is used for JS code formatting. To ensure that your code is correctly -formatted, run the following to format or check your code in-place: - -- `npm run fmt` -- `npm run check-fmt` - -Alternatively, use a prettier plugin for your favourite IDE. +This suite is still a work in progress. We have scenarios marked `@broken` that are known broken and require fixing. +We also have scenarios commented out entirely. These scenarios are being reactivated and having any missing steps implemented +on a "when we have time" basis. -[ESLint](https://eslint.org) is used to statically analyzes the code to quickly find problems. To ensure your code -conforms to the linting standard, run the following to fix or check your code in-place: +## CI -- `npm run lint-fix` -- `npm run lint` +Every PR will run this suite but only on `@critical` tags. Each night the rest of the suite will run except `@long-running`. +Once a week over the weekend `@long-running` scenarios will run. \ No newline at end of file diff --git a/integration_tests/cucumber.js b/integration_tests/cucumber.js deleted file mode 100644 index 5b5dd3baf7..0000000000 --- a/integration_tests/cucumber.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - default: "--tags 'not @long-running and not @wallet-ffi and not @broken' ", - none: " ", - ci: "--tags '@critical and not @long-running and not @broken '", - critical: "--format @cucumber/pretty-formatter --tags @critical", - "non-critical": - "--tags 'not @critical and not @long-running and not @broken'", - "long-running": "--tags '@long-running and not @broken'", -}; diff --git a/integration_tests/environment b/integration_tests/environment deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/integration_tests/features/MergeMining.feature b/integration_tests/features/MergeMining.feature deleted file mode 100644 index 3541a1f3f9..0000000000 --- a/integration_tests/features/MergeMining.feature +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2022 The Tari Project -# SPDX-License-Identifier: BSD-3-Clause - -@merge-mining @base-node - Feature: Merge Mining - - Scenario: Merge Mining Functionality Test Without Submitting To Origin - Given I have a seed node NODE - And I have wallet WALLET connected to all seed nodes - And I have a merge mining proxy PROXY connected to NODE and WALLET with origin submission disabled - When I ask for a block height from proxy PROXY - Then Proxy response height is valid - When I ask for a block template from proxy PROXY - Then Proxy response block template is valid - When I submit a block through proxy PROXY - Then Proxy response block submission is valid without submitting to origin - - Scenario: Merge Mining Functionality Test With Submitting To Origin - Given I have a seed node NODE - And I have wallet WALLET connected to all seed nodes - And I have a merge mining proxy PROXY connected to NODE and WALLET with origin submission enabled - When I ask for a block height from proxy PROXY - Then Proxy response height is valid - When I ask for a block template from proxy PROXY - Then Proxy response block template is valid - When I submit a block through proxy PROXY - Then Proxy response block submission is valid with submitting to origin - When I ask for the last block header from proxy PROXY - Then Proxy response for last block header is valid - When I ask for a block header by hash using last block header from proxy PROXY - Then Proxy response for block header by hash is valid - - # BROKEN: get_block_template returns error 500 - @critical @broken - Scenario: Simple Merge Mining - Given I have a seed node NODE - And I have wallet WALLET connected to all seed nodes - And I have a merge mining proxy PROXY connected to NODE and WALLET with default config - When I merge mine 2 blocks via PROXY - Then all nodes are at height 2 - diff --git a/integration_tests/features/Recovery.feature b/integration_tests/features/Recovery.feature deleted file mode 100644 index d311530395..0000000000 --- a/integration_tests/features/Recovery.feature +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2022 The Tari Project -# SPDX-License-Identifier: BSD-3-Clause - -@recovery -Feature: Recovery - - Scenario Outline: Blockchain database recovery - Given I have 2 seed nodes - And I have a base node B connected to all seed nodes - When I mine blocks on B - Then all nodes are at height - When I stop node B - And I run blockchain recovery on node B - And I start base node B - Then all nodes are at height - Examples: - | NumBlocks | - | 10 | - - # Takes 1min+ on Circle CI - @long-running - Examples: - | NumBlocks | - | 25 | - | 50 | diff --git a/integration_tests/features/Reorgs.feature b/integration_tests/features/Reorgs.feature deleted file mode 100644 index a70211270d..0000000000 --- a/integration_tests/features/Reorgs.feature +++ /dev/null @@ -1,294 +0,0 @@ -# Copyright 2022 The Tari Project -# SPDX-License-Identifier: BSD-3-Clause - -@reorg @base-node -Feature: Reorgs - - @critical - Scenario: Simple reorg to stronger chain - # Chain 1 - # Note: Use more than 1 base node to speed up the test - Given I have a seed node SEED_B - And I have a base node B connected to seed SEED_B - And I have wallet WB connected to base node B - And I have mining node BM connected to base node B and wallet WB - And mining node BM mines 3 blocks with min difficulty 1 and max difficulty 50 - # Chain 2 - # Note: Use more than 1 base node to speed up the test - Given I have a seed node SEED_C - And I have a base node C connected to seed SEED_C - And I have wallet WC connected to base node C - And I have mining node CM connected to base node C and wallet WC - And mining node CM mines 10 blocks with min difficulty 51 and max difficulty 9999999999 - # Connect chain 1 and 2 - Then node B is at height 3 - And node C is at height 10 - Given I have a base node SA connected to nodes B,C - Then node SA is at height 10 - And node B is at height 10 - And node C is at height 10 - - - @critical - Scenario: Simple reorg with burned output - # Chain 1 - # Note: Use more than 1 base node to speed up the test - Given I have a seed node SEED_B - And I have a base node B connected to seed SEED_B - And I have wallet WB connected to base node B - And I have mining node BM connected to base node B and wallet WB - And mining node BM mines 10 blocks with min difficulty 1 and max difficulty 1 - - When I wait for wallet WB to have at least 55000000000 uT - When I create a burn transaction of 1000000 uT from WB at fee 100 - And mining node BM mines 5 blocks with min difficulty 1 and max difficulty 1 - # Chain 2 - # Note: Use more than 1 base node to speed up the test - Given I have a seed node SEED_C - And I have a base node C connected to seed SEED_C - And I have wallet WC connected to base node C - And I have mining node CM connected to base node C and wallet WC - And mining node CM mines 17 blocks with min difficulty 1 and max difficulty 1 - # Connect chain 1 and 2 - Then node B is at height 15 - And node C is at height 17 - Given I have a base node SA connected to nodes B,C - Then node SA is at height 17 - And node B is at height 17 - And node C is at height 17 - - @critical - Scenario: Node rolls back reorg on invalid block - Given I have a seed node SA - And I have a base node B connected to seed SA - When I mine 5 blocks on B - Then node B is at height 5 - When I save the tip on B as BTip1 - # Try a few times to insert an invalid block - And I mine a block on B at height 3 with an invalid MMR - And I mine a block on B at height 3 with an invalid MMR - And I mine a block on B at height 4 with an invalid MMR - And I mine a block on B at height 4 with an invalid MMR - Then node B is at tip BTip1 - - @reorg - Scenario: Pruned mode reorg simple - Given I have a base node NODE1 connected to all seed nodes - And I have wallet WALLET1 connected to base node NODE1 - And I have mining node MINING1 connected to base node NODE1 and wallet WALLET1 - When mining node MINING1 mines 5 blocks with min difficulty 1 and max difficulty 20 - Then all nodes are at height 5 - Given I have a pruned node PNODE2 connected to node NODE1 with pruning horizon set to 5 - And I have wallet WALLET2 connected to base node PNODE2 - And I have mining node MINING2 connected to base node PNODE2 and wallet WALLET2 - When mining node MINING1 mines 4 blocks with min difficulty 1 and max difficulty 20 - Then all nodes are at height 9 - When mining node MINING2 mines 5 blocks with min difficulty 1 and max difficulty 20 - Then all nodes are at height 14 - When I stop node PNODE2 - When mining node MINING1 mines 3 blocks with min difficulty 1 and max difficulty 20 - And node NODE1 is at height 17 - And I stop node NODE1 - And I start base node PNODE2 - When mining node MINING2 mines 6 blocks with min difficulty 2 and max difficulty 1000000 - And node PNODE2 is at height 20 - When I start base node NODE1 - Then all nodes are at height 20 - - @reorg @flaky - Scenario: Pruned mode reorg past horizon - Given I have a base node NODE1 connected to all seed nodes - And I have wallet WALLET1 connected to base node NODE1 - And I have mining node MINING1 connected to base node NODE1 and wallet WALLET1 - Given I have a base node NODE2 connected to node NODE1 - And I have wallet WALLET2 connected to base node NODE2 - And I have mining node MINING2 connected to base node NODE2 and wallet WALLET2 - When I mine a block on NODE1 with coinbase CB1 - Then all nodes are at height 1 - And I stop node NODE1 - And mining node MINING2 mines 19 blocks with min difficulty 20 and max difficulty 1000000 - And node NODE2 is at height 20 - And I stop node NODE2 - When I start base node NODE1 - And mining node MINING1 mines 3 blocks with min difficulty 1 and max difficulty 20 - And node NODE1 is at height 4 - When I create a transaction TX1 spending CB1 to UTX1 - When I submit transaction TX1 to NODE1 - Then NODE1 has TX1 in MEMPOOL state - And mining node MINING1 mines 6 blocks with min difficulty 1 and max difficulty 20 - And node NODE1 is at height 10 - Given I have a pruned node PNODE1 connected to node NODE1 with pruning horizon set to 5 - Then node PNODE1 is at height 10 - When I start base node NODE2 - Then all nodes are at height 20 - # Because TX1 should have been re_orged out we should be able to spend CB1 again - When I create a transaction TX2 spending CB1 to UTX2 - When I submit transaction TX2 to PNODE1 - Then PNODE1 has TX2 in MEMPOOL state - - @reorg - Scenario: Zero-conf reorg with spending - Given I have a base node NODE1 connected to all seed nodes - Given I have a base node NODE2 connected to node NODE1 - When I mine 14 blocks on NODE1 - When I mine a block on NODE1 with coinbase CB1 - When I mine 4 blocks on NODE1 - When I create a custom fee transaction TX1 spending CB1 to UTX1 with fee 20 - When I create a custom fee transaction TX11 spending UTX1 to UTX11 with fee 20 - When I submit transaction TX1 to NODE1 - When I submit transaction TX11 to NODE1 - When I mine 1 blocks on NODE1 - Then NODE1 has TX1 in MINED state - And NODE1 has TX11 in MINED state - And all nodes are at height 20 - And I stop node NODE1 - And node NODE2 is at height 20 - When I mine a block on NODE2 with coinbase CB2 - When I mine 3 blocks on NODE2 - When I create a custom fee transaction TX2 spending CB2 to UTX2 with fee 20 - When I create a custom fee transaction TX21 spending UTX2 to UTX21 with fee 20 - When I submit transaction TX2 to NODE2 - When I submit transaction TX21 to NODE2 - When I mine 1 blocks on NODE2 - Then node NODE2 is at height 25 - And NODE2 has TX2 in MINED state - And NODE2 has TX21 in MINED state - And I stop node NODE2 - When I start base node NODE1 - And node NODE1 is at height 20 - When I mine a block on NODE1 with coinbase CB3 - When I mine 3 blocks on NODE1 - When I create a custom fee transaction TX3 spending CB3 to UTX3 with fee 20 - When I create a custom fee transaction TX31 spending UTX3 to UTX31 with fee 20 - When I submit transaction TX3 to NODE1 - When I submit transaction TX31 to NODE1 - When I mine 1 blocks on NODE1 - Then NODE1 has TX3 in MINED state - And NODE1 has TX31 in MINED state - And node NODE1 is at height 25 - When I start base node NODE2 - Then all nodes are on the same chain tip - - Scenario Outline: Massive multiple reorg - # - # Chain 1a: - # Mine X1 blocks - # - Given I have a seed node SEED_A1 - # Add multiple base nodes to ensure more robust comms - And I have a base node NODE_A1 connected to seed SEED_A1 - And I have a base node NODE_A2 connected to seed SEED_A1 - When I mine blocks with difficulty 1 on SEED_A1 - Then all nodes are on the same chain at height - # - # Chain 1b: - # Mine Y1 blocks - # - And I have a seed node SEED_A2 - # Add multiple base nodes to ensure more robust comms - And I have a base node NODE_A3 connected to seed SEED_A2 - And I have a base node NODE_A4 connected to seed SEED_A2 - When I mine blocks with difficulty 1 on SEED_A2 - Then node NODE_A3 is at height - Then node NODE_A4 is at height - # - # Connect Chain 1a and 1b - # - And I connect node NODE_A1 to node NODE_A3 - And I connect node NODE_A2 to node NODE_A4 - And I connect node SEED_A1 to node SEED_A2 - Then node SEED_A1 is in state LISTENING - Then node SEED_A2 is in state LISTENING - Then all nodes are on the same chain at height - # - # Chain 2a: - # Mine X2 blocks - # - Given I have a seed node SEED_B1 - # Add multiple base nodes to ensure more robust comms - And I have a base node NODE_B1 connected to seed SEED_B1 - And I have a base node NODE_B2 connected to seed SEED_B1 - When I mine blocks with difficulty 1 on SEED_B1 - Then node NODE_B1 is at height - Then node NODE_B2 is at height - # - # Chain 2b: - # Mine Y2 blocks (orphan_storage_capacity default set to 10) - # - And I have a seed node SEED_B2 - # Add multiple base nodes to ensure more robust comms - And I have a base node NODE_B3 connected to seed SEED_B2 - And I have a base node NODE_B4 connected to seed SEED_B2 - When I mine blocks with difficulty 1 on SEED_B2 - Then node NODE_B3 is at height - Then node NODE_B4 is at height - # - # Connect Chain 2a and 2b - # - And I connect node NODE_B1 to node NODE_B3 - And I connect node NODE_B2 to node NODE_B4 - And I connect node SEED_B1 to node SEED_B2 - Then node SEED_B2 is in state LISTENING - Then node SEED_B1 is in state LISTENING - Then node SEED_B2 is at height - Then node NODE_B1 is at height - Then node NODE_B2 is at height - Then node NODE_B3 is at height - Then node NODE_B4 is at height - # - # Connect Chain 1 and 2 - # - And I connect node NODE_A1 to node NODE_B1 - And I connect node NODE_A3 to node NODE_B3 - And I connect node SEED_A1 to node SEED_B1 - Then all nodes are on the same chain at height - - Examples: - | X1 | Y1 | X2 | Y2 | - | 5 | 10 | 15 | 20 | - - @long-running - Examples: - | X1 | Y1 | X2 | Y2 | - | 100 | 125 | 150 | 175 | - | 1010 | 1110 | 1210 | 1310 | - - @reorg - Scenario: Full block sync with small reorg - Given I have a base node NODE1 - And I have wallet WALLET1 connected to base node NODE1 - And I have mining node MINER1 connected to base node NODE1 and wallet WALLET1 - And I have a base node NODE2 connected to node NODE1 - And I have wallet WALLET2 connected to base node NODE2 - And I have mining node MINER2 connected to base node NODE2 and wallet WALLET2 - And mining node MINER1 mines 5 blocks with min difficulty 1 and max difficulty 10 - Then all nodes are at height 5 - Given I stop node NODE2 - And mining node MINER1 mines 5 blocks with min difficulty 1 and max difficulty 1 - Then node NODE1 is at height 10 - Given I stop node NODE1 - And I start base node NODE2 - And mining node MINER2 mines 7 blocks with min difficulty 2 and max difficulty 100000 - Then node NODE2 is at height 12 - When I start base node NODE1 - Then all nodes are on the same chain at height 12 - - @reorg @long-running - Scenario: Full block sync with large reorg - Given I have a base node NODE1 - And I have wallet WALLET1 connected to base node NODE1 - And I have mining node MINER1 connected to base node NODE1 and wallet WALLET1 - And I have a base node NODE2 connected to node NODE1 - And I have wallet WALLET2 connected to base node NODE2 - And I have mining node MINER2 connected to base node NODE2 and wallet WALLET2 - And mining node MINER1 mines 5 blocks with min difficulty 1 and max difficulty 10 - Then all nodes are at height 5 - Given I stop node NODE2 - And mining node MINER1 mines 1001 blocks with min difficulty 1 and max difficulty 10 - Then node NODE1 is at height 1006 - Given I stop node NODE1 - And I start base node NODE2 - And mining node MINER2 mines 1500 blocks with min difficulty 11 and max difficulty 100000 - Then node NODE2 is at height 1505 - When I start base node NODE1 - Then all nodes are on the same chain at height 1505 diff --git a/integration_tests/features/StressTest.feature b/integration_tests/features/StressTest.feature deleted file mode 100644 index 3fe8b7d39a..0000000000 --- a/integration_tests/features/StressTest.feature +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright 2022 The Tari Project -# SPDX-License-Identifier: BSD-3-Clause - -@stress-test -Feature: Stress Test - - Scenario Outline: Ramped Stress Test - Given I have a seed node NODE1 - And I have stress-test wallet WALLET_A connected to the seed node NODE1 with broadcast monitoring timeout - And I have mining node MINER connected to base node NODE1 and wallet WALLET_A - # We mine some blocks before starting the other nodes to avoid a spinning sync state when all the nodes are at height 0 - And I have a seed node NODE2 - And I have base nodes connected to all seed nodes - And I have stress-test wallet WALLET_B connected to the seed node NODE2 with broadcast monitoring timeout - When mining node MINER mines 6 blocks - # There need to be at least as many mature coinbase UTXOs in the wallet coin splits required for the number of transactions - When mining node MINER mines blocks - Then all nodes are on the same chain tip - When I wait for wallet WALLET_A to have at least 5100000000 uT - - Then I coin split tari in wallet WALLET_A to produce UTXOs of 5000 uT each with fee_per_gram 20 uT - When mining node MINER mines 3 blocks - When mining node MINER mines blocks - Then all nodes are on the same chain tip - Then wallet WALLET_A detects all transactions as Mined_Confirmed - When I send transactions of 1111 uT each from wallet WALLET_A to wallet WALLET_B at fee_per_gram 4 - # Mine enough blocks for the first block of transactions to be confirmed. - When mining node MINER mines 4 blocks - Then all nodes are on the same chain tip - # Now wait until all transactions are detected as confirmed in WALLET_A, continue to mine blocks if transactions - # are not found to be confirmed as sometimes the previous mining occurs faster than transactions are submitted - # to the mempool - Then while mining via SHA3 miner MINER all transactions in wallet WALLET_A are found to be Mined_Confirmed - # Then wallet WALLET_B detects all transactions as Mined_Confirmed - Then while mining via node NODE1 all transactions in wallet WALLET_B are found to be Mined_Confirmed - - @flaky - Examples: - | NumTransactions | NumCoinsplitsNeeded | NumNodes | MonitoringTimeout | - | 10 | 1 | 3 | 10 | - - @long-running - Examples: - | NumTransactions | NumCoinsplitsNeeded | NumNodes | MonitoringTimeout | - | 100 | 1 | 3 | 10 | - | 1000 | 3 | 3 | 60 | - - @long-running - Examples: - | NumTransactions | NumCoinsplitsNeeded | NumNodes | MonitoringTimeout | - | 10000 | 21 | 3 | 600 | - - @long-running - Scenario: Simple Stress Test - Given I have a seed node NODE1 - And I have stress-test wallet WALLET_A connected to the seed node NODE1 with broadcast monitoring timeout 60 - And I have mining node MINER connected to base node NODE1 and wallet WALLET_A - When mining node MINER mines 1 blocks - And I have a seed node NODE2 - And I have 1 base nodes connected to all seed nodes - And I have stress-test wallet WALLET_B connected to the seed node NODE2 with broadcast monitoring timeout 60 - # We need to ensure the coinbase lock heights are reached; mine enough blocks - # The following line is how you could mine directly on the node - When mining node MINER mines 8 blocks - Then all nodes are on the same chain tip - When I wait for wallet WALLET_A to have at least 15100000000 uT - - Then I coin split tari in wallet WALLET_A to produce 2000 UTXOs of 5000 uT each with fee_per_gram 4 uT - - # Make sure enough blocks are mined for the coin split transaction to be confirmed - When mining node MINER mines 8 blocks - - Then all nodes are on the same chain tip - Then wallet WALLET_A detects all transactions as Mined_Confirmed - When I send 2000 transactions of 1111 uT each from wallet WALLET_A to wallet WALLET_B at fee_per_gram 4 - # Mine enough blocks for the first block of transactions to be confirmed. - When mining node MINER mines 4 blocks - Then all nodes are on the same chain tip - # Now wait until all transactions are detected as confirmed in WALLET_A, continue to mine blocks if transactions - # are not found to be confirmed as sometimes the previous mining occurs faster than transactions are submitted - # to the mempool - Then while mining via SHA3 miner MINER all transactions in wallet WALLET_A are found to be Mined_Confirmed - # Then wallet WALLET_B detects all transactions as Mined_Confirmed - Then while mining via node NODE1 all transactions in wallet WALLET_B are found to be Mined_Confirmed diff --git a/integration_tests/features/WalletCli.feature b/integration_tests/features/WalletCli.feature deleted file mode 100644 index f53669e00d..0000000000 --- a/integration_tests/features/WalletCli.feature +++ /dev/null @@ -1,157 +0,0 @@ -# Copyright 2022 The Tari Project -# SPDX-License-Identifier: BSD-3-Clause - -@wallet-cli -Feature: Wallet CLI - - Scenario: As a user I want to change base node for a wallet via command line - Given I have a base node NODE1 connected to all seed nodes - And I have a base node NODE2 connected to all seed nodes - And I have wallet WALLET connected to base node NODE1 - Then I change base node of WALLET to NODE2 via command line - - Scenario: As a user I want to set and clear custom base node for a wallet via command line - Given I have a base node NODE1 - And I have a base node NODE2 - And I have wallet WALLET connected to base node NODE1 - Then I set custom base node of WALLET to NODE2 via command line - And I clear custom base node of wallet WALLET via command line - - Scenario: As a user I want to change password via command line - Given I have a seed node SEED - Given I have wallet WALLET connected to all seed nodes - When I stop wallet WALLET - And I change the password of wallet WALLET to changedpwd via command line - Then the password of wallet WALLET is not kensentme - Then the password of wallet WALLET is changedpwd - - Scenario: As a user I want to get balance via command line - Given I have a base node BASE - And I have wallet WALLET connected to base node BASE - And I have mining node MINE connected to base node BASE and wallet WALLET - And mining node MINE mines 5 blocks - Then I wait for wallet WALLET to have at least 1000000 uT - And I stop wallet WALLET - Then I get balance of wallet WALLET is at least 1000000 uT via command line - - @long-running - Scenario: As a user I want to send tari via command line - Given I have a seed node SEED - And I have a base node BASE connected to seed SEED - And I have wallet SENDER connected to base node BASE - And I have wallet RECEIVER connected to base node BASE - And I have mining node MINE connected to base node BASE and wallet SENDER - And mining node MINE mines 5 blocks - Then I wait for wallet SENDER to have at least 1100000 uT - # TODO: Remove this wait when the wallet CLI commands involving transactions will only commence with a valid - # TODO: base node connection. - And I wait 30 seconds - And I stop wallet SENDER - And I send 1000000 uT from SENDER to RECEIVER via command line - Then wallet SENDER has at least 1 transactions that are all TRANSACTION_STATUS_BROADCAST and not cancelled - Then wallet RECEIVER has at least 1 transactions that are all TRANSACTION_STATUS_BROADCAST and not cancelled - And mining node MINE mines 5 blocks - Then I wait for wallet RECEIVER to have at least 1000000 uT - - @critical - Scenario: As a user I want to burn tari via command line - Given I have a seed node SEED - And I have a base node BASE connected to seed SEED - And I have wallet WALLET connected to base node BASE - And I have mining node MINER connected to base node BASE and wallet WALLET - And mining node MINER mines 12 blocks - Then I mine 3 blocks on BASE - Then all nodes are at height 15 - When I wait for wallet WALLET to have at least 221552530060 uT - When I create a burn transaction of 201552500000 uT from WALLET via command line - When I mine 5 blocks on BASE - Then all nodes are at height 20 - Then I get balance of wallet WALLET is at least 20000000000 uT via command line - # TODO: verify the actual burned kernel - - @long-running - Scenario: As a user I want to send one-sided via command line - Given I have a seed node SEED - And I have a base node BASE connected to seed SEED - And I have wallet SENDER connected to base node BASE - And I have wallet RECEIVER connected to base node BASE - And I have mining node MINE connected to base node BASE and wallet SENDER - And mining node MINE mines 5 blocks - Then I wait for wallet SENDER to have at least 1100000 uT - # TODO: Remove this wait when the wallet CLI commands involving transactions will only commence with a valid - # TODO: base node connection. - And I wait 30 seconds - And I stop wallet SENDER - And I send one-sided 1000000 uT from SENDER to RECEIVER via command line - Then wallet SENDER has at least 1 transactions that are all TRANSACTION_STATUS_BROADCAST and not cancelled - And mining node MINE mines 5 blocks - Then I wait for wallet RECEIVER to have at least 1000000 uT - - @long-running - Scenario: As a user I want to make-it-rain via command line - Given I have a seed node SEED - And I have a base node BASE connected to seed SEED - And I have wallet SENDER connected to base node BASE - And I have wallet RECEIVER connected to base node BASE - And I have mining node MINE connected to base node BASE and wallet SENDER - And mining node MINE mines 15 blocks - Then wallets SENDER should have EXACTLY 12 spendable coinbase outputs - # TODO: Remove this wait when the wallet CLI commands involving transactions will only commence with a valid - # TODO: base node connection. - And I wait 30 seconds - And I stop wallet SENDER - And I make it rain from wallet SENDER 1 tx per sec 10 sec 8000 uT 100 increment to RECEIVER via command line - Then wallet SENDER has at least 10 transactions that are all TRANSACTION_STATUS_BROADCAST and not cancelled - Then wallet RECEIVER has at least 10 transactions that are all TRANSACTION_STATUS_BROADCAST and not cancelled - And mining node MINE mines 5 blocks - Then I wait for wallet RECEIVER to have at least 84500 uT - - @long-running - Scenario: As a user I want to coin-split via command line - Given I have a seed node SEED - And I have a base node BASE connected to seed SEED - And I have wallet WALLET connected to base node BASE - And I have mining node MINE connected to base node BASE and wallet WALLET - And mining node MINE mines 4 blocks - Then I wait for wallet WALLET to have at least 1100000 uT - # TODO: Remove this wait when the wallet CLI commands involving transactions will only commence with a valid - # TODO: base node connection. - And I wait 30 seconds - And I stop wallet WALLET - And I do coin split on wallet WALLET to 10000 uT 10 coins via command line - Then wallet WALLET has at least 1 transactions that are all TRANSACTION_STATUS_BROADCAST and not cancelled - And mining node MINE mines 5 blocks - Then wallet WALLET has at least 1 transactions that are all TRANSACTION_STATUS_MINED_CONFIRMED and not cancelled - And I stop wallet WALLET - Then I get count of utxos of wallet WALLET and it's at least 10 via command line - - Scenario: As a user I want to count utxos via command line - Given I have a base node BASE - And I have wallet WALLET connected to base node BASE - And I have mining node MINE connected to base node BASE and wallet WALLET - And mining node MINE mines 4 blocks - Then I wait for wallet WALLET to have at least 1000000 uT - And I stop wallet WALLET - Then I get count of utxos of wallet WALLET and it's at least 1 via command line - - Scenario: As a user I want to export utxos via command line - Given I have a base node BASE - And I have wallet WALLET connected to base node BASE - And I have mining node MINE connected to base node BASE and wallet WALLET - And mining node MINE mines 4 blocks - Then I wait for wallet WALLET to have at least 1000000 uT - And I export the utxos of wallet WALLET via command line - - @flaky - Scenario: As a user I want to discover-peer via command line - Given I have a seed node SEED - And I have wallet WALLET connected to seed node SEED - And I have a base node BASE1 connected to seed SEED - And I have a base node BASE2 connected to seed SEED - And I discover peer BASE2 on wallet WALLET via command line - Then WALLET is connected to BASE2 - - Scenario: As a user I want to run whois via command line - Given I have a base node BASE - And I have wallet WALLET connected to base node BASE - Then I run whois BASE on wallet WALLET via command line diff --git a/integration_tests/features/WalletMonitoring.feature b/integration_tests/features/WalletMonitoring.feature deleted file mode 100644 index a0901e2f22..0000000000 --- a/integration_tests/features/WalletMonitoring.feature +++ /dev/null @@ -1,152 +0,0 @@ -# Copyright 2022 The Tari Project -# SPDX-License-Identifier: BSD-3-Clause - -@wallet-monitoring @wallet -Feature: Wallet Monitoring - - - @flaky - Scenario: Wallets monitoring coinbase after a reorg - # - # Chain 1: - # Collects 10 coinbases into one wallet - # - Given I have a seed node SEED_A - # Add multiple base nodes to ensure more robust comms - And I have a base node NODE_A1 connected to seed SEED_A - And I have wallet WALLET_A1 connected to seed node SEED_A - And I have mining node MINING_A connected to base node SEED_A and wallet WALLET_A1 - And mining node MINING_A mines 10 blocks - Then all nodes are at height 10 - And I list all COINBASE transactions for wallet WALLET_A1 - Then wallet WALLET_A1 has 10 coinbase transactions - Then all COINBASE transactions for wallet WALLET_A1 are valid - Then wallet WALLET_A1 detects at least 7 coinbase transactions as Mined_Confirmed - # - # Chain 2: - # Collects 10 coinbases into one wallet - # - And I have a seed node SEED_B - # Add multiple base nodes to ensure more robust comms - And I have a base node NODE_B1 connected to seed SEED_B - And I have wallet WALLET_B1 connected to seed node SEED_B - And I have mining node MINING_B connected to base node SEED_B and wallet WALLET_B1 - And mining node MINING_B mines 10 blocks - Then all nodes are at height 10 - And I list all COINBASE transactions for wallet WALLET_B1 - Then wallet WALLET_B1 has 10 coinbase transactions - Then all COINBASE transactions for wallet WALLET_B1 are valid - Then wallet WALLET_B1 detects at least 7 coinbase transactions as Mined_Confirmed - # - # Connect Chain 1 and 2 - # - And I have a SHA3 miner NODE_C connected to all seed nodes - Then all nodes are at height 10 - # When tip advances past required confirmations, invalid coinbases still being monitored will be cancelled. - And mining node NODE_C mines 6 blocks - Then all nodes are at height 16 - # Wait for coinbase statuses to change in the wallet - When I wait 30 seconds - And I list all COINBASE transactions for wallet WALLET_A1 - And I list all COINBASE transactions for wallet WALLET_B1 - Then all COINBASE transactions for wallet WALLET_A1 and wallet WALLET_B1 have consistent but opposing cancellation - - # 18+ mins on circle ci - @long-running - Scenario: Wallets monitoring normal transactions after a reorg - # - # Chain 1: - # Collects 10 coinbases into one wallet, send 7 transactions - # - And I have a seed node SEED_A - # Add multiple base nodes to ensure more robust comms - And I have a base node NODE_A1 connected to seed SEED_A - And I have wallet WALLET_A1 connected to seed node SEED_A - And I have wallet WALLET_A2 connected to seed node SEED_A - And I have mining node MINING_A connected to base node SEED_A and wallet WALLET_A1 - When mining node MINING_A mines 10 blocks with min difficulty 20 and max difficulty 9999999999 - Then node SEED_A is at height 10 - Then node NODE_A1 is at height 10 - Then wallet WALLET_A1 detects exactly 7 coinbase transactions as Mined_Confirmed - # Use 7 of the 10 coinbase UTXOs in transactions (others require 3 confirmations) - And I multi-send 7 transactions of 1000000 uT from wallet WALLET_A1 to wallet WALLET_A2 at fee 100 - When mining node MINING_A mines 10 blocks with min difficulty 20 and max difficulty 9999999999 - Then node SEED_A is at height 20 - Then node NODE_A1 is at height 20 - Then wallet WALLET_A2 detects all transactions as Mined_Confirmed - Then all NORMAL transactions for wallet WALLET_A1 are valid - Then wallet WALLET_A1 detects exactly 17 coinbase transactions as Mined_Confirmed - # - # Chain 2: - # Collects 10 coinbases into one wallet, send 7 transactions - # - And I have a seed node SEED_B - # Add multiple base nodes to ensure more robust comms - And I have a base node NODE_B1 connected to seed SEED_B - And I have wallet WALLET_B1 connected to seed node SEED_B - And I have wallet WALLET_B2 connected to seed node SEED_B - And I have mining node MINING_B connected to base node SEED_B and wallet WALLET_B1 - When mining node MINING_B mines 10 blocks with min difficulty 1 and max difficulty 2 - Then node SEED_B is at height 10 - Then node NODE_B1 is at height 10 - Then wallet WALLET_B1 detects exactly 7 coinbase transactions as Mined_Confirmed - # Use 7 of the 10 coinbase UTXOs in transactions (others require 3 confirmations) - And I multi-send 7 transactions of 1000000 uT from wallet WALLET_B1 to wallet WALLET_B2 at fee 100 - When mining node MINING_B mines 10 blocks with min difficulty 1 and max difficulty 2 - Then node SEED_B is at height 20 - Then node NODE_B1 is at height 20 - Then wallet WALLET_B2 detects all transactions as Mined_Confirmed - Then all NORMAL transactions for wallet WALLET_B1 are valid - Then wallet WALLET_B1 detects exactly 17 coinbase transactions as Mined_Confirmed - # - # Connect Chain 1 and 2 - # - And I have a SHA3 miner NODE_C connected to all seed nodes - Then all nodes are at height 20 - # When tip advances past required confirmations, invalid coinbases still being monitored will be cancelled. - And mining node NODE_C mines 6 blocks - Then all nodes are at height 26 - Then wallet WALLET_A1 detects exactly 20 coinbase transactions as Mined_Confirmed - Then wallet WALLET_B1 detects exactly 17 coinbase transactions as Mined_Confirmed - And I list all NORMAL transactions for wallet WALLET_A1 - And I list all NORMAL transactions for wallet WALLET_B1 - # TODO: Uncomment this step when wallets can handle reorg -# Then all NORMAL transactions for wallet WALLET_A1 and wallet WALLET_B1 have consistent but opposing cancellation - And I list all NORMAL transactions for wallet WALLET_A2 - And I list all NORMAL transactions for wallet WALLET_B2 - # TODO: Uncomment this step when wallets can handle reorg -# Then all NORMAL transactions for wallet WALLET_A2 and wallet WALLET_B2 have consistent but opposing cancellation - When I wait 1 seconds - - Scenario Outline: Verify all coinbases in hybrid mining are accounted for - Given I have a seed node SEED_A - And I have a SHA3 miner MINER_SEED_A connected to seed node SEED_A - - And I have a base node NODE1 connected to seed SEED_A - And I have wallet WALLET1 connected to base node NODE1 - And I have a merge mining proxy PROXY1 connected to NODE1 and WALLET1 with default config - - And I have a base node NODE2 connected to seed SEED_A - And I have wallet WALLET2 connected to base node NODE2 - And I have mining node MINER2 connected to base node NODE2 and wallet WALLET2 - - When I co-mine blocks via merge mining proxy PROXY1 and mining node MINER2 - Then all nodes are on the same chain tip - - And mining node MINER_SEED_A mines 5 blocks - Then all nodes are on the same chain tip - - When I wait 1 seconds - Then wallets WALLET1,WALLET2 should have AT_LEAST spendable coinbase outputs - - @flaky - Examples: - | numBlocks | - | 10 | - - @long-running @flaky - Examples: - | numBlocks | - | 100 | - | 1000 | - | 4500 | diff --git a/integration_tests/features/WalletQuery.feature b/integration_tests/features/WalletQuery.feature deleted file mode 100644 index bc7fd106d3..0000000000 --- a/integration_tests/features/WalletQuery.feature +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright 2022 The Tari Project -# SPDX-License-Identifier: BSD-3-Clause - -@wallet-query @wallet -Feature: Wallet Querying - - Scenario: As a wallet I want to query the status of utxos in blocks - Given I have a seed node WalletSeedA - When I mine a block on WalletSeedA with coinbase CB1 - Then node WalletSeedA is at height 1 - Then the UTXO CB1 has been mined according to WalletSeedA - - @critical - Scenario: As a wallet I want to submit a transaction - Given I have a seed node SeedA - When I mine a block on SeedA with coinbase CB1 - When I mine 2 blocks on SeedA - When I create a transaction TX1 spending CB1 to UTX1 - When I submit transaction TX1 to SeedA - Then TX1 is in the mempool - When I mine 2 blocks on SeedA - Then the UTXO UTX1 has been mined according to SeedA - - - @critical - Scenario: As a wallet I cannot submit a locked coinbase transaction - # Using GRPC - Given I have a seed node SeedA - When I mine a block on SeedA with coinbase CB1 - When I create a transaction TX1 spending CB1 to UTX1 - When I submit locked transaction TX1 to SeedA - Then TX1 should not be in the mempool - When I mine 2 blocks on SeedA - When I submit transaction TX1 to SeedA - Then TX1 is in the mempool - When I mine 1 blocks on SeedA - Then the UTXO UTX1 has been mined according to SeedA diff --git a/integration_tests/features/WalletRecovery.feature b/integration_tests/features/WalletRecovery.feature deleted file mode 100644 index 5f6fe1baa7..0000000000 --- a/integration_tests/features/WalletRecovery.feature +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright 2022 The Tari Project -# SPDX-License-Identifier: BSD-3-Clause - -@wallet-recovery @wallet -Feature: Wallet Recovery - - @critical - Scenario: Wallet recovery with connected base node staying online - Given I have a seed node NODE - And I have 1 base nodes connected to all seed nodes - And I have wallet WALLET_A connected to all seed nodes - And I have wallet WALLET_B connected to all seed nodes - And I have mining node MINER connected to base node NODE and wallet WALLET_A - When mining node MINER mines 10 blocks - When I mine 5 blocks on NODE - When I wait for wallet WALLET_A to have at least 55000000000 uT - Then all nodes are at height 15 - And I send 200000 uT from wallet WALLET_A to wallet WALLET_B at fee 25 - And I have mining node MINER_B connected to base node NODE and wallet WALLET_B - When mining node MINER_B mines 2 blocks - When I mine 5 blocks on NODE - Then all nodes are at height 22 - Then I stop wallet WALLET_B - When I recover wallet WALLET_B into wallet WALLET_C connected to all seed nodes - When I wait for wallet WALLET_C to have at least 10000200000 uT - And I have wallet WALLET_D connected to all seed nodes - And I send 100000 uT from wallet WALLET_C to wallet WALLET_D at fee 25 - When I mine 5 blocks on NODE - Then all nodes are at height 27 - Then I wait for wallet WALLET_D to have at least 100000 uT - - Scenario Outline: Multiple Wallet recovery from seed node - Given I have a seed node NODE - And I have non-default wallets connected to all seed nodes using DirectAndStoreAndForward - And I have individual mining nodes connected to each wallet and base node NODE - Then I have each mining node mine 3 blocks - Then all nodes are at height 3* - Then I stop all wallets - When I recover all wallets connected to all seed nodes - Then I wait for recovered wallets to have at least 15000000000 uT - - Examples: - | NumWallets | - | 4 | - - @long-running - Examples: - | NumWallets | - | 5 | - | 10 | - | 20 | - - # BROKEN: recovery scans 15 blocks but does not actually recover any value. Tested manually and recovery works. Suspect something to do with using localnet (just a guess). - @critical @broken - Scenario: Recover one-sided payments - Given I have a seed node NODE - And I have 1 base nodes connected to all seed nodes - And I have wallet WALLET_A connected to all seed nodes - And I have wallet WALLET_B connected to all seed nodes - And I have mining node MINER connected to base node NODE and wallet WALLET_A - When mining node MINER mines 10 blocks - Then all nodes are at height 10 - And I stop wallet WALLET_B - # Send 2 one-sided payments to WALLET_B so it can spend them in two cases - Then I send a one-sided transaction of 1000000 uT from WALLET_A to WALLET_B at fee 20 - Then I send a one-sided transaction of 1000000 uT from WALLET_A to WALLET_B at fee 20 - When mining node MINER mines 5 blocks - Then all nodes are at height 15 - When I recover wallet WALLET_B into wallet WALLET_C connected to all seed nodes - # BREAKS HERE - Then I wait for wallet WALLET_C to have at least 2000000 uT - # Send one of the recovered outputs back to Wallet A as a one-sided transactions - Then I send a one-sided transaction of 900000 uT from WALLET_C to WALLET_A at fee 20 - When mining node MINER mines 5 blocks - Then all nodes are at height 20 - Then I wait for wallet WALLET_C to have less than 1100000 uT - # Send the remaining recovered UTXO to self in standard MW transaction - Then I send 1000000 uT from wallet WALLET_C to wallet WALLET_C at fee 20 - Then I wait for wallet WALLET_C to have less than 100000 uT - When mining node MINER mines 5 blocks - Then all nodes are at height 25 - Then I wait for wallet WALLET_C to have at least 1000000 uT diff --git a/integration_tests/features/WalletRoutingMechanism.feature b/integration_tests/features/WalletRoutingMechanism.feature deleted file mode 100644 index 96472eba69..0000000000 --- a/integration_tests/features/WalletRoutingMechanism.feature +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2022 The Tari Project -# SPDX-License-Identifier: BSD-3-Clause - -@wallet-routing_mechanism @wallet -Feature: Wallet Routing Mechanism - - @flaky - Scenario Outline: Wallets transacting via specified routing mechanism only - Given I have a seed node NODE - And I have base nodes connected to all seed nodes - And I have non-default wallet WALLET_A connected to all seed nodes using - And I have mining node MINER connected to base node NODE and wallet WALLET_A - And I have non-default wallets connected to all seed nodes using - # We need to ensure the coinbase lock heights are gone and we have enough individual UTXOs; mine enough blocks - And mining node MINER mines 20 blocks - Then all nodes are at height 20 - # TODO: This wait is needed to stop base nodes from shutting down - When I wait 1 seconds - When I wait for wallet WALLET_A to have at least 100000000 uT - #When I print the world - And I multi-send 1000000 uT from wallet WALLET_A to all wallets at fee 100 - # TODO: This wait is needed to stop next merge mining task from continuing - When I wait 1 seconds - And mining node MINER mines 1 blocks - Then all nodes are at height 21 - Then all wallets detect all transactions as Mined_Unconfirmed - # TODO: This wait is needed to stop next merge mining task from continuing - When I wait 1 seconds - And mining node MINER mines 11 blocks - Then all nodes are at height 32 - Then all wallets detect all transactions as Mined_Confirmed - # TODO: This wait is needed to stop base nodes from shutting down - When I wait 1 seconds - @long-running - Examples: - | NumBaseNodes | NumWallets | Mechanism | - | 5 | 5 | DirectAndStoreAndForward | - | 5 | 5 | DirectOnly | - - @long-running - Examples: - | NumBaseNodes | NumWallets | Mechanism | - | 5 | 5 | StoreAndForwardOnly | - - Scenario: Store and forward TX - Given I have a seed node SEED - And I have a base node BASE connected to seed SEED - And I have wallet SENDER connected to base node BASE - And I have wallet RECEIVER connected to base node BASE - And I stop wallet RECEIVER - And I have mining node MINE connected to base node BASE and wallet SENDER - And mining node MINE mines 5 blocks - Then I wait for wallet SENDER to have at least 1000000 uT - And I send 1000000 uT from wallet SENDER to wallet RECEIVER at fee 100 - And I wait 121 seconds - And I stop wallet SENDER - And I wait 360 seconds - And I restart wallet RECEIVER - And I wait 121 seconds - And I stop wallet RECEIVER - And I restart wallet SENDER - And wallet SENDER detects all transactions are at least Broadcast \ No newline at end of file diff --git a/integration_tests/features/WalletTransactions.feature b/integration_tests/features/WalletTransactions.feature deleted file mode 100644 index 0b57c5d8a1..0000000000 --- a/integration_tests/features/WalletTransactions.feature +++ /dev/null @@ -1,419 +0,0 @@ -# Copyright 2022 The Tari Project -# SPDX-License-Identifier: BSD-3-Clause - -@wallet-transact @wallet -Feature: Wallet Transactions - - @critical @flaky - Scenario: Wallet sending and receiving one-sided transactions - Given I have a seed node NODE - And I have 1 base nodes connected to all seed nodes - And I have wallet WALLET_A connected to all seed nodes - And I have wallet WALLET_B connected to all seed nodes - And I have wallet WALLET_C connected to all seed nodes - And I have mining node MINER connected to base node NODE and wallet WALLET_A - When mining node MINER mines 15 blocks - Then all nodes are at height 15 - When I wait for wallet WALLET_A to have at least 55000000000 uT - Then I send a one-sided transaction of 1000000 uT from WALLET_A to WALLET_B at fee 100 - Then I send a one-sided transaction of 1000000 uT from WALLET_A to WALLET_B at fee 100 - When mining node MINER mines 5 blocks - Then all nodes are at height 20 - Then I wait for wallet WALLET_B to have at least 2000000 uT - # Spend one of the recovered UTXOs to self in a standard MW transaction - Then I send 900000 uT from wallet WALLET_B to wallet WALLET_B at fee 20 - Then I wait for wallet WALLET_B to have less than 1100000 uT - When mining node MINER mines 5 blocks - Then all nodes are at height 25 - Then I wait for wallet WALLET_B to have at least 1900000 uT - # Make a one-sided payment to a new wallet that is big enough to ensure the second recovered output is spent - Then I send a one-sided transaction of 1500000 uT from WALLET_B to WALLET_C at fee 20 - Then I wait for wallet WALLET_B to have less than 1000000 uT - When mining node MINER mines 5 blocks - Then all nodes are at height 30 - Then I wait for wallet WALLET_C to have at least 1500000 uT - - @critical - Scenario: Wallet sending and receiving one-sided stealth transactions - Given I have a seed node NODE - And I have 1 base nodes connected to all seed nodes - And I have wallet WALLET_A connected to all seed nodes - And I have wallet WALLET_B connected to all seed nodes - And I have wallet WALLET_C connected to all seed nodes - And I have mining node MINER connected to base node NODE and wallet WALLET_A - When mining node MINER mines 15 blocks - Then all nodes are at height 15 - When I wait for wallet WALLET_A to have at least 55000000000 uT - Then I send a one-sided stealth transaction of 1000000 uT from WALLET_A to WALLET_B at fee 100 - Then I send a one-sided stealth transaction of 1000000 uT from WALLET_A to WALLET_B at fee 100 - When mining node MINER mines 5 blocks - Then all nodes are at height 20 - Then I wait for wallet WALLET_B to have at least 2000000 uT - # Spend one of the recovered UTXOs to self in a standard MW transaction - Then I send 900000 uT from wallet WALLET_B to wallet WALLET_B at fee 20 - Then I wait for wallet WALLET_B to have less than 1100000 uT - When mining node MINER mines 5 blocks - Then all nodes are at height 25 - Then I wait for wallet WALLET_B to have at least 1900000 uT - # Make a one-sided payment to a new wallet that is big enough to ensure the second recovered output is spent - Then I send a one-sided stealth transaction of 1500000 uT from WALLET_B to WALLET_C at fee 20 - Then I wait for wallet WALLET_B to have less than 1000000 uT - When mining node MINER mines 5 blocks - Then all nodes are at height 30 - Then I wait for wallet WALLET_C to have at least 1500000 uT - - Scenario: Wallet imports unspent output - Given I have a seed node NODE - And I have 1 base nodes connected to all seed nodes - And I have wallet WALLET_A connected to all seed nodes - And I have wallet WALLET_B connected to all seed nodes - And I have wallet WALLET_C connected to all seed nodes - And I have mining node MINER connected to base node NODE and wallet WALLET_A - When mining node MINER mines 5 blocks - Then all nodes are at height 5 - Then I wait for wallet WALLET_A to have at least 10000000000 uT - And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 - Then mining node MINER mines 5 blocks - Then all nodes are at height 10 - Then I wait for wallet WALLET_B to have at least 1000000 uT - Then I stop wallet WALLET_B - Then I import WALLET_B unspent outputs to WALLET_C - Then I wait for wallet WALLET_C to have at least 1000000 uT - Then I restart wallet WALLET_C - Then I wait for wallet WALLET_C to have at least 1000000 uT - Then I check if last imported transactions are valid in wallet WALLET_C - - Scenario: Wallet has two connected miners, coinbase's are computed correctly - Given I have a seed node NODE - And I have 1 base nodes connected to all seed nodes - And I have wallet WALLET_A connected to all seed nodes - And I have mining node MINER connected to base node NODE and wallet WALLET_A - And I have mining node MINER2 connected to base node NODE and wallet WALLET_A - When mining node MINER mines 2 blocks - When mining node MINER2 mines 2 blocks - When mining node MINER mines 3 blocks - When mining node MINER2 mines 3 blocks - Then all nodes are at height 10 - Then I wait for wallet WALLET_A to have at least 20000000000 uT - - @flaky - Scenario: Wallet imports spent outputs that become invalidated - Given I have a seed node NODE - And I have 1 base nodes connected to all seed nodes - And I have wallet WALLET_A connected to all seed nodes - And I have wallet WALLET_B connected to all seed nodes - And I have wallet WALLET_C connected to all seed nodes - And I have mining node MINER connected to base node NODE and wallet WALLET_A - When mining node MINER mines 5 blocks - Then all nodes are at height 5 - Then I wait for wallet WALLET_A to have at least 10000000000 uT - And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 - Then mining node MINER mines 5 blocks - Then all nodes are at height 10 - Then I wait for wallet WALLET_B to have at least 1000000 uT - When I send 900000 uT from wallet WALLET_B to wallet WALLET_A at fee 100 - Then mining node MINER mines 5 blocks - Then all nodes are at height 15 - When I wait for wallet WALLET_B to have at least 50000 uT - Then I stop wallet WALLET_B - Then I import WALLET_B spent outputs to WALLET_C - Then I wait for wallet WALLET_C to have at least 1000000 uT - Then I restart wallet WALLET_C - Then I wait for wallet WALLET_C to have less than 1 uT - # TODO Either remove the check for invalid Faux tx and change the test name or implement a new way to invalidate Faux Tx - # The concept of invalidating the Faux transaction doesn't exist in this branch anymore. There has been talk of removing the Faux transaction - # for imported UTXO's anyway so until that is decided we will just check that the imported output becomes Spent - #Then I check if last imported transactions are invalid in wallet WALLET_C - - @flaky - Scenario: Wallet imports reorged outputs that become invalidated - # Chain 1 - Given I have a seed node SEED_B - And I have a base node B connected to seed SEED_B - And I have wallet WB connected to base node B - And I have wallet WALLET_RECEIVE_TX connected to base node B - And I have wallet WALLET_IMPORTED connected to base node B - And I have mining node BM connected to base node B and wallet WB - And mining node BM mines 4 blocks with min difficulty 1 and max difficulty 50 - Then I wait for wallet WB to have at least 1000000 uT - And I send 1000000 uT from wallet WB to wallet WALLET_RECEIVE_TX at fee 100 - Then mining node BM mines 4 blocks with min difficulty 50 and max difficulty 100 - When node B is at height 8 - Then I wait for wallet WALLET_RECEIVE_TX to have at least 1000000 uT - Then I stop wallet WALLET_RECEIVE_TX - Then I import WALLET_RECEIVE_TX unspent outputs to WALLET_IMPORTED - Then I wait for wallet WALLET_IMPORTED to have at least 1000000 uT - # This triggers a validation of the imported outputs - Then I restart wallet WALLET_IMPORTED - # Chain 2 - Given I have a seed node SEED_C - And I have a base node C connected to seed SEED_C - And I have wallet WC connected to base node C - And I have mining node CM connected to base node C and wallet WC - And mining node CM mines 10 blocks with min difficulty 1000 and max difficulty 9999999999 - # Connect chain 1 and 2 - Then node B is at height 8 - And node C is at height 10 - Given I have a base node SA connected to nodes B,C - Then node SA is at height 10 - And node B is at height 10 - And node C is at height 10 - Then I restart wallet WALLET_IMPORTED - Then I wait for wallet WALLET_IMPORTED to have less than 1 uT - And mining node CM mines 1 blocks with min difficulty 1000 and max difficulty 9999999999 - And node B is at height 11 - And node C is at height 11 - # TODO Either remove the check for invalid Faux tx and change the test name or implement a new way to invalidate Faux Tx - # The concept of invalidating the Faux transaction doesn't exist in this branch anymore. There has been talk of removing the Faux transaction - # for imported UTXO's anyway so until that is decided we will just check that the imported output becomes invalid - # Then I check if last imported transactions are invalid in wallet WALLET_IMPORTED - - Scenario: Wallet imports faucet UTXO - Given I have a seed node NODE - And I have 1 base nodes connected to all seed nodes - And I have wallet WALLET_A connected to all seed nodes - And I have wallet WALLET_B connected to all seed nodes - And I have wallet WALLET_C connected to all seed nodes - And I have mining node MINER connected to base node NODE and wallet WALLET_A - When mining node MINER mines 5 blocks - Then all nodes are at height 5 - Then I wait for wallet WALLET_A to have at least 10000000000 uT - And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 - When mining node MINER mines 6 blocks - Then all nodes are at height 11 - Then I wait for wallet WALLET_B to have at least 1000000 uT - Then I stop wallet WALLET_B - Then I import WALLET_B unspent outputs as faucet outputs to WALLET_C - Then I wait for wallet WALLET_C to have at least 1000000 uT - And I send 500000 uT from wallet WALLET_C to wallet WALLET_A at fee 100 - When mining node MINER mines 6 blocks - Then all nodes are at height 17 - Then I wait for wallet WALLET_C to have at least 400000 uT - - Scenario: Wallet should display all transactions made - Given I have a seed node NODE - And I have 1 base nodes connected to all seed nodes - And I have wallet WALLET_A connected to all seed nodes - And I have wallet WALLET_B connected to all seed nodes - And I have mining node MINER connected to base node NODE and wallet WALLET_A - When mining node MINER mines 10 blocks - Then all nodes are at height 10 - Then I wait for wallet WALLET_A to have at least 10000000000 uT - And I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 - And I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 - And I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 - And I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 - And I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 - Then mining node MINER mines 5 blocks - Then all nodes are at height 15 - Then I wait for wallet WALLET_B to have at least 500000 uT - Then I check if wallet WALLET_B has 5 transactions - Then I restart wallet WALLET_B - Then I check if wallet WALLET_B has 5 transactions - - Scenario: Wallet clearing out invalid transactions after a reorg - # - # Chain 1: - # Collects 7 coinbases into one wallet, send 7 transactions - # Stronger chain - # - Given I have a seed node SEED_A - And I have a base node NODE_A1 connected to seed SEED_A - And I have wallet WALLET_A1 connected to seed node SEED_A - And I have wallet WALLET_A2 connected to seed node SEED_A - And I have mining node MINER_A1 connected to base node SEED_A and wallet WALLET_A1 - When mining node MINER_A1 mines 7 blocks with min difficulty 200 and max difficulty 100000 - Then node SEED_A is at height 7 - Then node NODE_A1 is at height 7 - When I mine 3 blocks on SEED_A - Then wallet WALLET_A1 detects at least 7 coinbase transactions as Mined_Confirmed - Then node SEED_A is at height 10 - Then node NODE_A1 is at height 10 - And I multi-send 7 transactions of 1000000 uT from wallet WALLET_A1 to wallet WALLET_A2 at fee 100 - # - # Chain 2: - # Collects 7 coinbases into one wallet, send 7 transactions - # Weaker chain - # - And I have a seed node SEED_B - And I have a base node NODE_B1 connected to seed SEED_B - And I have wallet WALLET_B1 connected to seed node SEED_B - And I have wallet WALLET_B2 connected to seed node SEED_B - And I have mining node MINER_B1 connected to base node SEED_B and wallet WALLET_B1 - When mining node MINER_B1 mines 7 blocks with min difficulty 1 and max difficulty 100 - Then node SEED_B is at height 7 - Then node NODE_B1 is at height 7 - When I mine 5 blocks on SEED_B - Then wallet WALLET_B1 detects at least 7 coinbase transactions as Mined_Confirmed - Then node SEED_B is at height 12 - Then node NODE_B1 is at height 12 - And I multi-send 7 transactions of 1000000 uT from wallet WALLET_B1 to wallet WALLET_B2 at fee 100 - # - # Connect Chain 1 and 2 in stages - # New node connects to weaker chain, receives all broadcast (not mined) transactions into mempool - # New node connects to stronger chain, then reorgs its complete chain - # New node mines blocks; no invalid inputs from the weaker chain should be used in the block template - # - And I have a base node NODE_C connected to seed SEED_B - Then node NODE_C is at height 12 - # Wait for the reorg to filter through - And I connect node SEED_A to node NODE_C - Then all nodes are at height 10 - When I mine 6 blocks on NODE_C - Then all nodes are at height 16 - - Scenario: Wallet send transactions while offline - Given I have a seed node SEED - And I have wallet WALLET_A connected to seed node SEED - And I have wallet WALLET_B connected to seed node SEED - And I have mining node MINER_A connected to base node SEED and wallet WALLET_A - When mining node MINER_A mines 1 blocks with min difficulty 1 and max difficulty 100000 - When I mine 4 blocks on SEED - Then I wait for wallet WALLET_A to have at least 1000000000 uT - When I stop wallet WALLET_B - When I stop node SEED - Then I wait 10 seconds - Then I send 100000000 uT without waiting for broadcast from wallet WALLET_A to wallet WALLET_B at fee 20 - Then I wait 10 seconds - And I start base node SEED - And I have a base node NODE_A connected to seed SEED - And I have a base node NODE_B connected to seed SEED - And I stop wallet WALLET_A - And I start wallet WALLET_A - And I start wallet WALLET_B - Then all nodes are at height 5 - When I mine 1 blocks on SEED - Then all nodes are at height 6 - Then wallet WALLET_B detects all transactions are at least Pending - Then I wait 1 seconds - - Scenario: Short wallet clearing out invalid transactions after a reorg - # - # Chain 1: - # Collects 7 coinbases into one wallet, send 7 transactions - # Stronger chain - # - Given I have a seed node SEED_A - And I have a base node NODE_A1 connected to seed SEED_A - And I have wallet WALLET_A1 connected to seed node SEED_A - And I have wallet WALLET_A2 connected to seed node SEED_A - And I have mining node MINER_A1 connected to base node SEED_A and wallet WALLET_A1 - When mining node MINER_A1 mines 1 blocks with min difficulty 200 and max difficulty 100000 - Then node SEED_A is at height 1 - Then node NODE_A1 is at height 1 - When I mine 3 blocks on SEED_A - Then wallet WALLET_A1 detects at least 1 coinbase transactions as Mined_Confirmed - Then node SEED_A is at height 4 - Then node NODE_A1 is at height 4 - And I multi-send 1 transactions of 10000 uT from wallet WALLET_A1 to wallet WALLET_A2 at fee 20 - # - # Chain 2: - # Collects 7 coinbases into one wallet, send 7 transactions - # Weaker chain - # - And I have a seed node SEED_B - And I have a base node NODE_B1 connected to seed SEED_B - And I have wallet WALLET_B1 connected to seed node SEED_B - And I have wallet WALLET_B2 connected to seed node SEED_B - And I have mining node MINER_B1 connected to base node SEED_B and wallet WALLET_B1 - When mining node MINER_B1 mines 2 blocks with min difficulty 1 and max difficulty 100 - Then node SEED_B is at height 2 - Then node NODE_B1 is at height 2 - When I mine 3 blocks on SEED_B - Then wallet WALLET_B1 detects at least 2 coinbase transactions as Mined_Confirmed - Then node SEED_B is at height 5 - Then node NODE_B1 is at height 5 - And I multi-send 2 transactions of 10000 uT from wallet WALLET_B1 to wallet WALLET_B2 at fee 20 - # - # Connect Chain 1 and 2 in stages - # New node connects to weaker chain, receives all broadcast (not mined) transactions into mempool - # New node connects to stronger chain, then reorgs its complete chain - # New node mines blocks; no invalid inputs from the weaker chain should be used in the block template - # - And I have a base node NODE_C connected to seed SEED_B - Then node NODE_C is at height 5 - # Wait for the reorg to filter through - And I connect node SEED_A to node NODE_C - Then all nodes are at height 4 - When I mine 2 blocks on NODE_C - Then all nodes are at height 6 - - @flaky @long-running - Scenario: Wallet SAF negotiation and cancellation with offline peers - Given I have a seed node NODE - And I have 1 base nodes connected to all seed nodes - And I have wallet WALLET_A connected to all seed nodes - And I have wallet WALLET_RECV connected to all seed nodes - And I have mining node MINER connected to base node NODE and wallet WALLET_A - And mining node MINER mines 5 blocks - Then all nodes are at height 5 - Then I wait for wallet WALLET_A to have at least 10000000000 uT - And I have non-default wallet WALLET_SENDER connected to all seed nodes using StoreAndForwardOnly - And I send 100000000 uT from wallet WALLET_A to wallet WALLET_SENDER at fee 100 - And mining node MINER mines 5 blocks - Then all nodes are at height 10 - Then I wait for wallet WALLET_SENDER to have at least 100000000 uT - And I stop wallet WALLET_RECV - And I send 1000000 uT without waiting for broadcast from wallet WALLET_SENDER to wallet WALLET_RECV at fee 100 - When wallet WALLET_SENDER detects last transaction is Pending - Then I stop wallet WALLET_SENDER - And I start wallet WALLET_RECV - And I wait 5 seconds - When wallet WALLET_RECV detects all transactions are at least Pending - Then I cancel last transaction in wallet WALLET_RECV - When I wait 15 seconds - Then I stop wallet WALLET_RECV - And I start wallet WALLET_SENDER - # This is a weirdness that I haven't been able to figure out. When you start WALLET_SENDER on the line above it - # requests SAF messages from the base nodes the base nodes get the request and attempt to send the stored messages - # but the connection fails. It requires a second reconnection and request for the SAF messages to be delivered. - And I wait 10 seconds - Then I restart wallet WALLET_SENDER - And I wait 10 seconds - Then I restart wallet WALLET_SENDER - When I wait 30 seconds - And mining node MINER mines 5 blocks - Then all nodes are at height 15 - When wallet WALLET_SENDER detects all transactions as Mined_Confirmed - And I start wallet WALLET_RECV - And I wait 5 seconds - Then I restart wallet WALLET_RECV - And I wait 5 seconds - Then I restart wallet WALLET_RECV - Then I wait for wallet WALLET_RECV to have at least 1000000 uT - - @critical - Scenario: Wallet should cancel stale transactions - Given I have a seed node NODE - And I have 1 base nodes connected to all seed nodes - And I have non-default wallet WALLET_SENDER connected to all seed nodes using StoreAndForwardOnly - And I have wallet WALLET_RECV connected to all seed nodes - And I have mining node MINER connected to base node NODE and wallet WALLET_SENDER - And mining node MINER mines 5 blocks - Then all nodes are at height 5 - Then I wait for wallet WALLET_SENDER to have at least 10000000000 uT - And I stop wallet WALLET_RECV - When I wait 15 seconds - And I send 1000000 uT without waiting for broadcast from wallet WALLET_SENDER to wallet WALLET_RECV at fee 100 - Then I cancel last transaction in wallet WALLET_SENDER - Then I restart wallet WALLET_RECV - When I wait 15 seconds - When wallet WALLET_RECV detects last transaction is Cancelled - -@critical - Scenario: Create burn transaction - Given I have a seed node NODE - And I have 2 base nodes connected to all seed nodes - And I have wallet WALLET_A connected to all seed nodes - And I have wallet WALLET_B connected to all seed nodes - And I have mining node MINER_A connected to base node NODE and wallet WALLET_A - And I have mining node MINER_B connected to base node NODE and wallet WALLET_B - When mining node MINER_A mines 12 blocks - When mining node MINER_B mines 3 blocks - Then all nodes are at height 15 - When I wait for wallet WALLET_A to have at least 221552530060 uT - When I create a burn transaction of 201552500000 uT from WALLET_A at fee 100 - When mining node MINER_B mines 5 blocks - Then all nodes are at height 20 - Then wallet WALLET_A detects all transactions as Mined_Confirmed - When I wait for wallet WALLET_A to have at least 20000000000 uT diff --git a/integration_tests/features/WalletTransfer.feature b/integration_tests/features/WalletTransfer.feature deleted file mode 100644 index 6af2d2714f..0000000000 --- a/integration_tests/features/WalletTransfer.feature +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright 2022 The Tari Project -# SPDX-License-Identifier: BSD-3-Clause - -@wallet-transfer @wallet -Feature: Wallet Transfer - - # This is probably the most important base layer test - # BROKEN: Runs fine when run by itself, but not with other tests - or maybe is flaky - @critical - Scenario: As a wallet send to a wallet connected to a different base node - Given I have a seed node SEED_A - And I have a seed node SEED_B - And I have a base node NODE_A connected to all seed nodes - And I have a base node NODE_B connected to all seed nodes - And I have wallet WALLET_A with 10T connected to base node NODE_A - And I have wallet WALLET_B connected to base node NODE_B - When I transfer 5T from WALLET_A to WALLET_B - And I mine 4 blocks on SEED_A - # BREAKS HERE - Then wallet WALLET_A has 5T - And wallet WALLET_B has 5T - - Scenario: As a wallet I want to submit multiple transfers - Given I have a seed node NODE - # Add a 2nd node otherwise initial sync will not succeed - And I have 1 base nodes connected to all seed nodes - And I have wallet Wallet_A connected to all seed nodes - And I have mining node MINER connected to base node NODE and wallet Wallet_A - And I have wallet Wallet_B connected to all seed nodes - And I have wallet Wallet_C connected to all seed nodes - When mining node MINER mines 2 blocks - Then all nodes are at height 2 - # Ensure the coinbase lock heights have expired - And mining node MINER mines 3 blocks - Then all nodes are at height 5 - # Ensure the coinbase lock heights have expired - And mining node MINER mines 5 blocks - Then all nodes are at height 10 - When I transfer 50000 uT from Wallet_A to Wallet_B and Wallet_C at fee 20 - And mining node MINER mines 10 blocks - Then all nodes are at height 20 - Then all wallets detect all transactions as Mined_Confirmed - - Scenario: As a wallet I want to submit transfers to myself - Given I have a seed node NODE - # Add a 2nd node otherwise initial sync will not succeed - And I have 1 base nodes connected to all seed nodes - And I have wallet Wallet_A connected to all seed nodes - And I have mining node MINER connected to base node NODE and wallet Wallet_A - When mining node MINER mines 10 blocks - Then all nodes are at height 10 - When I transfer 50000 uT to self from wallet Wallet_A at fee 25 - And I mine 5 blocks on NODE - Then all nodes are at height 15 - Then all wallets detect all transactions as Mined_Confirmed - - Scenario: As a wallet I want to create a HTLC transaction - Given I have a seed node NODE - # Add a 2nd node otherwise initial sync will not succeed - And I have 1 base nodes connected to all seed nodes - And I have wallet WALLET_A connected to all seed nodes - And I have wallet WALLET_B connected to all seed nodes - And I have mining node MINER connected to base node NODE and wallet WALLET_A - When mining node MINER mines 10 blocks - Then I wait for wallet WALLET_A to have at least 10000000000 uT - When I broadcast HTLC transaction with 5000000000 uT from wallet WALLET_A to wallet WALLET_B at fee 20 - And mining node MINER mines 6 blocks - And I claim an HTLC transaction with wallet WALLET_B at fee 20 - And mining node MINER mines 6 blocks - Then I wait for wallet WALLET_B to have at least 4000000000 uT - - Scenario: As a wallet I want to claim a HTLC refund transaction - Given I have a seed node NODE - # Add a 2nd node otherwise initial sync will not succeed - And I have 1 base nodes connected to all seed nodes - And I have wallet WALLET_A connected to all seed nodes - And I have wallet WALLET_B connected to all seed nodes - And I have wallet WALLET_C connected to all seed nodes - And I have mining node MINER connected to base node NODE and wallet WALLET_A - And I have mining node MINER_2 connected to base node NODE and wallet WALLET_C - When mining node MINER mines 10 blocks - Then I wait for wallet WALLET_A to have at least 10000000000 uT - When I broadcast HTLC transaction with 5000000000 uT from wallet WALLET_A to wallet WALLET_B at fee 20 - # atomic swaps are set at lock of 720 blocks - And mining node MINER_2 mines 720 blocks - And I claim an HTLC refund transaction with wallet WALLET_A at fee 20 - And mining node MINER_2 mines 6 blocks - Then I wait for wallet WALLET_A to have at least 9000000000 uT diff --git a/integration_tests/features/support/ffi_steps.js b/integration_tests/features/support/ffi_steps.js deleted file mode 100644 index 81c351ed5a..0000000000 --- a/integration_tests/features/support/ffi_steps.js +++ /dev/null @@ -1,750 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const { Given, When, Then } = require("@cucumber/cucumber"); -const expect = require("chai").expect; - -const { sleep, waitForIterate } = require("../../helpers/util"); - -Then("I want to get emoji id of ffi wallet {word}", async function (name) { - let wallet = this.getWallet(name); - let emoji_id = wallet.identifyEmoji(); - console.log(emoji_id); - expect(emoji_id.length).to.be.equal( - 22 * 3, // 22 emojis, 3 bytes per one emoji - `Emoji id has wrong length : ${emoji_id}` - ); -}); - -When( - "I send {int} uT from ffi wallet {word} to wallet {word} at fee {int}", - function (amount, sender, receiver, feePerGram) { - let ffiWallet = this.getWallet(sender); - let result = ffiWallet.sendTransaction( - this.getWalletPubkey(receiver), - amount, - feePerGram, - `Send from ffi ${sender} to ${receiver} at fee ${feePerGram}`, - false - ); - console.log(result); - } -); - -When( - "I send {int} uT from ffi wallet {word} to wallet {word} at fee {int} via one-sided transactions", - function (amount, sender, receiver, feePerGram) { - let ffiWallet = this.getWallet(sender); - let result = ffiWallet.sendTransaction( - this.getWalletPubkey(receiver), - amount, - feePerGram, - `Send from ffi ${sender} to ${receiver} at fee ${feePerGram}`, - true - ); - console.log(result); - } -); - -When( - "I set passphrase {word} of ffi wallet {word}", - function (passphrase, name) { - let wallet = this.getWallet(name); - wallet.applyEncryption(passphrase); - } -); - -Then( - "I have {int} received and {int} send transaction in ffi wallet {word}", - { timeout: 120 * 1000 }, - async function (received, send, name) { - let wallet = this.getWallet(name); - let completed = wallet.getCompletedTxs(); - let inbound = 0; - let outbound = 0; - let length = completed.getLength(); - let inboundTxs = wallet.getInboundTxs(); - inbound += inboundTxs.getLength(); - inboundTxs.destroy(); - let outboundTxs = wallet.getOutboundTxs(); - outbound += outboundTxs.getLength(); - outboundTxs.destroy(); - for (let i = 0; i < length; i++) { - { - let tx = completed.getAt(i); - if (tx.isOutbound()) { - outbound++; - } else { - inbound++; - } - tx.destroy(); - } - } - completed.destroy(); - - expect(outbound, "Outbound transaction count mismatch").to.be.equal(send); - expect(inbound, "Inbound transaction count mismatch").to.be.equal(received); - } -); - -When( - "I add contact with alias {word} and pubkey {word} to ffi wallet {word}", - function (alias, walletName, ffiWalletName) { - let ffiWallet = this.getWallet(ffiWalletName); - ffiWallet.addContact(alias, this.getWalletPubkey(walletName)); - } -); - -Then( - "I have contact with alias {word} and pubkey {word} in ffi wallet {word}", - function (alias, walletName, ffiWalletName) { - let wallet = this.getWalletPubkey(walletName); - let ffiWallet = this.getWallet(ffiWalletName); - let contacts = ffiWallet.getContactList(); - let length = contacts.getLength(); - let found = false; - for (let i = 0; i < length; i++) { - { - let contact = contacts.getAt(i); - let hex = contact.getPubkeyHex(); - if (wallet === hex) { - found = true; - } - contact.destroy(); - } - } - contacts.destroy(); - expect(found).to.be.equal(true); - } -); - -When( - "I remove contact with alias {word} from ffi wallet {word}", - function (alias, walletName) { - let ffiWallet = this.getWallet(walletName); - let contacts = ffiWallet.getContactList(); - let length = contacts.getLength(); - for (let i = 0; i < length; i++) { - { - let contact = contacts.getAt(i); - let calias = contact.getAlias(); - if (alias === calias) { - ffiWallet.removeContact(contact); - } - contact.destroy(); - } - } - contacts.destroy(); - } -); - -Then( - "I don't have contact with alias {word} in ffi wallet {word}", - function (alias, walletName) { - let ffiWallet = this.getWallet(walletName); - let contacts = ffiWallet.getContactList(); - let length = contacts.getLength(); - let found = false; - for (let i = 0; i < length; i++) { - { - let contact = contacts.getAt(i); - let calias = contact.getAlias(); - if (alias === calias) { - found = true; - } - contact.destroy(); - } - } - contacts.destroy(); - expect(found).to.be.equal(false); - } -); - -Then( - "I wait for ffi wallet {word} to have at least {int} contacts to be {word}", - { timeout: 125 * 1000 }, - async function (walletName, amount, status) { - const online = "Online"; - const offline = "Offline"; - const neverSeen = "NeverSeen"; - expect( - status === online || status === offline || status === neverSeen - ).to.equal(true); - - let wallet = this.getWallet(walletName); - - console.log("\n"); - console.log( - "Waiting for " + - walletName + - " to have at least " + - amount + - " contacts with status '" + - status + - "'" - ); - - let contactsWithStatus; - await waitForIterate( - () => { - { - contactsWithStatus = 0; - wallet.getLivenessData().forEach(function (value, _key) { - console.error("STATUS: ", value); - if (value.online_status === status) { - contactsWithStatus++; - } - }); - return contactsWithStatus >= amount; - } - }, - true, - 1000, - 60 - ); - - if (!(contactsWithStatus >= amount)) { - console.log( - walletName + - " does not have at least " + - amount + - " contacts with status '" + - status + - "'!" - ); - } - expect(contactsWithStatus >= amount).to.equal(true); - } -); - -When( - "I set base node {word} for ffi wallet {word}", - function (node, walletName) { - let wallet = this.getWallet(walletName); - let peer = this.nodes[node].peerAddress().split("::"); - wallet.addBaseNodePeer(peer[0], peer[1]); - } -); - -Then( - "I wait for ffi wallet {word} to have {int} pending outbound transaction(s)", - { timeout: 125 * 1000 }, - async function (walletName, count) { - let wallet = this.getWallet(walletName); - let broadcast = wallet.getOutboundTransactions(); - let length = broadcast.getLength(); - broadcast.destroy(); - let retries = 1; - const retries_limit = 120; - while (length != count && retries <= retries_limit) { - await sleep(1000); - broadcast = wallet.getOutboundTransactions(); - length = broadcast.getLength(); - broadcast.destroy(); - ++retries; - } - expect(length, "Number of pending messages mismatch").to.be.equal(count); - } -); - -Then( - "I cancel all outbound transactions on ffi wallet {word} and it will cancel {int} transaction", - function (walletName, count) { - const wallet = this.getWallet(walletName); - let txs = wallet.getOutboundTransactions(); - let cancelled = 0; - for (let i = 0; i < txs.getLength(); i++) { - let tx = txs.getAt(i); - let cancellation = wallet.cancelPendingTransaction(tx.getTransactionID()); - tx.destroy(); - if (cancellation) { - cancelled++; - } - } - txs.destroy(); - expect(cancelled).to.be.equal(count); - } -); - -Given( - "I have a ffi wallet {word} connected to base node {word}", - { timeout: 15 * 1000 }, - async function (walletName, nodeName) { - let ffiWallet = await this.createAndAddFFIWallet(walletName, null); - let peer = this.nodes[nodeName].peerAddress().split("::"); - ffiWallet.addBaseNodePeer(peer[0], peer[1]); - } -); - -Given( - "I have a ffi wallet {word} connected to seed node {word}", - { timeout: 15 * 1000 }, - async function (walletName, nodeName) { - let ffiWallet = await this.createAndAddFFIWallet(walletName, null); - let peer = this.seeds[nodeName].peerAddress().split("::"); - ffiWallet.addBaseNodePeer(peer[0], peer[1]); - } -); - -Then( - "I recover wallet {word} into ffi wallet {word} from seed words on node {word}", - async function (walletName, ffiWalletName, node) { - let wallet = this.getWallet(walletName); - const seed_words_text = wallet.getSeedWords(); - wallet.stop(); - await sleep(1000); - let ffiWallet = await this.createAndAddFFIWallet( - ffiWalletName, - seed_words_text - ); - let peer = this.nodes[node].peerAddress().split("::"); - ffiWallet.addBaseNodePeer(peer[0], peer[1]); - ffiWallet.startRecovery(peer[0]); - } -); - -Then( - "I retrieve the mnemonic word list for {word} from ffi wallet {word}", - async function (language, walletName) { - const wallet = this.getWallet(walletName); - const mnemonicWordList = wallet.getMnemonicWordListForLanguage(language); - console.log("Mnemonic word list for", language, ":", mnemonicWordList); - expect(mnemonicWordList.length).to.equal(2048); - } -); - -Then( - "Check callbacks for finished inbound tx on ffi wallet {word}", - async function (walletName) { - const wallet = this.getWallet(walletName); - expect(wallet.receivedTransaction).to.be.greaterThanOrEqual(1); - expect(wallet.transactionBroadcast).to.be.greaterThanOrEqual(1); - wallet.clearCallbackCounters(); - } -); - -Then( - "Check callbacks for finished outbound tx on ffi wallet {word}", - async function (walletName) { - const wallet = this.getWallet(walletName); - expect(wallet.receivedTransactionReply).to.be.greaterThanOrEqual(1); - expect(wallet.transactionBroadcast).to.be.greaterThanOrEqual(1); - wallet.clearCallbackCounters(); - } -); - -Then( - "I wait for ffi wallet {word} to receive {int} transaction", - { timeout: 125 * 1000 }, - async function (walletName, amount) { - let wallet = this.getWallet(walletName); - - console.log("\n"); - console.log( - "Waiting for " + walletName + " to receive " + amount + " transaction(s)" - ); - - await waitForIterate( - () => { - return wallet.getCounters().received >= amount; - }, - true, - 1000, - 120 - ); - - if (wallet.getCounters().received < amount) { - console.log( - `Expected to receive at least ${amount} transaction(s) but got ${ - wallet.getCounters().received - }` - ); - } else { - console.log(wallet.getCounters()); - } - expect(wallet.getCounters().received).to.be.gte(amount); - } -); - -Then( - "I wait for ffi wallet {word} to receive {int} finalization", - { timeout: 125 * 1000 }, - async function (walletName, amount) { - let wallet = this.getWallet(walletName); - - console.log("\n"); - console.log( - "Waiting for " + - walletName + - " to receive " + - amount + - " transaction finalization(s)" - ); - - await waitForIterate( - () => { - return wallet.getCounters().finalized >= amount; - }, - true, - 1000, - 120 - ); - - if (!(wallet.getCounters().finalized >= amount)) { - console.log("Counter not adequate!"); - } else { - console.log(wallet.getCounters()); - } - expect(wallet.getCounters().finalized >= amount).to.equal(true); - } -); - -Then( - "I wait for ffi wallet {word} to receive {int} broadcast", - { timeout: 125 * 1000 }, - async function (walletName, amount) { - let wallet = this.getWallet(walletName); - - console.log("\n"); - console.log( - "Waiting for " + - walletName + - " to receive " + - amount + - " transaction broadcast(s)" - ); - - await waitForIterate( - () => { - return wallet.getCounters().broadcast >= amount; - }, - true, - 1000, - 120 - ); - - if (!(wallet.getCounters().broadcast >= amount)) { - console.log("Counter not adequate!"); - } else { - console.log(wallet.getCounters()); - } - expect(wallet.getCounters().broadcast >= amount).to.equal(true); - } -); - -Then( - "I wait for ffi wallet {word} to receive {int} mined", - { timeout: 125 * 1000 }, - async function (walletName, amount) { - let wallet = this.getWallet(walletName); - - console.log("\n"); - console.log( - "Waiting for " + - walletName + - " to receive " + - amount + - " transaction(s) mined" - ); - - await waitForIterate( - () => { - return wallet.getCounters().mined >= amount; - }, - true, - 1000, - 120 - ); - - if (!(wallet.getCounters().mined >= amount)) { - console.log("Counter not adequate!"); - } else { - console.log(wallet.getCounters()); - } - expect(wallet.getCounters().mined >= amount).to.equal(true); - } -); - -Then( - "I wait for ffi wallet {word} to receive {word} {int} SAF message", - { timeout: 125 * 1000 }, - async function (walletName, comparison, amount) { - const atLeast = "AT_LEAST"; - const exactly = "EXACTLY"; - expect(comparison === atLeast || comparison === exactly).to.equal(true); - let wallet = this.getWallet(walletName); - console.log( - "\nWaiting for " + - walletName + - " to receive " + - comparison + - " " + - amount + - " SAF messages(s)" - ); - - await waitForIterate( - () => { - return wallet.getCounters().saf >= amount; - }, - true, - 1000, - 120 - ); - - if (wallet.getCounters().saf < amount) { - console.log( - `Expected to receive ${amount} SAF messages but received ${ - wallet.getCounters().saf - }` - ); - } else { - console.log(wallet.getCounters()); - } - if (comparison === atLeast) { - expect(wallet.getCounters().saf).to.be.gte(amount); - } else { - expect(wallet.getCounters().saf).to.be.equal(amount); - } - } -); - -Then( - "I wait for ffi wallet {word} to have at least {int} uT", - { timeout: 125 * 1000 }, - async function (walletName, amount) { - let wallet = this.getWallet(walletName); - - console.log("\n"); - console.log( - "Waiting for " + walletName + " balance to be at least " + amount + " uT" - ); - - await waitForIterate( - () => { - return wallet.getBalance().available >= amount; - }, - true, - 1000, - 120 - ); - - let balance = wallet.pollBalance().available; - - if (!(balance >= amount)) { - console.log("Balance not adequate!"); - } - - expect(balance >= amount).to.equal(true); - } -); - -Then( - "ffi wallet {word} detects {word} {int} ffi transactions to be {word}", - { timeout: 125 * 1000 }, - async function (walletName, comparison, amount, status) { - // Pending -> Completed -> Broadcast -> Mined Unconfirmed -> Mined Confirmed - const atLeast = "AT_LEAST"; - const exactly = "EXACTLY"; - expect(comparison === atLeast || comparison === exactly).to.equal(true); - const broadcast = "TRANSACTION_STATUS_BROADCAST"; - const fauxUnconfirmed = "TRANSACTION_STATUS_FAUX_UNCONFIRMED"; - const fauxConfirmed = "TRANSACTION_STATUS_FAUX_CONFIRMED"; - expect( - status === broadcast || - status === fauxUnconfirmed || - status === fauxConfirmed - ).to.equal(true); - const wallet = this.getWallet(walletName); - - console.log("\n"); - console.log( - "Waiting for " + - walletName + - " to have detected " + - comparison + - " " + - amount + - " " + - status + - " transaction(s)" - ); - - await waitForIterate( - () => { - switch (status) { - case broadcast: - return wallet.getCounters().broadcast >= amount; - case fauxUnconfirmed: - return wallet.getCounters().fauxUnconfirmed >= amount; - case fauxConfirmed: - return wallet.getCounters().fauxConfirmed >= amount; - default: - expect(status).to.equal("please add this<< TransactionStatus"); - } - }, - true, - 1000, - 120 - ); - - let amountOfCallbacks; - switch (status) { - case broadcast: - amountOfCallbacks = wallet.getCounters().broadcast; - break; - case fauxUnconfirmed: - amountOfCallbacks = wallet.getCounters().fauxUnconfirmed; - break; - case fauxConfirmed: - amountOfCallbacks = wallet.getCounters().fauxConfirmed; - break; - default: - expect(status).to.equal("please add this<< TransactionStatus"); - } - - if (!(amountOfCallbacks >= amount)) { - console.log("\nCounter not adequate!", wallet.getCounters()); - } else { - console.log(wallet.getCounters()); - } - if (comparison === atLeast) { - expect(amountOfCallbacks >= amount).to.equal(true); - } else { - expect(amountOfCallbacks === amount).to.equal(true); - } - } -); - -Then( - "I wait for recovery of ffi wallet {word} to finish", - { timeout: 600 * 1000 }, - function (walletName) { - const wallet = this.getWallet(walletName); - while (!wallet.recoveryFinished) { - sleep(1000).then(); - } - } -); - -When("I start ffi wallet {word}", async function (walletName) { - let wallet = this.getWallet(walletName); - await wallet.startNew(null, null); -}); - -When( - "I restart ffi wallet {word} connected to base node {word}", - async function (walletName, node) { - console.log(`Starting ${walletName}`); - let wallet = this.getWallet(walletName); - await wallet.restart(); - let [pub_key, address] = this.nodes[node].peerAddress().split("::"); - wallet.addBaseNodePeer(pub_key, address); - } -); - -Then("I want to get public key of ffi wallet {word}", function (name) { - let wallet = this.getWallet(name); - let public_key = wallet.identify(); - expect(public_key.length).to.be.equal( - 32, - `Public key has wrong length : ${public_key}` - ); -}); - -Then( - "I want to view the transaction kernels for completed transactions in ffi wallet {word}", - { timeout: 20 * 1000 }, - function (name) { - let ffiWallet = this.getWallet(name); - let transactions = ffiWallet.getCompletedTxs(); - let length = transactions.getLength(); - expect(length > 0).to.equal(true); - for (let i = 0; i < length; i++) { - let tx = transactions.getAt(i); - let kernel = tx.getKernel(); - let data = kernel.asObject(); - console.log("Transaction kernel info:"); - console.log(data); - expect(data.excess.length > 0).to.equal(true); - expect(data.nonce.length > 0).to.equal(true); - expect(data.sig.length > 0).to.equal(true); - kernel.destroy(); - tx.destroy(); - } - transactions.destroy(); - } -); - -When( - "I stop ffi wallet {word}", - { timeout: 20 * 1000 }, - async function (walletName) { - console.log(`Stopping ${walletName}`); - let wallet = this.getWallet(walletName); - await wallet.stop(); - wallet.resetCounters(); - } -); - -Then( - "I start TXO validation on ffi wallet {word}", - { timeout: 125 * 1000 }, - async function (walletName) { - const wallet = this.getWallet(walletName); - await wallet.startTxoValidation(); - while (!wallet.getTxoValidationStatus().txo_validation_complete) { - await sleep(1000); - } - } -); - -Then( - "I start TX validation on ffi wallet {word}", - { timeout: 125 * 1000 }, - async function (walletName) { - const wallet = this.getWallet(walletName); - await wallet.startTxValidation(); - while (!wallet.getTxValidationStatus().tx_validation_complete) { - await sleep(1000); - } - } -); - -Then( - "I wait for ffi wallet {word} to connect to {word}", - { timeout: 125 * 1000 }, - async function (walletName, nodeName) { - const wallet = this.getWallet(walletName, true); - const client = this.getClient(nodeName); - // let client = await node.connectClient(); - let nodeIdentity = await client.identify(); - - await waitForIterate( - () => { - let publicKeys = wallet.listConnectedPublicKeys(); - return ( - publicKeys && - publicKeys.some((p) => p === nodeIdentity.public_key.toString("hex")) - ); - }, - true, - 1000, - 10 - ); - } -); - -Then( - "The fee per gram stats for {word} are {int}, {int}, {int}", - { timeout: 125 * 1000 }, - async function (walletName, min_fee, avg_fee, max_fee) { - const wallet = this.getWallet(walletName); - const feePerGramStats = await wallet.getFeePerGramStats(5); - expect(feePerGramStats.getLength()).to.be.greaterThanOrEqual(1); - const feePerGramStat = feePerGramStats.getAt(0); - expect(feePerGramStat.getMinFeePerGram()).to.be.equal(min_fee); - expect(feePerGramStat.getAvgFeePerGram()).to.be.equal(avg_fee); - expect(feePerGramStat.getMaxFeePerGram()).to.be.equal(max_fee); - } -); diff --git a/integration_tests/features/support/merge_mining_proxy_steps.js b/integration_tests/features/support/merge_mining_proxy_steps.js deleted file mode 100644 index f77655ef72..0000000000 --- a/integration_tests/features/support/merge_mining_proxy_steps.js +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2022. The Tari Project -// -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following -// disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the -// following disclaimer in the documentation and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote -// products derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -const { Given, When, Then } = require("@cucumber/cucumber"); -const MergeMiningProxyProcess = require("../../helpers/mergeMiningProxyProcess"); -const expect = require("chai").expect; - -Given( - /I have a merge mining proxy (.*) connected to (.*) and (.*) with default config/, - { timeout: 120 * 1000 }, // The timeout must make provision for testing the monerod URL /get_height response - async function (mmProxy, node, wallet) { - const baseNode = this.getNode(node); - const walletNode = this.getWallet(wallet); - const proxy = new MergeMiningProxyProcess( - mmProxy, - baseNode.getGrpcAddress(), - this.getClient(node), - walletNode.getGrpcAddress(), - this.logFilePathProxy, - true - ); - await proxy.startNew(); - this.addProxy(mmProxy, proxy); - } -); - -Given( - /I have a merge mining proxy (.*) connected to (.*) and (.*) with origin submission disabled/, - { timeout: 120 * 1000 }, // The timeout must make provision for testing the monerod URL /get_height response - async function (mmProxy, node, wallet) { - const baseNode = this.getNode(node); - const walletNode = this.getWallet(wallet); - const proxy = new MergeMiningProxyProcess( - mmProxy, - baseNode.getGrpcAddress(), - this.getClient(node), - walletNode.getGrpcAddress(), - this.logFilePathProxy, - false - ); - await proxy.startNew(); - this.addProxy(mmProxy, proxy); - } -); - -Given( - /I have a merge mining proxy (.*) connected to (.*) and (.*) with origin submission enabled/, - { timeout: 120 * 1000 }, // The timeout must make provision for testing the monerod URL /get_height response - async function (mmProxy, node, wallet) { - const baseNode = this.getNode(node); - const walletNode = this.getWallet(wallet); - const proxy = new MergeMiningProxyProcess( - mmProxy, - baseNode.getGrpcAddress(), - this.getClient(node), - walletNode.getGrpcAddress(), - this.logFilePathProxy, - true - ); - await proxy.startNew(); - this.addProxy(mmProxy, proxy); - } -); - -When(/I ask for a block height from proxy (.*)/, async function (mmProxy) { - this.lastResult = "NaN"; - const proxy = this.getProxy(mmProxy); - const proxyClient = proxy.createClient(); - const height = await proxyClient.getHeight(); - this.lastResult = height; -}); - -Then("Proxy response height is valid", function () { - expect(Number.isInteger(this.lastResult)).to.be.true; -}); - -When(/I ask for a block template from proxy (.*)/, async function (mmProxy) { - this.lastResult = {}; - const proxy = this.getProxy(mmProxy); - const proxyClient = proxy.createClient(); - const template = await proxyClient.getBlockTemplate(); - this.lastResult = template; -}); - -Then("Proxy response block template is valid", function () { - expect(this.lastResult).to.be.an("object"); - expect(this.lastResult).to.not.be.null; - expect(this.lastResult._aux).to.not.be.undefined; - expect(this.lastResult.status).to.equal("OK"); -}); - -When(/I submit a block through proxy (.*)/, async function (mmProxy) { - const blockTemplateBlob = this.lastResult.blocktemplate_blob; - const proxy = this.getProxy(mmProxy); - const proxyClient = proxy.createClient(); - const result = await proxyClient.submitBlock(blockTemplateBlob); - this.lastResult = result; -}); - -Then( - "Proxy response block submission is valid with submitting to origin", - function () { - expect(this.lastResult.result).to.be.an("object"); - expect(this.lastResult.result).to.not.be.null; - expect(this.lastResult.result._aux).to.not.be.undefined; - expect(this.lastResult.result.status).to.equal("OK"); - } -); - -Then( - "Proxy response block submission is valid without submitting to origin", - function () { - expect(this.lastResult.result).to.not.be.null; - expect(this.lastResult.status).to.equal("OK"); - } -); - -When( - /I ask for the last block header from proxy (.*)/, - async function (mmProxy) { - const proxy = this.getProxy(mmProxy); - const proxyClient = proxy.createClient(); - const result = await proxyClient.getLastBlockHeader(); - this.lastResult = result; - } -); - -Then("Proxy response for last block header is valid", function () { - expect(this.lastResult).to.be.an("object"); - expect(this.lastResult).to.not.be.null; - expect(this.lastResult.result._aux).to.not.be.undefined; - expect(this.lastResult.result.status).to.equal("OK"); - this.lastResult = this.lastResult.result.block_header.hash; -}); - -When( - /I ask for a block header by hash using last block header from proxy (.*)/, - async function (mmProxy) { - const proxy = this.getProxy(mmProxy); - const proxyClient = proxy.createClient(); - const result = await proxyClient.getBlockHeaderByHash(this.lastResult); - this.lastResult = result; - } -); - -Then("Proxy response for block header by hash is valid", function () { - expect(this.lastResult).to.be.an("object"); - expect(this.lastResult).to.not.be.null; - expect(this.lastResult.result.status).to.equal("OK"); -}); diff --git a/integration_tests/features/support/mining_node_steps.js b/integration_tests/features/support/mining_node_steps.js deleted file mode 100644 index add119e098..0000000000 --- a/integration_tests/features/support/mining_node_steps.js +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright 2022. The Tari Project -// -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following -// disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the -// following disclaimer in the documentation and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote -// products derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -const { Given, When } = require("@cucumber/cucumber"); -const MiningNodeProcess = require("../../helpers/miningNodeProcess"); -const { withTimeout } = require("../../helpers/util"); - -Given( - /I have a SHA3 miner (.*) connected to seed node (.*)/, - { timeout: 20 * 1000 }, - async function (name, seed) { - // add the base_node - await this.createAndAddNode(name, this.seeds[seed].peerAddress(), this); - const node = this.getNode(name); - - // Add the wallet connected to the above base node - await this.createAndAddWallet(name, node.peerAddress(), this); - - // Now lets add a standalone miner to both - const wallet = this.getWallet(name); - const miningNode = new MiningNodeProcess( - name, - node.getGrpcAddress(), - this.getClient(name), - wallet.getGrpcAddress(), - this.logFilePathMiningNode - ); - this.addMiningNode(name, miningNode); - } -); - -Given( - /I have a SHA3 miner (.*) connected to node (.*)/, - { timeout: 20 * 1000 }, - async function (name, basenode) { - // add the base_node - await this.createAndAddNode(name, this.nodes[basenode].peerAddress(), this); - const node = this.getNode(name); - - // Add the wallet connected to the above base node - await this.createAndAddWallet(name, node.peerAddress(), this); - - // Now lets add a standalone miner to both - const wallet = this.getWallet(name); - const miningNode = new MiningNodeProcess( - name, - node.getGrpcAddress(), - this.getClient(name), - wallet.getGrpcAddress(), - this.logFilePathMiningNode - ); - this.addMiningNode(name, miningNode); - } -); - -Given( - /I have a SHA3 miner (.*) connected to all seed nodes/, - { timeout: 20 * 1000 }, - async function (name) { - // add the base_node - await this.createAndAddNode(name, this.seedAddresses(), this); - const node = this.getNode(name); - // Add the wallet connected to the above base node - await this.createAndAddWallet(name, node.peerAddress(), this); - - // Now lets add a standalone miner to both - - const wallet = this.getWallet(name); - const miningNode = new MiningNodeProcess( - name, - node.getGrpcAddress(), - this.getClient(name), - wallet.getGrpcAddress(), - this.logFilePathMiningNode - ); - this.addMiningNode(name, miningNode); - } -); - -Given( - /I have mining node (.*) connected to (?:base|seed) node (.*) and wallet (.*)/, - async function (miner, node, wallet) { - await this.createMiningNode(miner, node, wallet); - } -); - -Given( - /I have individual mining nodes connected to each wallet and (?:base|seed) node (.*)/, - async function (node) { - let walletNames = Object.keys(this.wallets); - const promises = []; - for (let i = 0; i < walletNames.length; i++) { - let name = "Miner_" + String(i).padStart(2, "0"); - promises.push(this.createMiningNode(name, node, walletNames[i])); - } - await Promise.all(promises); - } -); - -Given( - /I have each mining node mine (\d+) blocks?$/, - { timeout: 1200 * 1000 }, // Must allow many blocks to be mined; dynamic time out below limits actual time - async function (numBlocks) { - let miningNodes = Object.keys(this.miners); - for (let i = 0; i < miningNodes.length; i++) { - console.log("MN", miningNodes[i]); - const miningNode = this.getMiningNode(miningNodes[i]); - await miningNode.init(numBlocks, null, 1, i + 2, false, null); - await withTimeout( - (10 + parseInt(numBlocks * miningNodes.length) * 1) * 1000, - await miningNode.startNew() - ); - } - } -); - -Given( - /I have mine-before-tip mining node (.*) connected to base node (.*) and wallet (.*)/, - function (miner, node, wallet) { - const baseNode = this.getNode(node); - const walletNode = this.getWallet(wallet); - const miningNode = new MiningNodeProcess( - miner, - baseNode.getGrpcAddress(), - this.getClient(node), - walletNode.getGrpcAddress(), - this.logFilePathMiningNode, - false - ); - this.addMiningNode(miner, miningNode); - } -); - -When( - /I mine (.*) blocks with difficulty (.*) on (.*)/, - { timeout: 20 * 1000 }, - async function (numBlocks, difficulty, node) { - const miner = await this.createMiningNode("temp", node, "temp"); - await miner.init( - parseInt(numBlocks), - null, - parseInt(difficulty), - parseInt(difficulty), - false, - null - ); - await miner.startNew(); - } -); - -When( - /mining node (.*) mines (\d+) blocks?$/, - { timeout: 1200 * 1000 }, // Must allow many blocks to be mined; dynamic time out below limits actual time - async function (miner, numBlocks) { - const miningNode = this.getMiningNode(miner); - // Don't wait for sync before mining. Also use a max difficulty of 1, since most tests assume - // that 1 block = 1 difficulty - await miningNode.init(numBlocks, null, 1, 1, false, null); - await withTimeout( - (10 + parseInt(numBlocks) * 1) * 1000, - await miningNode.startNew() - ); - } -); diff --git a/integration_tests/features/support/node_steps.js b/integration_tests/features/support/node_steps.js deleted file mode 100644 index 500a51cfba..0000000000 --- a/integration_tests/features/support/node_steps.js +++ /dev/null @@ -1,718 +0,0 @@ -// Copyright 2022. The Tari Project -// -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following -// disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the -// following disclaimer in the documentation and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote -// products derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -const { Given, When, Then } = require("@cucumber/cucumber"); -const { - waitForIterate, - waitFor, - waitForPredicate, - withTimeout, -} = require("../../helpers/util"); -const { - AUTOUPDATE_HASHES_TXT_URL, - AUTOUPDATE_HASHES_TXT_SIG_URL, - AUTOUPDATE_HASHES_TXT_BAD_SIG_URL, -} = require("../../helpers/constants"); -const expect = require("chai").expect; - -Given(/I have a seed node (.*)/, { timeout: 20 * 1000 }, async function (name) { - return await this.createSeedNode(name); -}); - -Given("I have {int} seed nodes", { timeout: 60 * 1000 }, async function (n) { - for (let i = 0; i < n; i++) { - const seedName = `SeedNode${i}`; - let initialized = false; - let count = 0; - while (!initialized) { - try { - await this.createSeedNode(seedName); - initialized = true; - } catch { - if (this.clients[seedName] !== undefined) { - delete this.clients[seedName]; - } - if (this.seeds[seedName] !== undefined) { - await this.seeds[seedName].stop(); - delete this.seeds[seedName]; - } - count += 1; - if (count >= 10) { - console.log( - seedName, - "could not be initialized, suspecting port conflicts" - ); - expect(initialized).to.equal(true); - } - } - } - } -}); - -Given( - /I have a base node (.*) connected to all seed nodes/, - { timeout: 20 * 1000 }, - async function (name) { - await this.createAndAddNode(name, this.seedAddresses()); - } -); - -Given( - /I have a base node (.*) connected to seed (.*)/, - { timeout: 20 * 1000 }, - async function (name, seedNode) { - await this.createAndAddNode(name, this.seeds[seedNode].peerAddress()); - } -); - -Given( - /I have a base node (.*) connected to nodes (.*)/, - { timeout: 20 * 1000 }, - async function (name, nodes) { - const addresses = []; - nodes = nodes.split(","); - for (let i = 0; i < nodes.length; i++) { - addresses.push(this.nodes[nodes[i]].peerAddress()); - } - await this.createAndAddNode(name, addresses); - } -); - -Given( - /I have a node (.*) with auto update enabled/, - { timeout: 20 * 1000 }, - async function (name) { - const node = await this.createNode(name, { - common: { - auto_update: { - check_interval: 10, - enabled: true, - dns_hosts: ["_test_autoupdate.tari.io"], - hashes_url: AUTOUPDATE_HASHES_TXT_URL, - hashes_sig_url: AUTOUPDATE_HASHES_TXT_SIG_URL, - }, - }, - }); - await node.startNew(); - await this.addNode(name, node); - } -); - -Given( - /I have a node (.*) with auto update configured with a bad signature/, - { timeout: 20 * 1000 }, - async function (name) { - const node = await this.createNode(name, { - common: { - auto_update: { - check_interval: 10, - enabled: true, - dns_hosts: ["_test_autoupdate.tari.io"], - hashes_url: AUTOUPDATE_HASHES_TXT_URL, - hashes_sig_url: AUTOUPDATE_HASHES_TXT_BAD_SIG_URL, - }, - }, - }); - await node.startNew(); - await this.addNode(name, node); - } -); - -Given( - /I have a base node (.*) connected to node (.*)/, - { timeout: 20 * 1000 }, - async function (name, node) { - await this.createAndAddNode(name, this.nodes[node].peerAddress()); - } -); - -Given( - /I have a base node (\S+)$/, - { timeout: 20 * 1000 }, - async function (name) { - await this.createAndAddNode(name); - } -); - -Given("I have {int} base nodes", { timeout: 60 * 1000 }, async function (n) { - for (let i = 0; i < n; i++) { - const nodeName = `BaseNode${i}`; - let initialized = false; - let count = 0; - while (!initialized) { - try { - await this.createAndAddNode(nodeName); - initialized = true; - } catch { - if (this.clients[nodeName] !== undefined) { - delete this.clients[nodeName]; - } - if (this.nodes[nodeName] !== undefined) { - await this.nodes[nodeName].stop(); - delete this.nodes[nodeName]; - } - count += 1; - if (count >= 10) { - console.log( - nodeName, - "could not be initialized, suspecting port conflicts" - ); - expect(initialized).to.equal(true); - } - } - } - } -}); - -Given("I have 1 base node", { timeout: 20 * 1000 }, async function () { - await this.createAndAddNode(`basenode`); -}); - -Given( - /I connect node (.*) to node (.*)/, - { timeout: 20 * 1000 }, - async function (nodeNameA, nodeNameB) { - console.log( - "Connecting (add new peer seed, shut down, then start up)", - nodeNameA, - "to", - nodeNameB - ); - const nodeA = this.getNode(nodeNameA); - const nodeB = this.getNode(nodeNameB); - nodeA.setPeerSeeds([nodeB.peerAddress()]); - console.log("Stopping node"); - await this.stopNode(nodeNameA); - console.log("Starting node"); - await this.startNode(nodeNameA); - } -); - -Given( - /I have a pruned node (.*) connected to node (.*) with pruning horizon set to (.*)/, - { timeout: 20 * 1000 }, - async function (name, connected_to, horizon) { - const node = this.createNode(name, { pruningHorizon: horizon }); - node.setPeerSeeds([this.nodes[connected_to].peerAddress()]); - await node.startNew(); - await this.addNode(name, node); - } -); - -Given( - /I have a lagging delayed node (.*) connected to node (.*) with blocks_behind_before_considered_lagging (\d+)/, - { timeout: 20 * 1000 }, - async function (name, node, delay) { - const miner = this.createNode(name, { - blocks_behind_before_considered_lagging: delay, - }); - miner.setPeerSeeds([this.nodes[node].peerAddress()]); - await miner.startNew(); - await this.addNode(name, miner); - } -); - -Given( - /I have a base node (.*) unconnected/, - { timeout: 20 * 1000 }, - async function (name) { - const node = this.createNode(name); - await node.startNew(); - await this.addNode(name, node); - } -); - -Given( - "I have {int} base nodes connected to all seed nodes", - { timeout: 60 * 1000 }, - async function (n) { - for (let i = 0; i < n; i++) { - const nodeName = `BaseNode${i}`; - let initialized = false; - let count = 0; - while (!initialized) { - try { - const miner = this.createNode(nodeName); - miner.setPeerSeeds([this.seedAddresses()]); - await miner.startNew(); - await this.addNode(nodeName, miner); - initialized = true; - } catch { - if (this.clients[nodeName] !== undefined) { - delete this.clients[nodeName]; - } - if (this.nodes[nodeName] !== undefined) { - await this.nodes[nodeName].stop(); - delete this.nodes[nodeName]; - } - count += 1; - if (count >= 10) { - console.log( - nodeName, - "could not be initialized, suspecting port conflicts" - ); - expect(initialized).to.equal(true); - } - } - } - } - } -); - -When(/I start base node (.*)/, { timeout: 20 * 1000 }, async function (name) { - await this.startNode(name); -}); - -Then( - /node (.*) is at height (\d+)/, - { timeout: 600 * 1000 }, - async function (name, height) { - const client = this.getClient(name); - const currentHeight = await waitForIterate( - () => client.getTipHeight(), - height, - 1000, - 5 * height // 5 seconds per block - ); - console.log( - `Node ${name} is at tip: ${currentHeight} (should be`, - height, - `)` - ); - expect(currentHeight).to.equal(height); - } -); - -Then( - /node (.*) has a pruned height of (\d+)/, - { timeout: 600 * 1000 }, - async function (name, height) { - const client = this.getClient(name); - await waitFor( - async () => await client.getPrunedHeight(), - height, - 1000, - height * 5 * 1000 // 5 seconds per block - ); - const currentHeight = await client.getPrunedHeight(); - console.log( - `Node ${name} has a pruned height: ${currentHeight} (should be`, - height, - `)` - ); - expect(currentHeight).to.equal(height); - } -); - -Then( - /node (.*) is at the same height as node (.*)/, - { timeout: 120 * 1000 }, - async function (nodeA, nodeB) { - var expectedHeight, currentHeight; - expectedHeight = parseInt(await this.getClient(nodeB).getTipHeight()); - for (let i = 1; i <= 12; i++) { - await waitFor( - async () => this.getClient(nodeA).getTipHeight(), - expectedHeight, - 10 * 1000 - ); - expectedHeight = parseInt(await this.getClient(nodeB).getTipHeight()); - currentHeight = await this.getClient(nodeA).getTipHeight(); - if (currentHeight === expectedHeight) { - break; - } - } - console.log( - `Node ${nodeA} is at tip: ${currentHeight} (should be`, - expectedHeight, - ")" - ); - expect(currentHeight).to.equal(expectedHeight); - } -); - -Then( - "all nodes are on the same chain at height {int}", - { timeout: 600 * 1000 }, - async function (height) { - let tipHash = null; - await this.forEachClientAsync(async (client, name) => { - await waitForIterate( - () => client.getTipHeight(), - height, - 1000, - 5 * height /* 5 seconds per block */ - ); - const currTip = await client.getTipHeader(); - let currTipHeaderHeight = parseInt(currTip.height); - console.log( - `${name} is at tip ${currTipHeaderHeight} (${currTip.hash.toString( - "hex" - )})` - ); - expect(currTipHeaderHeight).to.equal(height); - if (!tipHash) { - tipHash = currTip.hash.toString("hex"); - console.log(`Node ${name} is at tip: ${tipHash}`); - } else { - const currTipHash = currTip.hash.toString("hex"); - console.log( - `Node ${name} is at tip: ${currTipHash} (should be ${tipHash})` - ); - expect(currTipHash).to.equal(tipHash); - } - }); - } -); - -Then( - "all nodes are on the same chain tip", - { timeout: 800 * 1000 }, - async function () { - await waitFor( - async () => { - let tipHash = null; - let height = null; - let result = true; - await this.forEachClientAsync(async (client, name) => { - const currTip = await client.getTipHeader(); - if (!tipHash) { - tipHash = currTip.hash.toString("hex"); - height = currTip.height; - console.log(`Node ${name} is at tip: #${height}, ${tipHash}`); - } else { - const currTipHash = currTip.hash.toString("hex"); - console.log( - `Node ${name} is at tip: #${currTip.height},${currTipHash} (should be #${height},${tipHash})` - ); - result = - result && currTipHash == tipHash && currTip.height == height; - } - }); - return result; - }, - true, - 600 * 1000, - 5 * 1000, - 5 - ); - } -); - -Then( - "all nodes are at height {int}", - { timeout: 800 * 1000 }, - async function (height) { - await this.all_nodes_are_at_height(height); - } -); - -Then( - "all nodes are at height {int}*{int}", - { timeout: 800 * 1000 }, - async function (a, b) { - await this.all_nodes_are_at_height(a * b); - } -); - -Then( - /node (.*) has reached initial sync/, - { timeout: 21 * 60 * 1000 }, - async function (node) { - const client = this.getClient(node); - await waitForPredicate( - async () => await client.initial_sync_achieved(), - 20 * 60 * 1000, - 1000 - ); - let result = await this.getClient(node).initial_sync_achieved(); - console.log(`Node ${node} response is: ${result}`); - expect(result).to.equal(true); - } -); - -Then(/node (.*) is in state (.*)/, async function (node, state) { - const client = this.getClient(node); - await waitForPredicate( - async () => (await client.get_node_state()) == state, - 20 * 60 * 1000, - 1000 - ); - let result = await this.getClient(node).get_node_state(); - console.log(`Node ${node} is in the current state: ${result}`); - expect(result).to.equal(state); -}); - -Then( - /all nodes are at the same height as node (.*)/, - { timeout: 120 * 1000 }, - async function (nodeB) { - let expectedHeight = parseInt(await this.getClient(nodeB).getTipHeight()); - console.log("Wait for all nodes to reach height of", expectedHeight); - await this.forEachClientAsync(async (client, name) => { - const newExpectedHeight = parseInt( - await this.getClient(nodeB).getTipHeight() - ); - if (newExpectedHeight !== expectedHeight) { - expectedHeight = newExpectedHeight; - console.log("Wait for all nodes to reach height of", expectedHeight); - } - let currentHeight; - for (let i = 1; i <= 12; i++) { - await waitFor( - async () => await client.getTipHeight(), - expectedHeight, - 10 * 1000 - ); - expectedHeight = parseInt(await this.getClient(nodeB).getTipHeight()); - currentHeight = parseInt(await client.getTipHeight()); - if (currentHeight === expectedHeight) { - break; - } - } - console.log( - `Node ${name} is at tip: ${currentHeight} (should be`, - expectedHeight, - ")" - ); - expect(currentHeight).to.equal(expectedHeight); - }); - } -); - -Then(/node (.*) is at tip (.*)/, async function (node, name) { - // console.log("\nheaders:", this.headers, "\n"); - const client = this.getClient(node); - const header = await client.getTipHeader(); - // console.log("\nheader:", header, "\n"); - const existingHeader = this.headers[name]; - // console.log("\nexistingHeader:", existingHeader, "\n"); - expect(existingHeader).to.not.be.null; - expect(existingHeader.hash.toString("hex")).to.equal( - header.hash.toString("hex") - ); -}); - -Then( - /node (.*) lists headers (\d+) to (\d+) with correct heights/, - async function (node, start, end) { - const client = this.getClient(node); - const fromHeight = end; - const numHeaders = end - start + 1; // inclusive - const headers = await client.getHeaders(fromHeight, numHeaders); - const heights = headers.map((header) => parseInt(header.height)); - for (let height = start; height <= end; height++) { - expect(heights).to.contain(height); - } - } -); - -When( - /I run blockchain recovery on node (\S*)/, - { timeout: 120 * 1000 }, - async function (name) { - await this.startNode(name, ["--rebuild-db"]); - } -); - -Then( - /meddling with block template data from node (.*) is not allowed/, - async function (baseNodeName) { - const baseNodeClient = this.getClient(baseNodeName); - - // No meddling with data - // - Current tip - const currHeight = await baseNodeClient.getTipHeight(); - // - New block - let newBlock = await baseNodeClient.mineBlockBeforeSubmit(0); - // - Submit block to base node - await baseNodeClient.submitMinedBlock(newBlock); - // - Verify new height - expect(await baseNodeClient.getTipHeight()).to.equal(currHeight + 1); - - // Meddle with data - kernel_mmr_size - // - New block - newBlock = await baseNodeClient.mineBlockBeforeSubmit(0); - // - Change kernel_mmr_size - newBlock.block.header.kernel_mmr_size = - parseInt(newBlock.block.header.kernel_mmr_size) + 1; - // - Try to submit illegal block to base node - try { - await baseNodeClient.submitMinedBlock(newBlock); - expect("Meddling with MMR size for Kernel not detected!").to.equal(""); - } catch (err) { - console.log( - "\nMeddle with kernel_mmr_size - error details (as expected):\n", - err.details - ); - expect( - err.details.includes( - "Block validation error: MMR size for Kernel does not match." - ) - ).to.equal(true); - } - - // Meddle with data - output_mmr_size - // - New block - newBlock = await baseNodeClient.mineBlockBeforeSubmit(0); - // - Change output_mmr_size - newBlock.block.header.output_mmr_size = - parseInt(newBlock.block.header.output_mmr_size) + 1; - // - Try to submit illegal block to base node - try { - await baseNodeClient.submitMinedBlock(newBlock); - expect("Meddling with MMR size for UTXO not detected!").to.equal(""); - } catch (err) { - console.log( - "Meddle with output_mmr_size - error details (as expected):\n", - err.details - ); - expect( - err.details.includes( - "Block validation error: MMR size for UTXO does not match." - ) - ).to.equal(true); - } - } -); - -Then( - /(.*) has (.*) in (.*) state/, - { timeout: 6 * 60 * 1000 }, // Must cater for long running transaction state changes, e.g. UNKNOWN -> NOT_STORED - async function (node, txn, pool) { - const client = this.getClient(node); - const sig = this.transactions[txn].body.kernels[0].excess_sig; - this.lastResult = await waitFor( - async () => { - let tx_result = await client.transactionStateResult(sig); - console.log( - `Node ${node} response for ${txn} is: ${tx_result}, should be: ${pool}` - ); - return tx_result === pool; - }, - true, - 6 * 60 * 1000, - 5 * 1000 - ); - expect(this.lastResult).to.equal(true); - } -); - -When( - /I mine a block on (.*) with coinbase (.*)/, - async function (name, coinbaseName) { - const tipHeight = await this.getClient(name).getTipHeight(); - let autoTransactionResult = await this.createTransactions( - name, - tipHeight + 1 - ); - expect(autoTransactionResult).to.equal(true); - await this.mineBlock(name, 0, (candidate) => { - this.addOutput(coinbaseName, candidate.originalTemplate.coinbase); - return candidate; - }); - } -); - -When( - /I mine (\d+) custom weight blocks on (.*) with weight (\d+)/, - { timeout: 1200 * 1000 }, // Must allow many blocks to be mined; time out below limits each block to be mined - async function (numBlocks, name, weight) { - const tipHeight = await this.getClient(name).getTipHeight(); - for (let i = 0; i < numBlocks; i++) { - let autoTransactionResult = await this.createTransactions( - name, - tipHeight + i + 1 - ); - expect(autoTransactionResult).to.equal(true); - // If a block cannot be mined quickly enough (or the process has frozen), timeout. - await withTimeout( - 5 * 1000, - this.mineBlock(name, parseInt(weight), (candidate) => { - this.addTransactionOutput( - tipHeight + i + 1 + 2, - candidate.originalTemplate.coinbase - ); - return candidate; - }) - ); - } - } -); - -When(/I submit block (.*) to (.*)/, async function (blockName, nodeName) { - await this.submitBlock(blockName, nodeName); -}); - -When( - /I mine a block on (.*) based on height (\d+)/, - async function (node, atHeight) { - const client = this.getClient(node); - const template = client.getPreviousBlockTemplate(atHeight); - const candidate = await client.getMinedCandidateBlock(0, template); - let autoTransactionResult = await this.createTransactions( - node, - parseInt(atHeight) - ); - expect(autoTransactionResult).to.equal(true); - this.addTransactionOutput( - parseInt(atHeight) + 1, - candidate.originalTemplate.coinbase - ); - await client.submitBlock( - candidate.template, - (block) => { - return block; - }, - (error) => { - // Expect an error - console.log(error); - return false; - } - ); - } -); - -When( - /I mine a block on (.*) at height (\d+) with an invalid MMR/, - async function (node, atHeight) { - const client = this.getClient(node); - const template = client.getPreviousBlockTemplate(atHeight); - const candidate = await client.getMinedCandidateBlock(0, template); - - await client - .submitBlock(candidate.template, (block) => { - // console.log("Candidate:", block); - block.block.header.output_mr[0] = 1; - // block.block.header.height = atHeight + 1; - // block.block.header.prev_hash = candidate.header.hash; - return block; - }) - .catch((err) => { - console.log("Received expected error. This is fine actually:", err); - }); - } -); - -When(/I stop node (.*)/, async function (name) { - await this.stopNode(name); -}); diff --git a/integration_tests/features/support/steps.js b/integration_tests/features/support/steps.js deleted file mode 100644 index 3d2867c3a5..0000000000 --- a/integration_tests/features/support/steps.js +++ /dev/null @@ -1,666 +0,0 @@ -// Copyright 2022. The Tari Project -// -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following -// disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the -// following disclaimer in the documentation and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote -// products derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -const assert = require("assert"); -const { When, Then } = require("@cucumber/cucumber"); -const expect = require("chai").expect; - -const { - waitFor, - waitForPredicate, - sleep, - withTimeout, -} = require("../../helpers/util"); -const { - getTransactionOutputHash, -} = require("../../helpers/transactionOutputHashing"); -const { ConnectivityStatus } = require("../../helpers/types"); -const TransactionBuilder = require("../../helpers/transactionBuilder"); - -Then(/all transactions must have succeeded/, function () { - expect(this.lastTransactionsSucceeded).to.be(true); -}); - -When( - /I import (.*) spent outputs to (.*)/, - async function (walletNameA, walletNameB) { - let walletA = this.getWallet(walletNameA); - let walletB = this.getWallet(walletNameB); - let clientB = await walletB.connectClient(); - - await walletA.exportSpentOutputs(); - let spent_outputs = await walletA.readExportedOutputs(); - let result = await clientB.importUtxos(spent_outputs); - this.lastResult = result.tx_ids; - } -); - -When( - /I import (.*) unspent outputs to (.*)/, - async function (walletNameA, walletNameB) { - let walletA = this.getWallet(walletNameA); - let walletB = this.getWallet(walletNameB); - let clientB = await walletB.connectClient(); - - await walletA.exportUnspentOutputs(); - let outputs = await walletA.readExportedOutputs(); - let result = await clientB.importUtxos(outputs); - this.lastResult = result.tx_ids; - } -); - -When( - /I import (.*) unspent outputs as faucet outputs to (.*)/, - async function (walletNameA, walletNameB) { - let walletA = this.getWallet(walletNameA); - let walletB = this.getWallet(walletNameB); - let clientB = await walletB.connectClient(); - - await walletA.exportUnspentOutputs(); - let outputs = await walletA.readExportedOutputsAsFaucetOutputs(); - let result = await clientB.importUtxos(outputs); - this.lastResult = result.tx_ids; - } -); - -When( - /I check if last imported transactions are invalid in wallet (.*)/, - async function (walletName) { - let wallet = this.getWallet(walletName); - let client = await wallet.connectClient(); - let found_txs = await client.getCompletedTransactions(); - //console.log("Found: ", found_txs); - let found_count = 0; - for ( - let imported_tx = 0; - imported_tx < this.lastResult.length; - imported_tx++ - ) { - for (let found_tx = 0; found_tx < found_txs.length; found_tx++) { - if (found_txs[found_tx].tx_id === this.lastResult[imported_tx]) { - found_count++; - expect(found_txs[found_tx].status).to.equal( - "TRANSACTION_STATUS_IMPORTED" - ); - expect(found_txs[found_tx].valid).to.equal(false); - } - } - } - expect(found_count).to.equal(this.lastResult.length); - } -); - -Then( - /(.*) does not have a new software update/, - { timeout: 65 * 1000 }, - async function (name) { - let client = await this.getNodeOrWalletClient(name); - await sleep(5000); - await waitFor( - async () => (await client.checkForUpdates()).has_update, - false, - 60 * 1000 - ); - expect( - (await client.checkForUpdates()).has_update, - "There should be no update" - ).to.be.false; - } -); - -Then( - /(.+) has a new software update/, - { timeout: 65 * 1000 }, - async function (name) { - let client = await this.getNodeOrWalletClient(name); - await waitFor( - async () => (await client.checkForUpdates()).has_update, - true, - 60 * 1000 - ); - expect( - (await client.checkForUpdates()).has_update, - "There should be update" - ).to.be.true; - } -); - -When( - /I create a transaction (.*) spending (.*) to (.*)/, - function (txnName, inputs, output) { - const txInputs = inputs.split(",").map((input) => this.outputs[input]); - const txn = new TransactionBuilder(); - txInputs.forEach((txIn) => txn.addInput(txIn)); - const txOutput = txn.addOutput(txn.getSpendableAmount()); - this.addOutput(output, txOutput); - this.transactions[txnName] = txn.build(); - } -); - -When( - /I create a custom transaction (.*) spending (.*) to (.*) with fee (\d+) and unique id '([^']+)'/i, - function (txnName, inputs, output, fee, unique_id) { - const txInputs = inputs.split(",").map((input) => this.outputs[input]); - const txn = new TransactionBuilder(); - txn.changeFee(fee); - txInputs.forEach((txIn) => txn.addInput(txIn)); - const txOutput = txn.addOutput(txn.getSpendableAmount(), { - unique_id, - }); - this.addOutput(output, txOutput); - this.transactions[txnName] = txn.build(); - } -); - -When( - /I create a custom fee transaction (.*) spending (.*) to (.*) with fee (\d+)/, - function (txnName, inputs, output, fee) { - const txInputs = inputs.split(",").map((input) => this.outputs[input]); - const txn = new TransactionBuilder(); - txn.changeFee(fee); - txInputs.forEach((txIn) => txn.addInput(txIn)); - const txOutput = txn.addOutput(txn.getSpendableAmount()); - this.addOutput(output, txOutput); - this.transactions[txnName] = txn.build(); - } -); - -When(/I submit transaction (.*) to (.*)/, async function (txn, node) { - this.lastResult = await this.getClient(node).submitTransaction( - this.transactions[txn] - ); - expect(this.lastResult.result).to.equal("ACCEPTED"); -}); - -When(/I submit locked transaction (.*) to (.*)/, async function (txn, node) { - this.lastResult = await this.getClient(node).submitTransaction( - this.transactions[txn] - ); - expect(this.lastResult.result).to.equal("REJECTED"); -}); - -When(/I spend outputs (.*) via (.*)/, async function (inputs, node) { - const txInputs = inputs.split(",").map((input) => this.outputs[input]); - console.log(txInputs); - const txn = new TransactionBuilder(); - txInputs.forEach((txIn) => txn.addInput(txIn)); - console.log(txn.getSpendableAmount()); - const output = txn.addOutput(txn.getSpendableAmount()); - console.log(output); - this.lastResult = await this.getClient(node).submitTransaction(txn.build()); - expect(this.lastResult.result).to.equal("ACCEPTED"); -}); - -// The number is rounded down. E.g. if 1% can fail out of 17, that is 16.83 have to succeed. -// It's means at least 16 have to succeed. -Then( - /(.*) is in the (.*) of all nodes(, where (\d+)% can fail)?/, - { timeout: 120 * 1000 }, - async function (txn, pool, canFail) { - const sig = this.transactions[txn].body.kernels[0].excess_sig; - await this.forEachClientAsync( - async (client, name) => { - await waitFor( - async () => await client.transactionStateResult(sig), - pool, - 115 * 1000 - ); - this.lastResult = await client.transactionState(sig); - console.log(`Node ${name} response is: ${this.lastResult.result}`); - }, - canFail ? parseInt(canFail) : 0 - ); - } -); - -Then(/(.*) is in the mempool/, function (_txn) { - expect(this.lastResult.result).to.equal("ACCEPTED"); -}); - -Then(/(.*) should not be in the mempool/, function (_txn) { - expect(this.lastResult.result).to.equal("REJECTED"); -}); - -When(/I save the tip on (.*) as (.*)/, async function (node, name) { - const client = this.getClient(node); - const header = await client.getTipHeader(); - this.headers[name] = header; -}); - -When( - /mining node (.*) mines (\d+) blocks with min difficulty (\d+) and max difficulty (\d+)/, - { timeout: 1200 * 1000 }, // Must allow many blocks to be mined; dynamic time out below limits actual time - async function (miner, numBlocks, min, max) { - const miningNode = this.getMiningNode(miner); - await miningNode.init( - numBlocks, - null, - min, - max, - miningNode.mineOnTipOnly, - null - ); - await withTimeout( - (10 + parseInt(numBlocks) * 1) * 1000, - await miningNode.startNew() - ); - } -); - -When("I mine {int} block(s)", { timeout: -1 }, async function (numBlocks) { - let name = this.currentBaseNodeName(); - // const tipHeight = await this.getClient(name).getTipHeight(); - for (let i = 0; i < numBlocks; i++) { - await withTimeout(60 * 1000, this.mineBlock(name, 0)); - } -}); - -When( - /I mine (\d+) blocks on (.*)/, - { timeout: 1200 * 1000 }, // Must allow many blocks to be mined; time out below limits each block to be mined - async function (numBlocks, name) { - const tipHeight = await this.getClient(name).getTipHeight(); - for (let i = 0; i < numBlocks; i++) { - let autoTransactionResult = await this.createTransactions( - name, - tipHeight + i + 1 - ); - expect(autoTransactionResult).to.equal(true); - await withTimeout( - 5 * 1000, - this.mineBlock(name, 0, (candidate) => { - this.addTransactionOutput( - tipHeight + i + 1 + 2, - candidate.originalTemplate.coinbase - ); - return candidate; - }) - ); - } - } -); - -When( - /I mine (\d+) blocks using wallet (.*) on (.*)/, - { timeout: 1200 * 1000 }, // Must allow many blocks to be mined; time out below limits each block to be mined - async function (numBlocks, walletName, nodeName) { - const nodeClient = this.getClient(nodeName); - const walletClient = await this.getWallet(walletName).connectClient(); - const tipHeight = await this.getClient(nodeName).getTipHeight(); - for (let i = 0; i < numBlocks; i++) { - let autoTransactionResult = await this.createTransactions( - nodeName, - tipHeight + 1 + i - ); - expect(autoTransactionResult).to.equal(true); - await withTimeout(5 * 1000, await nodeClient.mineBlock(walletClient)); - } - } -); - -When( - /I merge mine (.*) blocks via (.*)/, - { timeout: 1200 * 1000 }, // Must allow many blocks to be mined; time out below limits each block to be mined - async function (numBlocks, mmProxy) { - for (let i = 0; i < numBlocks; i++) { - await withTimeout(5 * 1000, await this.mergeMineBlock(mmProxy)); - } - } -); - -When( - /I co-mine (.*) blocks via merge mining proxy (.*) and mining node (.*)/, - { timeout: 15000 * 1000 }, // Must allow many blocks to be mined; dynamic time out below limits actual time - async function (numBlocks, mmProxy, miner) { - const sha3MiningPromise = withTimeout( - parseInt(numBlocks) * 4 * 1000, - this.sha3MineBlocksUntilHeightIncreasedBy(miner, numBlocks, 120, true) - ); - const mergeMiningPromise = withTimeout( - parseInt(numBlocks) * 4 * 1000, - this.mergeMineBlocksUntilHeightIncreasedBy(mmProxy, numBlocks) - ); - await Promise.all([sha3MiningPromise, mergeMiningPromise]).then( - ([res1, res2]) => { - console.log( - "Co-mining", - numBlocks, - "blocks concluded, tips at [", - res1, - ",", - res2, - "]" - ); - } - ); - } -); - -When( - /I mine but do not submit a block (.*) on (.*)/, - async function (blockName, nodeName) { - const tipHeight = await this.getClient(nodeName).getTipHeight(); - let autoTransactionResult = await this.createTransactions( - nodeName, - tipHeight + 1 - ); - expect(autoTransactionResult).to.equal(true); - await this.mineBlock( - nodeName, - null, - (block) => { - this.addTransactionOutput( - tipHeight + 1 + 2, - block.originalTemplate.coinbase - ); - this.saveBlock(blockName, block); - return false; - }, - 0 - ); - } -); - -Then( - /the UTXO (.*) has been mined according to (.*)/, - async function (outputName, nodeName) { - const client = this.getClient(nodeName); - const hash = getTransactionOutputHash(this.outputs[outputName].output); - const lastResult = await client.fetchMatchingUtxos([hash]); - - expect( - lastResult, - `UTXO (${outputName}) not found with hash ${hash.toString("hex")}` - ).to.be.an("array").that.is.not.empty; - - expect(lastResult[0].output.commitment.toString("hex")).to.equal( - this.outputs[outputName].output.commitment.toString("hex") - ); - } -); - -Then("I receive an error containing {string}", function (_string) { - // TODO -}); - -Then(/(.*) should have (\d+) peers/, async function (nodeName, peerCount) { - await sleep(500); - const client = this.getClient(nodeName); - const peers = await client.getPeers(); - expect(peers.length).to.equal(peerCount); -}); - -Then(/(.*) has at least (\d+) peers/, async function (nodeName, peerCount) { - const client = this.getClient(nodeName); - await waitForPredicate(async () => { - const peers = await client.getPeers(); - return peers.length >= peerCount; - }, 10 * 1000); -}); - -When("I print the world", function () { - console.log(this); -}); - -When("I wait {int} seconds", { timeout: 600 * 1000 }, async function (seconds) { - console.log("Waiting for", seconds, "seconds"); - await sleep(seconds * 1000); - console.log("Waiting finished"); -}); - -Then( - /while mining via SHA3 miner (.*) all transactions in wallet (.*) are found to be Mined_Confirmed/, - { timeout: 1200 * 1000 }, // Must allow for many transactions; dynamic time out used below - async function (miner, walletName) { - const wallet = this.getWallet(walletName); - const walletClient = await wallet.connectClient(); - const walletInfo = await walletClient.identify(); - const miningNode = this.getMiningNode(miner); - - const txIds = this.transactionsMap.get(walletInfo.public_key); - if (txIds === undefined) { - console.log("\nNo transactions for " + walletName + "!"); - expect(false).to.equal(true); - } - console.log( - "\nDetecting", - txIds.length, - "transactions as Mined_Confirmed: ", - walletName, - txIds - ); - for (let i = 0; i < txIds.length; i++) { - console.log( - "(" + - (i + 1) + - "/" + - txIds.length + - ") - " + - wallet.name + - ": Waiting for TxId:" + - txIds[i] + - " to be detected as Mined_Confirmed in the wallet ..." - ); - await waitFor( - async () => { - if (await walletClient.isTransactionMinedConfirmed(txIds[i])) { - return true; - } else { - await miningNode.init(1, null, 1, 100000, false, null); - await miningNode.startNew(); - return false; - } - }, - true, - (60 + txIds.length * 1) * 1000, - 5 * 1000, - 5 - ); - const isTransactionMinedConfirmed = - await walletClient.isTransactionMinedConfirmed(txIds[i]); - expect(isTransactionMinedConfirmed).to.equal(true); - } - } -); - -When(/I request the difficulties of a node (.*)/, async function (node) { - const client = this.getClient(node); - const difficulties = await client.getNetworkDifficulties(2, 0, 2); - this.lastResult = difficulties; -}); - -Then("difficulties are available", function () { - assert.strictEqual(this.lastResult.length, 3); - // check genesis block, chain in reverse height order - expect(this.lastResult[2].difficulty).to.equal("1"); - expect(this.lastResult[2].estimated_hash_rate).to.equal("0"); - expect(this.lastResult[2].height).to.equal("2"); - expect(this.lastResult[2].pow_algo).to.equal("0"); - expect(this.lastResult[2].sha3_estimated_hash_rate).to.equal("0"); - expect(this.lastResult[2].monero_estimated_hash_rate).to.equal("0"); -}); - -When( - "I wait for {word} to connect to {word}", - async function (firstNode, secondNode) { - const firstNodeClient = await this.getNodeOrWalletClient(firstNode); - const secondNodeClient = await this.getNodeOrWalletClient(secondNode); - const secondNodeIdentity = await secondNodeClient.identify(); - - await waitForPredicate(async () => { - let peers = await firstNodeClient.listConnectedPeers(); - return peers.some( - (p) => secondNodeIdentity.public_key.toString("hex") === p.public_key - ); - }, 50 * 1000); - } -); - -Then(/(.*) is connected to (.*)/, async function (firstNode, secondNode) { - const firstNodeClient = await this.getNodeOrWalletClient(firstNode); - const secondNodeClient = await this.getNodeOrWalletClient(secondNode); - const secondNodeIdentity = await secondNodeClient.identify(); - let peers = await firstNodeClient.listConnectedPeers(); - expect( - peers.some( - (p) => - secondNodeIdentity.public_key.toString("hex") === - p.public_key.toString("hex") - ) - ).to.be.true; -}); - -When( - /I wait for (.*) to have (.*) connectivity/, - async function (nodeName, expectedStatus) { - const node = await this.getNodeOrWalletClient(nodeName); - const expected = ConnectivityStatus[expectedStatus.toUpperCase()]; - assert( - expected !== undefined, - `Invalid connectivity state ${expectedStatus}` - ); - await waitForPredicate(async () => { - let info = await node.getNetworkStatus(); - return info.status === expected; - }, 50 * 1000); - } -); - -When( - /I wait for (.*) to have (\d+) node connections/, - async function (nodeName, numConnections) { - const node = await this.getNodeOrWalletClient(nodeName); - numConnections = +numConnections; - await waitForPredicate(async () => { - let info = await node.getNetworkStatus(); - if (info.num_node_connections > numConnections) { - console.warn( - `Node ${nodeName} has more connections than expected. Expected = ${numConnections} Got = ${info.num_node_connections}` - ); - } - return info.num_node_connections === numConnections; - }, 50 * 1000); - } -); - -Then( - /I wait until base node (.*) has (.*) unconfirmed transactions in its mempool/, - { timeout: 120 * 1000 }, - async function (baseNode, numTransactions) { - const client = this.getClient(baseNode); - await waitFor( - async () => { - let stats = await client.getMempoolStats(); - return stats.unconfirmed_txs; - }, - numTransactions, - 115 * 1000 - ); - - let stats = await client.getMempoolStats(); - console.log( - "Base node", - baseNode, - "has ", - stats.unconfirmed_txs, - " unconfirmed transaction in its mempool" - ); - expect(stats.unconfirmed_txs).to.equal(numTransactions); - } -); - -Then( - /node (.*) lists heights (\d+) to (\d+)/, - async function (node, first, last) { - const client = this.getClient(node); - const start = first; - const end = last; - let heights = []; - - for (let i = start; i <= end; i++) { - heights.push(i); - } - const blocks = await client.getBlocks(heights); - const results = blocks.map((result) => - parseInt(result.block.header.height) - ); - let i = 0; // for ordering check - for (let height = start; height <= end; height++) { - expect(results[i]).equal(height); - i++; - } - } -); - -Then( - "I wait for recovery of wallet {word} to finish", - { timeout: 600 * 1000 }, - async function (wallet_name) { - const wallet = this.getWallet(wallet_name); - while (wallet.recoveryInProgress) { - await sleep(1000); - } - expect(wallet.recoveryProgress[1]).to.be.greaterThan(0); - expect(wallet.recoveryProgress[0]).to.be.equal(wallet.recoveryProgress[1]); - } -); - -When( - "I have {int} base nodes with pruning horizon {int} force syncing on node {word}", - { timeout: 60 * 1000 }, - async function (nodes_count, horizon, force_sync_to) { - const force_sync_address = this.getNode(force_sync_to).peerAddress(); - for (let i = 0; i < nodes_count; i++) { - const nodeName = `BaseNode${i}`; - let initialized = false; - let count = 0; - while (!initialized) { - try { - const base_node = this.createNode(nodeName, { - pruningHorizon: horizon, - }); - base_node.setPeerSeeds([force_sync_address]); - base_node.setForceSyncPeers([force_sync_address]); - await base_node.startNew(); - await this.addNode(nodeName, base_node); - initialized = true; - } catch { - if (this.clients[nodeName] !== undefined) { - delete this.clients[nodeName]; - } - if (this.nodes[nodeName] !== undefined) { - await this.nodes[nodeName].stop(); - delete this.nodes[nodeName]; - } - count += 1; - if (count >= 10) { - console.log( - nodeName, - "could not be initialized, suspecting port conflicts" - ); - expect(initialized).to.equal(true); - } - } - } - } - } -); diff --git a/integration_tests/features/support/wallet_cli_steps.js b/integration_tests/features/support/wallet_cli_steps.js deleted file mode 100644 index 569b774d65..0000000000 --- a/integration_tests/features/support/wallet_cli_steps.js +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright 2022. The Tari Project -// -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following -// disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the -// following disclaimer in the documentation and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote -// products derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -const { Given, Then, When } = require("@cucumber/cucumber"); -const { expect } = require("chai"); -const { waitFor, sleep } = require("../../helpers/util"); - -Given( - /I change the password of wallet (.*) to (.*) via command line/, - async function (name, newPassword) { - let wallet = this.getWallet(name); - await wallet.changePassword("kensentme", newPassword); - } -); - -Then( - /the password of wallet (.*) is (not)? ?(.*)/, - async function (name, is_not, password) { - let wallet = this.getWallet(name); - try { - await wallet.start({ password }); - } catch (error) { - expect(error).to.equal( - is_not === "not" ? "Incorrect password" : undefined - ); - } - } -); - -Given( - "I change base node of {word} to {word} via command line", - async function (wallet_name, base_node_name) { - let wallet = this.getWallet(wallet_name); - let base_node = this.getNode(base_node_name); - let output = await wallet.runCommand( - `set-base-node ${base_node.peerAddress().replace("::", " ")}` - ); - let parse = output.buffer.match(/Setting base node peer\.\.\./); - expect(parse, "Parsing the output buffer failed").to.not.be.null; - } -); - -async function wallet_run_command( - wallet, - command, - timeOutSeconds = 15, - message = "", - printMessage = true -) { - if (message === "") { - message = "Wallet CLI command:\n '" + command + "'"; - } - if (printMessage) { - console.log(message); - } - let output; - await waitFor( - async () => { - try { - output = await wallet.runCommand(command); - } catch (error) { - console.log(error); - return false; - } - return true; - }, - true, - timeOutSeconds * 1000, - 5 * 1000, - 5 - ); - return output; -} - -Then( - "I get balance of wallet {word} is at least {int} uT via command line", - { timeout: 180 * 1000 }, - async function (name, amount) { - let wallet = this.getWallet(name); - let output = await wallet_run_command(wallet, "get-balance", 180); - let parse = output.buffer.match(/Available balance: (\d*.\d*) T/); - expect(parse, "Parsing the output buffer failed").to.not.be.null; - expect(parseFloat(parse[1])).to.be.greaterThanOrEqual(amount / 1000000); - } -); - -Then( - "I get balance of wallet {word} is at most {int} uT via command line", - { timeout: 180 * 1000 }, - async function (name, amount) { - let wallet = this.getWallet(name); - let output = await wallet_run_command(wallet, "get-balance", 180); - let parse = output.buffer.match(/Available balance: (\d*.\d*) T/); - expect(parse, "Parsing the output buffer failed").to.not.be.null; - expect(parseFloat(parse[1])).to.be.lessThanOrEqual(amount / 1000000); - } -); - -When( - "I send {int} uT from {word} to {word} via command line", - { timeout: 180 * 1000 }, - async function (amount, sender, receiver) { - let wallet = this.getWallet(sender); - let dest_pubkey = this.getWalletPubkey(receiver); - await wallet_run_command( - wallet, - `send-tari ${amount} ${dest_pubkey} test message`, - 180 - ); - // await wallet.sendTari(dest_pubkey, amount, "test message"); - } -); - -When( - "I create a burn transaction of {int} uT from {word} via command line", - { timeout: 180 * 1000 }, - async function (amount, name) { - let wallet = this.getWallet(name); - await wallet_run_command(wallet, `burn-tari ${amount}`, 180); - } -); - -When( - "I send one-sided {int} uT from {word} to {word} via command line", - { timeout: 180 * 1000 }, - async function (amount, sender, receiver) { - let wallet = this.getWallet(sender); - let dest_pubkey = this.getWalletPubkey(receiver); - await wallet_run_command( - wallet, - `send-one-sided ${amount} ${dest_pubkey} test message`, - 180 - ); - // await wallet.sendOneSided(dest_pubkey, amount, "test message"); - } -); - -Then( - "I make it rain from wallet {word} {int} tx per sec {int} sec {int} uT {int} increment to {word} via command line", - { timeout: 300 * 1000 }, - async function (sender, freq, duration, amount, amount_inc, receiver) { - let wallet = this.getWallet(sender); - let dest_pubkey = this.getWalletPubkey(receiver); - await wallet_run_command( - wallet, - `make-it-rain ${freq} ${duration} ${amount} ${amount_inc} now ${dest_pubkey} negotiated test message`, - 300 - ); - } -); - -Then( - "I get count of utxos of wallet {word} and it's at least {int} via command line", - async function (name, amount) { - let wallet = this.getWallet(name); - let output = await wallet_run_command(wallet, `count-utxos`); - let parse = output.buffer.match(/Total number of UTXOs: (\d+)/); - expect(parse, "Parsing the output buffer failed").to.not.be.null; - expect(parseInt(parse[1])).to.be.greaterThanOrEqual(amount); - } -); - -When( - "I do coin split on wallet {word} to {int} uT {int} coins via command line", - { timeout: 180 * 1000 }, - async function (name, amount_per_coin, number_of_coins) { - let wallet = this.getWallet(name); - await wallet_run_command( - wallet, - `coin-split ${amount_per_coin} ${number_of_coins}`, - 180 - ); - } -); - -When( - "I discover peer {word} on wallet {word} via command line", - { timeout: 120 * 1000 }, // Ample time should be allowed for peer discovery - async function (node, name) { - let wallet = this.getWallet(name); - let peer = this.getNode(node).peerAddress().split("::")[0]; - let output = await wallet_run_command(wallet, `discover-peer ${peer}`, 120); - let parse = output.buffer.match(/Discovery succeeded/); - expect(parse, "Parsing the output buffer failed").to.not.be.null; - } -); - -When( - "I run whois {word} on wallet {word} via command line", - { timeout: 20 * 1000 }, - async function (who, name) { - await sleep(5000); - let wallet = this.getWallet(name); - let pubkey = this.getNode(who).peerAddress().split("::")[0]; - let output = await wallet_run_command(wallet, `whois ${pubkey}`, 20); - let parse = output.buffer.match(/Public Key: (.+)\n/); - expect(parse, "Parsing the output buffer failed").to.not.be.null; - expect(parse[1]).to.be.equal(pubkey); - } -); - -When( - "I set custom base node of {word} to {word} via command line", - async function (wallet_name, base_node_name) { - let wallet = this.getWallet(wallet_name); - let base_node = this.getNode(base_node_name); - let output = await wallet_run_command( - wallet, - `set-custom-base-node ${base_node.peerAddress().replace("::", " ")}` - ); - let parse = output.buffer.match( - /Custom base node peer saved in wallet database\./ - ); - expect(parse, "Parsing the output buffer failed").to.not.be.null; - } -); - -When( - "I clear custom base node of wallet {word} via command line", - async function (name) { - let wallet = this.getWallet(name); - let output = await wallet_run_command(wallet, "clear-custom-base-node"); - let parse = output.buffer.match( - /Custom base node peer cleared from wallet database./ - ); - expect(parse, "Parsing the output buffer failed").to.not.be.null; - } -); - -When( - "I export the utxos of wallet {word} via command line", - async function (name) { - let wallet = this.getWallet(name); - let output = await wallet_run_command(wallet, "export-utxos"); - let parse_cnt = output.buffer.match(/Total number of UTXOs: (\d+)/); - expect(parse_cnt, "Parsing the output buffer failed").to.not.be.null; - let utxo_cnt = parseInt(parse_cnt[1]); - for (let i = 1; i <= utxo_cnt; ++i) { - let regex = new RegExp(`${i}. Value: \\d*.\\d* T`); - expect(output.buffer.match(regex), "Parsing the output buffer failed").to - .not.be.null; - } - } -); diff --git a/integration_tests/features/support/wallet_steps.js b/integration_tests/features/support/wallet_steps.js deleted file mode 100644 index b94a60c931..0000000000 --- a/integration_tests/features/support/wallet_steps.js +++ /dev/null @@ -1,2412 +0,0 @@ -// Copyright 2022. The Tari Project -// -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following -// disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the -// following disclaimer in the documentation and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote -// products derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -const { Given, When, Then } = require("@cucumber/cucumber"); -const WalletProcess = require("../../helpers/walletProcess"); -const { - waitFor, - consoleLogBalance, - sleep, - consoleLogTransactionDetails, -} = require("../../helpers/util"); -const { - AUTOUPDATE_HASHES_TXT_URL, - AUTOUPDATE_HASHES_TXT_SIG_URL, - AUTOUPDATE_HASHES_TXT_BAD_SIG_URL, - BLOCK_REWARD, - CONFIRMATION_PERIOD, -} = require("../../helpers/constants"); -const { PaymentType } = require("../../helpers/types"); -const expect = require("chai").expect; - -Given("I have {int} wallet(s)", { timeout: -1 }, async function (numWallets) { - for (let i = 0; i < numWallets; i++) { - await this.createAndAddWallet(`wallet${i}`, "", {}); - } -}); - -Given( - /I have a wallet (.*) with auto update enabled/, - { timeout: 20 * 1000 }, - async function (name) { - await this.createAndAddWallet(name, "", { - common: { - auto_update: { - check_interval: 10, - enabled: true, - dns_hosts: ["_test_autoupdate.tari.io"], - hashes_url: AUTOUPDATE_HASHES_TXT_URL, - hashes_sig_url: AUTOUPDATE_HASHES_TXT_SIG_URL, - }, - }, - }); - } -); - -Given( - /I have a wallet (.*) with auto update configured with a bad signature/, - { timeout: 20 * 1000 }, - async function (name) { - await this.createAndAddWallet(name, "", { - common: { - auto_update: { - check_interval: 10, - enabled: true, - dns_hosts: ["_test_autoupdate.tari.io"], - hashes_url: AUTOUPDATE_HASHES_TXT_URL, - hashes_sig_url: AUTOUPDATE_HASHES_TXT_BAD_SIG_URL, - }, - }, - }); - } -); - -Given( - /I have stress-test wallet (.*) connected to the seed node (.*) with broadcast monitoring timeout (.*)/, - { timeout: 20 * 1000 }, - async function (walletName, seedName, timeout) { - const wallet = new WalletProcess( - walletName, - false, - { broadcastMonitoringTimeout: timeout }, - this.logFilePathWallet - ); - wallet.setPeerSeeds([this.seeds[seedName].peerAddress()]); - await wallet.startNew(); - this.addWallet(walletName, wallet); - let walletClient = await this.getWallet(walletName).connectClient(); - let walletInfo = await walletClient.identify(); - this.addWalletPubkey(walletName, walletInfo.public_key.toString("hex")); - } -); - -Given( - /I have stress-test wallet (.*) connected to all the seed nodes with broadcast monitoring timeout (.*)/, - { timeout: 20 * 1000 }, - async function (name, timeout) { - const wallet = new WalletProcess( - name, - false, - { broadcastMonitoringTimeout: timeout }, - this.logFilePathWallet - ); - wallet.setPeerSeeds([this.seedAddresses()]); - await wallet.startNew(); - this.addWallet(name, wallet); - let walletClient = await this.getWallet(name).connectClient(); - let walletInfo = await walletClient.identify(); - this.addWalletPubkey(name, walletInfo.public_key.toString("hex")); - } -); - -Given( - /I have (a )?wallet (.*) connected to seed node (.*)/, - { timeout: 20 * 1000 }, - async function (a, walletName, seedName) { - await this.createAndAddWallet( - walletName, - this.seeds[seedName].peerAddress() - ); - } -); - -Given( - "I have wallet {word} with {int}T connected to base node {word}", - { timeout: 120 * 1000 }, - async function (walletName, balance, nodeName) { - await this.createAndAddWallet( - walletName, - this.getNode(nodeName).peerAddress() - ); - let numberOfBlocks = Math.ceil(balance / BLOCK_REWARD); - console.log("Creating miner"); - let tempMiner = await this.createMiningNode( - "tempMiner", - nodeName, - walletName - ); - console.log(numberOfBlocks); - console.log("starting miner"); - await tempMiner.mineBlocksUntilHeightIncreasedBy(numberOfBlocks, 1, false); - // mine some blocks to confirm - await this.mineBlocks(nodeName, CONFIRMATION_PERIOD); - let walletClient = await this.getWallet(walletName).connectClient(); - await waitFor( - // TODO: 1T == 1000000ut - async () => walletClient.isBalanceAtLeast(balance * 1000), - true, - 120 * 1000, - 5 * 1000, - 5 - ); - } -); - -Given( - "I have wallet {word} connected to base node {word}", - { timeout: 20 * 1000 }, - async function (walletName, nodeName) { - await this.createAndAddWallet( - walletName, - this.nodes[nodeName].peerAddress() - ); - } -); - -Given( - /I have wallet (.*) connected to all seed nodes/, - { timeout: 20 * 1000 }, - async function (name) { - await this.createAndAddWallet(name, this.seedAddresses()); - } -); - -Given( - /I have non-default wallet (.*) connected to all seed nodes using (.*)/, - { timeout: 20 * 1000 }, - async function (name, mechanism) { - // mechanism: DirectOnly, StoreAndForwardOnly, DirectAndStoreAndForward - const wallet = new WalletProcess( - name, - false, - { routingMechanism: mechanism }, - this.logFilePathWallet - ); - console.log(wallet.name, wallet.options); - wallet.setPeerSeeds([this.seedAddresses()]); - await wallet.startNew(); - this.addWallet(name, wallet); - let walletClient = await this.getWallet(name).connectClient(); - let walletInfo = await walletClient.identify(); - this.addWalletPubkey(name, walletInfo.public_key.toString("hex")); - } -); - -Given( - /I have (.*) non-default wallets connected to all seed nodes using (.*)/, - { timeout: 190 * 1000 }, - async function (n, mechanism) { - // mechanism: DirectOnly, StoreAndForwardOnly, DirectAndStoreAndForward - const promises = []; - for (let i = 0; i < n; i++) { - let name = "Wallet_" + String(i).padStart(2, "0"); - promises.push( - this.createAndAddWallet(name, [this.seedAddresses()], { - routingMechanism: mechanism, - }) - ); - } - await Promise.all(promises); - } -); - -Given( - /I recover wallet (.*) into wallet (.*) connected to all seed nodes/, - { timeout: 30 * 1000 }, - async function (walletNameA, walletNameB) { - let walletA = this.getWallet(walletNameA); - const seedWords = walletA.getSeedWords(); - console.log( - "Recover " + - walletNameA + - " into " + - walletNameB + - ", seed words:\n " + - seedWords - ); - const walletB = new WalletProcess( - walletNameB, - false, - {}, - this.logFilePathWallet, - seedWords - ); - walletB.setPeerSeeds(this.seedAddresses()); - await walletB.startNew(); - this.addWallet(walletNameB, walletB); - let walletClient = await this.getWallet(walletNameB).connectClient(); - let walletInfo = await walletClient.identify(); - this.addWalletPubkey(walletNameB, walletInfo.public_key.toString("hex")); - } -); - -Given( - /I recover all wallets connected to all seed nodes/, - { timeout: 120 * 1000 }, - async function () { - for (let walletName in this.wallets) { - let wallet = this.getWallet(walletName); - const seedWords = wallet.getSeedWords(); - let recoveredWalletName = "recovered_" + wallet.name; - console.log( - "Recover " + - wallet.name + - " into " + - recoveredWalletName + - ", seed words:\n " + - seedWords - ); - const walletB = new WalletProcess( - recoveredWalletName, - false, - {}, - this.logFilePathWallet, - seedWords - ); - - walletB.setPeerSeeds([this.seedAddresses()]); - await walletB.startNew(); - this.addWallet(recoveredWalletName, walletB); - let walletClient = await this.getWallet( - recoveredWalletName - ).connectClient(); - let walletInfo = await walletClient.identify(); - this.addWalletPubkey( - recoveredWalletName, - walletInfo.public_key.toString("hex") - ); - } - } -); - -Given( - /I recover wallet (.*) into (\d+) wallets connected to all seed nodes/, - { timeout: 30 * 1000 }, - async function (walletNameA, numwallets) { - let walletA = this.getWallet(walletNameA); - const seedWords = walletA.getSeedWords(); - for (let i = 1; i <= numwallets; i++) { - console.log( - "Recover " + - walletNameA + - " into wallet " + - i + - ", seed words:\n " + - seedWords - ); - const wallet = new WalletProcess( - i, - false, - {}, - this.logFilePathWallet, - seedWords - ); - wallet.setPeerSeeds([this.seedAddresses()]); - await wallet.startNew(); - this.addWallet(i, wallet); - let walletClient = await this.getWallet(i.toString()).connectClient(); - let walletInfo = await walletClient.identify(); - this.addWalletPubkey(wallet, walletInfo.public_key.toString("hex")); - } - } -); - -Then( - /I wait for recovered wallets to have at least (\d+) uT/, - { timeout: 60 * 1000 }, - async function (amount) { - for (let walletName in this.wallets) { - if (walletName.split("_")[0] == "recovered") { - const walletClient = await this.getWallet(walletName).connectClient(); - console.log("\n"); - console.log( - "Waiting for wallet " + - walletName + - " balance to be at least " + - amount + - " uT" - ); - - await waitFor( - async () => walletClient.isBalanceAtLeast(amount), - true, - 20 * 1000, - 5 * 1000, - 5 - ); - consoleLogBalance(await walletClient.getBalance()); - if (!(await walletClient.isBalanceAtLeast(amount))) { - console.log("Balance not adequate!"); - } - expect(await walletClient.isBalanceAtLeast(amount)).to.equal(true); - } - } - } -); - -Then( - /Wallet (.*) and (\d+) wallets have the same balance/, - { timeout: 120 * 1000 }, - async function (wallet, numwallets) { - const walletClient = await this.getWallet(wallet).connectClient(); - let balance = await walletClient.getBalance(); - for (let i = 1; i <= numwallets; i++) { - const walletClient2 = await this.getWallet(i.toString()).connectClient(); - let balance2 = await walletClient2.getBalance(); - expect(balance === balance2); - } - } -); - -When(/I stop wallet ([^\s]+)/, async function (walletName) { - let wallet = this.getWallet(walletName); - await wallet.stop(); -}); - -When(/I stop all wallets/, async function () { - for (let walletName in this.wallets) { - let wallet = this.getWallet(walletName); - await wallet.stop(); - } -}); - -When(/I start wallet (.*)/, async function (walletName) { - let wallet = this.getWallet(walletName); - await wallet.start(); -}); - -When(/I restart wallet (.*)/, async function (walletName) { - let wallet = this.getWallet(walletName); - await wallet.stop(); - await wallet.start(); -}); - -When( - /I check if wallet (.*) has (.*) transactions/, - async function (walletName, count) { - let wallet = this.getWallet(walletName); - let client = await wallet.connectClient(); - let txs = await client.getCompletedTransactions(); - expect(count).to.equal(txs.length.toString()); - } -); - -When( - /I check if last imported transactions are valid in wallet (.*)/, - async function (walletName) { - let wallet = this.getWallet(walletName); - let client = await wallet.connectClient(); - let found_txs = await client.getCompletedTransactions(); - - let found_count = 0; - for ( - let imported_tx = 0; - imported_tx < this.lastResult.length; - imported_tx++ - ) { - for (let found_tx = 0; found_tx < found_txs.length; found_tx++) { - if (found_txs[found_tx].tx_id === this.lastResult[imported_tx]) { - found_count++; - expect(found_txs[found_tx].status).to.equal( - "TRANSACTION_STATUS_IMPORTED" - ); - expect(found_txs[found_tx].valid).to.equal(true); - } - } - } - expect(found_count).to.equal(this.lastResult.length); - } -); - -Then( - "wallet {word} has {int}T", - { timeout: 120 * 1000 }, - async function (wallet, amount) { - // TODO: 1T == 1000000ut - await this.waitForWalletToHaveBalance(wallet, amount * 1000); - } -); - -Then( - /I wait for wallet (.*) to have at least (.*) uT/, - { timeout: 120 * 1000 }, - async function (wallet, amount) { - await this.waitForWalletToHaveBalance(wallet, amount); - } -); - -Then( - /I wait for wallet (.*) to have less than (.*) uT/, - { timeout: 120 * 1000 }, - async function (wallet, amount) { - let walletClient = await this.getWallet(wallet).connectClient(); - console.log("\n"); - console.log( - "Waiting for " + wallet + " balance to less than " + amount + " uT" - ); - - await waitFor( - async () => walletClient.isBalanceLessThan(amount), - true, - 115 * 1000, - 5 * 1000, - 5 - ); - consoleLogBalance(await walletClient.getBalance()); - if (!(await walletClient.isBalanceLessThan(amount))) { - console.log("Balance has not dropped below specified amount!"); - } - expect(await walletClient.isBalanceLessThan(amount)).to.equal(true); - } -); - -Then( - /wallet (.*) and wallet (.*) have the same balance/, - { timeout: 120 * 1000 }, - async function (walletNameA, walletNameB) { - const walletClientA = await this.getWallet(walletNameA).connectClient(); - var balanceA = await walletClientA.getBalance(); - console.log("\n", walletNameA, "balance:"); - consoleLogBalance(balanceA); - const walletClientB = await this.getWallet(walletNameB).connectClient(); - for (let i = 1; i <= 12; i++) { - await waitFor( - async () => walletClientB.isBalanceAtLeast(balanceA.available_balance), - true, - 5 * 1000 - ); - balanceA = await walletClientA.getBalance(); - if (walletClientB.isBalanceAtLeast(balanceA.available_balance) === true) { - break; - } - } - const balanceB = await walletClientB.getBalance(); - console.log(walletNameB, "balance:"); - consoleLogBalance(balanceB); - expect(balanceA.available_balance).to.equal(balanceB.available_balance); - } -); - -When( - /I send (.*) uT from wallet (.*) to wallet (.*) at fee (.*)/, - { timeout: 120 * 1000 }, - async function (tariAmount, source, dest, feePerGram) { - await this.transfer(tariAmount, source, dest, feePerGram); - } -); - -When( - "I transfer {int}T from {word} to {word}", - { timeout: 120 * 1000 }, - async function (tariAmount, source, dest) { - await this.transfer(tariAmount * 1000, source, dest, 10); - } -); - -When( - /I broadcast HTLC transaction with (.*) uT from wallet (.*) to wallet (.*) at fee (.*)/, - { timeout: 25 * 5 * 1000 }, - async function (tariAmount, source, dest, feePerGram) { - const sourceClient = await this.getWallet(source).connectClient(); - const destClient = await this.getWallet(dest).connectClient(); - - const sourceInfo = await sourceClient.identify(); - const destInfo = await destClient.identify(); - console.log("Starting HTLC transaction of", tariAmount, "to", dest); - let success = false; - let retries = 1; - const retries_limit = 25; - while (!success && retries <= retries_limit) { - await waitFor( - async () => { - try { - this.lastResult = await sourceClient.sendHtlc({ - recipient: { - address: destInfo.public_key.toString("hex"), - amount: tariAmount, - fee_per_gram: feePerGram, - message: "msg", - }, - }); - } catch (error) { - console.log(error); - return false; - } - return true; - }, - true, - 20 * 1000, - 5 * 1000, - 5 - ); - - success = this.lastResult.is_success; - if (!success) { - const wait_seconds = 5; - console.log( - " " + - this.lastResult.failure_message + - ", trying again after " + - wait_seconds + - "s (" + - retries + - " of " + - retries_limit + - ")" - ); - await sleep(wait_seconds * 1000); - retries++; - } - } - if (success) { - this.addTransaction( - sourceInfo.public_key.toString("hex"), - this.lastResult.transaction_id - ); - this.addTransaction( - destInfo.public_key.toString("hex"), - this.lastResult.transaction_id - ); - } - expect(success).to.equal(true); - //lets now wait for this transaction to be at least broadcast before we continue. - await waitFor( - async () => - sourceClient.isTransactionAtLeastBroadcast( - this.lastResult.transaction_id - ), - true, - 60 * 1000, - 5 * 1000, - 5 - ); - let transactionPending = await sourceClient.isTransactionAtLeastBroadcast( - this.lastResult.transaction_id - ); - expect(transactionPending).to.equal(true); - } -); - -When( - /I claim an HTLC transaction with wallet (.*) at fee (.*)/, - { timeout: 25 * 5 * 1000 }, - async function (source, feePerGram) { - const sourceClient = await this.getWallet(source).connectClient(); - - const sourceInfo = await sourceClient.identify(); - console.log("Claiming HTLC transaction of", source); - let success = false; - let retries = 1; - const retries_limit = 25; - while (!success && retries <= retries_limit) { - await waitFor( - async () => { - try { - this.lastResult = await sourceClient.claimHtlc({ - output: this.lastResult.output_hash, - pre_image: this.lastResult.pre_image, - fee_per_gram: feePerGram, - }); - } catch (error) { - console.log(error); - return false; - } - return true; - }, - true, - 20 * 1000, - 5 * 1000, - 5 - ); - - success = this.lastResult.results.is_success; - if (!success) { - const wait_seconds = 5; - console.log( - " " + - this.lastResult.results.failure_message + - ", trying again after " + - wait_seconds + - "s (" + - retries + - " of " + - retries_limit + - ")" - ); - await sleep(wait_seconds * 1000); - retries++; - } - } - - if (success) { - this.addTransaction( - sourceInfo.public_key.toString("hex"), - this.lastResult.results.transaction_id - ); - } - expect(success).to.equal(true); - //lets now wait for this transaction to be at least broadcast before we continue. - await waitFor( - async () => - sourceClient.isTransactionAtLeastBroadcast( - this.lastResult.results.transaction_id - ), - true, - 60 * 1000, - 5 * 1000, - 5 - ); - - let transactionPending = await sourceClient.isTransactionAtLeastBroadcast( - this.lastResult.results.transaction_id - ); - - expect(transactionPending).to.equal(true); - } -); - -When( - /I claim an HTLC refund transaction with wallet (.*) at fee (.*)/, - { timeout: 25 * 5 * 1000 }, - async function (source, feePerGram) { - const sourceClient = await this.getWallet(source).connectClient(); - - const sourceInfo = await sourceClient.identify(); - console.log("Claiming HTLC refund transaction of", source); - let success = false; - let retries = 1; - let hash = this.lastResult.output_hash; - const retries_limit = 25; - while (!success && retries <= retries_limit) { - await waitFor( - async () => { - try { - this.lastResult = await sourceClient.claimHtlcRefund({ - output_hash: hash, - fee_per_gram: feePerGram, - }); - } catch (error) { - console.log(error); - return false; - } - return true; - }, - true, - 20 * 1000, - 5 * 1000, - 5 - ); - - success = this.lastResult.results.is_success; - if (!success) { - const wait_seconds = 5; - console.log( - " " + - this.lastResult.results.failure_message + - ", trying again after " + - wait_seconds + - "s (" + - retries + - " of " + - retries_limit + - ")" - ); - await sleep(wait_seconds * 1000); - retries++; - } - } - - if (success) { - this.addTransaction( - sourceInfo.public_key.toString("hex"), - this.lastResult.results.transaction_id - ); - } - expect(success).to.equal(true); - //lets now wait for this transaction to be at least broadcast before we continue. - await waitFor( - async () => - sourceClient.isTransactionAtLeastBroadcast( - this.lastResult.results.transaction_id - ), - true, - 60 * 1000, - 5 * 1000, - 5 - ); - - let transactionPending = await sourceClient.isTransactionAtLeastBroadcast( - this.lastResult.results.transaction_id - ); - - expect(transactionPending).to.equal(true); - } -); - -When( - /I send(.*) uT without waiting for broadcast from wallet (.*) to wallet (.*) at fee (.*)/, - { timeout: 120 * 1000 }, - async function (tariAmount, source, dest, feePerGram) { - const sourceWallet = this.getWallet(source); - const sourceClient = await sourceWallet.connectClient(); - const sourceInfo = await sourceClient.identify(); - - const destPublicKey = this.getWalletPubkey(dest).toString("hex"); - - this.lastResult = await this.send_tari( - sourceWallet, - dest, - destPublicKey, - tariAmount, - feePerGram - ); - expect(this.lastResult.results[0].is_success).to.equal(true); - this.addTransaction( - sourceInfo.public_key, - this.lastResult.results[0].transaction_id - ); - this.addTransaction( - destPublicKey, - this.lastResult.results[0].transaction_id - ); - console.log( - " Transaction '" + - this.lastResult.results[0].transaction_id + - "' is_success(" + - this.lastResult.results[0].is_success + - ")" - ); - } -); - -When( - /I multi-send (.*) transactions of (.*) uT from wallet (.*) to wallet (.*) at fee (.*)/, - { timeout: 120 * 1000 }, - async function (number, tariAmount, source, dest, fee) { - console.log("\n"); - const sourceClient = await this.getWallet(source).connectClient(); - const sourceInfo = await sourceClient.identify(); - const destClient = await this.getWallet(dest).connectClient(); - const destInfo = await destClient.identify(); - let tx_ids = []; - for (let i = 0; i < number; i++) { - this.lastResult = await this.send_tari( - this.getWallet(source), - destInfo.name, - destInfo.public_key.toString("hex"), - tariAmount, - fee - ); - expect(this.lastResult.results[0].is_success).to.equal(true); - tx_ids.push(this.lastResult.results[0].transaction_id); - this.addTransaction( - sourceInfo.public_key, - this.lastResult.results[0].transaction_id - ); - this.addTransaction( - destInfo.public_key, - this.lastResult.results[0].transaction_id - ); - // console.log(" Transaction '" + this.lastResult.results[0]["transaction_id"] + "' is_success(" + - // this.lastResult.results[0]["is_success"] + ")"); - } - //lets now wait for this transaction to be at least broadcast before we continue. - let waitfor_result = await waitFor( - async () => { - let result = true; - for (let i = 0; i < number; i++) { - result = - result && sourceClient.isTransactionAtLeastBroadcast(tx_ids[i]); - } - return result; - }, - true, - 60 * 1000, - 5 * 1000, - 5 - ); - expect(waitfor_result).to.equal(true); - } -); - -When( - /I multi-send (.*) uT from wallet (.*) to all wallets at fee (.*)/, - { timeout: 25 * 5 * 1000 }, - async function (tariAmount, source, fee) { - const sourceWalletClient = await this.getWallet(source).connectClient(); - const sourceInfo = await sourceWalletClient.identify(); - let tx_ids = []; - for (const wallet in this.wallets) { - if (this.getWallet(source).name === this.getWallet(wallet).name) { - continue; - } - const destClient = await this.getWallet(wallet).connectClient(); - const destInfo = await destClient.identify(); - this.lastResult = await this.send_tari( - this.getWallet(source), - destInfo.name, - destInfo.public_key.toString("hex"), - tariAmount, - fee - ); - expect(this.lastResult.results[0].is_success).to.equal(true); - tx_ids.push(this.lastResult.results[0].transaction_id); - this.addTransaction( - sourceInfo.public_key.toString("hex"), - this.lastResult.results[0].transaction_id - ); - this.addTransaction( - destInfo.public_key.toString("hex"), - this.lastResult.results[0].transaction_id - ); - // console.log(" Transaction '" + this.lastResult.results[0]["transaction_id"] + "' is_success(" + - // this.lastResult.results[0]["is_success"] + ")"); - } - let waitfor_result = await waitFor( - async () => { - let result = true; - tx_ids.forEach( - (id) => - (result = - result && sourceWalletClient.isTransactionAtLeastBroadcast(id)) - ); - return result; - }, - true, - 60 * 1000, - 5 * 1000, - 5 - ); - expect(waitfor_result).to.equal(true); - } -); - -When( - /I transfer (.*) uT from (.*) to (.*) and (.*) at fee (.*)/, - { timeout: 40 * 1000 }, - async function (tariAmount, source, dest1, dest2, feePerGram) { - const sourceClient = await this.getWallet(source).connectClient(); - const destClient1 = await this.getWallet(dest1).connectClient(); - const destClient2 = await this.getWallet(dest2).connectClient(); - - const sourceInfo = await sourceClient.identify(); - const dest1Info = await destClient1.identify(); - const dest2Info = await destClient2.identify(); - console.log( - "Starting transfer of", - tariAmount, - "to", - dest1, - "and to", - dest2 - ); - let success = false; - let retries = 1; - const retries_limit = 25; - let lastResult; - while (!success && retries <= retries_limit) { - await waitFor( - async () => { - try { - lastResult = await sourceClient.transfer({ - recipients: [ - { - address: dest1Info.public_key.toString("hex"), - amount: tariAmount, - fee_per_gram: feePerGram, - message: "msg", - }, - { - address: dest2Info.public_key.toString("hex"), - amount: tariAmount, - fee_per_gram: feePerGram, - message: "msg", - }, - ], - }); - } catch (error) { - console.log(error); - return false; - } - return true; - }, - true, - 20 * 1000, - 5 * 1000, - 5 - ); - - success = - lastResult.results[0].is_success && lastResult.results[1].is_success; - if (!success) { - const wait_seconds = 5; - console.log( - " " + - lastResult.results[0].failure_message + - ", trying again after " + - wait_seconds + - "s (" + - retries + - " of " + - retries_limit + - ")" - ); - await sleep(wait_seconds * 1000); - retries++; - } - } - if (success) { - this.addTransaction( - sourceInfo.public_key.toString("hex"), - lastResult.results[0].transaction_id - ); - this.addTransaction( - sourceInfo.public_key.toString("hex"), - lastResult.results[1].transaction_id - ); - this.addTransaction( - dest1Info.public_key.toString("hex"), - lastResult.results[0].transaction_id - ); - this.addTransaction( - dest2Info.public_key.toString("hex"), - lastResult.results[1].transaction_id - ); - } - expect(success).to.equal(true); - } -); - -When( - /I transfer (.*) uT to self from wallet (.*) at fee (.*)/, - { timeout: 120 * 1000 }, - async function (tariAmount, source, feePerGram) { - const sourceClient = await this.getWallet(source).connectClient(); - const sourceInfo = await sourceClient.identify(); - this.lastResult = await this.send_tari( - this.getWallet(source), - sourceInfo.name, - sourceInfo.public_key.toString("hex"), - tariAmount, - feePerGram - ); - - expect(this.lastResult.results[0].is_success).to.equal(true); - this.addTransaction( - sourceInfo.public_key.toString("hex"), - this.lastResult.results[0].transaction_id - ); - console.log( - " Transaction '" + - this.lastResult.results[0].transaction_id + - "' is_success(" + - this.lastResult.results[0].is_success + - ")" - ); - //lets now wait for this transaction to be at least broadcast before we continue. - await waitFor( - async () => - sourceClient.isTransactionAtLeastBroadcast( - this.lastResult.results[0].transaction_id - ), - true, - 60 * 1000, - 5 * 1000, - 5 - ); - let transactionPending = await sourceClient.isTransactionAtLeastBroadcast( - this.lastResult.results[0].transaction_id - ); - expect(transactionPending).to.equal(true); - } -); - -When( - /I transfer (.*) uT from (.*) to ([A-Za-z0-9,]+) at fee (.*)/, - { timeout: 120 * 1000 }, - async function (amount, source, dests, feePerGram) { - const wallet = this.getWallet(source); - const client = await wallet.connectClient(); - const destWallets = await Promise.all( - dests.split(",").map((dest) => this.getWallet(dest).connectClient()) - ); - - console.log("Starting Transfer of", amount, "to"); - let output; - await waitFor( - async () => { - try { - const recipients = destWallets.map((w) => ({ - address: w.public_key.toString("hex"), - amount: amount, - fee_per_gram: feePerGram, - message: "msg", - })); - output = await client.transfer({ recipients }); - } catch (error) { - console.log(error); - return false; - } - return true; - }, - true, - 20 * 1000, - 5 * 1000, - 5 - ); - - console.log("output", output); - this.lastResult = output; - } -); - -When( - /I send a one-sided transaction of (.*) uT from (.*) to (.*) at fee (.*)/, - { timeout: 65 * 1000 }, - async function (amount, source, dest, feePerGram) { - const sourceWallet = this.getWallet(source); - const sourceClient = await sourceWallet.connectClient(); - const sourceInfo = await sourceClient.identify(); - - const destPublicKey = this.getWalletPubkey(dest); - - const lastResult = await this.send_tari( - sourceWallet, - dest, - destPublicKey.toString("hex"), - amount, - feePerGram, - PaymentType.ONE_SIDED - ); - expect(lastResult.results[0].is_success).to.equal(true); - - this.addTransaction( - sourceInfo.public_key.toString("hex"), - lastResult.results[0].transaction_id - ); - //lets now wait for this transaction to be at least broadcast before we continue. - await waitFor( - async () => - sourceClient.isTransactionAtLeastBroadcast( - lastResult.results[0].transaction_id - ), - true, - 60 * 1000, - 5 * 1000, - 5 - ); - let transactionPending = await sourceClient.isTransactionAtLeastBroadcast( - lastResult.results[0].transaction_id - ); - expect(transactionPending).to.equal(true); - } -); - -When( - /I send a one-sided stealth transaction of (.*) uT from (.*) to (.*) at fee (.*)/, - { timeout: 65 * 1000 }, - async function (amount, source, dest, feePerGram) { - const sourceWallet = this.getWallet(source); - const sourceClient = await sourceWallet.connectClient(); - const sourceInfo = await sourceClient.identify(); - - const destPublicKey = this.getWalletPubkey(dest); - - const lastResult = await this.send_tari( - sourceWallet, - dest, - destPublicKey.toString("hex"), - amount, - feePerGram, - PaymentType.ONE_SIDED_TO_STEALTH_ADDRESS - ); - expect(lastResult.results[0].is_success).to.equal(true); - - this.addTransaction( - sourceInfo.public_key.toString("hex"), - lastResult.results[0].transaction_id - ); - //lets now wait for this transaction to be at least broadcast before we continue. - await waitFor( - async () => - sourceClient.isTransactionAtLeastBroadcast( - lastResult.results[0].transaction_id - ), - true, - 60 * 1000, - 5 * 1000, - 5 - ); - let transactionPending = await sourceClient.isTransactionAtLeastBroadcast( - lastResult.results[0].transaction_id - ); - expect(transactionPending).to.equal(true); - } -); - -When( - /I create a burn transaction of (.*) uT from (.*) at fee (.*)/, - { timeout: 65 * 1000 }, - async function (amount, source, feePerGram) { - const sourceWallet = this.getWallet(source); - const sourceClient = await sourceWallet.connectClient(); - const sourceInfo = await sourceClient.identify(); - - const lastResult = await this.burn_tari(sourceWallet, amount, feePerGram); - expect(lastResult.is_success).to.equal(true); - - this.addTransaction( - sourceInfo.public_key.toString("hex"), - lastResult.transaction_id - ); - //lets now wait for this transaction to be at least broadcast before we continue. - await waitFor( - async () => - sourceClient.isTransactionAtLeastBroadcast(lastResult.transaction_id), - true, - 60 * 1000, - 5 * 1000, - 5 - ); - let transactionPending = await sourceClient.isTransactionAtLeastBroadcast( - lastResult.transaction_id - ); - expect(transactionPending).to.equal(true); - } -); - -When( - /I cancel last transaction in wallet (.*)/, - { timeout: 20 * 1000 }, - async function (walletName) { - const wallet = this.getWallet(walletName); - const walletClient = await wallet.connectClient(); - - let lastTxId = this.lastResult.results[0].transaction_id; - console.log( - "Attempting to cancel transaction ", - lastTxId, - "from wallet", - walletName - ); - - let result = await walletClient.cancelTransaction(lastTxId); - console.log( - "Cancellation successful? ", - result.success, - result.failure_message - ); - expect(result.success).to.be.true; - } -); - -Then( - /Batch transfer of (.*) transactions was a success from (.*) to ([A-Za-z0-9,]+)/, - async function (txCount, walletListStr) { - const clients = await Promise.all( - walletListStr.split(",").map((s) => { - const wallet = this.getWallet(s); - return wallet.connectClient(); - }) - ); - - const resultObj = this.lastResult.results; - console.log(resultObj); - let successCount = 0; - for (let i = 0; i < txCount; i++) { - const obj = resultObj[i]; - if (!obj.is_success) { - console.log(obj.transaction_id, "failed"); - expect(obj.is_success).to.be.true; - } else { - console.log( - "Transaction", - obj.transaction_id, - "passed from original request succeeded" - ); - const req = { - transaction_ids: [obj.transaction_id.toString()], - }; - console.log(req); - for (const client of clients) { - try { - const tx = await client.getTransactionInfo(req); - successCount++; - console.log(tx); - } catch (err) { - console.log( - obj.transaction_id.toString(), - "not found in :", - await client.identify() - ); - } - } - } - } - - console.log( - `Number of successful transactions is ${successCount} of ${txCount}` - ); - expect(successCount).to.equal(txCount); - console.log("All transactions found"); - } -); - -Then( - /wallet (.*) detects all transactions are at least Pending/, - { timeout: 1200 * 1000 }, // Must allow for many transactions; dynamic time out used below - async function (walletName) { - // Note: This initial step can take a long time if network conditions are not favourable - // Pending -> Completed -> Broadcast -> Mined Unconfirmed -> Mined Confirmed - const wallet = this.getWallet(walletName); - const walletClient = await wallet.connectClient(); - const walletInfo = await walletClient.identify(); - - const txIds = this.transactionsMap.get( - walletInfo.public_key.toString("hex") - ); - if (txIds === undefined) { - console.log("\nNo transactions for " + walletName + "!"); - expect(false).to.equal(true); - } - console.log( - "\nDetecting", - txIds.length, - "transactions as at least Pending: ", - walletName, - txIds - ); - for (let i = 0; i < txIds.length; i++) { - console.log( - "(" + - (i + 1) + - "/" + - txIds.length + - ") - " + - wallet.name + - ": Waiting for TxId:" + - txIds[i] + - " to register at least Pending in the wallet ..." - ); - await waitFor( - async () => await walletClient.isTransactionAtLeastPending(txIds[i]), - true, - (60 + txIds.length * 1) * 1000, - 5 * 1000, - 5 - ); - const transactionPending = await walletClient.isTransactionAtLeastPending( - txIds[i] - ); - expect(transactionPending).to.equal(true); - } - } -); - -Then( - /all wallets detect all transactions are at least Pending/, - { timeout: 1200 * 1000 }, // Must allow for many transactions; dynamic time out used below - async function () { - // Note: This initial step to register pending can take a long time if network conditions are not favourable - // Pending -> Completed -> Broadcast -> Mined Unconfirmed -> Mined Confirmed - for (const walletName in this.wallets) { - const wallet = this.getWallet(walletName); - const walletClient = await wallet.connectClient(); - const walletInfo = await walletClient.identify(); - - const txIds = this.transactionsMap.get( - walletInfo.public_key.toString("hex") - ); - if (txIds === undefined) { - console.log("\nNo transactions for " + walletName + "!"); - expect(false).to.equal(true); - } - console.log( - "\nDetecting", - txIds.length, - "transactions as at least Pending: ", - walletName, - txIds - ); - for (let i = 0; i < txIds.length; i++) { - console.log( - "(" + - (i + 1) + - "/" + - txIds.length + - ") - " + - wallet.name + - ": Waiting for TxId:" + - txIds[i] + - " to register at least Pending in the wallet ..." - ); - await waitFor( - async () => walletClient.isTransactionAtLeastPending(txIds[i]), - true, - (60 + txIds.length * 1) * 1000, - 5 * 1000, - 5 - ); - const transactionPending = - await walletClient.isTransactionAtLeastPending(txIds[i]); - expect(transactionPending).to.equal(true); - } - } - } -); - -Then( - /wallet (.*) detects last transaction is Pending/, - { timeout: 120 * 1000 }, - async function (walletName) { - const wallet = this.getWallet(walletName); - const walletClient = await wallet.connectClient(); - - let lastTxId = this.lastResult.results[0].transaction_id; - console.log( - "Waiting for Transaction ", - lastTxId, - "to be pending in wallet", - walletName - ); - - await waitFor( - async () => walletClient.isTransactionPending(lastTxId), - true, - 115 * 1000, - 5 * 1000, - 5 - ); - const transactionPending = await walletClient.isTransactionPending( - lastTxId - ); - - expect(transactionPending).to.equal(true); - } -); - -Then( - /wallet (.*) detects last transaction is Cancelled/, - { timeout: 120 * 1000 }, - async function (walletName) { - const wallet = this.getWallet(walletName); - const walletClient = await wallet.connectClient(); - - let lastTxId = this.lastResult.results[0].transaction_id; - console.log( - "Waiting for Transaction ", - lastTxId, - "to be cancelled in wallet", - walletName - ); - - await waitFor( - async () => walletClient.isTransactionCancelled(lastTxId), - true, - 115 * 1000, - 5 * 1000, - 5 - ); - const transactionPending = await walletClient.isTransactionPending( - lastTxId - ); - - expect(transactionPending).to.equal(true); - } -); - -Then( - /wallet (.*) detects all transactions are at least Completed/, - { timeout: 1200 * 1000 }, // Must allow for many transactions; dynamic time out used below - async function (walletName) { - // Pending -> Completed -> Broadcast -> Mined Unconfirmed -> Mined Confirmed - const wallet = this.getWallet(walletName); - const walletClient = await wallet.connectClient(); - const walletInfo = await walletClient.identify(); - - const txIds = this.transactionsMap.get( - walletInfo.public_key.toString("hex") - ); - if (txIds === undefined) { - console.log("\nNo transactions for " + walletName + "!"); - expect(false).to.equal(true); - } - console.log( - "\nDetecting", - txIds.length, - "transactions as at least Completed: ", - walletName, - txIds - ); - for (let i = 0; i < txIds.length; i++) { - // Get details - console.log( - "(" + - (i + 1) + - "/" + - txIds.length + - ") - " + - wallet.name + - ": Waiting for TxId:" + - txIds[i] + - " to register at least Completed in the wallet ..." - ); - await waitFor( - async () => walletClient.isTransactionAtLeastCompleted(txIds[i]), - true, - (60 + txIds.length * 1) * 1000, - 5 * 1000, - 5 - ); - const transactionCompleted = - await walletClient.isTransactionAtLeastCompleted(txIds[i]); - expect(transactionCompleted).to.equal(true); - } - } -); - -Then( - /all wallets detect all transactions are at least Completed/, - { timeout: 1200 * 1000 }, // Must allow for many transactions; dynamic time out used below - async function () { - // Pending -> Completed -> Broadcast -> Mined Unconfirmed -> Mined Confirmed - for (const walletName in this.wallets) { - const wallet = this.getWallet(walletName); - const walletClient = await wallet.connectClient(); - const walletInfo = await walletClient.identify(); - - const txIds = this.transactionsMap.get( - walletInfo.public_key.toString("hex") - ); - if (txIds === undefined) { - console.log("\nNo transactions for " + walletName + "!"); - expect(false).to.equal(true); - } - console.log( - "\nDetecting", - txIds.length, - "transactions as at least Completed: ", - walletName, - txIds - ); - for (let i = 0; i < txIds.length; i++) { - // Get details - console.log( - "(" + - (i + 1) + - "/" + - txIds.length + - ") - " + - wallet.name + - ": Waiting for TxId:" + - txIds[i] + - " to register at least Completed in the wallet ..." - ); - await waitFor( - async () => walletClient.isTransactionAtLeastCompleted(txIds[i]), - true, - (60 + txIds.length * 1) * 1000, - 5 * 1000, - 5 - ); - const transactionCompleted = - await walletClient.isTransactionAtLeastCompleted(txIds[i]); - expect(transactionCompleted).to.equal(true); - } - } - } -); - -Then( - /wallet (.*) detects all transactions are at least Broadcast/, - { timeout: 1200 * 1000 }, // Must allow for many transactions; dynamic time out used below - async function (walletName) { - // Pending -> Completed -> Broadcast -> Mined Unconfirmed -> Mined Confirmed - const wallet = this.getWallet(walletName); - const walletClient = await wallet.connectClient(); - const walletInfo = await walletClient.identify(); - - let txIds = this.transactionsMap.get(walletInfo.public_key.toString("hex")); - console.log(walletName, txIds); - if (txIds === undefined) { - console.log("\nNo transactions for " + walletName + "!"); - expect(false).to.equal(true); - } - console.log( - "\nDetecting", - txIds.length, - "transactions as at least Broadcast: ", - walletName, - txIds - ); - for (let i = 0; i < txIds.length; i++) { - // Get details - console.log( - "(" + - (i + 1) + - "/" + - txIds.length + - ") - " + - wallet.name + - ": Waiting for TxId:" + - txIds[i] + - " to register at least Broadcast in the wallet ..." - ); - await waitFor( - async () => walletClient.isTransactionAtLeastBroadcast(txIds[i]), - true, - (60 + txIds.length * 1) * 1000, - 5 * 1000, - 5 - ); - const transactionBroadcasted = - await walletClient.isTransactionAtLeastBroadcast(txIds[i]); - expect(transactionBroadcasted).to.equal(true); - } - } -); - -Then( - /all wallets detect all transactions are at least Broadcast/, - { timeout: 1200 * 1000 }, // Must allow for many transactions; dynamic time out used below - async function () { - // Pending -> Completed -> Broadcast -> Mined Unconfirmed -> Mined Confirmed - for (const walletName in this.wallets) { - const wallet = this.getWallet(walletName); - const walletClient = await wallet.connectClient(); - const walletInfo = await walletClient.identify(); - - const txIds = this.transactionsMap.get( - walletInfo.public_key.toString("hex") - ); - if (txIds === undefined) { - console.log("\nNo transactions for " + walletName + "!"); - expect(false).to.equal(true); - } - console.log( - "\nDetecting", - txIds.length, - "transactions as at least Broadcast: ", - walletName, - txIds - ); - for (let i = 0; i < txIds.length; i++) { - // Get details - console.log( - "(" + - (i + 1) + - "/" + - txIds.length + - ") - " + - wallet.name + - ": Waiting for TxId:" + - txIds[i] + - " to register at least Broadcast in the wallet ..." - ); - await waitFor( - async () => walletClient.isTransactionAtLeastBroadcast(txIds[i]), - true, - (60 + txIds.length * 1) * 1000, - 5 * 1000, - 5 - ); - const transactionBroadcasted = - await walletClient.isTransactionAtLeastBroadcast(txIds[i]); - expect(transactionBroadcasted).to.equal(true); - } - } - } -); - -Then( - /wallet (.*) detects all transactions are at least Mined_Unconfirmed/, - { timeout: 1200 * 1000 }, // Must allow for many transactions; dynamic time out used below - async function (walletName) { - // Pending -> Completed -> Broadcast -> Mined Unconfirmed -> Mined Confirmed - const wallet = this.getWallet(walletName); - const walletClient = await wallet.connectClient(); - const walletInfo = await walletClient.identify(); - - const txIds = this.transactionsMap.get( - walletInfo.public_key.toString("hex") - ); - if (txIds === undefined) { - console.log("\nNo transactions for " + walletName + "!"); - expect(false).to.equal(true); - } - console.log( - "\nDetecting", - txIds.length, - "transactions as at least Mined_Unconfirmed: ", - walletName, - txIds - ); - for (let i = 0; i < txIds.length; i++) { - console.log( - "(" + - (i + 1) + - "/" + - txIds.length + - ") - " + - wallet.name + - ": Waiting for TxId:" + - txIds[i] + - " to be detected as Mined_Unconfirmed in the wallet ..." - ); - await waitFor( - async () => walletClient.isTransactionAtLeastMinedUnconfirmed(txIds[i]), - true, - (60 + txIds.length * 1) * 1000, - 5 * 1000, - 5 - ); - const isTransactionAtLeastMinedUnconfirmed = - await walletClient.isTransactionAtLeastMinedUnconfirmed(txIds[i]); - expect(isTransactionAtLeastMinedUnconfirmed).to.equal(true); - } - } -); - -Then( - /all wallets detect all transactions are at least Mined_Unconfirmed/, - { timeout: 1200 * 1000 }, // Must allow for many transactions; dynamic time out used below - async function () { - // Pending -> Completed -> Broadcast -> Mined Unconfirmed -> Mined Confirmed - for (const walletName in this.wallets) { - const wallet = this.getWallet(walletName); - const walletClient = await wallet.connectClient(); - const walletInfo = await walletClient.identify(); - - const txIds = this.transactionsMap.get( - walletInfo.public_key.toString("hex") - ); - if (txIds === undefined) { - console.log("\nNo transactions for " + walletName + "!"); - expect(false).to.equal(true); - } - console.log( - "\nDetecting", - txIds.length, - "transactions as at least Mined_Unconfirmed: ", - walletName, - txIds - ); - for (let i = 0; i < txIds.length; i++) { - console.log( - "(" + - (i + 1) + - "/" + - txIds.length + - ") - " + - wallet.name + - ": Waiting for TxId:", - txIds[i] + " to be detected as Mined_Unconfirmed in the wallet ..." - ); - await waitFor( - async () => - walletClient.isTransactionAtLeastMinedUnconfirmed(txIds[i]), - true, - (60 + txIds.length * 1) * 1000, - 5 * 1000, - 5 - ); - const isTransactionAtLeastMinedUnconfirmed = - await walletClient.isTransactionAtLeastMinedUnconfirmed(txIds[i]); - expect(isTransactionAtLeastMinedUnconfirmed).to.equal(true); - } - } - } -); - -Then( - /wallet (.*) detects all transactions as Mined_Unconfirmed/, - { timeout: 1200 * 1000 }, // Must allow for many transactions; dynamic time out used below - async function (walletName) { - // Pending -> Completed -> Broadcast -> Mined Unconfirmed -> Mined Confirmed - const wallet = this.getWallet(walletName); - const walletClient = await wallet.connectClient(); - const walletInfo = await walletClient.identify(); - - const txIds = this.transactionsMap.get( - walletInfo.public_key.toString("hex") - ); - if (txIds === undefined) { - console.log("\nNo transactions for " + walletName + "!"); - expect(false).to.equal(true); - } - console.log( - "\nDetecting", - txIds.length, - "transactions as Mined_Unconfirmed: ", - walletName, - txIds - ); - for (let i = 0; i < txIds.length; i++) { - console.log( - "(" + - (i + 1) + - "/" + - txIds.length + - ") - " + - wallet.name + - ": Waiting for TxId:" + - txIds[i] + - " to be detected as Mined_Unconfirmed in the wallet ..." - ); - await waitFor( - async () => walletClient.isTransactionMinedUnconfirmed(txIds[i]), - true, - (60 + txIds.length * 1) * 1000, - 5 * 1000, - 5 - ); - const isTransactionMinedUnconfirmed = - await walletClient.isTransactionMinedUnconfirmed(txIds[i]); - expect(isTransactionMinedUnconfirmed).to.equal(true); - } - } -); - -Then( - /all wallets detect all transactions as Mined_Unconfirmed/, - { timeout: 1200 * 1000 }, // Must allow for many transactions; dynamic time out used below - async function () { - // Pending -> Completed -> Broadcast -> Mined Unconfirmed -> Mined Confirmed - for (const walletName in this.wallets) { - const wallet = this.getWallet(walletName); - const walletClient = await wallet.connectClient(); - const walletInfo = await walletClient.identify(); - - const txIds = this.transactionsMap.get( - walletInfo.public_key.toString("hex") - ); - if (txIds === undefined) { - console.log("\nNo transactions for " + walletName + "!"); - expect(false).to.equal(true); - } - console.log( - "\nDetecting", - txIds.length, - "transactions as Mined_Unconfirmed: ", - walletName, - txIds - ); - for (let i = 0; i < txIds.length; i++) { - console.log( - "(" + - (i + 1) + - "/" + - txIds.length + - ") - " + - wallet.name + - ": Waiting for TxId:" + - txIds[i] + - " to be detected as Mined_Unconfirmed in the wallet ..." - ); - await waitFor( - async () => walletClient.isTransactionMinedUnconfirmed(txIds[i]), - true, - (60 + txIds.length * 1) * 1000, - 5 * 1000, - 5 - ); - const isTransactionMinedUnconfirmed = - await walletClient.isTransactionMinedUnconfirmed(txIds[i]); - expect(isTransactionMinedUnconfirmed).to.equal(true); - } - } - } -); - -Then( - /wallet (.*) detects all transactions as Mined_Confirmed/, - { timeout: 1200 * 1000 }, // Must allow for many transactions; dynamic time out used below - async function (walletName) { - // Pending -> Completed -> Broadcast -> Mined Unconfirmed -> Mined Confirmed - const wallet = this.getWallet(walletName); - const walletClient = await wallet.connectClient(); - const walletInfo = await walletClient.identify(); - - const txIds = this.transactionsMap.get( - walletInfo.public_key.toString("hex") - ); - if (txIds === undefined) { - console.log("\nNo transactions for " + walletName + "!"); - expect(false).to.equal(true); - } - console.log( - "\nDetecting", - txIds.length, - "transactions as Mined_Confirmed: ", - walletName, - txIds - ); - for (let i = 0; i < txIds.length; i++) { - console.log( - "(" + - (i + 1) + - "/" + - txIds.length + - ") - " + - wallet.name + - ": Waiting for TxId:" + - txIds[i] + - " to be detected as Mined_Confirmed in the wallet ..." - ); - await waitFor( - async () => walletClient.isTransactionMinedConfirmed(txIds[i]), - true, - (60 + txIds.length * 1) * 1000, - 5 * 1000, - 5 - ); - const isTransactionMinedConfirmed = - await walletClient.isTransactionMinedConfirmed(txIds[i]); - expect(isTransactionMinedConfirmed).to.equal(true); - } - } -); - -Then( - /while mining via node (.*) all transactions in wallet (.*) are found to be Mined_Confirmed/, - { timeout: 1200 * 1000 }, // Must allow for many transactions; dynamic time out used below - async function (nodeName, walletName) { - const wallet = this.getWallet(walletName); - const walletClient = await wallet.connectClient(); - const walletInfo = await walletClient.identify(); - const nodeClient = this.getClient(nodeName); - const txIds = this.transactionsMap.get( - walletInfo.public_key.toString("hex") - ); - if (txIds === undefined) { - console.log("\nNo transactions for " + walletName + "!"); - throw new Error("No transactions for " + walletName + "!"); - } - console.log( - "\nDetecting", - txIds.length, - "transactions as Mined_Confirmed: ", - walletName, - txIds - ); - for (let i = 0; i < txIds.length; i++) { - console.log( - "(" + - (i + 1) + - "/" + - txIds.length + - ") - " + - wallet.name + - ": Waiting for TxId:" + - txIds[i] + - " to be detected as Mined_Confirmed in the wallet ..." - ); - await waitFor( - async () => { - if (await walletClient.isTransactionMinedConfirmed(txIds[i])) { - return true; - } else { - const tipHeight = await this.getClient(nodeName).getTipHeight(); - let autoTransactionResult = await this.createTransactions( - nodeName, - tipHeight + 1 - ); - expect(autoTransactionResult).to.equal(true); - await nodeClient.mineBlock(walletClient); - return false; - } - }, - true, - (60 + txIds.length * 1) * 1000, - 5 * 1000, - 5 - ); - const isTransactionMinedConfirmed = - await walletClient.isTransactionMinedConfirmed(txIds[i]); - expect(isTransactionMinedConfirmed).to.equal(true); - } - } -); - -Then( - /all wallets detect all transactions as Mined_Confirmed/, - { timeout: 1200 * 1000 }, - async function () { - // Pending -> Completed -> Broadcast -> Mined Unconfirmed -> Mined Confirmed - for (const walletName in this.wallets) { - const wallet = this.getWallet(walletName); - const walletClient = await wallet.connectClient(); - const walletInfo = await walletClient.identify(); - - const txIds = this.transactionsMap.get( - walletInfo.public_key.toString("hex") - ); - if (txIds === undefined) { - console.log("\nNo transactions for " + walletName + "!"); - expect(false).to.equal(true); - } - console.log( - "\nDetecting", - txIds.length, - "transactions as Mined_Confirmed: ", - walletName, - txIds - ); - for (let i = 0; i < txIds.length; i++) { - console.log( - "(" + - (i + 1) + - "/" + - txIds.length + - ") - " + - wallet.name + - ": Waiting for TxId:" + - txIds[i] + - " to be detected as Mined_Confirmed in the wallet ..." - ); - await waitFor( - async () => walletClient.isTransactionMinedConfirmed(txIds[i]), - true, - (60 + txIds.length * 1) * 1000, - 5 * 1000, - 5 - ); - const isTransactionMinedConfirmed = - await walletClient.isTransactionMinedConfirmed(txIds[i]); - expect(isTransactionMinedConfirmed).to.equal(true); - } - } - } -); - -When( - /I list all (.*) transactions for wallet (.*)/, - async function (transaction_type, walletName) { - const wallet = this.getWallet(walletName); - const walletClient = await wallet.connectClient(); - var transactions; - var type; - if (transaction_type === "NORMAL") { - transactions = await walletClient.getAllNormalTransactions(); - type = "NORMAL"; - } else { - transactions = await walletClient.getAllCoinbaseTransactions(); - type = "COINBASE"; - } - console.log("\nListing all `" + type + "` transactions: ", walletName); - if (transactions.length > 0) { - for (let i = 0; i < transactions.length; i++) { - consoleLogTransactionDetails(transactions[i]); - } - } else { - console.log(" No `" + type + "` transactions found!"); - } - } -); - -Then( - /wallet (.*) has (.*) coinbase transactions/, - async function (walletName, count) { - const walletClient = await this.getWallet(walletName).connectClient(); - const transactions = await walletClient.getAllCoinbaseTransactions(); - expect(transactions.length).to.equal(Number(count)); - this.resultStack.push([walletName, transactions.length]); - } -); - -Then( - /wallet (.*) detects at least (.*) coinbase transactions as Mined_Confirmed/, - { timeout: 120 * 1000 }, - async function (walletName, count) { - const walletClient = await this.getWallet(walletName).connectClient(); - await waitFor( - async () => walletClient.areCoinbasesConfirmedAtLeast(count), - true, - 110 * 1000, - 5 * 1000, - 5 - ); - const transactions = - await walletClient.getAllSpendableCoinbaseTransactions(); - expect(parseInt(transactions.length) >= parseInt(count)).to.equal(true); - } -); - -Then( - /wallet (.*) detects exactly (.*) coinbase transactions as Mined_Confirmed/, - { timeout: 120 * 1000 }, - async function (walletName, count) { - const walletClient = await this.getWallet(walletName).connectClient(); - await waitFor( - async () => walletClient.areCoinbasesConfirmedAtLeast(count), - true, - 110 * 1000, - 5 * 1000, - 5 - ); - const transactions = - await walletClient.getAllSpendableCoinbaseTransactions(); - expect(parseInt(transactions.length) === parseInt(count)).to.equal(true); - } -); - -Then( - /wallets ([A-Za-z0-9,]+) should have (.*) (.*) spendable coinbase outputs/, - { timeout: 610 * 1000 }, - async function (wallets, comparison, amountOfCoinBases) { - const atLeast = "AT_LEAST"; - const exactly = "EXACTLY"; - expect(comparison === atLeast || comparison === exactly).to.equal(true); - const walletClients = await Promise.all( - wallets.split(",").map((wallet) => this.getWallet(wallet).connectClient()) - ); - let coinbaseCount = 0; - for (const client of walletClients) { - coinbaseCount += await client.countAllCoinbaseTransactions(); - } - let spendableCoinbaseCount; - await waitFor( - async () => { - spendableCoinbaseCount = 0; - for (const client of walletClients) { - const count = await client.countAllSpendableCoinbaseTransactions(); - console.log(client.name, "count", count); - spendableCoinbaseCount += count; - } - if (comparison === atLeast) { - console.log( - spendableCoinbaseCount, - spendableCoinbaseCount >= parseInt(amountOfCoinBases) - ); - return spendableCoinbaseCount >= parseInt(amountOfCoinBases); - } else { - console.log( - spendableCoinbaseCount, - spendableCoinbaseCount === parseInt(amountOfCoinBases) - ); - return spendableCoinbaseCount === parseInt(amountOfCoinBases); - } - }, - true, - 600 * 1000, - 5 * 1000, - 5 - ); - - console.log( - "Found", - coinbaseCount, - "coinbases in wallets", - wallets, - "with", - spendableCoinbaseCount, - "being valid and Mined_Confirmed, expected", - comparison, - amountOfCoinBases, - "\n" - ); - if (comparison === atLeast) { - expect(spendableCoinbaseCount >= parseInt(amountOfCoinBases)).to.equal( - true - ); - } else { - expect(spendableCoinbaseCount === parseInt(amountOfCoinBases)).to.equal( - true - ); - } - } -); - -Then( - /wallet (.*) has at least (.*) transactions that are all (.*) and not cancelled/, - { timeout: 610 * 1000 }, - async function (walletName, numberOfTransactions, transactionStatus) { - const walletClient = await this.getWallet(walletName).connectClient(); - console.log( - walletName + - ": waiting for " + - numberOfTransactions + - " transactions to be " + - transactionStatus + - " and not cancelled..." - ); - var transactions; - var numberCorrect; - var statusCorrect; - await waitFor( - async () => { - numberCorrect = true; - statusCorrect = true; - transactions = await walletClient.getAllNormalTransactions(); - if (transactions.length < parseInt(numberOfTransactions)) { - console.log( - "Has", - transactions.length, - "transactions, need", - numberOfTransactions - ); - numberCorrect = false; - return false; - } - for (let i = 0; i < transactions.length; i++) { - if ( - transactions[i]["status"] !== transactionStatus || - transactions[i]["is_cancelled"] - ) { - console.log( - "Transaction " + - i + - 1 + - " has " + - transactions[i]["status"] + - " (need " + - transactionStatus + - ") and is not cancelled(" + - transactions[i]["is_cancelled"] + - ")" - ); - statusCorrect = false; - return false; - } - } - return true; - }, - true, - 600 * 1000, - 5 * 1000, - 5 - ); - - if (transactions === undefined) { - expect("\nNo transactions found!").to.equal(""); - } - expect(numberCorrect && statusCorrect).to.equal(true); - } -); - -Then( - /all (.*) transactions for wallet (.*) and wallet (.*) have consistent but opposing cancellation status/, - async function (transaction_type, walletNameA, walletNameB) { - let walletClientA = await this.getWallet(walletNameA).connectClient(); - let walletClientB = await this.getWallet(walletNameB).connectClient(); - var transactionsA; - var transactionsB; - var type; - if (transaction_type === "NORMAL") { - transactionsA = await walletClientA.getAllNormalTransactions(); - transactionsB = await walletClientB.getAllNormalTransactions(); - type = "NORMAL"; - } else { - transactionsA = await walletClientA.getAllCoinbaseTransactions(); - transactionsB = await walletClientB.getAllCoinbaseTransactions(); - type = "COINBASE"; - } - if (transactionsA === undefined || transactionsB === undefined) { - expect("\nNo `" + type + "` transactions found!").to.equal(""); - } - let cancelledA = transactionsA[0]["is_cancelled"]; - for (let i = 0; i < transactionsA.length; i++) { - if (cancelledA !== transactionsA[i]["is_cancelled"]) { - expect( - "\n" + - walletNameA + - "'s `" + - type + - "` transactions do not have a consistent cancellation status" - ).to.equal(""); - } - } - let cancelledB = transactionsB[0]["is_cancelled"]; - for (let i = 0; i < transactionsB.length; i++) { - if (cancelledB !== transactionsB[i]["is_cancelled"]) { - expect( - "\n" + - walletNameB + - "'s `" + - type + - "` transactions do not have a consistent cancellation status" - ).to.equal(""); - } - } - expect(cancelledA).to.equal(!cancelledB); - } -); - -Then( - /all (.*) transactions for wallet (.*) are valid/, - async function (transaction_type, walletName) { - let walletClient = await this.getWallet(walletName).connectClient(); - var transactions; - var type; - if (transaction_type === "NORMAL") { - transactions = await walletClient.getAllNormalTransactions(); - type = "NORMAL"; - } else { - transactions = await walletClient.getAllCoinbaseTransactions(); - type = "COINBASE"; - } - if (transactions === undefined) { - expect("\nNo `" + type + "` transactions found!").to.equal(""); - } - for (let i = 0; i < transactions.length; i++) { - expect(transactions[i]["is_cancelled"]).to.equal(false); - } - } -); - -When( - /I coin split tari in wallet (.*) to produce (.*) UTXOs of (.*) uT each with fee_per_gram (.*) uT/, - { timeout: 4800 * 1000 }, - async function (walletName, splitNum, splitValue, feePerGram) { - console.log("\n"); - const numberOfSplits = Math.ceil(splitNum / 499); - let splitsLeft = splitNum; - - const wallet = this.getWallet(walletName); - const walletClient = await wallet.connectClient(); - const walletInfo = await walletClient.identify(); - - console.log( - "Performing", - numberOfSplits, - "coin splits to produce", - splitNum, - "outputs of", - splitValue, - "uT" - ); - - for (let i = 0; i < numberOfSplits; i++) { - const splits = Math.min(499, splitsLeft); - splitsLeft -= splits; - let result; - await waitFor( - async () => { - try { - result = await walletClient.coin_split({ - amount_per_split: splitValue, - split_count: splits, - fee_per_gram: feePerGram, - message: "Cucumber coinsplit", - lockheight: 0, - }); - } catch (error) { - console.log(error); - return false; - } - return true; - }, - true, - 4700 * 1000, - 5 * 1000, - 5 - ); - let waitfor_result = await waitFor( - async () => { - return walletClient.isTransactionAtLeastBroadcast(result.tx_id); - }, - true, - 60 * 1000, - 5 * 1000, - 5 - ); - expect(waitfor_result).to.equal(true); - console.log( - "Coin split", - i + 1, - "/", - numberOfSplits, - " completed with TxId: ", - result - ); - this.addTransaction(walletInfo.public_key.toString("hex"), result.tx_id); - this.lastResult = result; - } - } -); - -When( - /I send (.*) transactions of (.*) uT each from wallet (.*) to wallet (.*) at fee_per_gram (.*)/, - { timeout: 120 * 1000 }, - async function ( - numTransactions, - amount, - sourceWallet, - destWallet, - feePerGram - ) { - console.log("\n"); - const sourceWalletClient = await this.getWallet( - sourceWallet - ).connectClient(); - const sourceInfo = await sourceWalletClient.identify(); - const destWalletClient = await this.getWallet(destWallet).connectClient(); - const destInfo = await destWalletClient.identify(); - console.log( - "Sending", - numTransactions, - "transactions from", - sourceWallet, - "to", - destWallet - ); - - let batch = 1; - let tx_ids = []; - - for (let i = 0; i < numTransactions; i++) { - const result = await this.send_tari( - this.getWallet(sourceWallet), - destInfo.name, - destInfo.public_key.toString("hex"), - amount, - feePerGram, - false, - "Transaction from " + sourceWallet + " to " + destWallet + " " + i, - false - ); - expect(result.results[0].is_success).to.equal(true); - tx_ids.push(result.results[0].transaction_id); - this.addTransaction( - sourceInfo.public_key.toString("hex"), - result.results[0].transaction_id - ); - this.addTransaction( - destInfo.public_key.toString("hex"), - result.results[0].transaction_id - ); - - if (i / 10 >= batch) { - batch++; - console.log(i, "/", numTransactions, " transactions sent"); - } - await sleep(50); - } - let waitfor_result = await waitFor( - async () => { - let result = true; - tx_ids.forEach( - (id) => - (result = - result && sourceWalletClient.isTransactionAtLeastBroadcast(id)) - ); - return result; - }, - true, - 60 * 1000, - 5 * 1000, - 5 - ); - expect(waitfor_result).to.equal(true); - console.log(numTransactions, " transactions successfully sent."); - } -); diff --git a/integration_tests/features/support/world.js b/integration_tests/features/support/world.js deleted file mode 100644 index 3ca31fc4ca..0000000000 --- a/integration_tests/features/support/world.js +++ /dev/null @@ -1,804 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const { - setWorldConstructor, - After, - BeforeAll, - Before, -} = require("@cucumber/cucumber"); - -const BaseNodeProcess = require("../../helpers/baseNodeProcess"); -const MergeMiningProxyProcess = require("../../helpers/mergeMiningProxyProcess"); -const WalletProcess = require("../../helpers/walletProcess"); -const WalletFFIClient = require("../../helpers/walletFFIClient"); -const MiningNodeProcess = require("../../helpers/miningNodeProcess"); -const TransactionBuilder = require("../../helpers/transactionBuilder"); -const glob = require("glob"); -const fs = require("fs"); -const archiver = require("archiver"); -const { waitFor, sleep, consoleLogBalance } = require("../../helpers/util"); -const { PaymentType } = require("../../helpers/types"); -const { expect } = require("chai"); -const InterfaceFFI = require("../../helpers/ffi/ffiInterface"); - -class CustomWorld { - constructor({ attach, parameters }) { - // this.variable = 0; - this.attach = attach; - this.seeds = {}; - this.nodes = {}; - this.dan_nodes = {}; - this.proxies = {}; - this.miners = {}; - this.wallets = {}; - this.walletsFFI = {}; - this.walletPubkeys = {}; - this.clients = {}; - this.headers = {}; - this.outputs = {}; - this.contract_definitions = {}; - this.constitutions = {}; - this.transactionOutputs = {}; - this.testrun = `run${Date.now()}`; - this.lastResult = null; - this.blocks = {}; - this.transactions = {}; - this.peers = {}; - this.transactionsMap = new Map(); - this.resultStack = []; - this.logFilePathBaseNode = - parameters.logFilePathBaseNode || "./log4rs/base_node.yml"; - this.logFilePathProxy = parameters.logFilePathProxy || "./log4rs/proxy.yml"; - this.logFilePathMiningNode = - parameters.logFilePathMiningNode || "./log4rs/miner.yml"; - this.logFilePathWallet = - parameters.logFilePathWallet || "./log4rs/wallet.yml"; - this.lastResult = {}; - } - - async createSeedNode(name) { - console.log(`seed:`, name); - const proc = new BaseNodeProcess( - `seed-${name}`, - false, - null, - this.logFilePathBaseNode - ); - await proc.startNew(); - this.seeds[name] = proc; - this.clients[name] = await proc.createGrpcClient(); - } - - seedAddresses() { - const res = []; - for (const property in this.seeds) { - res.push(this.seeds[property].peerAddress()); - } - return res; - } - - getRandomSeedName() { - let keys = Object.keys(this.seeds); - let r = Math.random() * keys.length; - return keys[r]; - } - - currentBaseNodeName() { - return Object.keys(this.nodes)[0]; - } - - currentWalletName() { - return Object.keys(this.wallets)[0]; - } - - currentWallet() { - return Object.values(this.wallets)[0]; - } - - /// Create but don't add the node - createNode(name, options) { - return new BaseNodeProcess(name, false, options, this.logFilePathBaseNode); - } - - async createAndAddNode(name, addresses) { - console.log(`Creating node ${name} connected to ${addresses}`); - const node = this.createNode(name); - if (addresses) { - if (Array.isArray(addresses)) { - node.setPeerSeeds(addresses); - } else { - node.setPeerSeeds([addresses]); - } - } - await node.startNew(); - await this.addNode(name, node); - } - - async addDanNode(name, process) { - this.dan_nodes[name] = process; - // this.clients[name] = await process.createGrpcClient(); - } - - async addNode(name, process) { - this.nodes[name] = process; - this.clients[name] = await process.createGrpcClient(); - } - - addMiningNode(name, process) { - this.miners[name] = process; - } - - addProxy(name, process) { - this.proxies[name] = process; - } - - async createAndAddWallet(name, nodeAddresses, options = {}) { - console.log(`Creating wallet ${name} connected to ${nodeAddresses}`); - const wallet = new WalletProcess( - name, - false, - options, - this.logFilePathWallet - ); - wallet.setPeerSeeds([nodeAddresses]); - await wallet.startNew(); - - this.addWallet(name, wallet); - let walletClient = await wallet.connectClient(); - let walletInfo = await walletClient.identify(); - this.walletPubkeys[name] = walletInfo.public_key.toString("hex"); - } - - async createAndAddFFIWallet(name, seed_words = null, passphrase = null) { - const wallet = new WalletFFIClient(name); - await wallet.startNew(seed_words, passphrase); - this.walletsFFI[name] = wallet; - this.walletPubkeys[name] = wallet.identify(); - return wallet; - } - - addWallet(name, process) { - this.wallets[name.toString()] = process; - } - - addWalletPubkey(name, pubkey) { - this.walletPubkeys[name] = pubkey; - } - - addOutput(name, output) { - this.outputs[name] = output; - } - - addTransactionOutput(spendHeight, output) { - if (this.transactionOutputs[spendHeight] == null) { - this.transactionOutputs[spendHeight] = [output]; - } else { - this.transactionOutputs[spendHeight].push(output); - } - } - - async createTransactions(name, height) { - this.lastTransactionsSucceeded = true; - let result = true; - const txInputs = this.transactionOutputs[height]; - if (txInputs == null) { - return result; - } - let i = 0; - const client = this.getClient(name); - for (const input of txInputs) { - // console.log(input); - // console.log(await client.fetchMatchingUtxos(input.hash)); - - const txn = new TransactionBuilder(); - txn.addInput(input); - txn.changeFee(1); - const txOutput = txn.addOutput(txn.getSpendableAmount()); - const completedTx = txn.build(); - - const submitResult = await client.submitTransaction(completedTx); - if (submitResult.result != "ACCEPTED") { - this.lastTransactionsSucceeded = false; - // result = false; - } else { - // Add the output to be spent... assumes it has been mined. - this.addTransactionOutput(height + 1, txOutput); - } - i++; - if (i > 9) { - //this is to make sure the blocks stay relatively empty so that the tests don't take too long - break; - } - } - console.log( - `Created ${i} transactions for node: ${name} at height: ${height}` - ); - return result; - } - - async mineBlock(name, weight, beforeSubmit, onError) { - await this.clients[name].mineBlockWithoutWallet( - beforeSubmit, - weight, - onError - ); - } - - async mineBlocks(name, num) { - for (let i = 0; i < num; i++) { - await this.mineBlock(name, 0); - } - } - - async baseNodeMineBlocksUntilHeightIncreasedBy(baseNode, wallet, numBlocks) { - let w = null; - if (wallet) { - let tmp = this.getWallet(wallet); - w = await tmp.connectClient(); - } - const promise = this.getClient(baseNode).mineBlocksUntilHeightIncreasedBy( - numBlocks, - w - ); - return promise; - } - - sha3MineBlocksUntilHeightIncreasedBy( - miner, - numBlocks, - minDifficulty, - mineOnTipOnly - ) { - const promise = this.getMiningNode(miner).mineBlocksUntilHeightIncreasedBy( - numBlocks, - minDifficulty, - mineOnTipOnly - ); - return promise; - } - - async mergeMineBlock(name) { - const client = this.proxies[name].createClient(); - await client.mineBlock(); - } - - mergeMineBlocksUntilHeightIncreasedBy(mmProxy, numBlocks) { - const promise = this.getProxy(mmProxy) - .createClient() - .mineBlocksUntilHeightIncreasedBy(numBlocks); - return promise; - } - - saveBlock(name, block) { - this.blocks[name] = block; - } - - async submitBlock(blockName, nodeName) { - await this.clients[nodeName] - .submitBlock(this.blocks[blockName].block) - .catch((err) => { - console.log("submit block error", err); - }); - // console.log(result); - } - - getClient(name) { - const client = this.clients[name]; - if (!client) { - throw new Error(`Node client not found with name '${name}'`); - } - return client; - } - - getNode(name) { - const node = this.nodes[name] || this.seeds[name] || this.dan_nodes[name]; - if (!node) { - throw new Error(`Node not found with name '${name}'`); - } - return node; - } - - getMiningNode(name) { - const miner = this.miners[name]; - if (!miner) { - throw new Error(`Miner not found with name '${name}'`); - } - return miner; - } - - async createMiningNode(name, node, wallet) { - const baseNode = this.getNode(node); - const walletNode = await this.getOrCreateWallet(wallet); - const miningNode = new MiningNodeProcess( - name, - baseNode.getGrpcAddress(), - this.getClient(node), - walletNode.getGrpcAddress(), - this.logFilePathMiningNode, - true - ); - this.addMiningNode(name, miningNode); - return miningNode; - } - - getWallet(name) { - const wallet = this.wallets[name] || this.walletsFFI[name]; - if (!wallet) { - throw new Error(`Wallet not found with name '${name}'`); - } - return wallet; - } - - getWalletPubkey(name) { - return this.walletPubkeys[name]; - } - - async getNodeOrWalletClient(name) { - let client = this.clients[name.trim()]; - if (client) { - client.isNode = true; - client.isWallet = false; - return client; - } - let wallet = this.wallets[name.trim()]; - if (wallet) { - client = await wallet.connectClient(); - client.isNode = false; - client.isWallet = true; - return client; - } - let ffi_wallet = this.walletsFFI[name.trim()]; - if (ffi_wallet) { - return ffi_wallet; - } - return null; - } - - async getOrCreateWallet(name) { - const wallet = this.wallets[name]; - if (wallet) { - return wallet; - } - await this.createAndAddWallet(name, this.seedAddresses()); - return this.wallets[name]; - } - - getProxy(name) { - return this.proxies[name]; - } - - async forEachClientAsync(f, canFailPercent = 0) { - const promises = []; - let total = 0; - let succeeded = 0; - let failed = 0; - - for (const property in this.seeds) { - promises.push(f(this.getClient(property), property)); - ++total; - } - for (const property in this.nodes) { - promises.push(f(this.getClient(property), property)); - ++total; - } - - // Round up the number of nodes that can fail. - let canFail = Math.ceil((total * canFailPercent) / 100); - - return new Promise((resolve, reject) => { - for (let promise of promises) { - Promise.resolve(promise) - .then(() => { - succeeded += 1; - console.log(`${succeeded} of ${total} (need ${total - canFail})`); - if (succeeded >= total - canFail) resolve(); - }) - .catch((err) => { - console.error(err); - failed += 1; - if (failed > canFail) - reject(`Too many failed. Expected at most ${canFail} failures`); - }); - } - }); - } - - async stopNode(name) { - const node = this.seeds[name] || this.nodes[name]; - await node.stop(); - console.log("\n", name, "stopped\n"); - } - - async startNode(name, args) { - const node = this.seeds[name] || this.nodes[name]; - await node.start({ args }); - console.log("\n", name, "started\n"); - } - - addTransaction(pubKey, txId) { - if (!this.transactionsMap.has(pubKey)) { - this.transactionsMap.set(pubKey, []); - } - this.transactionsMap.get(pubKey).push(txId); - } - - async send_tari( - sourceWallet, - destWalletName, - destWalletPubkey, - tariAmount, - feePerGram, - paymentType = PaymentType.STANDARD_MIMBLEWIMBLE, - message = "", - printMessage = true - ) { - const isOneSided = - paymentType === PaymentType.ONE_SIDED || - paymentType === PaymentType.ONE_SIDED_TO_STEALTH_ADDRESS; - - const sourceWalletClient = await sourceWallet.connectClient(); - console.log( - sourceWallet.name + - " sending " + - tariAmount + - "uT one-sided(" + - isOneSided + - ") to " + - destWalletName + - " `" + - destWalletPubkey + - "`" - ); - if (printMessage) { - console.log(message); - } - let success = false; - let retries = 1; - const retries_limit = 25; - let lastResult; - while (!success && retries <= retries_limit) { - await waitFor( - async () => { - try { - switch (paymentType) { - case PaymentType.STANDARD_MIMBLEWIMBLE: - lastResult = await sourceWalletClient.transfer({ - recipients: [ - { - address: destWalletPubkey, - amount: tariAmount, - fee_per_gram: feePerGram, - message: message, - paymentType: PaymentType.STANDARD_MIMBLEWIMBLE, - }, - ], - }); - break; - - case PaymentType.ONE_SIDED: - lastResult = await sourceWalletClient.transfer({ - recipients: [ - { - address: destWalletPubkey, - amount: tariAmount, - fee_per_gram: feePerGram, - message: message, - payment_type: PaymentType.ONE_SIDED, - }, - ], - }); - break; - - case PaymentType.ONE_SIDED_TO_STEALTH_ADDRESS: - lastResult = await sourceWalletClient.transfer({ - recipients: [ - { - address: destWalletPubkey, - amount: tariAmount, - fee_per_gram: feePerGram, - message: message, - payment_type: PaymentType.ONE_SIDED_TO_STEALTH_ADDRESS, - }, - ], - }); - break; - - default: - console.log("unrecognized payment type"); - break; - } - } catch (error) { - console.log(error); - return false; - } - return true; - }, - true, - 20 * 1000, - 5 * 1000, - 5 - ); - success = lastResult.results[0].is_success; - if (!success) { - const wait_seconds = 5; - console.log( - " " + - lastResult.results[0].failure_message + - ", trying again after " + - wait_seconds + - "s (" + - retries + - " of " + - retries_limit + - ")" - ); - await sleep(wait_seconds * 1000); - retries++; - } - } - return lastResult; - } - - async burn_tari( - sourceWallet, - tariAmount, - feePerGram, - message = "", - printMessage = true - ) { - const sourceWalletClient = await sourceWallet.connectClient(); - console.log(sourceWallet.name + " burning " + tariAmount + "uT"); - if (printMessage) { - console.log(message); - } - let success = false; - let retries = 1; - const retries_limit = 25; - let lastResult; - while (!success && retries <= retries_limit) { - await waitFor( - async () => { - try { - lastResult = await sourceWalletClient.burn({ - amount: tariAmount, - fee_per_gram: feePerGram, - message: message, - }); - } catch (error) { - console.log(error); - return false; - } - return true; - }, - true, - 20 * 1000, - 5 * 1000, - 5 - ); - success = lastResult.is_success; - if (!success) { - const wait_seconds = 5; - console.log( - " " + - lastResult.failure_message + - ", trying again after " + - wait_seconds + - "s (" + - retries + - " of " + - retries_limit + - ")" - ); - await sleep(wait_seconds * 1000); - retries++; - } - } - return lastResult; - } - - async transfer( - tariAmount, - source, - dest, - feePerGram, - paymentType = PaymentType.STANDARD_MIMBLEWIMBLE - ) { - const sourceWallet = this.getWallet(source); - const sourceClient = await sourceWallet.connectClient(); - const sourceInfo = await sourceClient.identify(); - - const destPublicKey = this.getWalletPubkey(dest).toString("hex"); - - this.lastResult = await this.send_tari( - sourceWallet, - dest, - destPublicKey, - tariAmount, - feePerGram, - paymentType - ); - expect(this.lastResult.results[0]["is_success"]).to.equal(true); - this.addTransaction( - sourceInfo.public_key, - this.lastResult.results[0]["transaction_id"] - ); - this.addTransaction( - destPublicKey, - this.lastResult.results[0]["transaction_id"] - ); - console.log( - " Transaction '" + - this.lastResult.results[0]["transaction_id"] + - "' is_success(" + - this.lastResult.results[0]["is_success"] + - ")" - ); - //lets now wait for this transaction to be at least broadcast before we continue. - await waitFor( - async () => - sourceClient.isTransactionAtLeastBroadcast( - this.lastResult.results[0]["transaction_id"] - ), - true, - 60 * 1000, - 5 * 1000, - 5 - ); - } - - async waitForWalletToHaveBalance(wallet, amount) { - const walletClient = await this.getWallet(wallet).connectClient(); - console.log("\n"); - console.log( - "Waiting for " + wallet + " balance to be at least " + amount + " uT" - ); - - await waitFor( - async () => await walletClient.isBalanceAtLeast(amount), - true, - 115 * 1000, - 5 * 1000, - 5 - ); - consoleLogBalance(await walletClient.getBalance()); - if (!(await walletClient.isBalanceAtLeast(amount))) { - console.log("Balance not adequate!"); - } - expect(await walletClient.isBalanceAtLeast(amount)).to.equal(true); - } - - async all_nodes_are_at_height(height) { - let result = true; - await waitFor( - async () => { - result = true; - await this.forEachClientAsync(async (client, name) => { - await waitFor( - async () => await client.getTipHeight(), - height, - 5 * height * 1000 /* 5 seconds per block */ - ); - const currTip = await client.getTipHeight(); - console.log( - `Node ${name} is at tip: ${currTip} (should be ${height})` - ); - result = result && currTip === height; - }); - return result; - }, - true, - 600 * 1000, - 5 * 1000, - 5 - ); - expect(result).to.equal(true); - } -} - -setWorldConstructor(CustomWorld); - -BeforeAll({ timeout: 2400000 }, async function () { - console.log( - "NOTE: Some tests may be excluded based on the profile used in /integration_tests/cucumber.js. If none was specified, `default` profile is used." - ); - const baseNode = new BaseNodeProcess("compile"); - console.log("Compiling base node..."); - await baseNode.init(); - await baseNode.compile(); - - const wallet = new WalletProcess("compile"); - console.log("Compiling wallet..."); - await wallet.init(); - await wallet.compile(); - - const mmProxy = new MergeMiningProxyProcess( - "compile", - "/ip4/127.0.0.1/tcp/9999", - null, - "/ip4/127.0.0.1/tcp/9998" - ); - - console.log("Compiling mmproxy..."); - await mmProxy.init(); - await mmProxy.compile(); - - const miningNode = new MiningNodeProcess( - "compile", - "/ip4/127.0.0.1/tcp/9999", - null, - "/ip4/127.0.0.1/tcp/9998" - // this.logFilePathMiningNode - ); - - console.log("Compiling miner..."); - await miningNode.init(1, 1, 1, 1, true, 1); - await miningNode.compile(); - console.log("Compiling wallet FFI..."); - await InterfaceFFI.compile(); - console.log("Finished compilation."); - console.log("Loading FFI interface.."); - await InterfaceFFI.init(); - console.log("FFI interface loaded."); - - console.log("World ready, now lets run some tests! :)"); -}); - -Before(async function (testCase) { - console.log(`\nTesting scenario: "${testCase.pickle.name}"\n`); -}); - -After(async function (testCase) { - console.log("Stopping nodes"); - await stopAndHandleLogs(this.walletsFFI, testCase, this); - await stopAndHandleLogs(this.seeds, testCase, this); - await stopAndHandleLogs(this.nodes, testCase, this); - await stopAndHandleLogs(this.proxies, testCase, this); - await stopAndHandleLogs(this.miners, testCase, this); - await stopAndHandleLogs(this.dan_nodes, testCase, this); - await stopAndHandleLogs(this.wallets, testCase, this); -}); - -async function stopAndHandleLogs(objects, testCase, context) { - for (const key in objects) { - try { - if (testCase.result.status !== "passed") { - await attachLogs(`${objects[key].baseDir}`, context); - } - await objects[key].stop(); - } catch (e) { - console.log(e); - // Continue with others - } - } -} - -function attachLogs(path, context) { - return new Promise((outerRes) => { - let zipFile = fs.createWriteStream(path + "/logzip.zip"); - const archive = archiver("zip", { - zlib: { level: 9 }, - }); - archive.pipe(zipFile); - - glob(path + "/**/*.log", {}, function (err, files) { - for (let i = 0; i < files.length; i++) { - // Append the file name at the bottom - fs.appendFileSync(files[i], `>>>> End of ${files[i]}`); - archive.append(fs.createReadStream(files[i]), { - name: files[i].replace("./temp", ""), - }); - } - archive.finalize().then(function () { - context.attach( - fs.createReadStream(path + "/logzip.zip"), - "application/zip", - function () { - fs.rmSync && fs.rmSync(path + "/logzip.zip"); - outerRes(); - } - ); - }); - }); - }); -} diff --git a/integration_tests/generate_report.js b/integration_tests/generate_report.js deleted file mode 100644 index 30c43143ea..0000000000 --- a/integration_tests/generate_report.js +++ /dev/null @@ -1,21 +0,0 @@ -const reporter = require("cucumber-html-reporter"); - -const args = process.argv.slice(2); -const options = { - theme: "bootstrap", - jsonFile: args[0] || "cucumber_output/tests.cucumber", - output: args[1] || "temp/reports/cucumber_report.html", - reportSuiteAsScenarios: true, - scenarioTimestamp: true, - launchReport: true, - // metadata: { - // "App Version":"0.3.2", - // "Test Environment": "STAGING", - // "Browser": "Chrome 54.0.2840.98", - // "Platform": "Windows 10", - // "Parallel": "Scenarios", - // "Executed": "Remote" - // } -}; - -reporter.generate(options); diff --git a/integration_tests/helpers/baseNodeClient.js b/integration_tests/helpers/baseNodeClient.js deleted file mode 100644 index 6a80a8e780..0000000000 --- a/integration_tests/helpers/baseNodeClient.js +++ /dev/null @@ -1,460 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const expect = require("chai").expect; -const grpc = require("@grpc/grpc-js"); -const protoLoader = require("@grpc/proto-loader"); -const TransactionBuilder = require("./transactionBuilder"); -const { SHA3 } = require("sha3"); -const { toLittleEndian, byteArrayToHex, tryConnect } = require("./util"); -const { PowAlgo } = require("./types"); -const cloneDeep = require("clone-deep"); -const grpcPromise = require("grpc-promise"); - -class BaseNodeClient { - constructor() { - this.client = null; - this.blockTemplates = {}; - } - - async connect(port) { - const PROTO_PATH = - __dirname + "/../../applications/tari_app_grpc/proto/base_node.proto"; - const packageDefinition = protoLoader.loadSync(PROTO_PATH, { - keepCase: true, - longs: String, - enums: String, - defaults: true, - oneofs: true, - }); - const protoDescriptor = grpc.loadPackageDefinition(packageDefinition); - const tari = protoDescriptor.tari.rpc; - this.client = await tryConnect( - () => - new tari.BaseNode( - "127.0.0.1:" + port, - grpc.credentials.createInsecure() - ) - ); - - grpcPromise.promisifyAll(this.client, { - metadata: new grpc.Metadata(), - }); - } - - getHeaderAt(height) { - return this.getHeaders(height, 1).then((header) => - header && header.length ? header[0] : null - ); - } - - getNetworkDifficulties(tip, start, end) { - return this.client - .getNetworkDifficulty() - .sendMessage({ from_tip: tip, start_height: start, end_height: end }); - } - - getPeers() { - return this.client - .getPeers() - .sendMessage({}) - .then((peers) => { - console.log("Got ", peers.length, " peers:"); - return peers; - }); - } - - getTipHeader() { - return this.getHeaders(0, 1).then((headers) => { - const header = headers[0]; - return Object.assign(header, { - height: +header.height, - }); - }); - } - - async getHeaders(from_height, num_headers, sorting = 0) { - return await this.client - .listHeaders() - .sendMessage({ from_height, num_headers, sorting }) - .then((resp) => resp.map((r) => r.header)); - } - - getTipHeight() { - return this.client - .getTipInfo() - .sendMessage({}) - .then((tip) => parseInt(tip.metadata.height_of_longest_chain)); - } - - getPrunedHeight() { - return this.client - .getTipInfo() - .sendMessage({}) - .then((tip) => parseInt(tip.metadata.pruned_height)); - } - - getPreviousBlockTemplate(height) { - return cloneDeep(this.blockTemplates["height" + height]); - } - - getBlockTemplate(weight) { - return this.client - .getNewBlockTemplate() - .sendMessage({ algo: { pow_algo: PowAlgo.SHA3 }, max_weight: weight }) - .then((template) => { - const res = { - minerData: template.miner_data, - block: template.new_block_template, - }; - this.blockTemplates[ - "height" + template.new_block_template.header.height - ] = cloneDeep(res); - return res; - }); - } - - submitBlockWithCoinbase(template, coinbase) { - const cb = coinbase; - template.body.outputs = template.body.outputs.concat(cb.outputs); - template.body.kernels = template.body.kernels.concat(cb.kernels); - return this.client - .getNewBlock() - .sendMessage(template) - .then((b) => { - return this.client.submitBlock().sendMessage(b.block); - }); - } - - submitTemplate(template, beforeSubmit) { - return this.client - .getNewBlock() - .sendMessage(template.template) - .then((b) => { - // console.log("Sha3 diff", this.getSha3Difficulty(b.block.header)); - if (beforeSubmit) { - b = beforeSubmit({ block: b, originalTemplate: template }); - if (!b) { - return Promise.resolve(); - } - b = b.block; - } - return this.client.submitBlock().sendMessage(b.block); - }); - } - - submitBlock(b) { - return this.client.submitBlock().sendMessage(b.block); - } - - submitTransaction(txn) { - return this.client - .submitTransaction() - .sendMessage({ transaction: txn }) - .then((res) => { - return res; - }); - } - - transactionState(txn) { - return this.client - .transactionState() - .sendMessage({ excess_sig: txn }) - .then((res) => { - return res; - }); - } - - checkForUpdates() { - return this.client.checkForUpdates().sendMessage({}); - } - - transactionStateResult(txn) { - return this.client - .transactionState() - .sendMessage({ excess_sig: txn }) - .then((res) => { - return res.result; - }); - } - - fetchMatchingUtxos(hashes) { - return this.client - .fetchMatchingUtxos() - .sendMessage({ hashes: hashes }) - .then((result) => { - return result; - }); - } - - async mineBlockBeforeSubmit(weight) { - // New block from base node including coinbase - const block = await this.getMinedCandidateBlock(weight); - const newBlock = await this.client - .getNewBlock() - .sendMessage(block.template); - return newBlock; - } - - async submitMinedBlock(newBlock) { - const response = await this.client - .submitBlock() - .sendMessage(newBlock.block); - return response; - } - - mineBlock(walletClient, weight) { - if (!walletClient) { - return this.mineBlockWithoutWallet(weight); - } - let currHeight; - let block; - return this.client - .getTipInfo() - .sendMessage({}) - .then((tip) => { - currHeight = parseInt(tip.metadata.height_of_longest_chain); - return this.client.getNewBlockTemplate().sendMessage({ - algo: { pow_algo: PowAlgo.SHA3 }, - max_weight: weight, - }); - }) - .then((template) => { - block = template.new_block_template; - const height = block.header.height; - return walletClient.client.inner.getCoinbase().sendMessage({ - reward: template.miner_data.reward, - fee: template.miner_data.total_fees, - height: height, - }); - }) - .then((coinbase) => { - const cb = coinbase.transaction; - block.body.outputs = block.body.outputs.concat(cb.body.outputs); - block.body.kernels = block.body.kernels.concat(cb.body.kernels); - return this.client.getNewBlock().sendMessage(block); - }) - .then((b) => { - return this.client.submitBlock().sendMessage(b.block); - }) - .then(() => { - return this.client.getTipInfo().sendMessage({}); - }) - .then((tipInfo) => { - expect(tipInfo.metadata.height_of_longest_chain).to.equal( - currHeight + 1 + "" - ); - }); - } - - async getMinedCandidateBlock(weight, existingBlockTemplate, walletClient) { - const builder = new TransactionBuilder(); - const blockTemplate = - existingBlockTemplate || (await this.getBlockTemplate(weight)); - const privateKey = toLittleEndian( - blockTemplate.block.header.height, - 256 - ).toString("hex"); - const height = parseInt(blockTemplate.block.header.height) + 2; - - let cb_outputs; - let cb_kernels; - if (!walletClient) { - const cb_builder = builder.generateCoinbase( - parseInt(blockTemplate.minerData.reward), - privateKey, - parseInt(blockTemplate.minerData.total_fees), - height - ); - cb_outputs = cb_builder.outputs; - cb_kernels = cb_builder.kernels; - } else { - const cb_wallet = await walletClient.client.inner - .getCoinbase() - .sendMessage({ - reward: parseInt(blockTemplate.minerData.reward), - fee: parseInt(blockTemplate.minerData.total_fees), - height: height, - }); - cb_outputs = cb_wallet.transaction.body.outputs; - cb_kernels = cb_wallet.transaction.body.kernels; - } - - const template = blockTemplate.block; - template.body.outputs = template.body.outputs.concat(cb_outputs); - template.body.kernels = template.body.kernels.concat(cb_kernels); - return { - template: template, - coinbase: { - output: cb_outputs[0], - privateKey: privateKey, - amount: - parseInt(blockTemplate.minerData.reward) + - parseInt(blockTemplate.minerData.total_fees), - scriptPrivateKey: privateKey, - scriptOffsetPrivateKey: Buffer.from( - "0000000000000000000000000000000000000000000000000000000000000000", - "hex" - ), - height: blockTemplate.block.header.height, - }, - }; - } - - async mineBlockWithoutWallet(beforeSubmit, weight, onError) { - const template = await this.getMinedCandidateBlock(weight); - return this.submitTemplate(template, beforeSubmit).then( - async () => { - // let tip = await this.getTipHeight(); - // console.log("Node is at tip:", tip); - }, - (err) => { - console.log("err submitting block:", err); - if (onError) { - if (!onError(err)) { - throw err; - } - // handled - } else { - throw err; - } - } - ); - } - - async mineBlockWithWallet(weight, walletClient, onError) { - const template = await this.getMinedCandidateBlock( - weight, - null, - walletClient - ); - return this.submitTemplate(template).then( - async () => { - // let tip = await this.getTipHeight(); - // console.log("Node is at tip:", tip); - }, - (err) => { - console.log("err submitting block:", err); - if (onError) { - if (!onError(err)) { - throw err; - } - // handled - } else { - throw err; - } - } - ); - } - - async mineBlocksUntilHeightIncreasedBy(numBlocks, walletClient) { - let tipHeight = parseInt(await this.getTipHeight()); - const height = (await this.getTipHeight()) + numBlocks; - const weight = 0; - let i = 0; - do { - if (i % 25 === 0) { - console.log( - "[base node client] Tip at", - tipHeight, - "...(stopping at " + height + ")" - ); - } - i += 1; - if (!walletClient) { - await this.mineBlockWithoutWallet(null, weight, null); - } else { - await this.mineBlockWithWallet(weight, walletClient); - } - tipHeight = await this.getTipHeight(); - } while (tipHeight < height); - return await this.getTipHeight(); - } - - getSha3Difficulty(header) { - const hash = new SHA3(256); - hash.update(toLittleEndian(header.version, 16)); - hash.update(toLittleEndian(parseInt(header.height), 64)); - hash.update(header.prev_hash); - const timestamp = parseInt(header.timestamp.seconds); - hash.update(toLittleEndian(timestamp, 64)); - hash.update(header.input_mr); - hash.update(header.output_mr); - hash.update(header.witness_mr); - hash.update(header.kernel_mr); - hash.update(header.total_kernel_offset); - hash.update(toLittleEndian(parseInt(header.nonce), 64)); - hash.update(toLittleEndian(header.pow.pow_algo)); - hash.update( - toLittleEndian(parseInt(header.pow.accumulated_monero_difficulty), 64) - ); - hash.update( - toLittleEndian(parseInt(header.pow.accumulated_sha_difficulty), 64) - ); - hash.update(header.pow.pow_data); - const first_round = hash.digest(); - const hash2 = new SHA3(256); - hash2.update(first_round); - const res = hash2.digest("hex"); - return res; - } - - async identify() { - const info = await this.client.identify().sendMessage({}); - return { - public_key: byteArrayToHex(info.public_key), - public_address: info.public_address, - node_id: byteArrayToHex(info.node_id), - }; - } - - async listConnectedPeers() { - const { connected_peers } = await this.client - .listConnectedPeers() - .sendMessage({}); - return connected_peers.map((peer) => ({ - ...peer, - public_key: byteArrayToHex(peer.public_key), - node_id: byteArrayToHex(peer.node_id), - supported_protocols: peer.supported_protocols.map((p) => - p.toString("utf8") - ), - features: +peer.features, - })); - } - - async getNetworkStatus() { - let resp = await this.client.getNetworkStatus().sendMessage({}); - return { - ...resp, - num_node_connections: +resp.num_node_connections, - }; - } - - async initial_sync_achieved() { - let result = await this.client.GetTipInfo().sendMessage({}); - return result.initial_sync_achieved; - } - - async get_node_state() { - let result = await this.client.GetTipInfo().sendMessage({}); - return result.base_node_state; - } - - static async create(port) { - const client = new BaseNodeClient(); - await client.connect(port); - return client; - } - - async getMempoolStats() { - const mempoolStats = await this.client.getMempoolStats().sendMessage({}); - return mempoolStats; - } - - async getBlocks(heights) { - return await this.client.getBlocks().sendMessage({ heights }); - } -} - -module.exports = BaseNodeClient; diff --git a/integration_tests/helpers/baseNodeProcess.js b/integration_tests/helpers/baseNodeProcess.js deleted file mode 100644 index 9980a561d2..0000000000 --- a/integration_tests/helpers/baseNodeProcess.js +++ /dev/null @@ -1,304 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const { spawn } = require("child_process"); -const { expect } = require("chai"); -const fs = require("fs"); -const path = require("path"); -const BaseNodeClient = require("./baseNodeClient"); -const { getFreePort } = require("./util"); -const dateFormat = require("dateformat"); -const { createEnv } = require("./config"); -const JSON5 = require("json5"); - -let outputProcess; -class BaseNodeProcess { - constructor(name, excludeTestEnvars, options, logFilePath, nodeFile) { - this.name = name; - this.logFilePath = logFilePath ? path.resolve(logFilePath) : logFilePath; - this.nodeFile = nodeFile; - this.peerSeeds = []; - this.options = Object.assign( - { - baseDir: "./temp/base_nodes", - }, - options || {} - ); - this.excludeTestEnvars = excludeTestEnvars; - } - - async init() { - this.port = await getFreePort(); - this.grpcPort = await getFreePort(); - this.name = `Basenode${this.port}-${this.name}`; - this.nodeFile = this.nodeFile || "nodeid.json"; - - let instance = 0; - do { - this.baseDir = `${this.options.baseDir}/${dateFormat( - new Date(), - "yyyymmddHHMM" - )}/${instance}/${this.name}`; - // Some tests failed during testing because the next base node process started in the previous process - // directory therefore using the previous blockchain database - if (fs.existsSync(this.baseDir)) { - instance++; - } else { - console.log("Making base dir ", this.baseDir); - fs.mkdirSync(`${this.baseDir}/log`, { recursive: true }); - break; - } - } while (fs.existsSync(this.baseDir)); - const args = ["--base-path", ".", "--init", "--network", "localnet"]; - const overrides = this.getOverrides(); - Object.keys(overrides).forEach((k) => { - args.push("-p"); - args.push(`${k}=${overrides[k]}`); - }); - if (this.logFilePath) { - args.push("--log-config", this.logFilePath); - } - - await this.run(await this.compile(), args); - console.log("Port:", this.port); - console.log("GRPC:", this.grpcPort); - console.log(`Starting node ${this.name}...`); - } - - async compile() { - if (!outputProcess) { - await this.runCommand("cargo", [ - "build", - "--release", - "--locked", - "--bin", - "tari_base_node", - "-Z", - "unstable-options", - "--out-dir", - process.cwd() + "/temp/out", - ]); - outputProcess = process.cwd() + "/temp/out/tari_base_node"; - } - return outputProcess; - } - - ensureNodeInfo() { - for (let i = 0; i < 100; i++) { - if (fs.existsSync(this.baseDir + "/" + this.nodeFile)) { - break; - } - } - if (!fs.existsSync(this.baseDir + "/" + this.nodeFile)) { - throw new Error( - `Node id file node found ${this.baseDir}/${this.nodeFile}` - ); - } - - this.nodeInfo = JSON5.parse( - fs.readFileSync(this.baseDir + "/" + this.nodeFile, "utf8") - ); - } - - peerAddress() { - this.ensureNodeInfo(); - const addr = this.nodeInfo.public_key + "::" + this.nodeInfo.public_address; - // console.log("Peer:", addr); - return addr; - } - - setPeerSeeds(addresses) { - this.peerSeeds = addresses; - } - - setForceSyncPeers(addresses) { - this.forceSyncPeers = addresses; - } - - getOverrides() { - return createEnv({ - network: "localnet", - baseNodeGrpcAddress: this.getGrpcAddress(), - isWallet: false, - baseNodePort: this.port, - peerSeeds: this.peerSeeds, - forceSyncPeers: this.forceSyncPeers, - nodeFile: this.nodeFile, - options: this.options, - }); - } - - getGrpcAddress() { - const address = "/ip4/127.0.0.1/tcp/" + this.grpcPort; - // console.log("Base Node GRPC Address:",address); - return address; - } - - getGrpcPort() { - return this.grpcPort; - } - - run(cmd, args) { - return new Promise((resolve, reject) => { - if (!fs.existsSync(this.baseDir + "/log")) { - fs.mkdirSync(this.baseDir + "/log", { recursive: true }); - } - - let overrides = this.excludeTestEnvars ? {} : this.getOverrides(); - - // clear the .env file - fs.writeFileSync(`${this.baseDir}/.overrides`, ""); - Object.keys(overrides).forEach((key) => { - fs.appendFileSync( - `${this.baseDir}/.overrides`, - ` -p ${key}=${overrides[key]}` - ); - }); - - // Create convenience script - this is NOT used to start the base node in cucumber - fs.writeFileSync( - `${this.baseDir}/start_node.sh`, - "bash -c \"RUST_BACKTRACE=1 cargo run --release --locked --bin tari_base_node -- -n --watch status -b . --network localnet $(grep -v '^#' .overrides)\"", - { mode: 0o777 } - ); - - // console.log("Running command ", cmd, args.join(" ")); - const ps = spawn(cmd, args, { - cwd: this.baseDir, - // shell: true, - env: { ...process.env, RUST_BACKTRACE: "1" }, - }); - - ps.stdout.on("data", (data) => { - //console.log(`stdout: ${data}`); - fs.appendFileSync(`${this.baseDir}/log/stdout.log`, data.toString()); - if ( - // Make this resilient by comparing uppercase and making provisioning that the first print message in the - // base node console is not always 'State: Starting up' - data - .toString() - .toUpperCase() - .match(/STATE: STARTING/) || - data - .toString() - .toUpperCase() - .match(/STATE: LISTENING/) || - data - .toString() - .toUpperCase() - .match(/STATE: SYNCING/) - ) { - resolve(ps); - } - }); - - ps.stderr.on("data", (data) => { - console.error(`stderr: ${data}`); - fs.appendFileSync(`${this.baseDir}/log/stderr.log`, data.toString()); - }); - - ps.on("close", (code) => { - const ps = this.ps; - this.ps = null; - if (code) { - console.log(`child process exited with code ${code}`); - reject(`child process exited with code ${code}`); - } else { - resolve(ps); - } - }); - - expect(ps.error).to.be.an("undefined"); - this.ps = ps; - }); - } - - runCommand(cmd, args, opts = { env: {} }) { - return new Promise((resolve, reject) => { - const ps = spawn(cmd, args, { - cwd: this.baseDir, - // shell: true, - env: { ...process.env, ...opts.env }, - }); - - ps.stdout.on("data", (data) => { - // console.log(`stdout: ${data}`); - fs.appendFileSync(`${this.baseDir}/log/stdout.log`, data.toString()); - resolve(ps); - }); - - ps.stderr.on("data", (data) => { - console.error(`stderr: ${data}`); - fs.appendFileSync(`${this.baseDir}/log/stderr.log`, data.toString()); - }); - - ps.on("close", (code) => { - const ps = this.ps; - this.ps = null; - if (code) { - console.log(`child process exited with code ${code}`); - reject(`child process exited with code ${code}`); - } else { - resolve(ps); - } - }); - }); - } - - async startNew() { - await this.init(); - const start = await this.start(); - return start; - } - - async startAndConnect() { - await this.startNew(); - return await this.createGrpcClient(); - } - - async start(opts = {}) { - const args = [ - "--non-interactive-mode", - "--watch", - "status", - "--base-path", - ".", - "--network", - opts.network || "localnet", - ]; - if (this.logFilePath) { - args.push("--log-config", this.logFilePath); - } - if (opts.args) { - args.concat(opts.args); - } - const overrides = Object.assign(this.getOverrides(), opts.config); - Object.keys(overrides).forEach((k) => { - args.push("-p"); - args.push(`${k}=${overrides[k]}`); - }); - let cmd = await this.compile(); - return await this.run(cmd, args); - } - - stop() { - return new Promise((resolve) => { - if (!this.ps) { - return resolve(); - } - this.ps.on("close", (code) => { - if (code) { - console.log(`child process exited with code ${code}`); - } - resolve(); - }); - this.ps.kill("SIGINT"); - }); - } - - async createGrpcClient() { - return await BaseNodeClient.create(this.grpcPort); - } -} - -module.exports = BaseNodeProcess; diff --git a/integration_tests/helpers/config.js b/integration_tests/helpers/config.js deleted file mode 100644 index 51ae68f4a7..0000000000 --- a/integration_tests/helpers/config.js +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -function mapEnvs(options) { - const res = {}; - if (options.blocks_behind_before_considered_lagging) { - res["localnet.base_node.blocks_behind_before_considered_lagging"] = - options.blocks_behind_before_considered_lagging; - } - if (options.pruningHorizon) { - // In the config toml file: `base_node.network.pruning_horizon` with `network = localnet` - res["localnet.base_node.storage.pruning_horizon"] = options.pruningHorizon; - res["localnet.base_node.storage.pruning_interval"] = "1"; - } - if ("numConfirmations" in options) { - res["wallet.num_required_confirmations"] = options.numConfirmations; - res["wallet.transactions.num_confirmations_required"] = - options.numConfirmations; - res["wallet.outputs.num_confirmations_required"] = options.numConfirmations; - } - if ("num_confirmations" in options) { - res["wallet.num_required_confirmations"] = options.num_confirmations; - res["wallet.transactions.num_confirmations_required"] = - options.num_confirmations; - res["wallet.outputs.num_confirmations_required"] = - options.num_confirmations; - } - if (options.routingMechanism) { - res["wallet.transactions.transaction_routing_mechanism"] = - options.routingMechanism; - } - if (options.broadcastMonitoringTimeout) { - res["wallet.transactions.broadcast_monitoring_timeout"] = - options.broadcastMonitoringTimeout; - } else { - res["wallet.transactions.broadcast_monitoring_timeout"] = "3"; - } - if (options.numMiningThreads) { - res["miner.num_mining_threads"] = options.numMiningThreads; - } - - if (options.network) { - res["base_node.network"] = options.network; - } - if (options.transport) { - res["localnet.base_node.transport"] = options.transport; - } - if (options.common && options.common.auto_update) { - let { auto_update } = options.common; - if (auto_update.enabled) { - res["localnet.auto_update.enabled"] = auto_update.enabled - ? "true" - : "false"; - } - if (auto_update.check_interval) { - res["localnet.auto_update.check_interval"] = auto_update.check_interval; - } - if (auto_update.dns_hosts) { - res["localnet.auto_update.dns_hosts"] = auto_update.dns_hosts.join(","); - } - if (auto_update.hashes_url) { - res["localnet.auto_update.hashes_url"] = auto_update.hashes_url; - } - if (auto_update.hashes_sig_url) { - res["localnet.auto_update.hashes_sig_url"] = auto_update.hashes_sig_url; - } - } - return res; -} - -function baseEnvs(peerSeeds = [], forceSyncPeers = [], _committee = []) { - const envs = { - ["base_node.network"]: "localnet", - ["wallet.network"]: "localnet", - ["localnet.base_node.data_dir"]: "localnet", - ["localnet.base_node.db_type"]: "lmdb", - ["localnet.base_node.storage.orphan_storage_capacity"]: "10", - ["localnet.base_node.storage.pruning_horizon"]: "0", - ["localnet.base_node.identity_file"]: "none.json", - ["localnet.base_node.tor_identity_file"]: "torid.json", - ["localnet.base_node.max_randomx_vms"]: "1", - ["localnet.base_node.metadata_auto_ping_interval"]: "15", - ["localnet.base_node.p2p.allow_test_addresses"]: true, - ["localnet.base_node.p2p.dht.flood_ban_max_msg_count"]: "100000", - ["localnet.base_node.p2p.dht.database_url"]: "localnet/dht.db", - ["localnet.p2p.seeds.dns_seeds_use_dnssec"]: "false", - ["localnet.base_node.lmdb.init_size_bytes"]: 16000000, - ["localnet.base_node.lmdb.grow_size_bytes"]: 16000000, - ["localnet.base_node.lmdb.resize_threshold_bytes"]: 1024, - - ["localnet.wallet.identity_file"]: "walletid.json", - ["localnet.wallet.contacts_auto_ping_interval"]: "5", - ["localnet.wallet.p2p.allow_test_addresses"]: true, - ["localnet.wallet.p2p.dht.flood_ban_max_msg_count"]: "100000", - ["localnet.wallet.p2p.dht.saf.auto_request"]: true, - - ["localnet.merge_mining_proxy.monerod_url"]: [ - "http://stagenet.xmr-tw.org:38081", - "http://stagenet.community.xmr.to:38081", - "http://monero-stagenet.exan.tech:38081", - "http://xmr-lux.boldsuck.org:38081", - "http://singapore.node.xmr.pm:38081", - ].join(","), - ["merge_mining_proxy.monerod_use_auth"]: false, - ["merge_mining_proxy.monerod_username"]: "", - ["merge_mining_proxy.monerod_password"]: "", - - ["merge_mining_proxy.wait_for_initial_sync_at_startup"]: false, - ["miner.num_mining_threads"]: "1", - ["miner.mine_on_tip_only"]: true, - ["miner.validate_tip_timeout_sec"]: "1", - }; - if (forceSyncPeers.length > 0) { - envs["localnet.base_node.force_sync_peers"] = forceSyncPeers.join(","); - } - if (peerSeeds.length > 0) { - envs["localnet.p2p.seeds.peer_seeds"] = peerSeeds.join(","); - } - - return envs; -} - -let defaultOpts = { - isWallet: false, - nodeFile: "newnodeid.json", - walletGrpcAddress: "/ip4/127.0.0.1/tcp/19082", - baseNodeGrpcAddress: "/ip4/127.0.0.1/tcp/19080", - walletPort: 8083, - baseNodePort: 8081, - proxyFullAddress: "127.0.0.1:8084", - transcoderFullAddress: "127.0.0.1:8085", - options: {}, - peerSeeds: [], - forceSyncPeers: [], - committee: [], -}; - -// TODO: We can split these per application now -function createEnv(opts) { - const finalOpts = { ...defaultOpts, ...opts }; - let { - nodeFile, - walletGrpcAddress, - walletPort, - baseNodeGrpcAddress, - baseNodePort, - network = "localnet", - proxyFullAddress, - peerSeeds, - forceSyncPeers, - options, - committee, - validatorNodeGrpcAddress, - } = finalOpts; - - const envs = baseEnvs(peerSeeds, forceSyncPeers, committee); - const configEnvs = { - [`${network}.base_node.grpc_address`]: baseNodeGrpcAddress, - [`${network}.base_node.identity_file`]: `${nodeFile}`, - [`${network}.base_node.p2p.transport.type`]: "tcp", - [`${network}.base_node.p2p.transport.tcp.listener_address`]: `/ip4/127.0.0.1/tcp/${baseNodePort}`, - [`${network}.base_node.p2p.public_address`]: `/ip4/127.0.0.1/tcp/${baseNodePort}`, - ["base_node.report_grpc_error"]: true, - - [`wallet.grpc_address`]: walletGrpcAddress, - [`${network}.wallet.grpc_address`]: walletGrpcAddress, - [`${network}.wallet.p2p.transport.type`]: "tcp", - [`${network}.wallet.p2p.transport.tcp.listener_address`]: `/ip4/127.0.0.1/tcp/${walletPort}`, - [`${network}.wallet.p2p.public_address`]: `/ip4/127.0.0.1/tcp/${walletPort}`, - - [`merge_mining_proxy.listener_address`]: proxyFullAddress, - [`${network}.merge_mining_proxy.base_node_grpc_address`]: - baseNodeGrpcAddress, - [`${network}.merge_mining_proxy.console_wallet_grpc_address`]: - walletGrpcAddress, - - [`miner.base_node_grpc_address`]: baseNodeGrpcAddress, - [`miner.wallet_grpc_address`]: walletGrpcAddress, - - [`validator_node.base_node_grpc_address`]: baseNodeGrpcAddress, - [`validator_node.wallet_grpc_address`]: walletGrpcAddress, - [`validator_node.p2p.transport.type`]: "tcp", - [`validator_node.grpc_address`]: validatorNodeGrpcAddress, - }; - let finalEnv = { ...envs, ...configEnvs, ...mapEnvs(options || {}) }; - return finalEnv; -} - -module.exports = { - createEnv, -}; diff --git a/integration_tests/helpers/constants.js b/integration_tests/helpers/constants.js deleted file mode 100644 index e303af5bae..0000000000 --- a/integration_tests/helpers/constants.js +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2022. The Tari Project -// -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following -// disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the -// following disclaimer in the documentation and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote -// products derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -const AUTOUPDATE_HASHES_TXT_URL = - "https://raw.githubusercontent.com/tari-project/tari/development/meta/hashes.txt"; -const AUTOUPDATE_HASHES_TXT_SIG_URL = - "https://raw.githubusercontent.com/tari-project/tari/development/meta/hashes.txt.sig"; -const AUTOUPDATE_HASHES_TXT_BAD_SIG_URL = - "https://raw.githubusercontent.com/tari-project/tari/development/meta/hashes.txt.bad.sig"; - -const BLOCK_REWARD = 5000; -const CONFIRMATION_PERIOD = 4; - -module.exports = { - AUTOUPDATE_HASHES_TXT_URL, - AUTOUPDATE_HASHES_TXT_BAD_SIG_URL, - AUTOUPDATE_HASHES_TXT_SIG_URL, - BLOCK_REWARD, - CONFIRMATION_PERIOD, -}; diff --git a/integration_tests/helpers/ffi/balance.js b/integration_tests/helpers/ffi/balance.js deleted file mode 100644 index 5891cc409e..0000000000 --- a/integration_tests/helpers/ffi/balance.js +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const InterfaceFFI = require("./ffiInterface"); - -class Balance { - ptr; - - pointerAssign(ptr) { - if (this.ptr) { - this.destroy(); - this.ptr = ptr; - } else { - this.ptr = ptr; - } - } - - getPtr() { - return this.ptr; - } - - getAvailable() { - return InterfaceFFI.balanceGetAvailable(this.ptr); - } - - getTimeLocked() { - return InterfaceFFI.balanceGetTimeLocked(this.ptr); - } - - getPendingIncoming() { - return InterfaceFFI.balanceGetPendingIncoming(this.ptr); - } - - getPendingOutgoing() { - return InterfaceFFI.balanceGetPendingOutgoing(this.ptr); - } - - destroy() { - if (this.ptr) { - InterfaceFFI.balanceDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = Balance; diff --git a/integration_tests/helpers/ffi/byteVector.js b/integration_tests/helpers/ffi/byteVector.js deleted file mode 100644 index f7e87d3e9b..0000000000 --- a/integration_tests/helpers/ffi/byteVector.js +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const InterfaceFFI = require("./ffiInterface"); - -class ByteVector { - ptr; - - pointerAssign(ptr) { - // Prevent pointer from being leaked in case of re-assignment - if (this.ptr) { - this.destroy(); - this.ptr = ptr; - } else { - this.ptr = ptr; - } - } - - fromBytes(input) { - let buf = Buffer.from(input, "utf-8"); // ensure encoding is utf=8, js default is utf-16 - let len = buf.length; // get the length - let result = new ByteVector(); - result.pointerAssign(InterfaceFFI.byteVectorCreate(buf, len)); - return result; - } - - getBytes() { - let result = []; - for (let i = 0; i < this.getLength(); i++) { - result.push(this.getAt(i)); - } - return result; - } - - getLength() { - return InterfaceFFI.byteVectorGetLength(this.ptr); - } - - getAt(position) { - return InterfaceFFI.byteVectorGetAt(this.ptr, position); - } - - getPtr() { - return this.ptr; - } - - destroy() { - if (this.ptr) { - InterfaceFFI.byteVectorDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = ByteVector; diff --git a/integration_tests/helpers/ffi/commsConfig.js b/integration_tests/helpers/ffi/commsConfig.js deleted file mode 100644 index 7524a91520..0000000000 --- a/integration_tests/helpers/ffi/commsConfig.js +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const InterfaceFFI = require("./ffiInterface"); -const utf8 = require("utf8"); - -class CommsConfig { - ptr; - - constructor( - public_address, - transport_ptr, - database_name, - datastore_path, - discovery_timeout_in_secs, - saf_message_duration_in_secs - ) { - let sanitize_address = utf8.encode(public_address); - let sanitize_db_name = utf8.encode(database_name); - let sanitize_db_path = utf8.encode(datastore_path); - this.ptr = InterfaceFFI.commsConfigCreate( - sanitize_address, - transport_ptr, - sanitize_db_name, - sanitize_db_path, - discovery_timeout_in_secs, - saf_message_duration_in_secs - ); - } - - getPtr() { - return this.ptr; - } - - destroy() { - if (this.ptr) { - InterfaceFFI.commsConfigDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = CommsConfig; diff --git a/integration_tests/helpers/ffi/completedTransaction.js b/integration_tests/helpers/ffi/completedTransaction.js deleted file mode 100644 index d2e7901239..0000000000 --- a/integration_tests/helpers/ffi/completedTransaction.js +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const InterfaceFFI = require("./ffiInterface"); -const PublicKey = require("./publicKey"); -const TransactionKernel = require("./transactionKernel"); - -class CompletedTransaction { - ptr; - - pointerAssign(ptr) { - if (this.ptr) { - this.destroy(); - this.ptr = ptr; - } else { - this.ptr = ptr; - } - } - - getPtr() { - return this.ptr; - } - - isOutbound() { - return InterfaceFFI.completedTransactionIsOutbound(this.ptr); - } - - getDestinationPublicKey() { - let result = new PublicKey(); - result.pointerAssign( - InterfaceFFI.completedTransactionGetDestinationPublicKey(this.ptr) - ); - return result; - } - - getSourcePublicKey() { - let result = new PublicKey(); - result.pointerAssign( - InterfaceFFI.completedTransactionGetSourcePublicKey(this.ptr) - ); - return result; - } - - getAmount() { - return InterfaceFFI.completedTransactionGetAmount(this.ptr); - } - - getFee() { - return InterfaceFFI.completedTransactionGetFee(this.ptr); - } - - getMessage() { - return InterfaceFFI.completedTransactionGetMessage(this.ptr); - } - - getStatus() { - return InterfaceFFI.completedTransactionGetStatus(this.ptr); - } - - getTransactionID() { - return InterfaceFFI.completedTransactionGetTransactionId(this.ptr); - } - - getTimestamp() { - return InterfaceFFI.completedTransactionGetTimestamp(this.ptr); - } - - getConfirmations() { - return InterfaceFFI.completedTransactionGetConfirmations(this.ptr); - } - - getKernel() { - let result = new TransactionKernel(); - result.pointerAssign(InterfaceFFI.completedTransactionGetKernel(this.ptr)); - return result; - } - - destroy() { - if (this.ptr) { - InterfaceFFI.completedTransactionDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = CompletedTransaction; diff --git a/integration_tests/helpers/ffi/completedTransactions.js b/integration_tests/helpers/ffi/completedTransactions.js deleted file mode 100644 index b3410a4961..0000000000 --- a/integration_tests/helpers/ffi/completedTransactions.js +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const CompletedTransaction = require("./completedTransaction"); -const InterfaceFFI = require("./ffiInterface"); - -class CompletedTransactions { - ptr; - - constructor(ptr) { - this.ptr = ptr; - } - - getLength() { - return InterfaceFFI.completedTransactionsGetLength(this.ptr); - } - - getAt(position) { - let result = new CompletedTransaction(); - result.pointerAssign( - InterfaceFFI.completedTransactionsGetAt(this.ptr, position) - ); - return result; - } - - destroy() { - if (this.ptr) { - InterfaceFFI.completedTransactionsDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = CompletedTransactions; diff --git a/integration_tests/helpers/ffi/contact.js b/integration_tests/helpers/ffi/contact.js deleted file mode 100644 index 83e13b8942..0000000000 --- a/integration_tests/helpers/ffi/contact.js +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const PublicKey = require("./publicKey"); -const InterfaceFFI = require("./ffiInterface"); - -class Contact { - ptr; - - pointerAssign(ptr) { - // Prevent pointer from being leaked in case of re-assignment - if (this.ptr) { - this.destroy(); - this.ptr = ptr; - } else { - this.ptr = ptr; - } - } - - getPtr() { - return this.ptr; - } - - getAlias() { - let alias = InterfaceFFI.contactGetAlias(this.ptr); - let result = alias.readCString(); - InterfaceFFI.stringDestroy(alias); - return result; - } - - getPubkey() { - let result = new PublicKey(); - result.pointerAssign(InterfaceFFI.contactGetPublicKey(this.ptr)); - return result; - } - - getPubkeyHex() { - let result = ""; - let pk = new PublicKey(); - pk.pointerAssign(InterfaceFFI.contactGetPublicKey(this.ptr)); - result = pk.getHex(); - pk.destroy(); - return result; - } - - destroy() { - if (this.ptr) { - InterfaceFFI.contactDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = Contact; diff --git a/integration_tests/helpers/ffi/contacts.js b/integration_tests/helpers/ffi/contacts.js deleted file mode 100644 index daa3e81ede..0000000000 --- a/integration_tests/helpers/ffi/contacts.js +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const Contact = require("./contact"); -const InterfaceFFI = require("./ffiInterface"); - -class Contacts { - ptr; - - constructor(ptr) { - this.ptr = ptr; - } - - getLength() { - return InterfaceFFI.contactsGetLength(this.ptr); - } - - getAt(position) { - let result = new Contact(); - result.pointerAssign(InterfaceFFI.contactsGetAt(this.ptr, position)); - return result; - } - - destroy() { - if (this.ptr) { - InterfaceFFI.contactsDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = Contacts; diff --git a/integration_tests/helpers/ffi/emojiSet.js b/integration_tests/helpers/ffi/emojiSet.js deleted file mode 100644 index 8b2866460f..0000000000 --- a/integration_tests/helpers/ffi/emojiSet.js +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const InterfaceFFI = require("./ffiInterface"); - -class EmojiSet { - ptr; - - constructor() { - this.ptr = InterfaceFFI.getEmojiSet(); - } - - getLength() { - return InterfaceFFI.emojiSetGetLength(this.ptr); - } - - getAt(position) { - return InterfaceFFI.emojiSetGetAt(this.ptr, position); - } - - list() { - let set = []; - for (let i = 0; i < this.getLength(); i++) { - let item = this.getAt(i); - set.push(Buffer.from(item.getBytes(), "utf-8").toString()); - item.destroy(); - } - return set; - } - - destroy() { - if (this.ptr) { - InterfaceFFI.byteVectorDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = EmojiSet; diff --git a/integration_tests/helpers/ffi/feePerGramStat.js b/integration_tests/helpers/ffi/feePerGramStat.js deleted file mode 100644 index fe849097f5..0000000000 --- a/integration_tests/helpers/ffi/feePerGramStat.js +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const InterfaceFFI = require("./ffiInterface"); - -class FeePerGramStat { - ptr; - - pointerAssign(ptr) { - if (this.ptr) { - this.destroy(); - this.ptr = ptr; - } else { - this.ptr = ptr; - } - } - - getPtr() { - return this.ptr; - } - - getOrder() { - return InterfaceFFI.feePerGramStatGetOrder(this.ptr); - } - - getMinFeePerGram() { - return InterfaceFFI.feePerGramStatGetMinFeePerGram(this.ptr); - } - - getAvgFeePerGram() { - return InterfaceFFI.feePerGramStatGetAvgFeePerGram(this.ptr); - } - - getMaxFeePerGram() { - return InterfaceFFI.feePerGramStatGetMaxFeePerGram(this.ptr); - } - - destroy() { - if (this.ptr) { - InterfaceFFI.feePerGramStatDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = FeePerGramStat; diff --git a/integration_tests/helpers/ffi/feePerGramStats.js b/integration_tests/helpers/ffi/feePerGramStats.js deleted file mode 100644 index f292ac2678..0000000000 --- a/integration_tests/helpers/ffi/feePerGramStats.js +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const FeePerGramStat = require("./feePerGramStat"); -const InterfaceFFI = require("./ffiInterface"); - -class FeePerGramStats { - ptr; - - constructor(ptr) { - this.ptr = ptr; - } - - getLength() { - return InterfaceFFI.feePerGramStatsGetLength(this.ptr); - } - - getAt(position) { - let result = new FeePerGramStat(); - result.pointerAssign(InterfaceFFI.feePerGramStatsGetAt(this.ptr, position)); - return result; - } - - destroy() { - if (this.ptr) { - InterfaceFFI.feePerGramStatsDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = FeePerGramStats; diff --git a/integration_tests/helpers/ffi/ffiInterface.js b/integration_tests/helpers/ffi/ffiInterface.js deleted file mode 100644 index f4acec215b..0000000000 --- a/integration_tests/helpers/ffi/ffiInterface.js +++ /dev/null @@ -1,1774 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -/** - * NB!: Modify with caution. - **/ - -const { expect } = require("chai"); -const ffi = require("ffi-napi"); -const ref = require("ref-napi"); -const dateFormat = require("dateformat"); -const { spawn } = require("child_process"); -const fs = require("fs"); - -class InterfaceFFI { - static void = ref.types.void; - static bool = ref.types.bool; - static int = ref.types.int; - static ulonglong = ref.types.uint64; // Note: 'ref.types.ulonglong' has a memory alignment problem - static uchar = ref.types.uchar; - static uint = ref.types.uint; - static string = ref.types.CString; - static ucharPtr = ref.refType(this.uchar); // uchar* - static ptr = ref.refType(this.void); //pointer is opaque - static stringPtr = ref.refType(this.string); - static intPtr = ref.refType(this.int); // int* - static boolPtr = ref.refType(this.bool); // bool* - static ushort = ref.types.ushort; - - //region Compile - static compile() { - return new Promise((resolve, _reject) => { - const cmd = "cargo"; - const args = [ - "build", - "--release", - "--locked", - "--package", - "tari_wallet_ffi", - "-Z", - "unstable-options", - "--out-dir", - process.cwd() + "/temp/out/ffi", - ]; - const baseDir = `./temp/base_nodes/${dateFormat( - new Date(), - "yyyymmddHHMM" - )}/WalletFFI-compile`; - if (!fs.existsSync(baseDir)) { - fs.mkdirSync(baseDir, { recursive: true }); - fs.mkdirSync(baseDir + "/log", { recursive: true }); - } - const ps = spawn(cmd, args); - ps.on("close", (_code) => { - resolve(ps); - }); - ps.stderr.on("data", (data) => { - console.log("stderr : ", data.toString()); - }); - ps.on("error", (error) => { - console.log("error : ", error.toString()); - }); - expect(ps.error).to.be.an("undefined"); - this.ps = ps; - }); - } - //endregion - - //region Interface - static fn; - - static loaded = false; - static ps = null; - static library = null; - - static async init() { - let platform = process.platform === "win32" ? "" : "lib"; - this.library = `${process.cwd()}/temp/out/ffi/${platform}tari_wallet_ffi`; - // Load the library - this.fn = ffi.Library(this.loaded ? null : this.library, { - transport_memory_create: [this.ptr, []], - transport_tcp_create: [this.ptr, [this.string, this.intPtr]], - transport_tor_create: [ - this.ptr, - [ - this.string, - this.ptr, - this.ushort, - this.bool, - this.string, - this.string, - this.intPtr, - ], - ], - transport_memory_get_address: [this.stringPtr, [this.ptr, this.intPtr]], - transport_config_destroy: [this.void, [this.ptr]], - string_destroy: [this.void, [this.string]], - byte_vector_create: [this.ptr, [this.ucharPtr, this.uint, this.intPtr]], - byte_vector_get_at: [this.uchar, [this.ptr, this.uint, this.intPtr]], - byte_vector_get_length: [this.uint, [this.ptr, this.intPtr]], - byte_vector_destroy: [this.void, [this.ptr]], - public_key_create: [this.ptr, [this.ptr, this.intPtr]], - public_key_get_bytes: [this.ptr, [this.ptr, this.intPtr]], - public_key_from_private_key: [this.ptr, [this.ptr, this.intPtr]], - public_key_from_hex: [this.ptr, [this.string, this.intPtr]], - public_key_destroy: [this.void, [this.ptr]], - public_key_to_emoji_id: [this.stringPtr, [this.ptr, this.intPtr]], - emoji_id_to_public_key: [this.ptr, [this.string, this.intPtr]], - private_key_create: [this.ptr, [this.ptr, this.intPtr]], - private_key_generate: [this.ptr, []], - private_key_get_bytes: [this.ptr, [this.ptr, this.intPtr]], - private_key_from_hex: [this.ptr, [this.string, this.intPtr]], - private_key_destroy: [this.void, [this.ptr]], - seed_words_create: [this.ptr, []], - seed_words_get_mnemonic_word_list_for_language: [ - this.ptr, - [this.string, this.intPtr], - ], - seed_words_get_length: [this.uint, [this.ptr, this.intPtr]], - seed_words_get_at: [this.stringPtr, [this.ptr, this.uint, this.intPtr]], - seed_words_push_word: [this.uchar, [this.ptr, this.string, this.intPtr]], - seed_words_destroy: [this.void, [this.ptr]], - contact_create: [this.ptr, [this.string, this.ptr, this.intPtr]], - contact_get_alias: [this.stringPtr, [this.ptr, this.intPtr]], - contact_get_public_key: [this.ptr, [this.ptr, this.intPtr]], - contact_destroy: [this.void, [this.ptr]], - contacts_get_length: [this.uint, [this.ptr, this.intPtr]], - contacts_get_at: [this.ptr, [this.ptr, this.uint, this.intPtr]], - contacts_destroy: [this.void, [this.ptr]], - completed_transaction_get_destination_public_key: [ - this.ptr, - [this.ptr, this.intPtr], - ], - completed_transaction_get_source_public_key: [ - this.ptr, - [this.ptr, this.intPtr], - ], - completed_transaction_get_amount: [ - this.ulonglong, - [this.ptr, this.intPtr], - ], - completed_transaction_get_fee: [this.ulonglong, [this.ptr, this.intPtr]], - completed_transaction_get_message: [ - this.stringPtr, - [this.ptr, this.intPtr], - ], - completed_transaction_get_status: [this.int, [this.ptr, this.intPtr]], - completed_transaction_get_transaction_id: [ - this.ulonglong, - [this.ptr, this.intPtr], - ], - completed_transaction_get_timestamp: [ - this.ulonglong, - [this.ptr, this.intPtr], - ], - completed_transaction_is_outbound: [this.bool, [this.ptr, this.intPtr]], - completed_transaction_get_confirmations: [ - this.ulonglong, - [this.ptr, this.intPtr], - ], - completed_transaction_destroy: [this.void, [this.ptr]], - completed_transaction_get_transaction_kernel: [ - this.ptr, - [this.ptr, this.intPtr], - ], - completed_transaction_get_cancellation_reason: [ - this.ptr, - [this.ptr, this.intPtr], - ], - completed_transactions_get_length: [this.uint, [this.ptr, this.intPtr]], - completed_transactions_get_at: [ - this.ptr, - [this.ptr, this.uint, this.intPtr], - ], - completed_transactions_destroy: [this.void, [this.ptr]], - transaction_kernel_get_excess_hex: [ - this.stringPtr, - [this.ptr, this.intPtr], - ], - transaction_kernel_get_excess_public_nonce_hex: [ - this.stringPtr, - [this.ptr, this.intPtr], - ], - transaction_kernel_get_excess_signature_hex: [ - this.stringPtr, - [this.ptr, this.intPtr], - ], - transaction_kernel_destroy: [this.void, [this.ptr]], - pending_outbound_transaction_get_transaction_id: [ - this.ulonglong, - [this.ptr, this.intPtr], - ], - pending_outbound_transaction_get_destination_public_key: [ - this.ptr, - [this.ptr, this.intPtr], - ], - pending_outbound_transaction_get_amount: [ - this.ulonglong, - [this.ptr, this.intPtr], - ], - pending_outbound_transaction_get_fee: [ - this.ulonglong, - [this.ptr, this.intPtr], - ], - pending_outbound_transaction_get_message: [ - this.stringPtr, - [this.ptr, this.intPtr], - ], - pending_outbound_transaction_get_timestamp: [ - this.ulonglong, - [this.ptr, this.intPtr], - ], - pending_outbound_transaction_get_status: [ - this.int, - [this.ptr, this.intPtr], - ], - pending_outbound_transaction_destroy: [this.void, [this.ptr]], - pending_outbound_transactions_get_length: [ - this.uint, - [this.ptr, this.intPtr], - ], - pending_outbound_transactions_get_at: [ - this.ptr, - [this.ptr, this.uint, this.intPtr], - ], - pending_outbound_transactions_destroy: [this.void, [this.ptr]], - pending_inbound_transaction_get_transaction_id: [ - this.ulonglong, - [this.ptr, this.intPtr], - ], - pending_inbound_transaction_get_source_public_key: [ - this.ptr, - [this.ptr, this.intPtr], - ], - pending_inbound_transaction_get_message: [ - this.stringPtr, - [this.ptr, this.intPtr], - ], - pending_inbound_transaction_get_amount: [ - this.ulonglong, - [this.ptr, this.intPtr], - ], - pending_inbound_transaction_get_timestamp: [ - this.ulonglong, - [this.ptr, this.intPtr], - ], - pending_inbound_transaction_get_status: [ - this.int, - [this.ptr, this.intPtr], - ], - pending_inbound_transaction_destroy: [this.void, [this.ptr]], - pending_inbound_transactions_get_length: [ - this.uint, - [this.ptr, this.intPtr], - ], - pending_inbound_transactions_get_at: [ - this.ptr, - [this.ptr, this.uint, this.intPtr], - ], - pending_inbound_transactions_destroy: [this.void, [this.ptr]], - transaction_send_status_decode: [this.uint, [this.ptr, this.intPtr]], - transaction_send_status_destroy: [this.void, [this.ptr]], - comms_config_create: [ - this.ptr, - [ - this.string, - this.ptr, - this.string, - this.string, - this.ulonglong, - this.ulonglong, - this.intPtr, - ], - ], - comms_config_destroy: [this.void, [this.ptr]], - comms_list_connected_public_keys: [this.ptr, [this.ptr, this.intPtr]], - wallet_create: [ - this.ptr, - [ - this.ptr, - this.string, - this.uint, - this.uint, - this.string, - this.ptr, - this.string, - this.ptr, - this.ptr, - this.ptr, - this.ptr, - this.ptr, - this.ptr, - this.ptr, - this.ptr, - this.ptr, - this.ptr, - this.ptr, - this.ptr, - this.ptr, - this.ptr, - this.ptr, - this.ptr, - this.boolPtr, - this.intPtr, - ], - ], - wallet_get_balance: [this.ptr, [this.ptr, this.intPtr]], - wallet_sign_message: [ - this.stringPtr, - [this.ptr, this.string, this.intPtr], - ], - wallet_verify_message_signature: [ - this.bool, - [this.ptr, this.ptr, this.string, this.string, this.intPtr], - ], - wallet_add_base_node_peer: [ - this.bool, - [this.ptr, this.ptr, this.string, this.intPtr], - ], - wallet_upsert_contact: [this.bool, [this.ptr, this.ptr, this.intPtr]], - wallet_remove_contact: [this.bool, [this.ptr, this.ptr, this.intPtr]], - balance_get_available: [this.ulonglong, [this.ptr, this.intPtr]], - balance_get_time_locked: [this.ulonglong, [this.ptr, this.intPtr]], - balance_get_pending_incoming: [this.ulonglong, [this.ptr, this.intPtr]], - balance_get_pending_outgoing: [this.ulonglong, [this.ptr, this.intPtr]], - liveness_data_get_public_key: [this.ptr, [this.ptr, this.intPtr]], - liveness_data_get_latency: [this.int, [this.ptr, this.intPtr]], - liveness_data_get_last_seen: [this.stringPtr, [this.ptr, this.intPtr]], - liveness_data_get_message_type: [this.int, [this.ptr, this.intPtr]], - liveness_data_get_online_status: [ - this.stringPtr, - [this.ptr, this.intPtr], - ], - wallet_get_fee_estimate: [ - this.ulonglong, - [ - this.ptr, - this.ulonglong, - this.ulonglong, - this.ulonglong, - this.ulonglong, - this.intPtr, - ], - ], - wallet_get_num_confirmations_required: [ - this.ulonglong, - [this.ptr, this.intPtr], - ], - wallet_set_num_confirmations_required: [ - this.void, - [this.ptr, this.ulonglong, this.intPtr], - ], - wallet_send_transaction: [ - this.ulonglong, - [ - this.ptr, - this.ptr, - this.ulonglong, - this.ptr, - this.ulonglong, - this.string, - this.bool, - this.intPtr, - ], - ], - wallet_get_contacts: [this.ptr, [this.ptr, this.intPtr]], - wallet_get_completed_transactions: [this.ptr, [this.ptr, this.intPtr]], - wallet_get_pending_outbound_transactions: [ - this.ptr, - [this.ptr, this.intPtr], - ], - wallet_get_public_key: [this.ptr, [this.ptr, this.intPtr]], - wallet_get_pending_inbound_transactions: [ - this.ptr, - [this.ptr, this.intPtr], - ], - wallet_get_cancelled_transactions: [this.ptr, [this.ptr, this.intPtr]], - wallet_get_completed_transaction_by_id: [ - this.ptr, - [this.ptr, this.ulonglong, this.intPtr], - ], - wallet_get_pending_outbound_transaction_by_id: [ - this.ptr, - [this.ptr, this.ulonglong, this.intPtr], - ], - wallet_get_pending_inbound_transaction_by_id: [ - this.ptr, - [this.ptr, this.ulonglong, this.intPtr], - ], - wallet_get_cancelled_transaction_by_id: [ - this.ptr, - [this.ptr, this.ulonglong, this.intPtr], - ], - wallet_import_external_utxo_as_non_rewindable: [ - this.ulonglong, - [ - this.ptr, - this.ulonglong, - this.ptr, - this.ptr, - this.ptr, - this.ptr, - this.ptr, - this.ptr, - this.ptr, - this.ptr, - this.ulonglong, - this.string, - this.intPtr, - ], - ], - wallet_start_txo_validation: [this.ulonglong, [this.ptr, this.intPtr]], - wallet_start_transaction_validation: [ - this.ulonglong, - [this.ptr, this.intPtr], - ], - wallet_restart_transaction_broadcast: [ - this.bool, - [this.ptr, this.intPtr], - ], - wallet_set_low_power_mode: [this.void, [this.ptr, this.intPtr]], - wallet_set_normal_power_mode: [this.void, [this.ptr, this.intPtr]], - wallet_cancel_pending_transaction: [ - this.bool, - [this.ptr, this.ulonglong, this.intPtr], - ], - wallet_coin_split: [ - this.ulonglong, - [ - this.ptr, - this.ulonglong, - this.ulonglong, - this.ulonglong, - this.string, - this.ulonglong, - this.intPtr, - ], - ], - wallet_get_seed_words: [this.ptr, [this.ptr, this.intPtr]], - wallet_apply_encryption: [ - this.void, - [this.ptr, this.string, this.intPtr], - ], - wallet_remove_encryption: [this.void, [this.ptr, this.intPtr]], - wallet_set_key_value: [ - this.bool, - [this.ptr, this.string, this.string, this.intPtr], - ], - wallet_get_value: [this.stringPtr, [this.ptr, this.string, this.intPtr]], - wallet_clear_value: [this.bool, [this.ptr, this.string, this.intPtr]], - wallet_is_recovery_in_progress: [this.bool, [this.ptr, this.intPtr]], - wallet_start_recovery: [ - this.bool, - [this.ptr, this.ptr, this.ptr, this.intPtr], - ], - wallet_destroy: [this.void, [this.ptr]], - balance_destroy: [this.void, [this.ptr]], - liveness_data_destroy: [this.void, [this.ptr]], - file_partial_backup: [this.void, [this.string, this.string, this.intPtr]], - log_debug_message: [this.void, [this.string]], - get_emoji_set: [this.ptr, []], - emoji_set_destroy: [this.void, [this.ptr]], - emoji_set_get_at: [this.ptr, [this.ptr, this.uint, this.intPtr]], - emoji_set_get_length: [this.uint, [this.ptr, this.intPtr]], - wallet_get_fee_per_gram_stats: [ - this.ptr, - [this.ptr, this.uint, this.intPtr], - ], - fee_per_gram_stats_get_length: [this.uint, [this.ptr, this.intPtr]], - fee_per_gram_stats_get_at: [this.ptr, [this.ptr, this.uint, this.intPtr]], - fee_per_gram_stats_destroy: [this.void, [this.ptr]], - fee_per_gram_stat_get_order: [this.ulonglong, [this.ptr, this.intPtr]], - fee_per_gram_stat_get_min_fee_per_gram: [ - this.ulonglong, - [this.ptr, this.intPtr], - ], - fee_per_gram_stat_get_avg_fee_per_gram: [ - this.ulonglong, - [this.ptr, this.intPtr], - ], - fee_per_gram_stat_get_max_fee_per_gram: [ - this.ulonglong, - [this.ptr, this.intPtr], - ], - fee_per_gram_stat_destroy: [this.void, [this.ptr]], - }); - - this.loaded = true; - } - //endregion - - static checkErrorResult(error, error_name) { - expect(error.deref()).to.equal(0, `Error in ${error_name}`); - } - - //region Helpers - static initError() { - let error = ref.alloc(ref.types.int); - return error; - } - - static initBool() { - let boolean = ref.alloc(ref.types.bool); - return boolean; - } - - static filePartialBackup(original_file_path, backup_file_path) { - let error = this.initError(); - let result = this.fn.file_partial_backup( - original_file_path, - backup_file_path, - error - ); - this.checkErrorResult(error, `filePartialBackup`); - return result; - } - - static logDebugMessage(msg) { - this.fn.log_debug_message(msg); - } - //endregion - - //region String - static stringDestroy(s) { - this.fn.string_destroy(s); - } - //endregion - - // region ByteVector - static byteVectorCreate(byte_array, element_count) { - let error = this.initError(); - let result = this.fn.byte_vector_create(byte_array, element_count, error); - this.checkErrorResult(error, `byteVectorCreate`); - return result; - } - - static byteVectorGetAt(ptr, i) { - let error = this.initError(); - let result = this.fn.byte_vector_get_at(ptr, i, error); - this.checkErrorResult(error, `byteVectorGetAt`); - return result; - } - - static byteVectorGetLength(ptr) { - let error = this.initError(); - let result = this.fn.byte_vector_get_length(ptr, error); - this.checkErrorResult(error, `byteVectorGetLength`); - return result; - } - - static byteVectorDestroy(ptr) { - this.fn.byte_vector_destroy(ptr); - } - //endregion - - //region PrivateKey - static privateKeyCreate(ptr) { - let error = this.initError(); - let result = this.fn.private_key_create(ptr, error); - this.checkErrorResult(error, `privateKeyCreate`); - return result; - } - - static privateKeyGenerate() { - return this.fn.private_key_generate(); - } - - static privateKeyGetBytes(ptr) { - let error = this.initError(); - let result = this.fn.private_key_get_bytes(ptr, error); - this.checkErrorResult(error, "privateKeyGetBytes"); - return result; - } - - static privateKeyFromHex(hex) { - let error = this.initError(); - let result = this.fn.private_key_from_hex(hex, error); - this.checkErrorResult(error, "privateKeyFromHex"); - return result; - } - - static privateKeyDestroy(ptr) { - this.fn.private_key_destroy(ptr); - } - - //endregion - - //region PublicKey - static publicKeyCreate(ptr) { - let error = this.initError(); - let result = this.fn.public_key_create(ptr, error); - this.checkErrorResult(error, `publicKeyCreate`); - return result; - } - - static publicKeyGetBytes(ptr) { - let error = this.initError(); - let result = this.fn.public_key_get_bytes(ptr, error); - this.checkErrorResult(error, `publicKeyGetBytes`); - return result; - } - - static publicKeyFromPrivateKey(ptr) { - let error = this.initError(); - let result = this.fn.public_key_from_private_key(ptr, error); - this.checkErrorResult(error, `publicKeyFromPrivateKey`); - return result; - } - - static publicKeyFromHex(hex) { - let error = this.initError(); - let result = this.fn.public_key_from_hex(hex, error); - this.checkErrorResult(error, `publicKeyFromHex`); - return result; - } - - static emojiIdToPublicKey(emoji) { - let error = this.initError(); - let result = this.fn.emoji_id_to_public_key(emoji, error); - this.checkErrorResult(error, `emojiIdToPublicKey`); - return result; - } - - static publicKeyToEmojiId(ptr) { - let error = this.initError(); - let result = this.fn.public_key_to_emoji_id(ptr, error); - this.checkErrorResult(error, `publicKeyToEmojiId`); - return result; - } - - static publicKeyDestroy(ptr) { - this.fn.public_key_destroy(ptr); - } - //endregion - - //region TransportType - static transportMemoryCreate() { - return this.fn.transport_memory_create(); - } - - static transportTcpCreate(listener_address) { - let error = this.initError(); - let result = this.fn.transport_tcp_create(listener_address, error); - this.checkErrorResult(error, `transportTcpCreate`); - return result; - } - - static transportTorCreate( - control_server_address, - tor_cookie, - tor_port, - socks_username, - socks_password - ) { - let error = this.initError(); - let result = this.fn.transport_tor_create( - control_server_address, - tor_cookie, - tor_port, - true, - socks_username, - socks_password, - error - ); - this.checkErrorResult(error, `transportTorCreate`); - return result; - } - - static transportMemoryGetAddress(transport) { - let error = this.initError(); - let result = this.fn.transport_memory_get_address(transport, error); - this.checkErrorResult(error, `transportMemoryGetAddress`); - return result; - } - - static transportConfigDestroy(transport) { - this.fn.transport_config_destroy(transport); - } - //endregion - - //region EmojiSet - static getEmojiSet() { - return this.fn.this.fn.get_emoji_set(); - } - - static emojiSetDestroy(ptr) { - this.fn.emoji_set_destroy(ptr); - } - - static emojiSetGetAt(ptr, position) { - let error = this.initError(); - let result = this.fn.emoji_set_get_at(ptr, position, error); - this.checkErrorResult(error, `emojiSetGetAt`); - return result; - } - - static emojiSetGetLength(ptr) { - let error = this.initError(); - let result = this.fn.emoji_set_get_length(ptr, error); - this.checkErrorResult(error, `emojiSetGetLength`); - return result; - } - //endregion - - //region SeedWords - static seedWordsCreate() { - return this.fn.seed_words_create(); - } - - static seedWordsGetMnemonicWordListForLanguage(language) { - let error = this.initError(); - let result = this.fn.seed_words_get_mnemonic_word_list_for_language( - language, - error - ); - this.checkErrorResult(error, `seedWordsGetMnemonicWordListForLanguage`); - return result; - } - - static seedWordsGetLength(ptr) { - let error = this.initError(); - let result = this.fn.seed_words_get_length(ptr, error); - this.checkErrorResult(error, `emojiSetGetLength`); - return result; - } - - static seedWordsGetAt(ptr, position) { - let error = this.initError(); - let result = this.fn.seed_words_get_at(ptr, position, error); - this.checkErrorResult(error, `seedWordsGetAt`); - return result; - } - - static seedWordsPushWord(ptr, word) { - let error = this.initError(); - let result = this.fn.seed_words_push_word(ptr, word, error); - this.checkErrorResult(error, `seedWordsPushWord`); - return result; - } - - static seedWordsDestroy(ptr) { - this.fn.seed_words_destroy(ptr); - } - //endregion - - //region CommsConfig - static commsConfigCreate( - public_address, - transport, - database_name, - datastore_path, - discovery_timeout_in_secs, - saf_message_duration_in_secs - ) { - let error = this.initError(); - let result = this.fn.comms_config_create( - public_address, - transport, - database_name, - datastore_path, - discovery_timeout_in_secs, - saf_message_duration_in_secs, - error - ); - this.checkErrorResult(error, `commsConfigCreate`); - return result; - } - - static commsConfigDestroy(ptr) { - this.fn.comms_config_destroy(ptr); - } - - static commsListConnectedPublicKeys(walletPtr) { - let error = this.initError(); - let result = this.fn.comms_list_connected_public_keys(walletPtr, error); - this.checkErrorResult(error, `commsListConnectedPublicKeys`); - return result; - } - //endregion - - //region Contact - static contactCreate(alias, public_key) { - let error = this.initError(); - let result = this.fn.contact_create(alias, public_key, error); - this.checkErrorResult(error, `contactCreate`); - return result; - } - - static contactGetAlias(ptr) { - let error = this.initError(); - let result = this.fn.contact_get_alias(ptr, error); - this.checkErrorResult(error, `contactGetAlias`); - return result; - } - - static contactGetPublicKey(ptr) { - let error = this.initError(); - let result = this.fn.contact_get_public_key(ptr, error); - this.checkErrorResult(error, `contactGetPublicKey`); - return result; - } - - static contactDestroy(ptr) { - this.fn.contact_destroy(ptr); - } - //endregion - - //region Contacts (List) - static contactsGetLength(ptr) { - let error = this.initError(); - let result = this.fn.contacts_get_length(ptr, error); - this.checkErrorResult(error, `contactsGetLength`); - return result; - } - - static contactsGetAt(ptr, position) { - let error = this.initError(); - let result = this.fn.contacts_get_at(ptr, position, error); - this.checkErrorResult(error, `contactsGetAt`); - return result; - } - - static contactsDestroy(ptr) { - this.fn.contacts_destroy(ptr); - } - //endregion - - //region CompletedTransaction - static completedTransactionGetDestinationPublicKey(ptr) { - let error = this.initError(); - let result = this.fn.completed_transaction_get_destination_public_key( - ptr, - error - ); - this.checkErrorResult(error, `completedTransactionGetDestinationPublicKey`); - return result; - } - - static completedTransactionGetSourcePublicKey(ptr) { - let error = this.initError(); - let result = this.fn.completed_transaction_get_source_public_key( - ptr, - error - ); - this.checkErrorResult(error, `completedTransactionGetSourcePublicKey`); - return result; - } - - static completedTransactionGetAmount(ptr) { - let error = this.initError(); - let result = this.fn.completed_transaction_get_amount(ptr, error); - this.checkErrorResult(error, `completedTransactionGetAmount`); - return result; - } - - static completedTransactionGetFee(ptr) { - let error = this.initError(); - let result = this.fn.completed_transaction_get_fee(ptr, error); - this.checkErrorResult(error, `completedTransactionGetFee`); - return result; - } - - static completedTransactionGetMessage(ptr) { - let error = this.initError(); - let result = this.fn.completed_transaction_get_message(ptr, error); - this.checkErrorResult(error, `completedTransactionGetMessage`); - return result; - } - - static completedTransactionGetStatus(ptr) { - let error = this.initError(); - let result = this.fn.completed_transaction_get_status(ptr, error); - this.checkErrorResult(error, `completedTransactionGetStatus`); - return result; - } - - static completedTransactionGetTransactionId(ptr) { - let error = this.initError(); - let result = this.fn.completed_transaction_get_transaction_id(ptr, error); - this.checkErrorResult(error, `completedTransactionGetTransactionId`); - return result; - } - - static completedTransactionGetTimestamp(ptr) { - let error = this.initError(); - let result = this.fn.completed_transaction_get_timestamp(ptr, error); - this.checkErrorResult(error, `completedTransactionGetTimestamp`); - return result; - } - - static completedTransactionIsOutbound(ptr) { - let error = this.initError(); - let result = this.fn.completed_transaction_is_outbound(ptr, error); - this.checkErrorResult(error, `completedTransactionGetIsOutbound`); - return result; - } - - static completedTransactionGetConfirmations(ptr) { - let error = this.initError(); - let result = this.fn.completed_transaction_get_confirmations(ptr, error); - this.checkErrorResult(error, `completedTransactionGetConfirmations`); - return result; - } - - static completedTransactionGetKernel(ptr) { - let error = this.initError(); - let result = this.fn.completed_transaction_get_transaction_kernel( - ptr, - error - ); - this.checkErrorResult(error, `completedTransactionGetKernel`); - return result; - } - - static completedTransactionGetCancellationReason(ptr) { - let error = this.initError(); - let result = this.fn.completed_transaction_get_cancellation_reason( - ptr, - error - ); - this.checkErrorResult(error, `completedTransactionGetCancellationReason`); - return result; - } - - static completedTransactionDestroy(ptr) { - this.fn.completed_transaction_destroy(ptr); - } - - //endregion - - //region TransactionKernel - static transactionKernelGetExcess(ptr) { - let error = this.initError(); - let result = this.fn.transaction_kernel_get_excess_hex(ptr, error); - this.checkErrorResult(error, `transactionKernelGetExcess`); - return result; - } - - static transactionKernelGetExcessPublicNonce(ptr) { - let error = this.initError(); - let result = this.fn.transaction_kernel_get_excess_public_nonce_hex( - ptr, - error - ); - this.checkErrorResult(error, `transactionKernelGetExcessPublicNonce`); - return result; - } - - static transactionKernelGetExcessSigntature(ptr) { - let error = this.initError(); - let result = this.fn.transaction_kernel_get_excess_signature_hex( - ptr, - error - ); - this.checkErrorResult(error, `transactionKernelGetExcessSigntature`); - return result; - } - - static transactionKernelDestroy(ptr) { - this.fn.transaction_kernel_destroy(ptr); - } - //endRegion - - //region CompletedTransactions (List) - static completedTransactionsGetLength(ptr) { - let error = this.initError(); - let result = this.fn.completed_transactions_get_length(ptr, error); - this.checkErrorResult(error, `completedTransactionsGetLength`); - return result; - } - - static completedTransactionsGetAt(ptr, position) { - let error = this.initError(); - let result = this.fn.completed_transactions_get_at(ptr, position, error); - this.checkErrorResult(error, `completedTransactionsGetAt`); - return result; - } - - static completedTransactionsDestroy(transactions) { - this.fn.completed_transactions_destroy(transactions); - } - //endregion - - //region PendingOutboundTransaction - static pendingOutboundTransactionGetTransactionId(ptr) { - let error = this.initError(); - let result = this.fn.pending_outbound_transaction_get_transaction_id( - ptr, - error - ); - this.checkErrorResult(error, `pendingOutboundTransactionGetTransactionId`); - return result; - } - - static pendingOutboundTransactionGetDestinationPublicKey(ptr) { - let error = this.initError(); - let result = - this.fn.pending_outbound_transaction_get_destination_public_key( - ptr, - error - ); - this.checkErrorResult( - error, - `pendingOutboundTransactionGetDestinationPublicKey` - ); - return result; - } - - static pendingOutboundTransactionGetAmount(ptr) { - let error = this.initError(); - let result = this.fn.pending_outbound_transaction_get_amount(ptr, error); - this.checkErrorResult(error, `pendingOutboundTransactionGetAmount`); - return result; - } - - static pendingOutboundTransactionGetFee(ptr) { - let error = this.initError(); - let result = this.fn.pending_outbound_transaction_get_fee(ptr, error); - this.checkErrorResult(error, `pendingOutboundTransactionGetFee`); - return result; - } - - static pendingOutboundTransactionGetMessage(ptr) { - let error = this.initError(); - let result = this.fn.pending_outbound_transaction_get_message(ptr, error); - this.checkErrorResult(error, `pendingOutboundTransactionGetMessage`); - return result; - } - - static pendingOutboundTransactionGetTimestamp(ptr) { - let error = this.initError(); - let result = this.fn.pending_outbound_transaction_get_timestamp(ptr, error); - this.checkErrorResult(error, `pendingOutboundTransactionGetTimestamp`); - return result; - } - - static pendingOutboundTransactionGetStatus(ptr) { - let error = this.initError(); - let result = this.fn.pending_outbound_transaction_get_status(ptr, error); - this.checkErrorResult(error, `pendingOutboundTransactionGetStatus`); - return result; - } - - static pendingOutboundTransactionDestroy(ptr) { - this.fn.pending_outbound_transaction_destroy(ptr); - } - //endregion - - //region PendingOutboundTransactions (List) - static pendingOutboundTransactionsGetLength(ptr) { - let error = this.initError(); - let result = this.fn.pending_outbound_transactions_get_length(ptr, error); - this.checkErrorResult(error, `pendingOutboundTransactionsGetLength`); - return result; - } - - static pendingOutboundTransactionsGetAt(ptr, position) { - let error = this.initError(); - let result = this.fn.pending_outbound_transactions_get_at( - ptr, - position, - error - ); - this.checkErrorResult(error, `pendingOutboundTransactionsGetAt`); - return result; - } - - static pendingOutboundTransactionsDestroy(ptr) { - this.fn.pending_outbound_transactions_destroy(ptr); - } - //endregion - - //region PendingInboundTransaction - static pendingInboundTransactionGetTransactionId(ptr) { - let error = this.initError(); - let result = this.fn.pending_inbound_transaction_get_transaction_id( - ptr, - error - ); - this.checkErrorResult(error, `pendingInboundTransactionGetTransactionId`); - return result; - } - - static pendingInboundTransactionGetSourcePublicKey(ptr) { - let error = this.initError(); - let result = this.fn.pending_inbound_transaction_get_source_public_key( - ptr, - error - ); - this.checkErrorResult(error, `pendingInboundTransactionGetSourcePublicKey`); - return result; - } - - static pendingInboundTransactionGetMessage(ptr) { - let error = this.initError(); - let result = this.fn.pending_inbound_transaction_get_message(ptr, error); - this.checkErrorResult(error, `pendingInboundTransactionGetMessage`); - return result; - } - - static pendingInboundTransactionGetAmount(ptr) { - let error = this.initError(); - let result = this.fn.pending_inbound_transaction_get_amount(ptr, error); - this.checkErrorResult(error, `pendingInboundTransactionGetAmount`); - return result; - } - - static pendingInboundTransactionGetTimestamp(ptr) { - let error = this.initError(); - let result = this.fn.pending_inbound_transaction_get_timestamp(ptr, error); - this.checkErrorResult(error, `pendingInboundTransactionGetTimestamp`); - return result; - } - - static pendingInboundTransactionGetStatus(ptr) { - let error = this.initError(); - let result = this.fn.pending_inbound_transaction_get_status(ptr, error); - this.checkErrorResult(error, `pendingInboundTransactionGetStatus`); - return result; - } - - static pendingInboundTransactionDestroy(ptr) { - this.fn.pending_inbound_transaction_destroy(ptr); - } - //endregion - - //region PendingInboundTransactions (List) - static pendingInboundTransactionsGetLength(ptr) { - let error = this.initError(); - let result = this.fn.pending_inbound_transactions_get_length(ptr, error); - this.checkErrorResult(error, `pendingInboundTransactionsGetLength`); - return result; - } - - static pendingInboundTransactionsGetAt(ptr, position) { - let error = this.initError(); - let result = this.fn.pending_inbound_transactions_get_at( - ptr, - position, - error - ); - this.checkErrorResult(error, `pendingInboundTransactionsGetAt`); - return result; - } - - static pendingInboundTransactionsDestroy(ptr) { - this.fn.pending_inbound_transactions_destroy(ptr); - } - //endregion - - //region TransactionSendStatus - static transactionSendStatusDecode(ptr) { - let error = this.initError(); - let result = this.fn.transaction_send_status_decode(ptr, error); - this.checkErrorResult(error, `transactionSendStatusDecode`); - return result; - } - - static transactionSendStatusDestroy(ptr) { - this.fn.transaction_send_status_destroy(ptr); - } - //endregion - - //region Callbacks - static createCallbackReceivedTransaction(fn) { - return ffi.Callback(this.void, [this.ptr], fn); - } - - static createCallbackReceivedTransactionReply(fn) { - return ffi.Callback(this.void, [this.ptr], fn); - } - - static createCallbackReceivedFinalizedTransaction(fn) { - return ffi.Callback(this.void, [this.ptr], fn); - } - - static createCallbackTransactionBroadcast(fn) { - return ffi.Callback(this.void, [this.ptr], fn); - } - - static createCallbackTransactionMined(fn) { - return ffi.Callback(this.void, [this.ptr], fn); - } - - static createCallbackTransactionMinedUnconfirmed(fn) { - return ffi.Callback(this.void, [this.ptr, this.ulonglong], fn); - } - - static createCallbackFauxTransactionConfirmed(fn) { - return ffi.Callback(this.void, [this.ptr], fn); - } - - static createCallbackFauxTransactionUnconfirmed(fn) { - return ffi.Callback(this.void, [this.ptr, this.ulonglong], fn); - } - - static createCallbackTransactionSendResult(fn) { - return ffi.Callback(this.void, [this.ulonglong, this.ptr], fn); - } - - static createCallbackTransactionCancellation(fn) { - return ffi.Callback(this.void, [this.ptr, this.ulonglong], fn); - } - static createCallbackTxoValidationComplete(fn) { - return ffi.Callback(this.void, [this.ulonglong, this.uchar], fn); - } - static createCallbackContactsLivenessUpdated(fn) { - return ffi.Callback(this.stringPtr, [this.ptr, this.intPtr], fn); - } - static createCallbackBalanceUpdated(fn) { - return ffi.Callback(this.void, [this.ptr], fn); - } - static createCallbackTransactionValidationComplete(fn) { - return ffi.Callback(this.void, [this.ulonglong, this.uchar], fn); - } - static createCallbackSafMessageReceived(fn) { - return ffi.Callback(this.void, [], fn); - } - static createRecoveryProgressCallback(fn) { - return ffi.Callback( - this.void, - [this.uchar, this.ulonglong, this.ulonglong], - fn - ); - } - static createCallbackConnectivityStatus(fn) { - return ffi.Callback(this.void, [this.ulonglong], fn); - } - //endregion - - static walletCreate( - config, - log_path, - num_rolling_log_files, - size_per_log_file_bytes, - passphrase, - seed_words, - network, - callback_received_transaction, - callback_received_transaction_reply, - callback_received_finalized_transaction, - callback_transaction_broadcast, - callback_transaction_mined, - callback_transaction_mined_unconfirmed, - callback_faux_transaction_confirmed, - callback_faux_transaction_unconfirmed, - callback_transaction_send_result, - callback_transaction_cancellation, - callback_txo_validation_complete, - callback_contacts_liveness_data_updated, - callback_balance_updated, - callback_transaction_validation_complete, - callback_saf_message_received, - callback_connectivity_status - ) { - let error = this.initError(); - let recovery_in_progress = this.initBool(); - - let result = this.fn.wallet_create( - config, - log_path, - num_rolling_log_files, - size_per_log_file_bytes, - passphrase, - seed_words, - network, - callback_received_transaction, - callback_received_transaction_reply, - callback_received_finalized_transaction, - callback_transaction_broadcast, - callback_transaction_mined, - callback_transaction_mined_unconfirmed, - callback_faux_transaction_confirmed, - callback_faux_transaction_unconfirmed, - callback_transaction_send_result, - callback_transaction_cancellation, - callback_txo_validation_complete, - callback_contacts_liveness_data_updated, - callback_balance_updated, - callback_transaction_validation_complete, - callback_saf_message_received, - callback_connectivity_status, - recovery_in_progress, - error - ); - this.checkErrorResult(error, `walletCreate`); - return result; - } - - static walletGetBalance(ptr) { - let error = this.initError(); - let result = this.fn.wallet_get_balance(ptr, error); - this.checkErrorResult(error, `walletGetBalance`); - return result; - } - - static walletGetPublicKey(ptr) { - let error = this.initError(); - let result = this.fn.wallet_get_public_key(ptr, error); - this.checkErrorResult(error, `walletGetPublicKey`); - return result; - } - - static walletSignMessage(ptr, msg) { - let error = this.initError(); - let result = this.fn.wallet_sign_message(ptr, msg, error); - this.checkErrorResult(error, `walletSignMessage`); - return result; - } - - static walletVerifyMessageSignature(ptr, public_key_ptr, hex_sig_nonce, msg) { - let error = this.initError(); - let result = this.fn.wallet_verify_message_signature( - ptr, - public_key_ptr, - hex_sig_nonce, - msg, - error - ); - this.checkErrorResult(error, `walletVerifyMessageSignature`); - return result; - } - - static walletAddBaseNodePeer(ptr, public_key_ptr, address) { - let error = this.initError(); - let result = this.fn.wallet_add_base_node_peer( - ptr, - public_key_ptr, - address, - error - ); - this.checkErrorResult(error, `walletAddBaseNodePeer`); - return result; - } - - static walletUpsertContact(ptr, contact_ptr) { - let error = this.initError(); - let result = this.fn.wallet_upsert_contact(ptr, contact_ptr, error); - this.checkErrorResult(error, `walletUpsertContact`); - return result; - } - - static walletRemoveContact(ptr, contact_ptr) { - let error = this.initError(); - let result = this.fn.wallet_remove_contact(ptr, contact_ptr, error); - this.checkErrorResult(error, `walletRemoveContact`); - return result; - } - - static balanceGetAvailable(ptr) { - let error = this.initError(); - let result = this.fn.balance_get_available(ptr, error); - this.checkErrorResult(error, `balanceGetAvailable`); - return result; - } - - static balanceGetTimeLocked(ptr) { - let error = this.initError(); - let result = this.fn.balance_get_time_locked(ptr, error); - this.checkErrorResult(error, `balanceGetTimeLocked`); - return result; - } - - static balanceGetPendingIncoming(ptr) { - let error = this.initError(); - let result = this.fn.balance_get_pending_incoming(ptr, error); - this.checkErrorResult(error, `balanceGetPendingIncoming`); - return result; - } - - static balanceGetPendingOutgoing(ptr) { - let error = this.initError(); - let result = this.fn.balance_get_pending_outgoing(ptr, error); - this.checkErrorResult(error, `balanceGetPendingOutgoing`); - return result; - } - - static livenessDataGetPublicKey(ptr) { - let error = this.initError(); - let result = this.fn.liveness_data_get_public_key(ptr, error); - this.checkErrorResult(error, `livenessDataGetPublicKey`); - return result; - } - - static livenessDataGetLatency(ptr) { - let error = this.initError(); - let result = this.fn.liveness_data_get_latency(ptr, error); - this.checkErrorResult(error, `livenessDataGetLatency`); - return result; - } - - static livenessDataGetLastSeen(ptr) { - let error = this.initError(); - let result = this.fn.liveness_data_get_last_seen(ptr, error); - this.checkErrorResult(error, `livenessDataGetLastSeen`); - return result; - } - - static livenessDataGetMessageType(ptr) { - let error = this.initError(); - let result = this.fn.liveness_data_get_message_type(ptr, error); - this.checkErrorResult(error, `livenessDataGetMessageType`); - return result; - } - - static livenessDataGetOnlineStatus(ptr) { - let error = this.initError(); - let result = this.fn.liveness_data_get_online_status(ptr, error); - this.checkErrorResult(error, `livenessDataGetOnlineStatus`); - return result; - } - - static walletGetFeeEstimate( - ptr, - amount, - fee_per_gram, - num_kernels, - num_outputs - ) { - let error = this.initError(); - let result = this.fn.wallet_get_fee_estimate( - ptr, - amount, - fee_per_gram, - num_kernels, - num_outputs, - error - ); - this.checkErrorResult(error, `walletGetFeeEstimate`); - return result; - } - - static walletGetNumConfirmationsRequired(ptr) { - let error = this.initError(); - let result = this.fn.wallet_get_num_confirmations_required(ptr, error); - this.checkErrorResult(error, `walletGetNumConfirmationsRequired`); - return result; - } - - static walletSetNumConfirmationsRequired(ptr, num) { - let error = this.initError(); - this.fn.wallet_set_num_confirmations_required(ptr, num, error); - this.checkErrorResult(error, `walletSetNumConfirmationsRequired`); - } - - static walletSendTransaction( - ptr, - destination, - amount, - fee_per_gram, - message, - one_sided - ) { - let error = this.initError(); - let result = this.fn.wallet_send_transaction( - ptr, - destination, - amount, - null, - fee_per_gram, - message, - one_sided, - error - ); - this.checkErrorResult(error, `walletSendTransaction`); - return result; - } - - static walletGetContacts(ptr) { - let error = this.initError(); - let result = this.fn.wallet_get_contacts(ptr, error); - this.checkErrorResult(error, `walletGetContacts`); - return result; - } - - static walletGetCompletedTransactions(ptr) { - let error = this.initError(); - let result = this.fn.wallet_get_completed_transactions(ptr, error); - this.checkErrorResult(error, `walletGetCompletedTransactions`); - return result; - } - - static walletGetPendingOutboundTransactions(ptr) { - let error = this.initError(); - let result = this.fn.wallet_get_pending_outbound_transactions(ptr, error); - this.checkErrorResult(error, `walletGetPendingOutboundTransactions`); - return result; - } - - static walletGetPendingInboundTransactions(ptr) { - let error = this.initError(); - let result = this.fn.wallet_get_pending_inbound_transactions(ptr, error); - this.checkErrorResult(error, `walletGetPendingInboundTransactions`); - return result; - } - - static walletGetCancelledTransactions(ptr) { - let error = this.initError(); - let result = this.fn.wallet_get_cancelled_transactions(ptr, error); - this.checkErrorResult(error, `walletGetCancelledTransactions`); - return result; - } - - static walletGetCompletedTransactionById(ptr, transaction_id) { - let error = this.initError(); - let result = this.fn.wallet_get_completed_transaction_by_id( - ptr, - transaction_id, - error - ); - this.checkErrorResult(error, `walletGetCompletedTransactionById`); - return result; - } - - static walletGetPendingOutboundTransactionById(ptr, transaction_id) { - let error = this.initError(); - let result = this.fn.wallet_get_pending_outbound_transaction_by_id( - ptr, - transaction_id, - error - ); - this.checkErrorResult(error, `walletGetPendingOutboundTransactionById`); - return result; - } - - static walletGetPendingInboundTransactionById(ptr, transaction_id) { - let error = this.initError(); - let result = this.fn.wallet_get_pending_inbound_transaction_by_id( - ptr, - transaction_id, - error - ); - this.checkErrorResult(error, `walletGetPendingInboundTransactionById`); - return result; - } - - static walletGetCancelledTransactionById(ptr, transaction_id) { - let error = this.initError(); - let result = this.fn.wallet_get_cancelled_transaction_by_id( - ptr, - transaction_id, - error - ); - this.checkErrorResult(error, `walletGetCancelledTransactionById`); - return result; - } - - static walletImportUtxo( - ptr, - amount, - spending_key_ptr, - source_public_key_ptr, - features_ptr, - metadata_signature_ptr, - sender_offset_public_key_ptr, - message - ) { - let error = this.initError(); - let result = this.fn.wallet_import_external_utxo_as_non_rewindable( - ptr, - amount, - spending_key_ptr, - source_public_key_ptr, - features_ptr, - metadata_signature_ptr, - sender_offset_public_key_ptr, - // script_private_key - spending_key_ptr, - // default Covenant - null, - // default EncryptedValue - null, - message, - error - ); - this.checkErrorResult(error, `walletImportUtxo`); - return result; - } - - static walletStartTxoValidation(ptr) { - let error = this.initError(); - let result = this.fn.wallet_start_txo_validation(ptr, error); - this.checkErrorResult(error, `walletStartTxoValidation`); - return result; - } - - static walletStartTransactionValidation(ptr) { - let error = this.initError(); - let result = this.fn.wallet_start_transaction_validation(ptr, error); - this.checkErrorResult(error, `walletStartTransactionValidation`); - return result; - } - - static walletGetFeePerGramStats(ptr, count) { - let error = this.initError(); - let result = this.fn.wallet_get_fee_per_gram_stats(ptr, count, error); - this.checkErrorResult(error, `walletGetFeePerGramStats`); - return result; - } - - //region FeePerGramStats (List) - static feePerGramStatsGetLength(ptr) { - let error = this.initError(); - let result = this.fn.fee_per_gram_stats_get_length(ptr, error); - this.checkErrorResult(error, "feePerGramStatsGetLength"); - return result; - } - - static feePerGramStatsGetAt(ptr, position) { - let error = this.initError(); - let result = this.fn.fee_per_gram_stats_get_at(ptr, position, error); - this.checkErrorResult(error, "feePerGramStatsGetAt"); - return result; - } - - static feePerGramStatsDestroy(ptr) { - this.fn.fee_per_gram_stats_destroy(ptr); - } - //endregion - - static feePerGramStatGetOrder(ptr) { - let error = this.initError(); - let result = this.fn.fee_per_gram_stat_get_order(ptr, error); - this.checkErrorResult(error, "feePerGramStatGetOrder"); - return result; - } - - static feePerGramStatGetMinFeePerGram(ptr) { - let error = this.initError(); - let result = this.fn.fee_per_gram_stat_get_min_fee_per_gram(ptr, error); - this.checkErrorResult(error, "feePerGramStatGetMinFeePerGram"); - return result; - } - - static feePerGramStatGetAvgFeePerGram(ptr) { - let error = this.initError(); - let result = this.fn.fee_per_gram_stat_get_avg_fee_per_gram(ptr, error); - this.checkErrorResult(error, "feePerGramStatGetAvgFeePerGram"); - return result; - } - - static feePerGramStatGetMaxFeePerGram(ptr) { - let error = this.initError(); - let result = this.fn.fee_per_gram_stat_get_max_fee_per_gram(ptr, error); - this.checkErrorResult(error, "feePerGramStatGetMaxFeePerGram"); - return result; - } - - static feePerGramStatDestroy(ptr) { - this.fn.fee_per_gram_stat_destroy(ptr); - } - - static walletRestartTransactionBroadcast(ptr) { - let error = this.initError(); - let result = this.fn.wallet_restart_transaction_broadcast(ptr, error); - this.checkErrorResult(error, `walletRestartTransactionBroadcast`); - return result; - } - - static walletSetLowPowerMode(ptr) { - let error = this.initError(); - this.fn.wallet_set_low_power_mode(ptr, error); - this.checkErrorResult(error, `walletSetLowPowerMode`); - } - - static walletSetNormalPowerMode(ptr) { - let error = this.initError(); - this.fn.wallet_set_normal_power_mode(ptr, error); - this.checkErrorResult(error, `walletSetNormalPowerMode`); - } - - static walletCancelPendingTransaction(ptr, transaction_id) { - let error = this.initError(); - let result = this.fn.wallet_cancel_pending_transaction( - ptr, - transaction_id, - error - ); - this.checkErrorResult(error, `walletCancelPendingTransaction`); - return result; - } - - static walletCoinSplit(ptr, amount, count, fee, msg, lock_height) { - let error = this.initError(); - let result = this.fn.wallet_coin_split( - ptr, - amount, - count, - fee, - msg, - lock_height, - error - ); - this.checkErrorResult(error, `walletCoinSplit`); - return result; - } - - static walletGetSeedWords(ptr) { - let error = this.initError(); - let result = this.fn.wallet_get_seed_words(ptr, error); - this.checkErrorResult(error, `walletGetSeedWords`); - return result; - } - - static walletApplyEncryption(ptr, passphrase) { - let error = this.initError(); - this.fn.wallet_apply_encryption(ptr, passphrase, error); - this.checkErrorResult(error, `walletApplyEncryption`); - } - - static walletRemoveEncryption(ptr) { - let error = this.initError(); - this.fn.wallet_remove_encryption(ptr, error); - this.checkErrorResult(error, `walletRemoveEncryption`); - } - - static walletSetKeyValue(ptr, key_ptr, value) { - let error = this.initError(); - let result = this.fn.wallet_set_key_value(ptr, key_ptr, value, error); - this.checkErrorResult(error, `walletSetKeyValue`); - return result; - } - - static walletGetValue(ptr, key_ptr) { - let error = this.initError(); - let result = this.fn.wallet_get_value(ptr, key_ptr, error); - this.checkErrorResult(error, `walletGetValue`); - return result; - } - - static walletClearValue(ptr, key_ptr) { - let error = this.initError(); - let result = this.fn.wallet_clear_value(ptr, key_ptr, error); - this.checkErrorResult(error, `walletClearValue`); - return result; - } - - static walletIsRecoveryInProgress(ptr) { - let error = this.initError(); - let result = this.fn.wallet_is_recovery_in_progress(ptr, error); - this.checkErrorResult(error, `walletIsRecoveryInProgress`); - return result; - } - - static walletStartRecovery( - ptr, - base_node_public_key_ptr, - recovery_progress_callback - ) { - let error = this.initError(); - let result = this.fn.wallet_start_recovery( - ptr, - base_node_public_key_ptr, - recovery_progress_callback, - error - ); - this.checkErrorResult(error, `walletStartRecovery`); - return result; - } - - static walletDestroy(ptr) { - this.fn.wallet_destroy(ptr); - } - - static balanceDestroy(ptr) { - this.fn.balance_destroy(ptr); - } - - static livenessDataDestroy(ptr) { - this.fn.liveness_data_destroy(ptr); - } - //endregion -} -module.exports = InterfaceFFI; diff --git a/integration_tests/helpers/ffi/livenessData.js b/integration_tests/helpers/ffi/livenessData.js deleted file mode 100644 index 953cce41ee..0000000000 --- a/integration_tests/helpers/ffi/livenessData.js +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const InterfaceFFI = require("./ffiInterface"); -const PublicKey = require("./publicKey"); -const { expect } = require("chai"); - -class LivenessData { - ptr; - - pointerAssign(ptr) { - if (this.ptr) { - this.destroy(); - this.ptr = ptr; - } else { - this.ptr = ptr; - } - } - - getPtr() { - return this.ptr; - } - - getPublicKey() { - let ptr = InterfaceFFI.livenessDataGetPublicKey(this.ptr); - let pk = new PublicKey(); - pk.pointerAssign(ptr); - let result = pk.getHex(); - pk.destroy(); - return result; - } - - getLatency() { - return InterfaceFFI.livenessDataGetLatency(this.ptr); - } - - getLastSeen() { - let lastSeen = InterfaceFFI.livenessDataGetLastSeen(this.ptr); - let result = lastSeen.readCString(); - InterfaceFFI.stringDestroy(lastSeen); - return result; - } - - getMessageType() { - let messageType = InterfaceFFI.livenessDataGetMessageType(this.ptr); - switch (messageType) { - case 0: - return "Ping"; - case 1: - return "Pong"; - case 2: - return "NoMessage"; - default: - expect(messageType).to.equal("please add this<< MessageType"); - } - } - - getOnlineStatus() { - let status = InterfaceFFI.livenessDataGetOnlineStatus(this.ptr); - const result = status.readCString(); - InterfaceFFI.stringDestroy(status); - return result; - } - - destroy() { - if (this.ptr) { - InterfaceFFI.livenessDataDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = LivenessData; diff --git a/integration_tests/helpers/ffi/pendingInboundTransaction.js b/integration_tests/helpers/ffi/pendingInboundTransaction.js deleted file mode 100644 index 86ee65244d..0000000000 --- a/integration_tests/helpers/ffi/pendingInboundTransaction.js +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const InterfaceFFI = require("./ffiInterface"); -const PublicKey = require("./publicKey"); - -class PendingInboundTransaction { - ptr; - - pointerAssign(ptr) { - if (this.ptr) { - this.destroy(); - this.ptr = ptr; - } else { - this.ptr = ptr; - } - } - - getPtr() { - return this.ptr; - } - - getSourcePublicKey() { - let result = new PublicKey(); - result.pointerAssign( - InterfaceFFI.pendingInboundTransactionGetSourcePublicKey(this.ptr) - ); - return result; - } - - getAmount() { - return InterfaceFFI.pendingInboundTransactionGetAmount(this.ptr); - } - - getMessage() { - return InterfaceFFI.pendingInboundTransactionGetMessage(this.ptr); - } - - getStatus() { - return InterfaceFFI.pendingInboundTransactionGetStatus(this.ptr); - } - - getTransactionID() { - return InterfaceFFI.pendingInboundTransactionGetTransactionId(this.ptr); - } - - getTimestamp() { - return InterfaceFFI.pendingInboundTransactionGetTimestamp(this.ptr); - } - - destroy() { - if (this.ptr) { - InterfaceFFI.pendingInboundTransactionDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = PendingInboundTransaction; diff --git a/integration_tests/helpers/ffi/pendingInboundTransactions.js b/integration_tests/helpers/ffi/pendingInboundTransactions.js deleted file mode 100644 index 31fd68ef32..0000000000 --- a/integration_tests/helpers/ffi/pendingInboundTransactions.js +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const PendingInboundTransaction = require("./pendingInboundTransaction"); -const InterfaceFFI = require("./ffiInterface"); - -class PendingInboundTransactions { - ptr; - - constructor(ptr) { - this.ptr = ptr; - } - - getLength() { - return InterfaceFFI.pendingInboundTransactionsGetLength(this.ptr); - } - - getAt(position) { - let result = new PendingInboundTransaction(); - result.pointerAssign( - InterfaceFFI.pendingInboundTransactionsGetAt(this.ptr, position) - ); - return result; - } - - destroy() { - if (this.ptr) { - InterfaceFFI.pendingInboundTransactionsDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = PendingInboundTransactions; diff --git a/integration_tests/helpers/ffi/pendingOutboundTransaction.js b/integration_tests/helpers/ffi/pendingOutboundTransaction.js deleted file mode 100644 index d84064d803..0000000000 --- a/integration_tests/helpers/ffi/pendingOutboundTransaction.js +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const InterfaceFFI = require("./ffiInterface"); -const PublicKey = require("./publicKey"); - -class PendingOutboundTransaction { - ptr; - - pointerAssign(ptr) { - if (this.ptr) { - this.ptr = ptr; - this.destroy(); - } else { - this.ptr = ptr; - } - } - - getPtr() { - return this.ptr; - } - - getDestinationPublicKey() { - let result = new PublicKey(); - result.pointerAssign( - InterfaceFFI.pendingOutboundTransactionGetDestinationPublicKey(this.ptr) - ); - return result; - } - - getAmount() { - return InterfaceFFI.pendingOutboundTransactionGetAmount(this.ptr); - } - - getFee() { - return InterfaceFFI.pendingOutboundTransactionGetFee(this.ptr); - } - - getMessage() { - return InterfaceFFI.pendingOutboundTransactionGetMessage(this.ptr); - } - - getStatus() { - return InterfaceFFI.pendingOutboundTransactionGetStatus(this.ptr); - } - - getTransactionID() { - return InterfaceFFI.pendingOutboundTransactionGetTransactionId(this.ptr); - } - - getTimestamp() { - return InterfaceFFI.pendingOutboundTransactionGetTimestamp(this.ptr); - } - - destroy() { - if (this.ptr) { - InterfaceFFI.pendingOutboundTransactionDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = PendingOutboundTransaction; diff --git a/integration_tests/helpers/ffi/pendingOutboundTransactions.js b/integration_tests/helpers/ffi/pendingOutboundTransactions.js deleted file mode 100644 index 522182c684..0000000000 --- a/integration_tests/helpers/ffi/pendingOutboundTransactions.js +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const PendingOutboundTransaction = require("./pendingOutboundTransaction"); -const InterfaceFFI = require("./ffiInterface"); - -class PendingOutboundTransactions { - ptr = undefined; - - constructor(ptr) { - this.ptr = ptr; - } - - getLength() { - return InterfaceFFI.pendingOutboundTransactionsGetLength(this.ptr); - } - - getAt(position) { - let result = new PendingOutboundTransaction(); - result.pointerAssign( - InterfaceFFI.pendingOutboundTransactionsGetAt(this.ptr, position) - ); - return result; - } - - destroy() { - if (this.ptr) { - InterfaceFFI.pendingOutboundTransactionsDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = PendingOutboundTransactions; diff --git a/integration_tests/helpers/ffi/privateKey.js b/integration_tests/helpers/ffi/privateKey.js deleted file mode 100644 index 0544d6d938..0000000000 --- a/integration_tests/helpers/ffi/privateKey.js +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const InterfaceFFI = require("./ffiInterface"); -const ByteVector = require("./byteVector"); -const utf8 = require("utf8"); - -class PrivateKey { - ptr; - pointerAssign(ptr) { - // Prevent pointer from being leaked in case of re-assignment - if (this.ptr) { - this.ptr = ptr; - this.destroy(); - } else { - this.ptr = ptr; - } - } - - generate() { - this.ptr = InterfaceFFI.privateKeyGenerate(); - } - - fromHexString(hex) { - let sanitize = utf8.encode(hex); // Make sure it's not UTF-16 encoded (JS default) - let result = new PrivateKey(); - result.pointerAssign(InterfaceFFI.privateKeyFromHex(sanitize)); - return result; - } - - fromByteVector(byte_vector) { - let result = new PrivateKey(); - result.pointerAssign(InterfaceFFI.privateKeyCreate(byte_vector.getPtr())); - return result; - } - - getPtr() { - return this.ptr; - } - - getBytes() { - let result = new ByteVector(); - result.pointerAssign(InterfaceFFI.privateKeyGetBytes(this.ptr)); - return result; - } - - getHex() { - const bytes = this.getBytes(); - const length = bytes.getLength(); - let byte_array = new Uint8Array(length); - for (let i = 0; i < length; i++) { - byte_array[i] = bytes.getAt(i); - } - bytes.destroy(); - let buffer = Buffer.from(byte_array, 0); - return buffer.toString("hex"); - } - - destroy() { - if (this.ptr) { - InterfaceFFI.privateKeyDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = PrivateKey; diff --git a/integration_tests/helpers/ffi/publicKey.js b/integration_tests/helpers/ffi/publicKey.js deleted file mode 100644 index e95d2162c8..0000000000 --- a/integration_tests/helpers/ffi/publicKey.js +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const InterfaceFFI = require("./ffiInterface"); -const ByteVector = require("./byteVector"); -const utf8 = require("utf8"); - -class PublicKey { - ptr; - - pointerAssign(ptr) { - // Prevent pointer from being leaked in case of re-assignment - if (this.ptr) { - this.destroy(); - this.ptr = ptr; - } else { - this.ptr = ptr; - } - } - - fromPrivateKey(key) { - let result = new PublicKey(); - result.pointerAssign(InterfaceFFI.publicKeyFromPrivateKey(key.getPtr())); - return result; - } - - static fromHexString(hex) { - let sanitize = utf8.encode(hex); // Make sure it's not UTF-16 encoded (JS default) - let result = new PublicKey(); - result.pointerAssign(InterfaceFFI.publicKeyFromHex(sanitize)); - return result; - } - - fromEmojiID(emoji) { - let sanitize = utf8.encode(emoji); // Make sure it's not UTF-16 encoded (JS default) - let result = new PublicKey(); - result.pointerAssign(InterfaceFFI.emojiIdToPublicKey(sanitize)); - return result; - } - - fromByteVector(byte_vector) { - let result = new PublicKey(); - result.pointerAssign(InterfaceFFI.publicKeyCreate(byte_vector.getPtr())); - return result; - } - - getPtr() { - return this.ptr; - } - - getBytes() { - let result = new ByteVector(); - result.pointerAssign(InterfaceFFI.publicKeyGetBytes(this.ptr)); - return result; - } - - getHex() { - const bytes = this.getBytes(); - const length = bytes.getLength(); - let byte_array = new Uint8Array(length); - for (let i = 0; i < length; i++) { - byte_array[i] = bytes.getAt(i); - } - bytes.destroy(); - let buffer = Buffer.from(byte_array, 0); - return buffer.toString("hex"); - } - - getEmojiId() { - let emoji_id = InterfaceFFI.publicKeyToEmojiId(this.ptr); - let result = emoji_id.readCString(); - InterfaceFFI.stringDestroy(emoji_id); - return result; - } - - destroy() { - if (this.ptr) { - InterfaceFFI.publicKeyDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = PublicKey; diff --git a/integration_tests/helpers/ffi/seedWords.js b/integration_tests/helpers/ffi/seedWords.js deleted file mode 100644 index 000d948193..0000000000 --- a/integration_tests/helpers/ffi/seedWords.js +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const InterfaceFFI = require("./ffiInterface"); -const utf8 = require("utf8"); -const { expect } = require("chai"); - -function mnemonicLanguageStepId() { - return [ - "CHINESE_SIMPLIFIED", - "ENGLISH", - "FRENCH", - "ITALIAN", - "JAPANESE", - "KOREAN", - "SPANISH", - ]; -} - -function mnemonicLanguageText() { - return [ - "ChineseSimplified", - "English", - "French", - "Italian", - "Japanese", - "Korean", - "Spanish", - ]; -} - -class SeedWords { - ptr; - - pointerAssign(ptr) { - // Prevent pointer from being leaked in case of re-assignment - if (this.ptr) { - this.destroy(); - this.ptr = ptr; - } else { - this.ptr = ptr; - } - } - - static fromText(seed_words_text) { - const seed_words = new SeedWords(); - seed_words.pointerAssign(InterfaceFFI.seedWordsCreate()); - const seed_words_list = seed_words_text.split(" "); - for (const seed_word of seed_words_list) { - InterfaceFFI.seedWordsPushWord( - seed_words.getPtr(), - utf8.encode(seed_word) - ); - } - return seed_words; - } - - static getMnemonicWordListForLanguage(language) { - const index = mnemonicLanguageStepId().indexOf(language); - if (index < 0) { - console.log( - "Mnemonic Language", - language, - "not recognized. Select from:\n", - mnemonicLanguageStepId() - ); - expect(index < 0).to.equal(false); - } - const seed_words = new SeedWords(); - seed_words.pointerAssign( - InterfaceFFI.seedWordsGetMnemonicWordListForLanguage( - utf8.encode(mnemonicLanguageText()[index]) - ) - ); - const mnemonicWords = []; - for (let i = 0; i < seed_words.getLength(); i++) { - mnemonicWords.push(seed_words.getAt(i)); - } - seed_words.destroy(); - return mnemonicWords; - } - - getLength() { - return InterfaceFFI.seedWordsGetLength(this.ptr); - } - - getPtr() { - return this.ptr; - } - - getAt(position) { - let seed_word = InterfaceFFI.seedWordsGetAt(this.ptr, position); - let result = seed_word.readCString(); - InterfaceFFI.stringDestroy(seed_word); - return result; - } - - destroy() { - if (this.ptr) { - InterfaceFFI.seedWordsDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = SeedWords; diff --git a/integration_tests/helpers/ffi/transactionKernel.js b/integration_tests/helpers/ffi/transactionKernel.js deleted file mode 100644 index 770349ac6d..0000000000 --- a/integration_tests/helpers/ffi/transactionKernel.js +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const InterfaceFFI = require("./ffiInterface"); - -class CompletedTransactionKernel { - ptr; - - pointerAssign(ptr) { - // Prevent pointer from being leaked in case of re-assignment - if (this.ptr) { - this.destroy(); - this.ptr = ptr; - } else { - this.ptr = ptr; - } - } - - getPtr() { - return this.ptr; - } - - getExcess() { - let strPtr = InterfaceFFI.transactionKernelGetExcess(this.ptr); - let result = strPtr.readCString(); - InterfaceFFI.stringDestroy(strPtr); - return result; - } - - getNonce() { - let strPtr = InterfaceFFI.transactionKernelGetExcessPublicNonce(this.ptr); - let result = strPtr.readCString(); - InterfaceFFI.stringDestroy(strPtr); - return result; - } - - getSignature() { - let strPtr = InterfaceFFI.transactionKernelGetExcessSigntature(this.ptr); - let result = strPtr.readCString(); - InterfaceFFI.stringDestroy(strPtr); - return result; - } - - asObject() { - return { - excess: this.getExcess(), - nonce: this.getNonce(), - sig: this.getSignature(), - }; - } - - destroy() { - if (this.ptr) { - InterfaceFFI.transactionKernelDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = CompletedTransactionKernel; diff --git a/integration_tests/helpers/ffi/transactionSendStatus.js b/integration_tests/helpers/ffi/transactionSendStatus.js deleted file mode 100644 index 44bb9b58eb..0000000000 --- a/integration_tests/helpers/ffi/transactionSendStatus.js +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const InterfaceFFI = require("./ffiInterface"); - -class TransactionSendStatus { - ptr; - - pointerAssign(ptr) { - if (this.ptr) { - this.destroy(); - this.ptr = ptr; - } else { - this.ptr = ptr; - } - } - - getPtr() { - return this.ptr; - } - - getSendStatus() { - return InterfaceFFI.transactionSendStatusDecode(this.ptr); - } - - destroy() { - if (this.ptr) { - InterfaceFFI.transactionSendStatusDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = TransactionSendStatus; diff --git a/integration_tests/helpers/ffi/transportConfig.js b/integration_tests/helpers/ffi/transportConfig.js deleted file mode 100644 index cb5ffd72a9..0000000000 --- a/integration_tests/helpers/ffi/transportConfig.js +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const InterfaceFFI = require("./ffiInterface"); -const utf8 = require("utf8"); - -class TransportConfig { - ptr; - type = "None"; - - pointerAssign(ptr, type) { - // Prevent pointer from being leaked in case of re-assignment - if (this.ptr) { - this.destroy(); - this.ptr = ptr; - this.type = type; - } else { - this.ptr = ptr; - this.type = type; - } - } - - getPtr() { - return this.ptr; - } - - getType() { - return this.type; - } - - static createMemory() { - let result = new TransportConfig(); - result.pointerAssign(InterfaceFFI.transportMemoryCreate(), "Memory"); - return result; - } - - static createTCP(listener_address) { - let sanitize = utf8.encode(listener_address); // Make sure it's not UTF-16 encoded (JS default) - let result = new TransportConfig(); - result.pointerAssign(InterfaceFFI.transportTcpCreate(sanitize), "TCP"); - return result; - } - - static createTor( - control_server_address, - tor_cookie, - tor_port, - socks_username, - socks_password - ) { - let sanitize_address = utf8.encode(control_server_address); - let sanitize_username = utf8.encode(socks_username); - let sanitize_password = utf8.encode(socks_password); - let result = new TransportConfig(); - result.pointerAssign( - InterfaceFFI.transportTorCreate( - sanitize_address, - tor_cookie.getPtr(), - tor_port, - sanitize_username, - sanitize_password - ), - "Tor" - ); - return result; - } - - getAddress() { - if (this.type === "Memory") { - let c_address = InterfaceFFI.transportMemoryGetAddress(this.getPtr()); - let result = c_address.readCString(); - InterfaceFFI.stringDestroy(c_address); - return result; - } else { - return "N/A"; - } - } - - destroy() { - this.type = "None"; - if (this.ptr) { - InterfaceFFI.transportConfigDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - } - } -} - -module.exports = TransportConfig; diff --git a/integration_tests/helpers/ffi/wallet.js b/integration_tests/helpers/ffi/wallet.js deleted file mode 100644 index 29752b2496..0000000000 --- a/integration_tests/helpers/ffi/wallet.js +++ /dev/null @@ -1,588 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const InterfaceFFI = require("./ffiInterface"); -const PublicKey = require("./publicKey"); -const CompletedTransaction = require("./completedTransaction"); -const CompletedTransactions = require("./completedTransactions"); -const PendingInboundTransaction = require("./pendingInboundTransaction"); -const PendingInboundTransactions = require("./pendingInboundTransactions"); -const PendingOutboundTransactions = require("./pendingOutboundTransactions"); -const Contact = require("./contact"); -const Contacts = require("./contacts"); -const Balance = require("./balance"); - -const utf8 = require("utf8"); -const LivenessData = require("./livenessData"); -const TransactionSendStatus = require("./transactionSendStatus"); -const FeePerGramStats = require("./feePerGramStats"); - -class WalletBalance { - available = 0; - timeLocked = 0; - pendingIn = 0; - pendingOut = 0; -} - -class Wallet { - ptr; - balance = new WalletBalance(); - livenessData = new Map(); - log_path = ""; - transactionReceived = 0; - transactionReplyReceived = 0; - transactionBroadcast = 0; - transactionMined = 0; - transactionMinedUnconfirmed = 0; - transactionFauxConfirmed = 0; - contactsLivenessDataUpdated = 0; - transactionFauxUnconfirmed = 0; - transactionSafMessageReceived = 0; - transactionCancelled = 0; - transactionFinalized = 0; - txo_validation_complete = false; - txo_validation_result = 0; - tx_validation_complete = false; - tx_validation_result = 0; - callback_received_transaction; - callback_received_transaction_reply; - callback_received_finalized_transaction; - callback_transaction_broadcast; - callback_transaction_mined; - callback_transaction_mined_unconfirmed; - callback_faux_transaction_confirmed; - callback_faux_transaction_unconfirmed; - callback_transaction_send_result; - callback_transaction_cancellation; - callback_txo_validation_complete; - callback_contacts_liveness_data_updated; - callback_balance_updated; - callback_transaction_validation_complete; - callback_saf_message_received; - callback_connectivity_status; - recoveryProgressCallback; - - getTxoValidationStatus() { - return { - txo_validation_complete: this.txo_validation_complete, - txo_validation_result: this.txo_validation_result, - }; - } - - getTxValidationStatus() { - return { - tx_validation_complete: this.tx_validation_complete, - tx_validation_result: this.tx_validation_result, - }; - } - - clearCallbackCounters() { - this.transactionReceived = - this.transactionReplyReceived = - this.transactionBroadcast = - this.transactionMined = - this.transactionFauxConfirmed = - this.contactsLivenessDataUpdated = - this.transactionSafMessageReceived = - this.transactionCancelled = - this.transactionMinedUnconfirmed = - this.transactionFauxUnconfirmed = - this.transactionFinalized = - 0; - } - - getCounters() { - return { - received: this.transactionReceived, - replyReceived: this.transactionReplyReceived, - broadcast: this.transactionBroadcast, - finalized: this.transactionFinalized, - minedUnconfirmed: this.transactionMinedUnconfirmed, - fauxUnconfirmed: this.transactionFauxUnconfirmed, - cancelled: this.transactionCancelled, - mined: this.transactionMined, - fauxConfirmed: this.transactionFauxConfirmed, - livenessDataUpdated: this.contactsLivenessDataUpdated, - saf: this.transactionSafMessageReceived, - }; - } - - getLivenessData() { - return this.livenessData; - } - - constructor( - comms_config_ptr, - log_path, - passphrase, - seed_words_ptr, - num_rolling_log_file = 50, - log_size_bytes = 102400, - network = "localnet" - ) { - //region Callbacks - this.callback_received_transaction = - InterfaceFFI.createCallbackReceivedTransaction( - this.onReceivedTransaction - ); - this.callback_received_transaction_reply = - InterfaceFFI.createCallbackReceivedTransactionReply( - this.onReceivedTransactionReply - ); - this.callback_received_finalized_transaction = - InterfaceFFI.createCallbackReceivedFinalizedTransaction( - this.onReceivedFinalizedTransaction - ); - this.callback_transaction_broadcast = - InterfaceFFI.createCallbackTransactionBroadcast( - this.onTransactionBroadcast - ); - this.callback_transaction_mined = - InterfaceFFI.createCallbackTransactionMined(this.onTransactionMined); - this.callback_transaction_mined_unconfirmed = - InterfaceFFI.createCallbackTransactionMinedUnconfirmed( - this.onTransactionMinedUnconfirmed - ); - this.callback_faux_transaction_confirmed = - InterfaceFFI.createCallbackFauxTransactionConfirmed( - this.onFauxTransactionConfirmed - ); - this.callback_faux_transaction_unconfirmed = - InterfaceFFI.createCallbackFauxTransactionUnconfirmed( - this.onFauxTransactionUnconfirmed - ); - this.callback_transaction_send_result = - InterfaceFFI.createCallbackTransactionSendResult( - this.onTransactionSendResult - ); - this.callback_transaction_cancellation = - InterfaceFFI.createCallbackTransactionCancellation( - this.onTransactionCancellation - ); - this.callback_txo_validation_complete = - InterfaceFFI.createCallbackTxoValidationComplete( - this.onTxoValidationComplete - ); - this.callback_contacts_liveness_data_updated = - InterfaceFFI.createCallbackContactsLivenessUpdated( - this.onContactsLivenessUpdated - ); - this.callback_balance_updated = InterfaceFFI.createCallbackBalanceUpdated( - this.onBalanceUpdated - ); - this.callback_transaction_validation_complete = - InterfaceFFI.createCallbackTransactionValidationComplete( - this.onTransactionValidationComplete - ); - this.callback_saf_message_received = - InterfaceFFI.createCallbackSafMessageReceived(this.onSafMessageReceived); - this.recoveryProgressCallback = InterfaceFFI.createRecoveryProgressCallback( - this.onRecoveryProgress - ); - this.callback_connectivity_status = - InterfaceFFI.createCallbackConnectivityStatus( - this.onConnectivityStatusChange - ); - //endregion - - this.transactionReceived = 0; - this.transactionReplyReceived = 0; - this.transactionBroadcast = 0; - this.transactionMined = 0; - this.transactionFauxConfirmed = 0; - this.contactsLivenessDataUpdated = 0; - this.transactionSafMessageReceived = 0; - this.transactionCancelled = 0; - this.transactionMinedUnconfirmed = 0; - this.transactionFauxUnconfirmed = 0; - this.transactionFinalized = 0; - this.recoveryFinished = true; - let sanitize = null; - let words = null; - if (passphrase) { - sanitize = utf8.encode(passphrase); - } - if (seed_words_ptr) { - words = seed_words_ptr; - } - if (!network) { - network = "localnet"; - } - - this.log_path = log_path; - this.ptr = InterfaceFFI.walletCreate( - comms_config_ptr, - utf8.encode(this.log_path), //`${this.baseDir}/log/wallet.log`, - num_rolling_log_file, - log_size_bytes, - sanitize, - words, - utf8.encode(network), - this.callback_received_transaction, - this.callback_received_transaction_reply, - this.callback_received_finalized_transaction, - this.callback_transaction_broadcast, - this.callback_transaction_mined, - this.callback_transaction_mined_unconfirmed, - this.callback_faux_transaction_confirmed, - this.callback_faux_transaction_unconfirmed, - this.callback_transaction_send_result, - this.callback_transaction_cancellation, - this.callback_txo_validation_complete, - this.callback_contacts_liveness_data_updated, - this.callback_balance_updated, - this.callback_transaction_validation_complete, - this.callback_saf_message_received, - this.callback_connectivity_status - ); - } - - onReceivedTransaction = (ptr) => { - // refer to outer scope in callback function otherwise this is null - let tx = new PendingInboundTransaction(); - tx.pointerAssign(ptr); - console.log( - `${new Date().toISOString()} received Transaction with txID ${tx.getTransactionID()}` - ); - tx.destroy(); - this.transactionReceived += 1; - }; - - onReceivedTransactionReply = (ptr) => { - let tx = new CompletedTransaction(); - tx.pointerAssign(ptr); - console.log( - `${new Date().toISOString()} received reply for Transaction with txID ${tx.getTransactionID()}.` - ); - tx.destroy(); - this.transactionReplyReceived += 1; - }; - - onReceivedFinalizedTransaction = (ptr) => { - let tx = new CompletedTransaction(); - tx.pointerAssign(ptr); - console.log( - `${new Date().toISOString()} received finalization for Transaction with txID ${tx.getTransactionID()}.` - ); - tx.destroy(); - this.transactionFinalized += 1; - }; - - onTransactionBroadcast = (ptr) => { - let tx = new CompletedTransaction(); - tx.pointerAssign(ptr); - console.log( - `${new Date().toISOString()} Transaction with txID ${tx.getTransactionID()} was broadcast.` - ); - tx.destroy(); - this.transactionBroadcast += 1; - }; - - onTransactionMined = (ptr) => { - let tx = new CompletedTransaction(); - tx.pointerAssign(ptr); - console.log( - `${new Date().toISOString()} Transaction with txID ${tx.getTransactionID()} was mined.` - ); - tx.destroy(); - this.transactionMined += 1; - }; - - onTransactionMinedUnconfirmed = (ptr, confirmations) => { - let tx = new CompletedTransaction(); - tx.pointerAssign(ptr); - console.log( - `${new Date().toISOString()} Transaction with txID ${tx.getTransactionID()} is mined unconfirmed with ${confirmations} confirmations.` - ); - tx.destroy(); - this.transactionMinedUnconfirmed += 1; - }; - - onFauxTransactionConfirmed = (ptr) => { - let tx = new CompletedTransaction(); - tx.pointerAssign(ptr); - console.log( - `${new Date().toISOString()} Faux transaction with txID ${tx.getTransactionID()} was confirmed.` - ); - tx.destroy(); - this.transactionFauxConfirmed += 1; - }; - - onFauxTransactionUnconfirmed = (ptr, confirmations) => { - let tx = new CompletedTransaction(); - tx.pointerAssign(ptr); - console.log( - `${new Date().toISOString()} Faux transaction with txID ${tx.getTransactionID()} is unconfirmed with ${confirmations} confirmations.` - ); - tx.destroy(); - this.transactionFauxUnconfirmed += 1; - }; - - onTransactionCancellation = (ptr, reason) => { - let tx = new CompletedTransaction(); - tx.pointerAssign(ptr); - console.log( - `${new Date().toISOString()} Transaction with txID ${tx.getTransactionID()} was cancelled with reason code ${reason}.` - ); - tx.destroy(); - this.transactionCancelled += 1; - }; - - onTransactionSendResult = (id, ptr) => { - let status = new TransactionSendStatus(ptr); - status.pointerAssign(ptr); - console.log( - `${new Date().toISOString()} callbackTransactionSendResult(${id}: (${status.getSendStatus()}))` - ); - status.destroy(); - }; - - onTxoValidationComplete = (request_key, validation_results) => { - console.log( - `${new Date().toISOString()} callbackTxoValidationComplete(${request_key},${validation_results})` - ); - this.txo_validation_complete = true; - this.txo_validation_result = validation_results; - }; - - onContactsLivenessUpdated = (ptr) => { - let data = new LivenessData(ptr); - data.pointerAssign(ptr); - const public_key = data.getPublicKey(); - this.addLivenessData( - public_key, - data.getLatency(), - data.getLastSeen(), - data.getMessageType(), - data.getOnlineStatus() - ); - console.log( - `${new Date().toISOString()} callbackContactsLivenessUpdated: received ${ - this.livenessData.get(public_key).message_type - } from contact ${public_key} with latency ${ - this.livenessData.get(public_key).latency - } at ${this.livenessData.get(public_key).last_seen} and is ${ - this.livenessData.get(public_key).online_status - }` - ); - this.contactsLivenessDataUpdated += 1; - data.destroy(); - }; - - addLivenessData(public_key, latency, last_seen, message_type, online_status) { - let data = { - latency: latency, - last_seen: last_seen, - message_type: message_type, - online_status: online_status, - }; - this.livenessData.set(public_key, data); - } - - onBalanceUpdated = (ptr) => { - let b = new Balance(); - b.pointerAssign(ptr); - this.balance.available = b.getAvailable(); - this.balance.timeLocked = b.getTimeLocked(); - this.balance.pendingIn = b.getPendingIncoming(); - this.balance.pendingOut = b.getPendingOutgoing(); - console.log( - `${new Date().toISOString()} callbackBalanceUpdated: available = ${ - this.balance.available - }, time locked = ${this.balance.timeLocked} pending incoming = ${ - this.balance.pendingIn - } pending outgoing = ${this.balance.pendingOut}` - ); - b.destroy(); - }; - - onTransactionValidationComplete = (request_key, validation_results) => { - console.log( - `${new Date().toISOString()} callbackTransactionValidationComplete(${request_key},${validation_results})` - ); - this.tx_validation_complete = true; - this.tx_validation_result = validation_results; - }; - - onSafMessageReceived = () => { - console.log(`${new Date().toISOString()} callbackSafMessageReceived()`); - this.transactionSafMessageReceived += 1; - }; - - onRecoveryProgress = (a, b, c) => { - console.log( - `${new Date().toISOString()} recoveryProgressCallback(${a},${b},${c})` - ); - if (a === 4) { - console.log(`Recovery completed, funds recovered: ${c} uT`); - } - }; - - startRecovery(base_node_pubkey) { - let node_pubkey = PublicKey.fromHexString(utf8.encode(base_node_pubkey)); - InterfaceFFI.walletStartRecovery( - this.ptr, - node_pubkey.getPtr(), - this.recoveryProgressCallback - ); - node_pubkey.destroy(); - } - - recoveryInProgress() { - return InterfaceFFI.walletIsRecoveryInProgress(this.ptr); - } - - onConnectivityStatusChange = (status) => { - console.log("Connectivity Status Changed to ", status); - }; - - getPublicKey() { - let ptr = InterfaceFFI.walletGetPublicKey(this.ptr); - let pk = new PublicKey(); - pk.pointerAssign(ptr); - let result = pk.getHex(); - pk.destroy(); - return result; - } - - getBalance() { - return this.balance; - } - - pollBalance() { - let b = new Balance(); - let ptr = InterfaceFFI.walletGetBalance(this.ptr); - b.pointerAssign(ptr); - this.balance.available = b.getAvailable(); - this.balance.timeLocked = b.getTimeLocked(); - this.balance.pendingIn = b.getPendingIncoming(); - this.balance.pendingOut = b.getPendingOutgoing(); - b.destroy(); - return this.balance; - } - - getEmojiId() { - let ptr = InterfaceFFI.walletGetPublicKey(this.ptr); - let pk = new PublicKey(); - pk.pointerAssign(ptr); - let result = pk.getEmojiId(); - pk.destroy(); - return result; - } - - addBaseNodePeer(public_key_hex, address) { - let public_key = PublicKey.fromHexString(utf8.encode(public_key_hex)); - let result = InterfaceFFI.walletAddBaseNodePeer( - this.ptr, - public_key.getPtr(), - utf8.encode(address) - ); - public_key.destroy(); - return result; - } - - sendTransaction(destination, amount, fee_per_gram, message, one_sided) { - let dest_public_key = PublicKey.fromHexString(utf8.encode(destination)); - let result = InterfaceFFI.walletSendTransaction( - this.ptr, - dest_public_key.getPtr(), - amount, - fee_per_gram, - utf8.encode(message), - one_sided - ); - dest_public_key.destroy(); - return result; - } - - applyEncryption(passphrase) { - InterfaceFFI.walletApplyEncryption(this.ptr, utf8.encode(passphrase)); - } - - getCompletedTransactions() { - let list_ptr = InterfaceFFI.walletGetCompletedTransactions(this.ptr); - return new CompletedTransactions(list_ptr); - } - - getInboundTransactions() { - let list_ptr = InterfaceFFI.walletGetPendingInboundTransactions(this.ptr); - return new PendingInboundTransactions(list_ptr); - } - - getOutboundTransactions() { - let list_ptr = InterfaceFFI.walletGetPendingOutboundTransactions(this.ptr); - return new PendingOutboundTransactions(list_ptr); - } - - getContacts() { - let list_ptr = InterfaceFFI.walletGetContacts(this.ptr); - return new Contacts(list_ptr); - } - - addContact(alias, pubkey_hex) { - let public_key = PublicKey.fromHexString(utf8.encode(pubkey_hex)); - let contact = new Contact(); - contact.pointerAssign( - InterfaceFFI.contactCreate(utf8.encode(alias), public_key.getPtr()) - ); - let result = InterfaceFFI.walletUpsertContact(this.ptr, contact.getPtr()); - contact.destroy(); - public_key.destroy(); - return result; - } - - removeContact(contact) { - let result = InterfaceFFI.walletRemoveContact(this.ptr, contact.getPtr()); - contact.destroy(); - return result; - } - - cancelPendingTransaction(tx_id) { - return InterfaceFFI.walletCancelPendingTransaction(this.ptr, tx_id); - } - - startTxoValidation() { - return InterfaceFFI.walletStartTxoValidation(this.ptr); - } - - startTxValidation() { - return InterfaceFFI.walletStartTransactionValidation(this.ptr); - } - - listConnectedPublicKeys() { - return InterfaceFFI.commsListConnectedPublicKeys(this.ptr); - } - - getFeePerGramStats(count) { - return new FeePerGramStats( - InterfaceFFI.walletGetFeePerGramStats(this.ptr, count) - ); - } - - destroy() { - if (this.ptr) { - InterfaceFFI.walletDestroy(this.ptr); - this.ptr = undefined; //prevent double free segfault - this.callback_received_transaction = - this.callback_received_transaction_reply = - this.callback_received_finalized_transaction = - this.callback_transaction_broadcast = - this.callback_transaction_mined = - this.callback_transaction_mined_unconfirmed = - this.callback_faux_transaction_confirmed = - this.callback_faux_transaction_unconfirmed = - this.callback_transaction_send_result = - this.callback_transaction_cancellation = - this.callback_txo_validation_complete = - this.callback_contacts_liveness_data_updated = - this.callback_balance_updated = - this.callback_transaction_validation_complete = - this.callback_saf_message_received = - this.recoveryProgressCallback = - this.callback_connectivity_status = - undefined; // clear callback function pointers - } - } -} - -module.exports = Wallet; diff --git a/integration_tests/helpers/hashing.js b/integration_tests/helpers/hashing.js deleted file mode 100644 index 4cce0bdb03..0000000000 --- a/integration_tests/helpers/hashing.js +++ /dev/null @@ -1,96 +0,0 @@ -/* - * // Copyright 2022. The Tari Project - * // - * // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * // following conditions are met: - * // - * // 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * // disclaimer. - * // - * // 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * // following disclaimer in the documentation and/or other materials provided with the distribution. - * // - * // 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote - * // products derived from this software without specific prior written permission. - * // - * // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE - * // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -const { blake2bInit, blake2bUpdate, blake2bFinal } = require("blakejs"); -const { toLittleEndian } = require("./util"); - -class Blake256 { - constructor() { - this.hasher = blake2bInit(32); - } - - chain(value, encoding = undefined) { - this.update(value, encoding); - return this; - } - - update(value, encoding = undefined) { - let buf = Buffer.isBuffer(value) ? value : Buffer.from(value, encoding); - blake2bUpdate(this.hasher, buf); - } - - finalize() { - return blake2bFinal(this.hasher); - } -} - -class DomainHashing { - constructor(label) { - this.hasher = new Blake256(); - this.update(Buffer.from(label, "utf-8")); - } - chain(value, encoding = undefined) { - this.update(value, encoding); - return this; - } - - update(value, encoding = undefined) { - let buf = Buffer.isBuffer(value) ? value : Buffer.from(value, encoding); - let le = toLittleEndian(buf.length, 64); - this.hasher.update(le); - this.hasher.update(buf); - } - - finalize() { - return this.hasher.finalize(); - } -} - -exports.DomainHashing = DomainHashing; -exports.Blake256 = Blake256; - -function hasherWithLabel(label) { - let len = toLittleEndian(label.length, 64); - let hasher = new Blake256(); - return hasher.chain(len).chain(label, "utf-8"); -} - -module.exports = { - // DomainHashing, - Blake256, - domainHashers: { - transactionKdf(label) { - return new DomainHashing( - `com.tari.base_layer.core.transactions.kdf.v0.${label}` - ); - }, - }, - consensusHashers: { - transactionHasher(label) { - return hasherWithLabel( - `com.tari.base_layer.core.transactions.v0.${label}` - ); - }, - }, -}; diff --git a/integration_tests/helpers/mergeMiningProxyClient.js b/integration_tests/helpers/mergeMiningProxyClient.js deleted file mode 100644 index 529755ba01..0000000000 --- a/integration_tests/helpers/mergeMiningProxyClient.js +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const axios = require("axios"); - -class MergeMiningProxyClient { - constructor(address, nodeClient) { - this.address = address; - this.baseNodeClient = nodeClient; - } - - async getHeight() { - const res = await axios.get(`${this.address}/get_height`); - return res.data.height; - } - - async getBlockTemplate() { - try { - const res = await axios.post(`${this.address}/json_rpc`, { - jsonrpc: "2.0", - id: "0", - method: "getblocktemplate", - params: { - wallet_address: - "5AUoj81i63cBUbiKY5jybsZXRDYb9CppmSjiZXC8ZYT6HZH6ebsQvBecYfRKDYoyzKF2uML9FKkTAc7nJvHKdoDYQEeteRW", - reserve_size: 60, - }, - }); - // console.log(res.data); - // console.log("Blocktemplate:", res.data.result.blocktemplate_blob); - return res.data.result; - } catch (e) { - console.error("getBlockTemplate error: ", e); - throw e; - } - } - - async submitBlock(block) { - const res = await axios.post(`${this.address}/json_rpc`, { - jsonrpc: "2.0", - id: "0", - method: "submit_block", - params: [block], - timeout: 60, - }); - return res.data; - } - - async getLastBlockHeader() { - const res = await axios.post(`${this.address}/json_rpc`, { - jsonrpc: "2.0", - id: "0", - method: "get_last_block_header", - }); - return res.data; - } - - async getBlockHeaderByHash(hash) { - const res = await axios.post(`${this.address}/json_rpc`, { - jsonrpc: "2.0", - id: "0", - method: "get_block_header_by_hash", - params: { - hash: hash, - }, - }); - return res.data; - } - - async mineBlock() { - // Mines a block in the same way that xmrig would - const template = await this.getBlockTemplate(); - // XMRig always calls this, so duplicated here - await this.getHeight(); - const block = template.blocktemplate_blob; - // Need to insert a nonce into the template as xmrig would for it to be a valid block. - await this.submitBlock(block); - } - - async mineBlocksUntilHeightIncreasedBy(numBlocks) { - let tipHeight = parseInt(await this.baseNodeClient.getTipHeight()); - const height = tipHeight + parseInt(numBlocks); - let i = 0; - do { - if (i % 25 === 0) { - console.log( - "[mmProxy client] Tip at", - tipHeight, - "...(stopping at " + height + ")" - ); - } - i += 1; - // Mines a block in the same way that xmrig would - const template = await this.getBlockTemplate(); - const block = template.blocktemplate_blob; - // Need to insert a nonce into the template as xmrig would for it to be a valid block. - tipHeight = parseInt(await this.baseNodeClient.getTipHeight()); - if (tipHeight >= height) { - break; - } - await this.submitBlock(block); - } while (tipHeight + 1 < height); - tipHeight = await this.baseNodeClient.getTipHeight(); - console.log("[mmProxy client] Tip is at target height", tipHeight); - return tipHeight; - } -} - -module.exports = MergeMiningProxyClient; diff --git a/integration_tests/helpers/mergeMiningProxyProcess.js b/integration_tests/helpers/mergeMiningProxyProcess.js deleted file mode 100644 index 0d38ad73d4..0000000000 --- a/integration_tests/helpers/mergeMiningProxyProcess.js +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const { getFreePort } = require("./util"); -const dateFormat = require("dateformat"); -const fs = require("fs"); -const path = require("path"); -const { spawn } = require("child_process"); -const { expect } = require("chai"); -const MergeMiningProxyClient = require("./mergeMiningProxyClient"); -const { createEnv } = require("./config"); -require("https"); -require("http"); - -let outputProcess; - -class MergeMiningProxyProcess { - constructor( - name, - baseNodeAddress, - baseNodeClient, - walletAddress, - logFilePath, - submitOrigin = true - ) { - this.name = name; - this.baseNodeAddress = baseNodeAddress; - this.baseNodeClient = baseNodeClient; - this.walletAddress = walletAddress; - this.submitOrigin = submitOrigin; - this.logFilePath = logFilePath ? path.resolve(logFilePath) : logFilePath; - } - - async init() { - this.port = await getFreePort(); - this.name = `MMProxy${this.port}-${this.name}`; - this.baseDir = `./temp/base_nodes/${dateFormat( - new Date(), - "yyyymmddHHMM" - )}/${this.name}`; - console.log( - "MergeMiningProxyProcess init - assign server GRPC:", - this.port - ); - if (!fs.existsSync(this.baseDir)) { - fs.mkdirSync(this.baseDir + "/log", { recursive: true }); - } - } - - run(cmd, args) { - return new Promise((resolve, reject) => { - const proxyFullAddress = "/ip4/127.0.0.1/tcp/" + this.port; - - const envs = createEnv({ - walletGrpcAddress: this.walletAddress, - baseNodeGrpcAddress: this.baseNodeAddress, - proxyFullAddress, - }); - const extraEnvs = { - ["merge_mining_proxy.submit_to_origin"]: this.submitOrigin, - }; - const completeEnvs = { ...envs, ...extraEnvs }; - Object.keys(completeEnvs).forEach((k) => { - args.push("-p"); - args.push(`${k}=${completeEnvs[k]}`); - }); - console.log(args.filter((s) => s !== "-p").join("\n")); - const ps = spawn(cmd, args); - - ps.stdout.on("data", (data) => { - // console.log(`stdout: ${data}`); - fs.appendFileSync(`${this.baseDir}/log/stdout.log`, data.toString()); - if (data.toString().match(/Listening on/)) { - resolve(ps); - } - }); - - ps.stderr.on("data", (data) => { - console.error(`stderr: ${data}`); - fs.appendFileSync(`${this.baseDir}/log/stderr.log`, data.toString()); - }); - - ps.on("close", (code) => { - const ps = this.ps; - this.ps = null; - if (code) { - console.log(`child process exited with code ${code}`); - reject(`child process exited with code ${code}`); - } else { - resolve(ps); - } - }); - - expect(ps.error).to.be.an("undefined"); - this.ps = ps; - }); - } - - async startNew() { - await this.init(); - const args = ["--base-path", ".", "--network", "localnet"]; - if (this.logFilePath) { - args.push("--log-config", this.logFilePath); - } - - return await this.run(await this.compile(), args); - } - - async compile() { - if (!outputProcess) { - await this.runCommand("cargo", [ - "build", - "--release", - "--locked", - "--bin", - "tari_merge_mining_proxy", - "-Z", - "unstable-options", - "--out-dir", - __dirname + "/../temp/out", - ]); - outputProcess = __dirname + "/../temp/out/tari_merge_mining_proxy"; - } - return outputProcess; - } - - runCommand(cmd, args, opts = { env: {} }) { - return new Promise((resolve, reject) => { - const ps = spawn(cmd, args, { - cwd: this.baseDir, - // shell: true, - env: { ...process.env, ...opts.env }, - }); - - ps.stdout.on("data", (data) => { - // console.log(`stdout: ${data}`); - fs.appendFileSync(`${this.baseDir}/log/stdout.log`, data.toString()); - resolve(ps); - }); - - ps.stderr.on("data", (data) => { - console.error(`stderr: ${data}`); - fs.appendFileSync(`${this.baseDir}/log/stderr.log`, data.toString()); - }); - - ps.on("close", (code) => { - const ps = this.ps; - this.ps = null; - if (code) { - console.log(`child process exited with code ${code}`); - reject(`child process exited with code ${code}`); - } else { - resolve(ps); - } - }); - }); - } - - stop() { - return new Promise((resolve) => { - if (!this.ps) { - return resolve(); - } - this.ps.on("close", (code) => { - if (code) { - console.log(`child process exited with code ${code}`); - } - resolve(); - }); - this.ps.kill("SIGINT"); - }); - } - - createClient() { - const address = "http://127.0.0.1:" + this.port; - // console.log("MergeMiningProxyProcess createClient - client address:", address); - return new MergeMiningProxyClient(address, this.baseNodeClient); - } -} - -module.exports = MergeMiningProxyProcess; diff --git a/integration_tests/helpers/miningNodeProcess.js b/integration_tests/helpers/miningNodeProcess.js deleted file mode 100644 index cea3aafea4..0000000000 --- a/integration_tests/helpers/miningNodeProcess.js +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const dateFormat = require("dateformat"); -const fs = require("fs"); -const path = require("path"); -const { spawn } = require("child_process"); -const { expect } = require("chai"); -const { createEnv } = require("./config"); - -let outputProcess; - -class MiningNodeProcess { - constructor( - name, - baseNodeAddress, - baseNodeClient, - walletAddress, - logFilePath, - mineOnTipOnly = true - ) { - this.name = `Miner-${name}`; - this.maxBlocks = 1; - this.mineTillHeight = 1000000; - this.walletAddress = walletAddress; - this.baseNodeAddress = baseNodeAddress; - this.minDiff = 0; - this.maxDiff = 100000; - this.baseNodeClient = baseNodeClient; - this.logFilePath = logFilePath ? path.resolve(logFilePath) : logFilePath; - this.mineOnTipOnly = mineOnTipOnly; - this.numMiningThreads = 4; - } - - async init( - maxBlocks, - mineTillHeight, - minDiff, - maxDiff, - mineOnTipOnly, - numMiningThreads - ) { - this.maxBlocks = maxBlocks || this.maxBlocks; - this.mineTillHeight = mineTillHeight || this.mineTillHeight; - this.minDiff = minDiff || this.minDiff; - this.maxDiff = Math.max(maxDiff || this.maxDiff, this.minDiff); - this.baseDir = `./temp/base_nodes/${dateFormat( - new Date(), - "yyyymmddHHMM" - )}/${this.name}`; - - // Can't use the || shortcut here because `false` is a valid value - this.mineOnTipOnly = - mineOnTipOnly === undefined || mineOnTipOnly === null - ? this.mineOnTipOnly - : mineOnTipOnly; - this.numMiningThreads = numMiningThreads || this.numMiningThreads; - if (!fs.existsSync(this.baseDir)) { - fs.mkdirSync(this.baseDir + "/log", { recursive: true }); - } - } - - run(cmd, args) { - return new Promise((resolve, reject) => { - const envs = createEnv({ - walletGrpcAddress: this.walletAddress, - baseNodeGrpcAddress: this.baseNodeAddress, - options: { - mineOnTipOnly: this.mineOnTipOnly, - numMiningThreads: this.numMiningThreads, - }, - }); - Object.keys(envs).forEach((k) => { - args.push("-p"); - args.push(`${k}=${envs[k]}`); - }); - - const ps = spawn(cmd, args, { - cwd: this.baseDir, - // shell: true, - env: { ...process.env }, - }); - - ps.stdout.on("data", (data) => { - // console.log(`stdout: ${data}`); - fs.appendFileSync(`${this.baseDir}/log/stdout.log`, data.toString()); - // resolve(ps); - }); - - ps.stderr.on("data", (data) => { - console.error(`stderr: ${data}`); - fs.appendFileSync(`${this.baseDir}/log/stderr.log`, data.toString()); - }); - - ps.on("close", (code) => { - const ps = this.ps; - this.ps = null; - if (code) { - console.log(`child process exited with code ${code}`); - reject(`child process exited with code ${code}`); - } else { - resolve(ps); - } - }); - - expect(ps.error).to.be.an("undefined"); - this.ps = ps; - }); - } - - runCommand(cmd, args, opts = { env: {} }) { - return new Promise((resolve, reject) => { - const ps = spawn(cmd, args, { - cwd: this.baseDir, - // shell: true, - env: { ...process.env, ...opts.env }, - }); - - ps.stdout.on("data", (data) => { - // console.log(`stdout: ${data}`); - fs.appendFileSync(`${this.baseDir}/log/stdout.log`, data.toString()); - resolve(ps); - }); - - ps.stderr.on("data", (data) => { - console.error(`stderr: ${data}`); - fs.appendFileSync(`${this.baseDir}/log/stderr.log`, data.toString()); - }); - - ps.on("close", (code) => { - const ps = this.ps; - this.ps = null; - if (code) { - console.log(`child process exited with code ${code}`); - reject(`child process exited with code ${code}`); - } else { - resolve(ps); - } - }); - }); - } - - async startNew() { - await this.init(); - const args = [ - "--base-path", - ".", - "--max-blocks", - this.maxBlocks, - "--mine-until-height", - this.mineTillHeight, - "--min-difficulty", - this.minDiff, - "--max-difficulty", - this.maxDiff, - ]; - if (this.logFilePath) { - args.push("--log-config", this.logFilePath); - } - return await this.run(await this.compile(), args, true); - } - - async compile() { - if (!outputProcess) { - await this.runCommand("cargo", [ - "build", - "--release", - "--locked", - "--bin", - "tari_miner", - "-Z", - "unstable-options", - "--out-dir", - __dirname + "/../temp/out", - ]); - outputProcess = __dirname + "/../temp/out/tari_miner"; - } - return outputProcess; - } - - stop() { - return new Promise((resolve) => { - if (!this.ps) { - return resolve(); - } - this.ps.on("close", (code) => { - if (code) { - console.log(`child process exited with code ${code}`); - } - resolve(); - }); - this.ps.kill("SIGINT"); - }); - } - - async mineBlocksUntilHeightIncreasedBy( - numBlocks, - minDifficulty, - mineOnTipOnly - ) { - const height = - parseInt(await this.baseNodeClient.getTipHeight()) + parseInt(numBlocks); - await this.init( - numBlocks, - height, - minDifficulty, - 9999999999, - mineOnTipOnly, - 1 - ); - await this.startNew(); - await this.stop(); - const tipHeight = await this.baseNodeClient.getTipHeight(); - console.log(`[${this.name}] Tip at ${tipHeight}`); - return tipHeight; - } -} - -module.exports = MiningNodeProcess; diff --git a/integration_tests/helpers/transactionBuilder.js b/integration_tests/helpers/transactionBuilder.js deleted file mode 100644 index d0acd2aa0a..0000000000 --- a/integration_tests/helpers/transactionBuilder.js +++ /dev/null @@ -1,507 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const tari_crypto = require("tari_crypto"); -const { - toLittleEndian, - littleEndianHexStringToBigEndianHexString, - combineTwoTariKeys, - assertBufferType, - varintEncode, -} = require("../helpers/util"); -const { featuresToConsensusBytes } = require("./transactionOutputHashing"); -const { consensusHashers } = require("./hashing"); -const { OutputType } = require("./types"); - -class TransactionBuilder { - constructor() { - this.kv = tari_crypto.KeyRing.new(); - this.inputs = []; - this.outputs = []; - this.fee = 100; - this.lockHeight = 0; - } - - generatePrivateKey(id) { - this.kv.new_key(id); - return this.kv.private_key(id); - } - - buildKernelChallenge(publicNonce, publicExcess, fee, lockHeight, features) { - const option_none = Buffer.from("00", "hex"); - let hash = consensusHashers - .transactionHasher("kernel_signature") - .chain(publicNonce, "hex") - .chain(publicExcess, "hex") - .chain(toLittleEndian(fee, 64)) - .chain(varintEncode(lockHeight)) - .chain(toLittleEndian(features, 8)) - .chain(option_none) - .finalize(); - - return Buffer.from(hash).toString("hex"); - } - - toLengthEncoded(buf) { - return Buffer.concat([varintEncode(buf.length), buf]); - } - - // Everything passed into this function must be a byte buffer except for features, minimumValuePromise - buildMetaChallenge( - script, - features, - scriptOffsetPublicKey, - publicNonce, - commitment, - covenant, - encryptedValue, - minimumValuePromise - ) { - assertBufferType(publicNonce, 32); - assertBufferType(script); - assertBufferType(scriptOffsetPublicKey, 32); - assertBufferType(commitment, 32); - assertBufferType(covenant); - assertBufferType(encryptedValue); - - // base_layer/core/src/transactions/transaction/transaction_output.rs - let hash = consensusHashers - .transactionHasher("metadata_signature") - .chain(publicNonce) - .chain(this.toLengthEncoded(script)) - .chain(featuresToConsensusBytes(features)) - .chain(scriptOffsetPublicKey) - .chain(commitment) - .chain(this.toLengthEncoded(covenant)) - .chain(encryptedValue) - .chain(toLittleEndian(minimumValuePromise, 64)) - .finalize(); - - return Buffer.from(hash); - } - - buildScriptChallenge( - publicNonce, - script, - input_data, - public_key, - commitment - ) { - assertBufferType(publicNonce); - assertBufferType(script); - assertBufferType(input_data); - assertBufferType(public_key, 32); - assertBufferType(commitment, 32); - let hash = consensusHashers - .transactionHasher("script_challenge") - .chain(publicNonce) - .chain(this.toLengthEncoded(script)) - .chain(this.toLengthEncoded(input_data)) - .chain(public_key) - .chain(commitment) - .finalize(); - - return Buffer.from(hash); - } - - changeFee(fee) { - this.fee = fee; - } - - addInput(input) { - let nopScriptBytes = Buffer.from([0x73]); - let scriptPublicKeyHex = tari_crypto.pubkey_from_secret( - input.scriptPrivateKey.toString("hex") - ); - let scriptPublicKey = Buffer.from(scriptPublicKeyHex, "hex"); - // The 0x04 is type code for a pubkey in TariScript - let input_data = Buffer.concat([Buffer.from([0x04]), scriptPublicKey]); - this.kv.new_key("common_nonce_1"); - this.kv.new_key("common_nonce_2"); - let private_nonce_1 = this.kv.private_key("common_nonce_1"); - let private_nonce_2 = this.kv.private_key("common_nonce_2"); - let public_nonce_hex = tari_crypto.commit_private_keys( - private_nonce_1, - private_nonce_2 - ).commitment; - let public_nonce = Buffer.from(public_nonce_hex, "hex"); - - let commitment = input.output.commitment; - let challenge = this.buildScriptChallenge( - public_nonce, - nopScriptBytes, - input_data, - scriptPublicKey, - commitment - ); - let amount_key = Buffer.from(toLittleEndian(input.amount, 256)).toString( - "hex" - ); - let total_key = combineTwoTariKeys( - input.scriptPrivateKey.toString(), - input.privateKey.toString() - ); - - let script_sig = tari_crypto.sign_comsig_challenge_with_nonce( - amount_key, - total_key, - private_nonce_1, - private_nonce_2, - challenge.toString("hex") - ); - this.inputs.push({ - input: { - features: input.output.features, - commitment: input.output.commitment, - script: nopScriptBytes, - input_data: input_data, - height: 0, - script_signature: { - public_nonce_commitment: Buffer.from(script_sig.public_nonce, "hex"), - signature_u: Buffer.from(script_sig.u, "hex"), - signature_v: Buffer.from(script_sig.v, "hex"), - }, - sender_offset_public_key: input.output.sender_offset_public_key, - covenant: Buffer.from([]), - encrypted_value: input.output.encrypted_value, - minimum_value_promise: input.output.minimum_value_promise, - }, - amount: input.amount, - privateKey: input.privateKey, - scriptPrivateKey: input.scriptPrivateKey, - }); - } - - addOutput(amount, features = {}) { - let key = Math.floor(Math.random() * 500000000000 + 1); - let privateKey = Buffer.from(toLittleEndian(key, 256)).toString("hex"); - let scriptKey = Math.floor(Math.random() * 500000000000 + 1); - let scriptPrivateKey = Buffer.from(toLittleEndian(scriptKey, 256)).toString( - "hex" - ); - let scriptOffsetPrivateKeyNum = Math.floor( - Math.random() * 500000000000 + 1 - ); - let scriptOffsetPrivateKey = Buffer.from( - toLittleEndian(scriptOffsetPrivateKeyNum, 256) - ).toString("hex"); - let scriptOffsetPublicKeyHex = tari_crypto.pubkey_from_secret( - scriptOffsetPrivateKey.toString("hex") - ); - let scriptOffsetPublicKey = Buffer.from(scriptOffsetPublicKeyHex, "hex"); - - let nopScriptBytes = Buffer.from([0x73]); - let covenantBytes = Buffer.from([]); - - let rangeproofFactory = tari_crypto.ExtendedRangeProofFactory.new(); - let rangeproof = rangeproofFactory.create_proof( - privateKey, - BigInt(amount) - ).proof; - let amount_key = Buffer.from(toLittleEndian(amount, 256)).toString("hex"); - this.kv.new_key("common_nonce_1"); - this.kv.new_key("common_nonce_2"); - let private_nonce_1 = this.kv.private_key("common_nonce_1"); - let private_nonce_2 = this.kv.private_key("common_nonce_2"); - let public_nonce_hex = tari_crypto.commit_private_keys( - private_nonce_1, - private_nonce_2 - ).commitment; - let public_nonce = Buffer.from(public_nonce_hex, "hex"); - let commitment = Buffer.from( - tari_crypto.commit(privateKey, BigInt(amount)).commitment, - "hex" - ); - let encryptedValue = Buffer.concat([ - Buffer.from(toLittleEndian(amount, 64)), - Buffer.alloc(16), - ]); - const outputFeatures = Object.assign({ - output_type: OutputType.STANDARD, - maturity: 0, - metadata: [], - // In case any of these change, update the buildMetaChallenge function - unique_id: features.unique_id - ? Buffer.from(features.unique_id, "utf8") - : null, - sidechain_feature: null, - parent_public_key: null, - asset: null, - mint_non_fungible: null, - sidechain_checkpoint: null, - committee_definition: null, - }); - let minimumValuePromise = 0; - let meta_challenge = this.buildMetaChallenge( - nopScriptBytes, - outputFeatures, - scriptOffsetPublicKey, - public_nonce, - commitment, - covenantBytes, - encryptedValue, - minimumValuePromise - ); - let total_key = combineTwoTariKeys( - scriptOffsetPrivateKey.toString(), - privateKey.toString() - ); - let meta_sig = tari_crypto.sign_comsig_challenge_with_nonce( - amount_key, - total_key, - private_nonce_1, - private_nonce_2, - meta_challenge.toString("hex") - ); - let output = { - amount: amount, - privateKey: privateKey, - scriptPrivateKey: scriptPrivateKey, - scriptOffsetPrivateKey: scriptOffsetPrivateKey, - output: { - features: outputFeatures, - commitment: commitment, - range_proof: Buffer.from(rangeproof, "hex"), - script: nopScriptBytes, - sender_offset_public_key: Buffer.from(scriptOffsetPublicKey, "hex"), - metadata_signature: { - public_nonce_commitment: Buffer.from(meta_sig.public_nonce, "hex"), - signature_u: Buffer.from(meta_sig.u, "hex"), - signature_v: Buffer.from(meta_sig.v, "hex"), - }, - covenant: covenantBytes, - encrypted_value: encryptedValue, - minimum_value_promise: minimumValuePromise, - }, - }; - this.outputs.push(output); - return output; - } - - getSpendableAmount() { - let sum = 0; - this.inputs.forEach((input) => (sum = sum + input.amount)); - return sum - this.fee; - } - - build() { - let totalPrivateKey = 0n; - let script_offset = tari_crypto.secret_key_from_hex_bytes( - "0000000000000000000000000000000000000000000000000000000000000000" - ); - - this.inputs.forEach((input) => { - totalPrivateKey -= BigInt( - littleEndianHexStringToBigEndianHexString(input.privateKey.toString()) - ); - - script_offset = tari_crypto.add_secret_keys( - script_offset, - input.scriptPrivateKey.toString("hex") - ); - }); - this.outputs.forEach((output) => { - totalPrivateKey += BigInt( - littleEndianHexStringToBigEndianHexString(output.privateKey.toString()) - ); - script_offset = tari_crypto.subtract_secret_keys( - script_offset, - output.scriptOffsetPrivateKey - ); - }); - - // We need to check for wrap around as these private keys are supposed to be unsigned integers, - // but in js these are floats. So we add the little endian number that is required to wrap the number so that it is - // again positive. This is the (max number -1) that tari_crypto can accommodate. - if (totalPrivateKey < 0) { - totalPrivateKey = - totalPrivateKey + - BigInt( - littleEndianHexStringToBigEndianHexString( - "edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010" - ) - ); - } - - let totalPrivateKeyHex = totalPrivateKey.toString(16); - while (totalPrivateKeyHex.length < 64) { - totalPrivateKeyHex = "0" + totalPrivateKeyHex; - } - let privateKey = - littleEndianHexStringToBigEndianHexString(totalPrivateKeyHex); - while (privateKey.length < 64) { - privateKey = "0" + privateKey; - } - - const excess = tari_crypto.commit(privateKey, BigInt(0)); - this.kv.new_key("common_nonce"); - const publicNonce = this.kv.public_key("common_nonce"); - let PublicKeyExcess = tari_crypto.pubkey_from_secret( - privateKey.toString("hex") - ); - const challenge = this.buildKernelChallenge( - publicNonce, - PublicKeyExcess, - this.fee, - this.lockHeight, - 0 - ); - const privateNonce = this.kv.private_key("common_nonce"); - const sig = tari_crypto.sign_challenge_with_nonce( - privateKey, - privateNonce, - challenge - ); - return { - offset: Buffer.from(toLittleEndian(0, 256), "hex"), - script_offset: Buffer.from(script_offset, "hex"), - body: { - inputs: this.inputs.map((i) => i.input), - outputs: this.outputs.map((o) => o.output), - kernels: [ - { - features: 0, - fee: this.fee, - lock_height: this.lockHeight, - excess: Buffer.from(excess.commitment, "hex"), - excess_sig: { - public_nonce: Buffer.from(sig.public_nonce, "hex"), - signature: Buffer.from(sig.signature, "hex"), - }, - }, - ], - }, - }; - } - - generateCoinbase(value, privateKey, fee, lockHeight) { - let coinbase = tari_crypto.commit(privateKey, BigInt(value + fee)); - let nopScriptBytes = Buffer.from([0x73]); - let covenantBytes = Buffer.from([]); - let scriptOffsetPrivateKeyNum = Math.floor( - Math.random() * 500000000000 + 1 - ); - let scriptOffsetPrivateKey = Buffer.from( - toLittleEndian(scriptOffsetPrivateKeyNum, 256) - ).toString("hex"); - let scriptOffsetPublicKeyHex = tari_crypto.pubkey_from_secret( - scriptOffsetPrivateKey.toString("hex") - ); - let scriptOffsetPublicKey = Buffer.from(scriptOffsetPublicKeyHex, "hex"); - - let rangeproofFactory = tari_crypto.ExtendedRangeProofFactory.new(); - let rangeproof = rangeproofFactory.create_proof( - privateKey.toString("hex"), - BigInt(value + fee) - ).proof; - const excess = tari_crypto.commit(privateKey, BigInt(0)); - this.kv.new_key("nonce"); - const public_nonce = this.kv.public_key("nonce"); - let PublicKeyExcess = tari_crypto.pubkey_from_secret( - privateKey.toString("hex") - ); - - const challenge = this.buildKernelChallenge( - public_nonce, - PublicKeyExcess, - 0, - 0, - 1 - ); - const private_nonce = this.kv.private_key("nonce"); - const sig = tari_crypto.sign_challenge_with_nonce( - privateKey, - private_nonce, - challenge - ); - - let amount_key = Buffer.from(toLittleEndian(value + fee, 256)).toString( - "hex" - ); - this.kv.new_key("common_nonce_1"); - this.kv.new_key("common_nonce_2"); - let private_nonce_1 = this.kv.private_key("common_nonce_1"); - let private_nonce_2 = this.kv.private_key("common_nonce_2"); - let public_nonce_c_hex = tari_crypto.commit_private_keys( - private_nonce_1, - private_nonce_2 - ).commitment; - let public_nonce_c = Buffer.from(public_nonce_c_hex, "hex"); - let commitment = Buffer.from( - tari_crypto.commit(privateKey, BigInt(value + fee)).commitment, - "hex" - ); - let encryptedValue = Buffer.concat([ - Buffer.from(toLittleEndian(value, 64)), - Buffer.alloc(16), - ]); - let outputFeatures = { - output_type: OutputType.COINBASE, - maturity: lockHeight, - metadata: [], - // In case any of these change, update the buildMetaChallenge function - unique_id: null, - parent_public_key: null, - asset: null, - mint_non_fungible: null, - sidechain_checkpoint: null, - committee_definition: null, - }; - let minimumValuePromise = 0; - let meta_challenge = this.buildMetaChallenge( - nopScriptBytes, - outputFeatures, - scriptOffsetPublicKey, - public_nonce_c, - commitment, - covenantBytes, - encryptedValue, - minimumValuePromise - ); - let total_key = combineTwoTariKeys( - scriptOffsetPrivateKey.toString(), - privateKey.toString() - ); - let meta_sig = tari_crypto.sign_comsig_challenge_with_nonce( - amount_key, - total_key, - private_nonce_1, - private_nonce_2, - meta_challenge.toString("hex") - ); - - return { - outputs: [ - { - features: outputFeatures, - commitment: Buffer.from(coinbase.commitment, "hex"), - range_proof: Buffer.from(rangeproof, "hex"), - script: nopScriptBytes, - sender_offset_public_key: Buffer.from(scriptOffsetPublicKey, "hex"), - metadata_signature: { - public_nonce_commitment: Buffer.from(meta_sig.public_nonce, "hex"), - signature_u: Buffer.from(meta_sig.u, "hex"), - signature_v: Buffer.from(meta_sig.v, "hex"), - }, - covenant: covenantBytes, - encrypted_value: encryptedValue, - minimum_value_promise: minimumValuePromise, - }, - ], - kernels: [ - { - features: 1, - fee: 0, - lock_height: 0, - excess: Buffer.from(excess.commitment, "hex"), - excess_sig: { - public_nonce: Buffer.from(sig.public_nonce, "hex"), - signature: Buffer.from(sig.signature, "hex"), - }, - }, - ], - }; - } -} - -module.exports = TransactionBuilder; diff --git a/integration_tests/helpers/transactionOutputHashing.js b/integration_tests/helpers/transactionOutputHashing.js deleted file mode 100644 index 21df98e064..0000000000 --- a/integration_tests/helpers/transactionOutputHashing.js +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const { consensusHashers } = require("./hashing"); -const { - toLittleEndian, - encodeOption, - toLengthEncoded, - assertBufferType, -} = require("./util"); - -const featuresToConsensusBytes = function (features) { - // base_layer\core\src\transactions\transaction\output_features.rs (fn consensus_encode) - - // TODO: Keep this number in sync with 'get_current_version()' in 'output_features_version.rs' - const OUTPUT_FEATURES_VERSION = 0x00; - - return Buffer.concat([ - // version - Buffer.from([OUTPUT_FEATURES_VERSION]), - // maturity - Buffer.from([parseInt(features.maturity || 0)]), - // output_type - Buffer.from([features.output_type]), - // sidechain_feature - // TODO: SideChainFeatures - encodeOption(null), - // metadata - // TODO: Vec (len is 0) - Buffer.from([0x00]), - ]); -}; - -const getTransactionOutputHash = function (output) { - // base_layer\core\src\transactions\transaction_components\mod.rs (fn hash_output) - - // TODO: Keep this number in sync with 'get_current_version()' in 'transaction_output_version.rs' - const OUTPUT_FEATURES_VERSION = 0x00; - - assertBufferType(output.commitment, 32); - assertBufferType(output.script); - assertBufferType(output.covenant); - assertBufferType(output.encrypted_value, 24); - assertBufferType(output.sender_offset_public_key, 32); - const hash = consensusHashers - .transactionHasher("transaction_output") - // version - .chain(Buffer.from([OUTPUT_FEATURES_VERSION])) - // features - .chain(featuresToConsensusBytes(output.features)) - // commitment - .chain(output.commitment) - // script - .chain(toLengthEncoded(output.script)) - // covenant - .chain(toLengthEncoded(output.covenant)) - // encrypted_value - .chain(output.encrypted_value) - // sender offset public key - .chain(output.sender_offset_public_key) - // minimum_value_promise - .chain(toLittleEndian(output.minimum_value_promise, 64)) - .finalize(); - - const hashBuffer = Buffer.from(hash); - // console.log( - // "\ngetTransactionOutputHash - hash", - // hashBuffer.toString("hex"), - // "\n" - // ); - return hashBuffer; -}; - -module.exports = { getTransactionOutputHash, featuresToConsensusBytes }; diff --git a/integration_tests/helpers/util.js b/integration_tests/helpers/util.js deleted file mode 100644 index cad74473b1..0000000000 --- a/integration_tests/helpers/util.js +++ /dev/null @@ -1,391 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const net = require("net"); -const varint = require("varint"); - -const NO_CONNECTION = 14; - -function getRandomInt(min, max) { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min + 1)) + min; -} - -function sleep(ms) { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); -} - -function withTimeout(ms, promise, message = "") { - const timeout = new Promise((_resolve, reject) => { - setTimeout( - () => reject(new Error(message || `Timed out after ${ms}ms`)), - ms - ); - }); - return Promise.race([timeout, promise]); -} - -async function tryConnect(makeClient, opts = {}) { - const options = Object.assign( - { - deadline: Infinity, - maxAttempts: 3, - }, - opts - ); - let attempts = 0; - for (;;) { - let client = makeClient(); - - // Don't log the uninteresting case - if (attempts > 0) { - console.warn( - `GRPC connection attempt ${attempts + 1}/${options.maxAttempts}` - ); - } - let error = await new Promise((resolve) => { - client.waitForReady(options.deadline, (err) => { - if (err) { - return resolve(err); - } - resolve(null); - }); - }); - - if (error) { - if (attempts >= options.maxAttempts) { - throw error; - } - attempts++; - console.error( - `Failed connection attempt ${attempts + 1}/${options.maxAttempts}` - ); - console.error(error); - await sleep(1000); - continue; - } - - return client; - } -} - -async function waitFor( - asyncTestFn, - toBe, - maxTimeMs, - pollInterval = 500, - skipLog = 50 -) { - const now = new Date(); - - let i = 0; - while (new Date() - now < maxTimeMs) { - try { - const value = await Promise.resolve(asyncTestFn()); - if (value === toBe) { - if (i > 1) { - console.log("waiting for process...", pollInterval, i, value); - } - return true; - } - if (i % skipLog === 0 && i > 1) { - console.log("waiting for process...", pollInterval, i, value); - } - await sleep(pollInterval); - i++; - } catch (e) { - if (i > 1) { - if (e && e.code && e.code === NO_CONNECTION) { - // console.log("No connection yet (waitFor)..."); - } else { - console.error("Error in waitFor: ", e); - } - } - await sleep(pollInterval); - } - } - return false; -} - -async function waitForIterate(testFn, toBe, sleepMs, maxIterations = 500) { - let count = 0; - let val = await Promise.resolve(testFn()); - while (val !== toBe) { - val = await Promise.resolve(testFn()); - if (count >= maxIterations) { - break; - } - count++; - await sleep(sleepMs); - process.stdout.write("."); - } - return val; -} - -async function waitForPredicate(predicate, timeOut, sleepMs = 500) { - let elapsed = 0; - while (elapsed < timeOut) { - const val = await predicate(); - if (val) { - return val; - } - await sleep(sleepMs); - elapsed += sleepMs; - process.stdout.write("."); - } - throw new Error(`Predicate was not truthy after ${timeOut} ms`); -} - -function dec2hex(n) { - return n ? [n % 256].concat(dec2hex(~~(n / 256))) : []; -} - -function toLittleEndianInner(n) { - let hexar = dec2hex(n); - hexar = hexar.map((h) => (h < 16 ? "0" : "") + h.toString(16)); - if (hexar.length < 4) { - return hexar.concat(Array(4 - hexar.length).fill("00")); - } else { - return hexar; - } -} - -function toLittleEndian(n, numBits) { - if (numBits % 8 !== 0) { - throw new Error("toLittleEndian: numBits not a multiple of 8"); - } - - switch (numBits) { - case 8: { - let buf = Buffer.alloc(numBits / 8); - buf.writeUint8(n); - return buf; - } - case 16: { - let buf = Buffer.alloc(numBits / 8); - buf.writeUint16LE(n); - return buf; - } - case 32: { - let buf = Buffer.alloc(numBits / 8); - buf.writeUInt32LE(n); - return buf; - } - case 64: { - let buf = Buffer.alloc(numBits / 8); - buf.writeUInt64LE(n); - return buf; - } - default: { - const s = toLittleEndianInner(n); - - for (let i = s.length; i < numBits / 8; i++) { - s.push("00"); - } - - const arr = Buffer.from(s.join(""), "hex"); - - return arr; - } - } -} - -function littleEndianHexStringToBigEndianHexString(string) { - if (!string) return undefined; - var len = string.length; - var bigEndianHexString = "0x"; - for (var i = 0; i < len / 2; i++) { - bigEndianHexString += string.substring(len - (i + 1) * 2, len - i * 2); - } - return bigEndianHexString; -} - -function hexSwitchEndianness(val) { - let res = ""; - for (let i = val.length - 2; i > 0; i -= 2) { - res += val[i] + val[i + 1]; - } - return res; -} - -const getFreePort = function () { - return new Promise((resolve, reject) => { - const srv = net.createServer(function (_sock) {}); - srv.listen(0, function () { - let { port } = srv.address(); - srv.close((err) => { - if (err) { - reject(err); - } else { - resolve(port); - } - }); - }); - srv.on("error", function (err) { - reject(err); - }); - }); -}; - -const encodeOption = function (value, encoding = "hex") { - let buffer; - if (value) { - buffer = Buffer.concat([ - Buffer.from([0x01]), - encoding ? Buffer.from(value, encoding) : value, - ]); - } else { - buffer = Buffer.from([0x00]); - } - return buffer; -}; - -function consoleLogTransactionDetails(txnDetails) { - console.log( - " Transaction " + - pad("'" + txnDetails.tx_id + "'", 24) + - " has status " + - pad("'" + txnDetails.status + "'", 40) + - " and " + - pad("is_cancelled(" + txnDetails.is_cancelled + ")", 21) - ); -} - -function consoleLogBalance(balance) { - console.log( - " Available " + - pad(balance.available_balance, 16) + - " uT, Pending incoming " + - pad(balance.pending_incoming_balance, 16) + - " uT, Pending outgoing " + - pad(balance.pending_outgoing_balance, 16) + - " uT" - ); -} - -function pad(str, length, padLeft = true) { - const padding = Array(length).join(" "); - if (typeof str === "undefined") return padding; - if (padLeft) { - return (padding + str).slice(-padding.length); - } else { - return (str + padding).substring(" ", padding.length); - } -} - -function combineTwoTariKeys(key1, key2) { - let total_key = - BigInt(littleEndianHexStringToBigEndianHexString(key1)) + - BigInt(littleEndianHexStringToBigEndianHexString(key2)); - if (total_key < 0) { - total_key = - total_key + - BigInt( - littleEndianHexStringToBigEndianHexString( - "edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010" - ) - ); - } - total_key = total_key.toString(16); - while (total_key.length < 64) { - total_key = "0" + total_key; - } - total_key = littleEndianHexStringToBigEndianHexString(total_key); - while (total_key.length < 64) { - total_key = "0" + total_key; - } - return total_key; -} - -const multiAddrToSocket = (string) => { - console.log("input:", string); - let match = string.match(/\/ip4\/(.*)\/tcp\/(.*)/); - if (!match) { - console.log("no match, returning as is", string); - return string; - } - let res = `${match[1]}:${match[2]}`; - console.log("returning: ", res); - return res; -}; - -const byteArrayToHex = (bytes) => - bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), ""); - -const convertHexStringToVec = (string) => - string.match(/.{2}/g).map((x) => parseInt(x, 16)); - -const convertStringToVec = (string) => - Array(string.length) - .fill() - .map((_, i) => string.charCodeAt(i)); - -const findUtxoWithOutputMessage = async (wallet, message) => { - let client = await wallet.connectClient(); - let accepted = []; - - do { - let found_txs = await client.getCompletedTransactions(); - accepted = found_txs.filter((txo) => { - return txo.message == message; - }); - - if (accepted.length > 0) { - break; - } - - await sleep(5000); - } while (accepted.length <= 0); - - return accepted; -}; - -function assertBufferType(buf, len = null) { - if (!Buffer.isBuffer(buf)) { - throw new Error("Expected buffer"); - } - if (len !== null) { - if (buf.length !== len) { - throw new Error("Expected buffer of length " + len); - } - } -} - -function varintEncode(num) { - return Buffer.from(varint.encode(num)); -} - -function toLengthEncoded(buf) { - return Buffer.concat([varintEncode(buf.length), buf]); -} - -module.exports = { - assertBufferType, - varintEncode, - getRandomInt, - sleep, - waitFor, - toLittleEndian, - littleEndianHexStringToBigEndianHexString, - // portInUse, - getFreePort, - hexSwitchEndianness, - consoleLogTransactionDetails, - tryConnect, - consoleLogBalance, - withTimeout, - combineTwoTariKeys, - byteArrayToHex, - convertHexStringToVec, - convertStringToVec, - waitForPredicate, - waitForIterate, - NO_CONNECTION, - multiAddrToSocket, - findUtxoWithOutputMessage, - encodeOption, - toLengthEncoded, -}; diff --git a/integration_tests/helpers/walletClient.js b/integration_tests/helpers/walletClient.js deleted file mode 100644 index 55a957a08e..0000000000 --- a/integration_tests/helpers/walletClient.js +++ /dev/null @@ -1,439 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const { Client } = require("wallet-grpc-client"); -const { - byteArrayToHex, - tryConnect, - convertStringToVec, - multiAddrToSocket, -} = require("./util"); - -function transactionStatus() { - return [ - "TRANSACTION_STATUS_IMPORTED", - "TRANSACTION_STATUS_COINBASE", - "TRANSACTION_STATUS_PENDING", - "TRANSACTION_STATUS_COMPLETED", - "TRANSACTION_STATUS_BROADCAST", - "TRANSACTION_STATUS_MINED_UNCONFIRMED", - "TRANSACTION_STATUS_MINED_CONFIRMED", - ]; -} - -class WalletClient { - constructor(name) { - this.client = null; - this.name = name; - } - - async connect(multiAddrOrSocket) { - this.client = await tryConnect(() => - Client.connect(multiAddrToSocket(multiAddrOrSocket)) - ); - } - - async getVersion() { - return await this.client.getVersion(); - } - - async checkForUpdates() { - return await this.client.checkForUpdates({}); - } - - async getBalance() { - return await this.client.getBalance().then((balance) => ({ - available_balance: parseInt(balance.available_balance), - pending_incoming_balance: parseInt(balance.pending_incoming_balance), - pending_outgoing_balance: parseInt(balance.pending_outgoing_balance), - })); - } - - async getCompletedTransactions() { - const data = await this.client.getCompletedTransactions(); - const transactions = []; - for (let i = 0; i < data.length; i++) { - transactions.push({ - tx_id: data[i].transaction.tx_id, - source_pk: data[i].transaction.source_pk.toString("hex"), - dest_pk: data[i].transaction.dest_pk.toString("hex"), - status: data[i].transaction.status, - direction: data[i].transaction.direction, - amount: data[i].transaction.amount, - fee: data[i].transaction.fee, - is_cancelled: data[i].transaction.is_cancelled, - excess_sig: data[i].transaction.excess_sig.toString("hex"), - timestamp: new Date( - Number(data[i].transaction.timestamp.seconds) * 1000 - ), - message: data[i].transaction.message, - }); - } - return transactions; - } - - async getAllCoinbaseTransactions() { - const data = await this.getCompletedTransactions(); - const transactions = []; - for (let i = 0; i < data.length; i++) { - if ( - data[i].message.includes("Coinbase Transaction for Block ") && - data[i].fee == 0 - ) { - transactions.push(data[i]); - } - } - return transactions; - } - - async getAllSpendableCoinbaseTransactions() { - const data = await this.getAllCoinbaseTransactions(); - const transactions = []; - for (let i = 0; i < data.length; i++) { - if (transactionStatus().indexOf(data[i].status) === 6) { - transactions.push(data[i]); - } - } - return transactions; - } - - async countAllCoinbaseTransactions() { - const data = await this.getCompletedTransactions(); - let count = 0; - for (let i = 0; i < data.length; i++) { - if ( - data[i].message.includes("Coinbase Transaction for Block ") && - data[i].fee == 0 - ) { - count += 1; - } - } - return count; - } - - async countAllSpendableCoinbaseTransactions() { - const data = await this.getAllCoinbaseTransactions(); - let count = 0; - for (let i = 0; i < data.length; i++) { - if (transactionStatus().indexOf(data[i].status) == 6) { - count += 1; - } - } - return count; - } - - async areCoinbasesConfirmedAtLeast(number) { - const data = await this.getAllSpendableCoinbaseTransactions(); - if (data.length >= number) { - return true; - } else { - return false; - } - } - - async getAllNormalTransactions() { - const data = await this.getCompletedTransactions(); - const transactions = []; - for (let i = 0; i < data.length; i++) { - if ( - !( - data[i].message.includes("Coinbase Transaction for Block ") && - data[i].fee == 0 - ) - ) { - transactions.push(data[i]); - } - } - return transactions; - } - - async transfer(args) { - return await this.client.transfer(args); - } - - async burn(args) { - return await this.client.CreateBurnTransaction(args); - } - - async sendHtlc(args) { - return await this.client.SendShaAtomicSwapTransaction(args); - } - - async claimHtlc(args) { - return await this.client.claimShaAtomicSwapTransaction(args); - } - - async claimHtlcRefund(args) { - return await this.client.ClaimHtlcRefundTransaction(args); - } - - async importUtxos(outputs) { - return await this.client.importUtxos({ - outputs: outputs, - }); - } - - async getTransactionInfo(args) { - return await this.client.getTransactionInfo(args); - } - - async identify() { - const info = await this.client.identify(); - return { - public_key: info.public_key, - public_address: info.public_address, - node_id: info.node_id, - }; - } - - async isTransactionRegistered(tx_id) { - try { - await this.getTransactionInfo({ - transaction_ids: [tx_id.toString()], - }); - return true; - } catch (err) { - // Any error here must be treated as if the required status was not achieved - return false; - } - } - - async isBalanceAtLeast(amount) { - try { - const balance = await this.getBalance(); - console.log( - "Waiting for available balance > amount", - balance.available_balance, - amount - ); - if (parseInt(balance.available_balance) >= parseInt(amount)) { - return true; - } else { - return false; - } - } catch (e) { - // Any error here must be treated as if the required status was not achieved - return false; - } - } - - async isBalanceLessThan(amount) { - try { - let balance = await this.getBalance(); - if (parseInt(balance["available_balance"]) < parseInt(amount)) { - return true; - } else { - return false; - } - } catch (err) { - // Any error here must be treated as if the required status was not achieved - return false; - } - } - - async isTransactionAtLeastPending(tx_id) { - try { - const txnDetails = await this.getTransactionInfo({ - transaction_ids: [tx_id.toString()], - }); - if (transactionStatus().indexOf(txnDetails.transactions[0].status) >= 2) { - return true; - } else { - return false; - } - } catch (err) { - // Any error here must be treated as if the required status was not achieved - return false; - } - } - - async isTransactionPending(tx_id) { - try { - const txnDetails = await this.getTransactionInfo({ - transaction_ids: [tx_id.toString()], - }); - if (transactionStatus().indexOf(txnDetails.transactions[0].status) == 2) { - return true; - } else { - return false; - } - } catch (err) { - // Any error here must be treated as if the required status was not achieved - return false; - } - } - - async isTransactionCancelled(tx_id) { - try { - const txnDetails = await this.getTransactionInfo({ - transaction_ids: [tx_id.toString()], - }); - if (transactionStatus().indexOf(txnDetails.transactions[0].status) == 7) { - return true; - } else { - return false; - } - } catch (err) { - // Any error here must be treated as if the required status was not achieved - return false; - } - } - - async isTransactionAtLeastCompleted(tx_id) { - try { - const txnDetails = await this.getTransactionInfo({ - transaction_ids: [tx_id.toString()], - }); - if (transactionStatus().indexOf(txnDetails.transactions[0].status) >= 3) { - return true; - } else { - return false; - } - } catch (err) { - // Any error here must be treated as if the required status was not achieved - return false; - } - } - - async isTransactionAtLeastBroadcast(tx_id) { - try { - const txnDetails = await this.getTransactionInfo({ - transaction_ids: [tx_id.toString()], - }); - if (transactionStatus().indexOf(txnDetails.transactions[0].status) >= 4) { - return true; - } else { - return false; - } - } catch (err) { - // Any error here must be treated as if the required status was not achieved - return false; - } - } - - async isTransactionAtLeastMinedUnconfirmed(tx_id) { - try { - const txnDetails = await this.getTransactionInfo({ - transaction_ids: [tx_id.toString()], - }); - if (transactionStatus().indexOf(txnDetails.transactions[0].status) >= 5) { - return true; - } else { - return false; - } - } catch (err) { - // Any error here must be treated as if the required status was not achieved - return false; - } - } - - async isTransactionMinedUnconfirmed(tx_id) { - try { - const txnDetails = await this.getTransactionInfo({ - transaction_ids: [tx_id.toString()], - }); - if (transactionStatus().indexOf(txnDetails.transactions[0].status) == 5) { - return true; - } else { - return false; - } - } catch (err) { - // Any error here must be treated as if the required status was not achieved - return false; - } - } - - async isTransactionMinedConfirmed(tx_id) { - try { - const txnDetails = await this.getTransactionInfo({ - transaction_ids: [tx_id.toString()], - }); - if (transactionStatus().indexOf(txnDetails.transactions[0].status) == 6) { - return true; - } else { - return false; - } - } catch (err) { - // Any error here must be treated as if the required status was not achieved - return false; - } - } - - async getTransactionDetails(tx_id) { - try { - const txnDetails = await this.getTransactionInfo({ - transaction_ids: [tx_id.toString()], - }); - return [true, txnDetails]; - } catch (err) { - return [false, err]; - } - } - - async coin_split(args) { - return await this.client.coinSplit(args); - } - - async listConnectedPeers() { - const { connected_peers } = await this.client.listConnectedPeers(); - return connected_peers.map((peer) => ({ - ...peer, - public_key: byteArrayToHex(peer.public_key), - node_id: byteArrayToHex(peer.node_id), - supported_protocols: peer.supported_protocols.map((p) => - p.toString("utf8") - ), - features: +peer.features, - })); - } - - async getNetworkStatus() { - let resp = await this.client.getNetworkStatus(); - return { - ...resp, - num_node_connections: +resp.num_node_connections, - }; - } - - async cancelTransaction(tx_id) { - try { - const result = await this.client.cancelTransaction({ - tx_id: tx_id, - }); - return { - success: result.is_success, - failure_message: result.failure_message, - }; - } catch (err) { - return { - success: false, - failure_message: err, - }; - } - } - - async registerAsset(name) { - let public_key = await this.client.registerAsset({ name }); - return public_key.public_key.toString("hex"); - } - - async getOwnedAssets() { - let assets = await this.client.getOwnedAssets(); - return assets.assets; - } - - async mintTokens(asset_public_key, names) { - let owner_commitments = await this.client.mintTokens({ - asset_public_key, - unique_ids: names.map((name) => convertStringToVec(name)), - }); - return owner_commitments.owner_commitments; - } - - async getOwnedTokens(asset_public_key) { - let tokens = await this.client.getOwnedTokens({ asset_public_key }); - return tokens.tokens; - } -} - -module.exports = WalletClient; diff --git a/integration_tests/helpers/walletFFIClient.js b/integration_tests/helpers/walletFFIClient.js deleted file mode 100644 index 5363b7ce56..0000000000 --- a/integration_tests/helpers/walletFFIClient.js +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const SeedWords = require("./ffi/seedWords"); -const TransportConfig = require("./ffi/transportConfig"); -const CommsConfig = require("./ffi/commsConfig"); -const Wallet = require("./ffi/wallet"); -const { getFreePort } = require("./util"); -const dateFormat = require("dateformat"); -const { sleep } = require("./util"); - -class WalletFFIClient { - name; - wallet; - comms_config; - transport; - seed_words; - pass_phrase; - port; - baseDir = ""; - - constructor(name) { - this.name = name; - } - - async startNew(seed_words_text, pass_phrase) { - this.port = await getFreePort(19000, 25000); - const name = `WalletFFI${this.port}-${this.name}`; - this.baseDir = `./temp/base_nodes/${dateFormat( - new Date(), - "yyyymmddHHMM" - )}/${name}`; - this.transport = TransportConfig.createTCP( - `/ip4/127.0.0.1/tcp/${this.port}` - ); - this.comms_config = new CommsConfig( - `/ip4/127.0.0.1/tcp/${this.port}`, - this.transport.getPtr(), - "wallet.dat", - this.baseDir, - 30, - 600 - ); - this.start(seed_words_text, pass_phrase); - } - - async restart(seed_words_text, pass_phrase) { - this.transport = TransportConfig.createTCP( - `/ip4/127.0.0.1/tcp/${this.port}` - ); - this.comms_config = new CommsConfig( - `/ip4/127.0.0.1/tcp/${this.port}`, - this.transport.getPtr(), - "wallet.dat", - this.baseDir, - 30, - 600 - ); - this.start(seed_words_text, pass_phrase); - } - - getTxoValidationStatus() { - return this.wallet.getTxoValidationStatus(); - } - - getTxValidationStatus() { - return this.wallet.getTxValidationStatus(); - } - - identify() { - return this.wallet.getPublicKey(); - } - - identifyEmoji() { - return this.wallet.getEmojiId(); - } - - getBalance() { - return this.wallet.getBalance(); - } - - pollBalance() { - return this.wallet.pollBalance(); - } - - addBaseNodePeer(public_key_hex, address) { - return this.wallet.addBaseNodePeer(public_key_hex, address); - } - - addContact(alias, pubkey_hex) { - return this.wallet.addContact(alias, pubkey_hex); - } - - getContactList() { - return this.wallet.getContacts(); - } - - getMnemonicWordListForLanguage(language) { - return SeedWords.getMnemonicWordListForLanguage(language); - } - - getCompletedTxs() { - return this.wallet.getCompletedTransactions(); - } - - getInboundTxs() { - return this.wallet.getInboundTransactions(); - } - - getOutboundTxs() { - return this.wallet.getOutboundTransactions(); - } - - removeContact(contact) { - return this.wallet.removeContact(contact); - } - - startRecovery(base_node_pubkey) { - this.wallet.startRecovery(base_node_pubkey); - } - - checkRecoveryInProgress() { - return this.wallet.recoveryInProgress(); - } - - applyEncryption(passphrase) { - this.wallet.applyEncryption(passphrase); - } - - startTxoValidation() { - this.wallet.startTxoValidation(); - } - - startTxValidation() { - this.wallet.startTxValidation(); - } - - listConnectedPublicKeys() { - this.wallet.listConnectedPublicKeys(); - } - - getCounters() { - return this.wallet.getCounters(); - } - - resetCounters() { - if (this.wallet) { - this.wallet.clearCallbackCounters(); - } - } - - getLivenessData() { - return this.wallet.getLivenessData(); - } - - sendTransaction(destination, amount, fee_per_gram, message, one_sided) { - return this.wallet.sendTransaction( - destination, - amount, - fee_per_gram, - message, - one_sided - ); - } - - start( - seed_words_text, - pass_phrase, - rolling_log_files = 50, - byte_size_per_log = 1048576 - ) { - this.pass_phrase = pass_phrase; - if (seed_words_text) { - let seed_words = SeedWords.fromText(seed_words_text); - this.seed_words = seed_words; - } - - let log_path = `${this.baseDir}/log/wallet.log`; - this.wallet = new Wallet( - this.comms_config.getPtr(), - log_path, - this.pass_phrase, - this.seed_words ? this.seed_words.getPtr() : null, - rolling_log_files, - byte_size_per_log, - "localnet" - ); - } - - getOutboundTransactions() { - return this.wallet.getOutboundTransactions(); - } - - getCancelledTransactions() { - return this.wallet.walletGetCancelledTransactions(); - } - - cancelPendingTransaction(tx_id) { - return this.wallet.cancelPendingTransaction(tx_id); - } - - getFeePerGramStats(count) { - return this.wallet.getFeePerGramStats(count); - } - - async stop() { - if (this.comms_config) { - // console.log("walletFFI destroy comms_config ..."); - await this.comms_config.destroy(); - this.comms_config = undefined; - // console.log("walletFFI destroy comms_config ... done!"); - await sleep(100); - } - if (this.transport) { - // console.log("walletFFI destroy transport ..."); - await this.transport.destroy(); - this.transport = undefined; - // console.log("walletFFI destroy transport ... done!"); - await sleep(100); - } - if (this.seed_words) { - // console.log("walletFFI destroy seed_words ..."); - await this.seed_words.destroy(); - this.seed_words = undefined; - // console.log("walletFFI destroy seed_words ... done!"); - } - if (this.wallet) { - // console.log("walletFFI destroy wallet ..."); - await this.wallet.destroy(); - this.wallet = undefined; - // console.log("walletFFI destroy wallet ... done!"); - } - } -} - -module.exports = WalletFFIClient; diff --git a/integration_tests/helpers/walletProcess.js b/integration_tests/helpers/walletProcess.js deleted file mode 100644 index 7868281d2f..0000000000 --- a/integration_tests/helpers/walletProcess.js +++ /dev/null @@ -1,471 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -const { getFreePort } = require("./util"); -const dateFormat = require("dateformat"); -const fs = require("fs"); -const path = require("path"); -const { spawn } = require("child_process"); -const { expect } = require("chai"); -const { createEnv } = require("./config"); -const WalletClient = require("./walletClient"); -const csvParser = require("csv-parser"); -const tari_crypto = require("tari_crypto"); -const { OutputType } = require("./types"); - -let outputProcess; - -class WalletProcess { - constructor(name, excludeTestEnvars, options, logFilePath, seedWords) { - this.name = name.toString(); - this.options = Object.assign( - { - baseDir: "./temp/base_nodes", - }, - options || {} - ); - this.logFilePath = logFilePath ? path.resolve(logFilePath) : logFilePath; - this.recoverWallet = !!seedWords; - this.seedWords = seedWords; - this.excludeTestEnvars = excludeTestEnvars; - } - - async init() { - this.port = await getFreePort(); - this.name = `Wallet${this.port}-${this.name}`; - this.grpcPort = await getFreePort(); - this.baseDir = `${this.options.baseDir}/${dateFormat( - new Date(), - "yyyymmddHHMM" - )}/${this.name}`; - this.seedWordsFile = path.resolve(this.baseDir + "/config/seed_words.log"); - if (!fs.existsSync(this.baseDir)) { - fs.mkdirSync(this.baseDir + "/log", { recursive: true }); - } - } - - getGrpcAddress() { - return "/ip4/127.0.0.1/tcp/" + this.grpcPort; - } - - getGrpcPort() { - return this.grpcPort; - } - - async connectClient() { - let client = new WalletClient(this.name); - let addr = this.getGrpcAddress(); - console.log(`Connecting to ${addr} (${this.name})`); - await client.connect(addr); - return client; - } - - getSeedWords() { - try { - return fs.readFileSync(this.seedWordsFile, "utf8"); - } catch (err) { - console.error("\n", this.name, ": Seed words file not found!\n", err); - } - } - - setPeerSeeds(addresses) { - this.peerSeeds = addresses; - } - - run(cmd, args, saveFile, input_buffer, output, waitForCommand) { - return new Promise((resolve, reject) => { - let overrides = {}; - const network = - this.options && this.options.network - ? this.options.network.toLowerCase() - : "localnet"; - overrides[`base_node.network`] = network; - if (!this.excludeTestEnvars) { - overrides = createEnv({ - network: "localnet", - isWallet: true, - nodeFile: "cwalletid.json", - options: this.options, - peerSeeds: this.peerSeeds, - walletPort: this.port, - walletGrpcAddress: this.getGrpcAddress(), - }); - } else if (this.options["grpc_console_wallet_address"]) { - overrides[`wallet.grpc_address`] = - this.options["grpc_console_wallet_address"]; - let regexMatch = - this.options["grpc_console_wallet_address"].match(/tcp\/(\d+)/); - this.grpcPort = parseInt(regexMatch[1]); - } - - console.log(`--------------------- ${this.name} ----------------------`); - const configArgs = []; - Object.keys(overrides).forEach((k) => { - configArgs.push("-p"); - configArgs.push(`${k}=${overrides[k]}`); - }); - if (saveFile) { - // clear the .env file - fs.writeFileSync(`${this.baseDir}/.overrides`, ""); - Object.keys(overrides).forEach((key) => { - fs.appendFileSync( - `${this.baseDir}/.overrides`, - `-p ${key}=${overrides[key]}` - ); - }); - } - - const ps = spawn(cmd, [...configArgs, ...args], { - cwd: this.baseDir, - // shell: true, - env: { ...process.env }, - }); - - if (input_buffer) { - // If we want to simulate user input we can do so here. - ps.stdin.write(input_buffer); - } - ps.stdout.on("data", (data) => { - console.log(`\nstdout: ${data}`); - if (output !== undefined && output.buffer !== undefined) { - output.buffer += data; - } - fs.appendFileSync(`${this.baseDir}/log/stdout.log`, data.toString()); - if ( - (!waitForCommand && - data.toString().match(/Tari Console Wallet running/i)) || - (waitForCommand && - data - .toString() - .match( - /(?=.*Tari Console Wallet running)(?=.*Command mode completed)/gim - )) - ) { - console.log("Wallet started"); - this.recoverWallet = false; - resolve(ps); - } - }); - - ps.stderr.on("data", (data) => { - console.error(`stderr: ${data}`); - fs.appendFileSync(`${this.baseDir}/log/stderr.log`, data.toString()); - }); - - ps.on("close", (code) => { - const ps = this.ps; - this.ps = null; - if (code === 112) { - reject("Incorrect password"); - } else if (code) { - console.log(`child process exited with code ${code}`); - reject(`child process exited with code ${code}`); - } else { - resolve(ps); - } - }); - expect(ps.error).to.be.undefined; - this.ps = ps; - }); - } - - async startNew() { - await this.init(); - return await this.start(); - } - - getOverrides() { - return createEnv({ - network: "localnet", - walletGrpcAddress: this.getGrpcAddress(), - isWallet: true, - walletPort: this.port, - peerSeeds: this.peerSeeds, - }); - } - - async compile() { - if (!outputProcess) { - let args = [ - "build", - "--release", - "--locked", - "--bin", - "tari_console_wallet", - "-Z", - "unstable-options", - "--out-dir", - process.cwd() + "/temp/out", - ]; - - await this.runShellCommand("cargo", args); - outputProcess = process.cwd() + "/temp/out/tari_console_wallet"; - } - return outputProcess; - } - - stop() { - return new Promise((resolve) => { - let name = this.name; - if (!this.ps) { - return resolve(); - } - this.ps.on("close", (code) => { - if (code) { - console.log(`child process (${name}) exited with code ${code}`); - } - resolve(); - }); - this.ps.kill("SIGINT"); - }); - } - - async start(opts = {}) { - const args = [ - "--base-path", - ".", - "--password", - opts.password || "kensentme", - "--seed-words-file-name", - this.seedWordsFile, - "--non-interactive", - "--enable-grpc", - "--network", - opts.network || (this.options || {}).network || "localnet", - ]; - if (this.recoverWallet) { - args.push("--recover", "--seed-words", this.seedWords); - } - if (this.logFilePath) { - args.push("--log-config", this.logFilePath); - } - const overrides = Object.assign(this.getOverrides(), opts.config); - Object.keys(overrides).forEach((k) => { - args.push("-p"); - args.push(`${k}=${overrides[k]}`); - }); - return await this.run(await this.compile(), args, true); - } - - async changePassword(oldPassword, newPassword) { - const args = [ - "--base-path", - ".", - "--password", - oldPassword, - "--update-password", - "--network", - "localnet", - ]; - if (this.logFilePath) { - args.push("--log-config", this.logFilePath); - } - const overrides = this.getOverrides(); - Object.keys(overrides).forEach((k) => { - args.push("-p"); - args.push(`${k}=${overrides[k]}`); - }); - // Set input_buffer to double confirmation of the new password - return await this.run( - await this.compile(), - args, - true, - newPassword + "\n" + newPassword + "\n" - ); - } - - async runCommand(command) { - // we need to quit the wallet before running a command - await this.stop(); - const args = [ - "--base-path", - ".", - "--password", - "kensentme", - "--non-interactive", - "--network", - "localnet", - "--enable-grpc", - ]; - if (this.logFilePath) { - args.push("--log-config", this.logFilePath); - } - - const overrides = this.getOverrides(); - Object.keys(overrides).forEach((k) => { - let v = overrides[k]; - if (typeof v !== "undefined") { - args.push("-p"); - args.push(`${k}=${v}`); - } - }); - - // Append command arguments - args.push(...command.split(" ")); - - let output = { buffer: "" }; - // In case we killed the wallet fast send enter. Because it will ask for the logs again (e.g. whois test) - - await this.run(await this.compile(), args, true, "\n", output, true); - return output; - } - - runShellCommand(cmd, args, opts = { env: {} }) { - return new Promise((resolve, reject) => { - const ps = spawn(cmd, args, { - cwd: this.baseDir, - // shell: true, - env: { ...process.env, ...opts.env }, - }); - - ps.stdout.on("data", (data) => { - // console.log(`stdout: ${data}`); - fs.appendFileSync(`${this.baseDir}/log/stdout.log`, data.toString()); - resolve(ps); - }); - - ps.stderr.on("data", (data) => { - console.error(`stderr: ${data}`); - fs.appendFileSync(`${this.baseDir}/log/stderr.log`, data.toString()); - }); - - ps.on("close", (code) => { - const ps = this.ps; - this.ps = null; - if (code) { - console.log(`child process exited with code ${code}`); - reject(`child process exited with code ${code}`); - } else { - resolve(ps); - } - }); - }); - } - - async exportSpentOutputs() { - await this.stop(); - const args = [ - "--base-path", - ".", - "--auto-exit", - "--password", - "kensentme", - "--network", - "localnet", - "--command", - "export-spent-utxos --csv-file exported_outputs.csv", - ]; - const overrides = this.getOverrides(); - Object.keys(overrides).forEach((k) => { - args.push("-p"); - args.push(`${k}=${overrides[k]}`); - }); - let output = { buffer: "" }; - outputProcess = __dirname + "/../temp/out/tari_console_wallet"; - await this.run(outputProcess, args, true, "\n", output, true); - } - - async exportUnspentOutputs() { - await this.stop(); - const args = [ - "--base-path", - ".", - "--auto-exit", - "--password", - "kensentme", - "--network", - "localnet", - "--command", - "export-utxos --csv-file exported_outputs.csv", - ]; - const overrides = this.getOverrides(); - Object.keys(overrides).forEach((k) => { - args.push("-p"); - args.push(`${k}=${overrides[k]}`); - }); - let output = { buffer: "" }; - outputProcess = __dirname + "/../temp/out/tari_console_wallet"; - await this.run(outputProcess, args, true, "\n", output, true); - } - - async readExportedOutputs() { - const filePath = path.resolve(this.baseDir + "/exported_outputs.csv"); - expect(fs.existsSync(filePath)).to.equal( - true, - "outputs export csv must exist" - ); - - let unblinded_outputs = await new Promise((resolve) => { - let unblinded_outputs = []; - fs.createReadStream(filePath) - .pipe(csvParser()) - .on("data", (row) => { - let unblinded_output = { - value: parseInt(row.value), - spending_key: Buffer.from(row.spending_key, "hex"), - features: { - output_type: OutputType.STANDARD, - maturity: parseInt(row.maturity) || 0, - }, - script: Buffer.from(row.script, "hex"), - input_data: Buffer.from(row.input_data, "hex"), - script_private_key: Buffer.from(row.script_private_key, "hex"), - sender_offset_public_key: Buffer.from( - row.sender_offset_public_key, - "hex" - ), - metadata_signature: { - public_nonce_commitment: Buffer.from(row.public_nonce, "hex"), - signature_u: Buffer.from(row.signature_u, "hex"), - signature_v: Buffer.from(row.signature_v, "hex"), - }, - }; - unblinded_outputs.push(unblinded_output); - }) - .on("end", () => { - resolve(unblinded_outputs); - }); - }); - - return unblinded_outputs; - } - - // Faucet outputs are only provided with an amount and spending key so we zero out the other output data - // and update the input data to be the public key of the spending key, make the script private key the spending key - // and then we can test if this output is still spendable when imported into the wallet. - async readExportedOutputsAsFaucetOutputs() { - let outputs = await this.readExportedOutputs(); - for (let i = 0; i < outputs.length; i++) { - outputs[i].metadata_signature = { - public_nonce_commitment: Buffer.from( - "0000000000000000000000000000000000000000000000000000000000000000", - "hex" - ), - signature_u: Buffer.from( - "0000000000000000000000000000000000000000000000000000000000000000", - "hex" - ), - signature_v: Buffer.from( - "0000000000000000000000000000000000000000000000000000000000000000", - "hex" - ), - }; - outputs[i].sender_offset_public_key = Buffer.from( - "0000000000000000000000000000000000000000000000000000000000000000", - "hex" - ); - outputs[i].script_private_key = outputs[i].spending_key; - let scriptPublicKey = tari_crypto.pubkey_from_secret( - outputs[i].spending_key.toString("hex") - ); - let input_data = Buffer.concat([ - Buffer.from([0x04]), - Buffer.from(scriptPublicKey, "hex"), - ]); - outputs[i].input_data = input_data; - } - return outputs; - } -} - -module.exports = WalletProcess; diff --git a/integration_tests/log4rs/cucumber.yml b/integration_tests/log4rs/cucumber.yml new file mode 100644 index 0000000000..93a1ebec90 --- /dev/null +++ b/integration_tests/log4rs/cucumber.yml @@ -0,0 +1,159 @@ +# See https://docs.rs/log4rs/1.1.1/log4rs/encode/pattern/index.html for deciphering the log pattern. +refresh_rate: 30 seconds +appenders: + # An appender named "stdout" that writes to stdout + stdout: + kind: rolling_file + path: "log/stdout.log" + policy: + kind: compound + trigger: + kind: size + limit: 100mb + roller: + kind: fixed_window + base: 1 + count: 10 + pattern: "log/stdout.{}.log" + encoder: + pattern: "{m}" + # An appender named "network" that writes to a file with a custom pattern encoder + network: + kind: rolling_file + path: "log/network.log" + policy: + kind: compound + trigger: + kind: size + limit: 100mb + roller: + kind: fixed_window + base: 1 + count: 10 + pattern: "log/network.{}.log" + encoder: + pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} {f}.{L} {i} [{t}] {l:5} {m}{n}" + base_layer_base_node: + kind: rolling_file + path: "log/base_node.log" + policy: + kind: compound + trigger: + kind: size + limit: 100mb + roller: + kind: fixed_window + base: 1 + count: 10 + pattern: "log/base_node.{}.log" + encoder: + pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} {f}.{L} {i} [{t}] {l:5} {m}{n}" + base_layer_wallet: + kind: rolling_file + path: "log/wallet.log" + policy: + kind: compound + trigger: + kind: size + limit: 100mb + roller: + kind: fixed_window + base: 1 + count: 10 + pattern: "log/wallet.{}.log" + encoder: + pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} {f}.{L} {i} [{t}] {l:5} {m}{n}" + # An appender named "other" that writes to a file with a custom pattern encoder + other: + kind: rolling_file + path: "log/other.log" + policy: + kind: compound + trigger: + kind: size + limit: 10mb + roller: + kind: fixed_window + base: 1 + count: 5 + pattern: "log/other.{}.log" + encoder: + pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} {f}.{L} {i} [{t}] {l:5} {m}{n}" +# We don't want prints during cucumber test, everything useful will in logs. +# root: +# level: warn +# appenders: +# - stdout + +loggers: + cucumber: + level: info + appenders: + - network + - base_layer_wallet + - base_layer_base_node + additive: true + stdout: + level: info # we have only single print, and it's info + appenders: + - stdout + c: + level: debug + appenders: + - base_layer_base_node + tari: + level: debug + appenders: + - base_layer_base_node + wallet: + level: debug + appenders: + - base_layer_wallet + # Route log events sent to the "comms" logger to the "network" appender + comms: + level: debug + appenders: + - network + # Route log events sent to the "p2p" logger to the "network" appender + p2p: + level: debug + appenders: + - network + # Route log events sent to the "yamux" logger to the "network" appender + yamux: + level: debug + appenders: + - network + # Route log events sent to the "mio" logger to the "network" appender + mio: + level: error + appenders: + - network + # Route log events sent to the "rustyline" logger to the "other" appender + rustyline: + level: error + appenders: + - other + additive: false + + # Route log events sent to the "tokio_util" logger to the "other" appender + tokio_util: + level: error + appenders: + - other + # Route PGP log events + pgp: + level: warn + appenders: + - other + # Route log events sent to the "tari_mm_proxy" logger to the "base_layer" appender + tari_mm_proxy: + level: debug + appenders: + - base_layer_base_node + # Route R2D2 log events + r2d2: + level: info + appenders: + - other + additive: false diff --git a/integration_tests/package-lock.json b/integration_tests/package-lock.json deleted file mode 100644 index ad7d876acd..0000000000 --- a/integration_tests/package-lock.json +++ /dev/null @@ -1,7102 +0,0 @@ -{ - "name": "integration_tests", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "integration_tests", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "archiver": "^5.3.1", - "axios": "^0.21.4", - "clone-deep": "^4.0.1", - "csv-parser": "^3.0.0", - "dateformat": "^3.0.3", - "glob": "^7.2.3", - "json5": "^2.2.2", - "sha3": "^2.1.3", - "tari_crypto": "v0.14.0", - "utf8": "^3.0.0", - "varint": "^6.0.0", - "wallet-grpc-client": "file:../clients/nodejs/wallet_grpc_client" - }, - "devDependencies": { - "@babel/core": "^7.19.1", - "@babel/eslint-parser": "^7.19.1", - "@babel/eslint-plugin": "^7.19.1", - "@cucumber/cucumber": "^8.5.3", - "@cucumber/pretty-formatter": "^1.0.0", - "@grpc/grpc-js": "^1.7.0", - "@grpc/proto-loader": "^0.5.5", - "blakejs": "^1.2.1", - "chai": "^4.3.6", - "cucumber-html-reporter": "^5.5.0", - "eslint": "^7.32.0", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-prettier": "^3.4.1", - "ffi-napi": "^4.0.3", - "grpc-promise": "^1.4.0", - "husky": "^6.0.0", - "prettier": "^2.7.1", - "ref-napi": "^3.0.3" - } - }, - "../clients/nodejs/wallet_grpc_client": { - "name": "@tari/wallet-grpc-client", - "version": "0.0.1", - "dependencies": { - "@grpc/grpc-js": "^1.7.0", - "@grpc/proto-loader": "^0.5.5", - "grpc-promise": "^1.4.0" - } - }, - "../clients/wallet_grpc_client": { - "extraneous": true, - "dependencies": { - "@grpc/grpc-js": "^1.3.6", - "@grpc/proto-loader": "^0.5.5", - "grpc-promise": "^1.4.0" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/code-frame/node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/code-frame/node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.1.tgz", - "integrity": "sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.1.tgz", - "integrity": "sha512-1H8VgqXme4UXCRv7/Wa1bq7RVymKOzC7znjyFM8KiEzwFqcKUKYNoQef4GhdklgNvoBXyW4gYhuBNCM5o1zImw==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", - "@babel/helper-compilation-targets": "^7.19.1", - "@babel/helper-module-transforms": "^7.19.0", - "@babel/helpers": "^7.19.0", - "@babel/parser": "^7.19.1", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.1", - "@babel/types": "^7.19.0", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/eslint-parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz", - "integrity": "sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==", - "dev": true, - "dependencies": { - "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || >=14.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.11.0", - "eslint": "^7.5.0 || ^8.0.0" - } - }, - "node_modules/@babel/eslint-plugin": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/eslint-plugin/-/eslint-plugin-7.19.1.tgz", - "integrity": "sha512-ElGPkQPapKMa3zVqXHkZYzuL7I5LbRw9UWBUArgWsdWDDb9XcACqOpBib5tRPA9XvbVZYrFUkoQPbiJ4BFvu4w==", - "dev": true, - "dependencies": { - "eslint-rule-composer": "^0.3.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || >=14.0.0" - }, - "peerDependencies": { - "@babel/eslint-parser": ">=7.11.0", - "eslint": ">=7.5.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz", - "integrity": "sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.19.0", - "@jridgewell/gen-mapping": "^0.3.2", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.1.tgz", - "integrity": "sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.19.1", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", - "dev": true, - "dependencies": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz", - "integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", - "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.15.7", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.19.0.tgz", - "integrity": "sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==", - "dev": true, - "dependencies": { - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.15.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.1.tgz", - "integrity": "sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz", - "integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.13.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.1.tgz", - "integrity": "sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.19.1", - "@babel/types": "^7.19.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", - "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", - "dev": true, - "dependencies": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types/node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@cucumber/ci-environment": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@cucumber/ci-environment/-/ci-environment-9.1.0.tgz", - "integrity": "sha512-jdnF6APXP3GawMue8kdMxhu6TBhyRUO4KDRxTowf06NtclLjIw2Ybpo9IcIOMvE8kHukvJyM00uxWX+CfS7JgQ==", - "dev": true - }, - "node_modules/@cucumber/cucumber": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@cucumber/cucumber/-/cucumber-8.5.3.tgz", - "integrity": "sha512-cLyMebI20K57JzOd22DJ3atGYwpxYz0YVCAl/iU9iY04A6uWY/6NoOSl8Vfd73ROjLA6T5ZJpGlfw5VkWJnq4A==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@cucumber/ci-environment": "9.1.0", - "@cucumber/cucumber-expressions": "16.0.0", - "@cucumber/gherkin": "24.0.0", - "@cucumber/gherkin-streams": "5.0.1", - "@cucumber/gherkin-utils": "8.0.0", - "@cucumber/html-formatter": "20.0.0", - "@cucumber/message-streams": "4.0.1", - "@cucumber/messages": "19.1.2", - "@cucumber/tag-expressions": "4.1.0", - "assertion-error-formatter": "^3.0.0", - "capital-case": "^1.0.4", - "chalk": "^4.1.2", - "cli-table3": "0.6.2", - "commander": "^9.0.0", - "duration": "^0.2.2", - "durations": "^3.4.2", - "figures": "^3.2.0", - "glob": "^7.1.6", - "has-ansi": "^4.0.1", - "indent-string": "^4.0.0", - "is-installed-globally": "^0.4.0", - "is-stream": "^2.0.0", - "knuth-shuffle-seeded": "^1.0.6", - "lodash.merge": "^4.6.2", - "lodash.mergewith": "^4.6.2", - "mz": "^2.7.0", - "progress": "^2.0.3", - "resolve-pkg": "^2.0.0", - "semver": "7.3.7", - "stack-chain": "^2.0.0", - "string-argv": "^0.3.1", - "strip-ansi": "6.0.1", - "supports-color": "^8.1.1", - "tmp": "^0.2.1", - "util-arity": "^1.1.0", - "verror": "^1.10.0", - "yup": "^0.32.11" - }, - "bin": { - "cucumber-js": "bin/cucumber.js" - }, - "engines": { - "node": "12 || 14 || >=16" - } - }, - "node_modules/@cucumber/cucumber-expressions": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@cucumber/cucumber-expressions/-/cucumber-expressions-16.0.0.tgz", - "integrity": "sha512-HTh+Pg7oQ5aLuCkSbD2Q6jBaE40M3R/XaLEz+UqD5d9dZRu6P38W4LTooV5bV6dZgBunlMLK8+6ug2ziYvRddw==", - "dev": true, - "dependencies": { - "regexp-match-indices": "1.0.2" - } - }, - "node_modules/@cucumber/cucumber/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@cucumber/cucumber/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@cucumber/cucumber/node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@cucumber/cucumber/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@cucumber/cucumber/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@cucumber/cucumber/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@cucumber/cucumber/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@cucumber/cucumber/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/@cucumber/gherkin": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/@cucumber/gherkin/-/gherkin-24.0.0.tgz", - "integrity": "sha512-b7OsnvX1B8myDAKMc+RAiUX9bzgtNdjGsiMj10O13xu2HBWIOQ19EqBJ4xLO5CFG/lGk1J/+L0lANQVowxLVBg==", - "dev": true, - "dependencies": { - "@cucumber/messages": "^19.0.0" - } - }, - "node_modules/@cucumber/gherkin-streams": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@cucumber/gherkin-streams/-/gherkin-streams-5.0.1.tgz", - "integrity": "sha512-/7VkIE/ASxIP/jd4Crlp4JHXqdNFxPGQokqWqsaCCiqBiu5qHoKMxcWNlp9njVL/n9yN4S08OmY3ZR8uC5x74Q==", - "dev": true, - "dependencies": { - "commander": "9.1.0", - "source-map-support": "0.5.21" - }, - "bin": { - "gherkin-javascript": "bin/gherkin" - }, - "peerDependencies": { - "@cucumber/gherkin": ">=22.0.0", - "@cucumber/message-streams": ">=4.0.0", - "@cucumber/messages": ">=17.1.1" - } - }, - "node_modules/@cucumber/gherkin-streams/node_modules/commander": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.1.0.tgz", - "integrity": "sha512-i0/MaqBtdbnJ4XQs4Pmyb+oFQl+q0lsAmokVUH92SlSw4fkeAcG3bVon+Qt7hmtF+u3Het6o4VgrcY3qAoEB6w==", - "dev": true, - "engines": { - "node": "^12.20.0 || >=14" - } - }, - "node_modules/@cucumber/gherkin-utils": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@cucumber/gherkin-utils/-/gherkin-utils-8.0.0.tgz", - "integrity": "sha512-8uIZInEe3cO1cASmy3BA0PbVFUI+xWBnZAxmICbVOPsZaMB85MtESZLafzErgfRQPsHf6uYbVagP7MIjNPM5Jw==", - "dev": true, - "dependencies": { - "@cucumber/messages": "^19.0.0", - "@teppeis/multimaps": "2.0.0", - "commander": "9.3.0" - }, - "bin": { - "gherkin-utils": "bin/gherkin-utils" - } - }, - "node_modules/@cucumber/gherkin-utils/node_modules/commander": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.3.0.tgz", - "integrity": "sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw==", - "dev": true, - "engines": { - "node": "^12.20.0 || >=14" - } - }, - "node_modules/@cucumber/html-formatter": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@cucumber/html-formatter/-/html-formatter-20.0.0.tgz", - "integrity": "sha512-I6ZzUZ0CkaaPm6QHThoCRmdcuEIV9kJlNjaWvQ3PRkJm0OscQkQ0SXL/j73U30RDPiCAWwtq6ZSeQrgkTnSK+Q==", - "dev": true, - "peerDependencies": { - "@cucumber/messages": ">=18" - } - }, - "node_modules/@cucumber/message-streams": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@cucumber/message-streams/-/message-streams-4.0.1.tgz", - "integrity": "sha512-Kxap9uP5jD8tHUZVjTWgzxemi/0uOsbGjd4LBOSxcJoOCRbESFwemUzilJuzNTB8pcTQUh8D5oudUyxfkJOKmA==", - "dev": true, - "peerDependencies": { - "@cucumber/messages": ">=17.1.1" - } - }, - "node_modules/@cucumber/messages": { - "version": "19.1.2", - "resolved": "https://registry.npmjs.org/@cucumber/messages/-/messages-19.1.2.tgz", - "integrity": "sha512-vhWkNmQco+7tk/DWqpN0/R9KTNvsKsXVfZ7IsJs+dEeWmTuRztklHq8lJalwMSQBl71+2/KqGHzOO4BMTC9wIQ==", - "dev": true, - "dependencies": { - "@types/uuid": "8.3.4", - "class-transformer": "0.5.1", - "reflect-metadata": "0.1.13", - "uuid": "8.3.2" - } - }, - "node_modules/@cucumber/messages/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@cucumber/pretty-formatter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@cucumber/pretty-formatter/-/pretty-formatter-1.0.0.tgz", - "integrity": "sha512-wcnIMN94HyaHGsfq72dgCvr1d8q6VGH4Y6Gl5weJ2TNZw1qn2UY85Iki4c9VdaLUONYnyYH3+178YB+9RFe/Hw==", - "dev": true, - "dependencies": { - "ansi-styles": "^5.0.0", - "cli-table3": "^0.6.0", - "figures": "^3.2.0", - "ts-dedent": "^2.0.0" - }, - "peerDependencies": { - "@cucumber/cucumber": ">=7.0.0", - "@cucumber/messages": "*" - } - }, - "node_modules/@cucumber/pretty-formatter/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@cucumber/tag-expressions": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@cucumber/tag-expressions/-/tag-expressions-4.1.0.tgz", - "integrity": "sha512-chTnjxV3vryL75N90wJIMdMafXmZoO2JgNJLYpsfcALL2/IQrRiny3vM9DgD5RDCSt1LNloMtb7rGey9YWxCsA==", - "dev": true - }, - "node_modules/@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.11.0", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@grpc/grpc-js": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.0.tgz", - "integrity": "sha512-wvKxal+40Xx11DXO2q5PfY3UiE25iwTb8SOz6A9IJII/V7d19x2ex0he+GJfVW0JZCaBjCPSjUB0yU9Ecm4WCw==", - "dev": true, - "dependencies": { - "@grpc/proto-loader": "^0.7.0", - "@types/node": ">=12.12.47" - }, - "engines": { - "node": "^8.13.0 || >=10.10.0" - } - }, - "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.2.tgz", - "integrity": "sha512-jCdyLIT/tdQ1zhrbTQnJNK5nbDf0GoBpy5jVNywBzzMDF+Vs6uEaHnfz46dMtDxkvwrF2hzk5Z67goliceH0sA==", - "dev": true, - "dependencies": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^7.0.0", - "yargs": "^16.2.0" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@grpc/grpc-js/node_modules/protobufjs": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.1.tgz", - "integrity": "sha512-d0nMQqS/aT3lfV8bKi9Gbg73vPd2LcDdTDOu6RE/M+h9DY8g1EmDzk3ADPccthEWfTBjkR2oxNdx9Gs8YubT+g==", - "dev": true, - "hasInstallScript": true, - "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/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@grpc/grpc-js/node_modules/protobufjs/node_modules/long": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", - "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==", - "dev": true - }, - "node_modules/@grpc/proto-loader": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.6.tgz", - "integrity": "sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==", - "dev": true, - "dependencies": { - "lodash.camelcase": "^4.3.0", - "protobufjs": "^6.8.6" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.0", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", - "dev": true - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", - "dev": true, - "dependencies": { - "eslint-scope": "5.1.1" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "dev": true - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "dev": true - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "dev": true - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "dev": true - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "dev": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "dev": true - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "dev": true - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "dev": true - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "dev": true - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "dev": true - }, - "node_modules/@teppeis/multimaps": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@teppeis/multimaps/-/multimaps-2.0.0.tgz", - "integrity": "sha512-TL1adzq1HdxUf9WYduLcQ/DNGYiz71U31QRgbnr0Ef1cPyOUOsBojxHVWpFeOSUucB6Lrs0LxFRA14ntgtkc9w==", - "dev": true, - "engines": { - "node": ">=10.17" - } - }, - "node_modules/@types/lodash": { - "version": "4.14.185", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.185.tgz", - "integrity": "sha512-evMDG1bC4rgQg4ku9tKpuMh5iBNEwNa3tf9zRHdP1qlv+1WUg44xat4IxCE14gIpZRGUUWAx2VhItCZc25NfMA==", - "dev": true - }, - "node_modules/@types/long": { - "version": "4.0.1", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==", - "dev": true - }, - "node_modules/@types/node": { - "version": "16.10.3", - "integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==", - "dev": true - }, - "node_modules/@types/uuid": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", - "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", - "dev": true - }, - "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true - }, - "node_modules/archiver": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.1.tgz", - "integrity": "sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==", - "dependencies": { - "archiver-utils": "^2.1.0", - "async": "^3.2.3", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.0.0", - "tar-stream": "^2.2.0", - "zip-stream": "^4.1.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "dependencies": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/archiver-utils/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "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" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/assertion-error-formatter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/assertion-error-formatter/-/assertion-error-formatter-3.0.0.tgz", - "integrity": "sha512-6YyAVLrEze0kQ7CmJfUgrLHb+Y7XghmL2Ie7ijVa2Y9ynP3LV+VDiwFk62Dn0qtqbmY0BT0ss6p1xxpiF2PYbQ==", - "dev": true, - "dependencies": { - "diff": "^4.0.1", - "pad-right": "^0.2.2", - "repeat-string": "^1.6.1" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" - }, - "node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "dependencies": { - "follow-redirects": "^1.14.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/buffer": { - "version": "5.7.1", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/blakejs": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", - "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "engines": { - "node": "*" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001406", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001406.tgz", - "integrity": "sha512-bWTlaXUy/rq0BBtYShc/jArYfBPjEV95euvZ8JVtO43oQExEN/WquoqpufFjNu4kSpi5cy5kMbNvzztWDfv1Jg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - } - ] - }, - "node_modules/capital-case": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", - "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", - "dev": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3", - "upper-case-first": "^2.0.2" - } - }, - "node_modules/chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/class-transformer": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", - "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", - "dev": true - }, - "node_modules/cli-table3": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.2.tgz", - "integrity": "sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/commander": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", - "integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==", - "dev": true, - "engines": { - "node": "^12.20.0 || >=14" - } - }, - "node_modules/compress-commons": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", - "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", - "dependencies": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.2", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" - }, - "node_modules/crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", - "bin": { - "crc32": "bin/crc32.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/crc32-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", - "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", - "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/csv-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.0.0.tgz", - "integrity": "sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "csv-parser": "bin/csv-parser" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/cucumber-html-reporter": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/cucumber-html-reporter/-/cucumber-html-reporter-5.5.0.tgz", - "integrity": "sha512-kF7vIwvTe7we7Wp/5uNZVZk+Ryozb688LpNvCNhou6N0RmLYPqaoV2aiN8GIB94JUBpribtlq6kDkEUHwxBVeQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "find": "^0.3.0", - "fs-extra": "^8.1.0", - "js-base64": "^2.3.2", - "jsonfile": "^5.0.0", - "lodash": "^4.17.11", - "node-emoji": "^1.10.0", - "open": "^6.4.0", - "uuid": "^3.3.3" - } - }, - "node_modules/d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "dependencies": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "node_modules/dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", - "engines": { - "node": "*" - } - }, - "node_modules/debug": { - "version": "4.3.2", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/duration": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/duration/-/duration-0.2.2.tgz", - "integrity": "sha512-06kgtea+bGreF5eKYgI/36A6pLXggY7oR4p1pq4SmdFBn1ReOL5D8RhG64VrqfTTKNucqqtBAwEj8aB88mcqrg==", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "~0.10.46" - } - }, - "node_modules/durations": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/durations/-/durations-3.4.2.tgz", - "integrity": "sha512-V/lf7y33dGaypZZetVI1eu7BmvkbC4dItq12OElLRpKuaU5JxQstV2zHwLv8P7cNbQ+KL1WD80zMCTx5dNC4dg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.254", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.254.tgz", - "integrity": "sha512-Sh/7YsHqQYkA6ZHuHMy24e6TE4eX6KZVsZb9E/DvU1nQRIrH4BflO/4k+83tfdYvDl+MObvlqHPRICzEdC9c6Q==", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dev": true, - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dev": true, - "dependencies": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", - "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-plugin-prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", - "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", - "dev": true, - "dependencies": { - "prettier-linter-helpers": "^1.0.0" - }, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "eslint": ">=5.0.0", - "prettier": ">=1.13.0" - }, - "peerDependenciesMeta": { - "eslint-config-prettier": { - "optional": true - } - } - }, - "node_modules/eslint-rule-composer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", - "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.11.0", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/semver": { - "version": "7.3.5", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.2.0", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "dev": true, - "dependencies": { - "type": "^2.7.2" - } - }, - "node_modules/ext/node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", - "dev": true - }, - "node_modules/extsprintf": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", - "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", - "dev": true, - "engines": [ - "node >=0.6.0" - ] - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/ffi-napi": { - "version": "4.0.3", - "integrity": "sha512-PMdLCIvDY9mS32RxZ0XGb95sonPRal8aqRhLbeEtWKZTe2A87qRFG9HjOhvG8EX2UmQw5XNRMIOT+1MYlWmdeg==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "debug": "^4.1.1", - "get-uv-event-loop-napi-h": "^1.0.5", - "node-addon-api": "^3.0.0", - "node-gyp-build": "^4.2.1", - "ref-napi": "^2.0.1 || ^3.0.2", - "ref-struct-di": "^1.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/find": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/find/-/find-0.3.0.tgz", - "integrity": "sha512-iSd+O4OEYV/I36Zl8MdYJO0xD82wH528SaCieTVHhclgiYNe9y+yPKSwK+A7/WsmHL1EZ+pYUJBXWTL5qofksw==", - "dev": true, - "dependencies": { - "traverse-chain": "~0.1.0" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.2", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", - "dev": true - }, - "node_modules/follow-redirects": { - "version": "1.14.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", - "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" - }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/fs-extra/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/get-symbol-from-current-process-h": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-from-current-process-h/-/get-symbol-from-current-process-h-1.0.2.tgz", - "integrity": "sha512-syloC6fsCt62ELLrr1VKBM1ggOpMdetX9hTrdW77UQdcApPHLmf7CI7OKcN1c9kYuNxKcDe4iJ4FY9sX3aw2xw==", - "dev": true - }, - "node_modules/get-uv-event-loop-napi-h": { - "version": "1.0.6", - "integrity": "sha512-t5c9VNR84nRoF+eLiz6wFrEp1SE2Acg0wS+Ysa2zF0eROes+LzOfuTaVHxGy8AbS8rq7FHEJzjnCZo1BupwdJg==", - "dev": true, - "dependencies": { - "get-symbol-from-current-process-h": "^1.0.1" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", - "dev": true, - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.8", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" - }, - "node_modules/grpc-promise": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/grpc-promise/-/grpc-promise-1.4.0.tgz", - "integrity": "sha512-4BBXHXb5OjjBh7luylu8vFqL6H6aPn/LeqpQaSBeRzO/Xv95wHW/WkU9TJRqaCTMZ5wq9jTSvlJWp0vRJy1pVA==", - "dev": true - }, - "node_modules/has-ansi": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-4.0.1.tgz", - "integrity": "sha512-Qr4RtTm30xvEdqUXbSBVWDu+PrTokJOwe/FU+VdfJPk+MXAPoeOzKpRyrDTnZIJwAkQ4oBLTU53nu0HrkF/Z2A==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/husky": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz", - "integrity": "sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ==", - "dev": true, - "bin": { - "husky": "lib/bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/js-base64": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", - "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", - "dev": true - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz", - "integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-5.0.0.tgz", - "integrity": "sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==", - "dev": true, - "dependencies": { - "universalify": "^0.1.2" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/knuth-shuffle-seeded": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/knuth-shuffle-seeded/-/knuth-shuffle-seeded-1.0.6.tgz", - "integrity": "sha512-9pFH0SplrfyKyojCLxZfMcvkhf5hH0d+UwR9nTVJ/DDQJGuzcXjTwB7TP7sDfehSudlGGaOLblmEWqv04ERVWg==", - "dev": true, - "dependencies": { - "seed-random": "~2.2.0" - } - }, - "node_modules/lazystream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", - "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", - "dependencies": { - "readable-stream": "^2.0.5" - }, - "engines": { - "node": ">= 0.6.3" - } - }, - "node_modules/lazystream/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "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" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "dev": true - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" - }, - "node_modules/lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==" - }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.mergewith": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", - "dev": true - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true - }, - "node_modules/lodash.union": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==" - }, - "node_modules/long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "dev": true - }, - "node_modules/loupe": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.1.tgz", - "integrity": "sha512-EN1D3jyVmaX4tnajVlfbREU4axL647hLec1h/PXAb8CPDMJiYitcWF2UeLVNttRqaIqQs4x+mRvXf+d+TlDrCA==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.0" - } - }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nanoclone": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", - "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==", - "dev": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "dev": true - }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-addon-api": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", - "dev": true - }, - "node_modules/node-emoji": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", - "dev": true, - "dependencies": { - "lodash": "^4.17.21" - } - }, - "node_modules/node-gyp-build": { - "version": "4.3.0", - "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", - "dev": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/open": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", - "dev": true, - "dependencies": { - "is-wsl": "^1.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/pad-right": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/pad-right/-/pad-right-0.2.2.tgz", - "integrity": "sha512-4cy8M95ioIGolCoMmm2cMntGR1lPLEbOMzOKu8bzjuJP6JpzEMQcDHmh7hHLYGgob+nKe1YHFMaG4V59HQa89g==", - "dev": true, - "dependencies": { - "repeat-string": "^1.5.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/property-expr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", - "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==", - "dev": true - }, - "node_modules/protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", - "dev": true, - "hasInstallScript": true, - "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.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdir-glob": { - "version": "1.1.1", - "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", - "dependencies": { - "minimatch": "^3.0.4" - } - }, - "node_modules/ref-napi": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ref-napi/-/ref-napi-3.0.3.tgz", - "integrity": "sha512-LiMq/XDGcgodTYOMppikEtJelWsKQERbLQsYm0IOOnzhwE9xYZC7x8txNnFC9wJNOkPferQI4vD4ZkC0mDyrOA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "debug": "^4.1.1", - "get-symbol-from-current-process-h": "^1.0.2", - "node-addon-api": "^3.0.0", - "node-gyp-build": "^4.2.1" - }, - "engines": { - "node": ">= 10.0" - } - }, - "node_modules/ref-struct-di": { - "version": "1.1.1", - "integrity": "sha512-2Xyn/0Qgz89VT+++WP0sTosdm9oeowLP23wRJYhG4BFdMUrLj3jhwHZNEytYNYgtPKLNTP3KJX4HEgBvM1/Y2g==", - "dev": true, - "dependencies": { - "debug": "^3.1.0" - } - }, - "node_modules/ref-struct-di/node_modules/debug": { - "version": "3.2.7", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", - "dev": true - }, - "node_modules/regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true - }, - "node_modules/regexp-match-indices": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regexp-match-indices/-/regexp-match-indices-1.0.2.tgz", - "integrity": "sha512-DwZuAkt8NF5mKwGGER1EGh2PRqyvhRhhLviH+R8y8dIuaQROlUfXjt4s9ZTXstIsSkptf06BSvwcEmmfheJJWQ==", - "dev": true, - "dependencies": { - "regexp-tree": "^0.1.11" - } - }, - "node_modules/regexp-tree": { - "version": "0.1.24", - "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.24.tgz", - "integrity": "sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw==", - "dev": true, - "bin": { - "regexp-tree": "bin/regexp-tree" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg/-/resolve-pkg-2.0.0.tgz", - "integrity": "sha512-+1lzwXehGCXSeryaISr6WujZzowloigEofRB+dj75y9RRa/obVcYgbHJd53tdYw8pvZj8GojXaaENws8Ktw/hQ==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-pkg/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/seed-random": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz", - "integrity": "sha512-34EQV6AAHQGhoc0tn/96a9Fsi6v2xdqe/dMUwljGRaFOzR3EgRmECvD0O8vi8X+/uQ50LGHfkNu/Eue5TPKZkQ==", - "dev": true - }, - "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/sha3": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/sha3/-/sha3-2.1.4.tgz", - "integrity": "sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg==", - "dependencies": { - "buffer": "6.0.3" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "node_modules/stack-chain": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-2.0.0.tgz", - "integrity": "sha512-GGrHXePi305aW7XQweYZZwiRwR7Js3MWoK/EHzzB9ROdc75nCnjSJVi21rdAGxFl+yCx2L2qdfl5y7NO4lTyqg==", - "dev": true - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string-argv": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", - "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", - "dev": true, - "engines": { - "node": ">=0.6.19" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/table": { - "version": "6.7.2", - "integrity": "sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g==", - "dev": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.6.3", - "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tari_crypto": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/tari_crypto/-/tari_crypto-0.14.0.tgz", - "integrity": "sha512-vwwI3SJ08Dcv4tGL+YVGE5LDmWwJnaRjZV2UNP0XoKKgZX88oMyKUup7RyQTpIaGOBTaqblI0uK79w3U+62ZRQ==" - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/toposort": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", - "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", - "dev": true - }, - "node_modules/traverse-chain": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz", - "integrity": "sha512-up6Yvai4PYKhpNp5PkYtx50m3KbwQrqDwbuZP/ItyL64YEWHAvH6Md83LFLV/GRSk/BoUVwwgUzX6SOQSbsfAg==", - "dev": true - }, - "node_modules/ts-dedent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", - "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", - "dev": true, - "engines": { - "node": ">=6.10" - } - }, - "node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true - }, - "node_modules/type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz", - "integrity": "sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist-lint": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/upper-case-first": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", - "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", - "dev": true, - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" - }, - "node_modules/util-arity": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/util-arity/-/util-arity-1.1.0.tgz", - "integrity": "sha512-kkyIsXKwemfSy8ZEoaIz06ApApnWsk5hQO0vLjZS6UkBiGiW++Jsyb8vSBoc0WKlffGoGs5yYy/j5pp8zckrFA==", - "dev": true - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/uuid": { - "version": "3.4.0", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/varint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", - "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==" - }, - "node_modules/verror": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", - "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/wallet-grpc-client": { - "resolved": "../clients/nodejs/wallet_grpc_client", - "link": true - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yup": { - "version": "0.32.11", - "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz", - "integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.15.4", - "@types/lodash": "^4.14.175", - "lodash": "^4.17.21", - "lodash-es": "^4.17.21", - "nanoclone": "^0.2.1", - "property-expr": "^2.0.4", - "toposort": "^2.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/zip-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", - "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", - "dependencies": { - "archiver-utils": "^2.1.0", - "compress-commons": "^4.1.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 10" - } - } - }, - "dependencies": { - "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "dev": true - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - } - } - }, - "@babel/compat-data": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.1.tgz", - "integrity": "sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg==", - "dev": true - }, - "@babel/core": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.1.tgz", - "integrity": "sha512-1H8VgqXme4UXCRv7/Wa1bq7RVymKOzC7znjyFM8KiEzwFqcKUKYNoQef4GhdklgNvoBXyW4gYhuBNCM5o1zImw==", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", - "@babel/helper-compilation-targets": "^7.19.1", - "@babel/helper-module-transforms": "^7.19.0", - "@babel/helpers": "^7.19.0", - "@babel/parser": "^7.19.1", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.1", - "@babel/types": "^7.19.0", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - } - }, - "@babel/eslint-parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz", - "integrity": "sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==", - "dev": true, - "requires": { - "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.0" - } - }, - "@babel/eslint-plugin": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/eslint-plugin/-/eslint-plugin-7.19.1.tgz", - "integrity": "sha512-ElGPkQPapKMa3zVqXHkZYzuL7I5LbRw9UWBUArgWsdWDDb9XcACqOpBib5tRPA9XvbVZYrFUkoQPbiJ4BFvu4w==", - "dev": true, - "requires": { - "eslint-rule-composer": "^0.3.0" - } - }, - "@babel/generator": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz", - "integrity": "sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==", - "dev": true, - "requires": { - "@babel/types": "^7.19.0", - "@jridgewell/gen-mapping": "^0.3.2", - "jsesc": "^2.5.1" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@babel/helper-compilation-targets": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.1.tgz", - "integrity": "sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.19.1", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "semver": "^6.3.0" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "dev": true - }, - "@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", - "dev": true, - "requires": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-transforms": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz", - "integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "dev": true - } - } - }, - "@babel/helper-simple-access": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", - "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.15.7", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "dev": true - }, - "@babel/helpers": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.19.0.tgz", - "integrity": "sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==", - "dev": true, - "requires": { - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - } - }, - "@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.15.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.1.tgz", - "integrity": "sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==", - "dev": true - }, - "@babel/runtime": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz", - "integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" - } - }, - "@babel/traverse": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.1.tgz", - "integrity": "sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.19.1", - "@babel/types": "^7.19.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", - "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", - "dev": true, - "requires": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", - "to-fast-properties": "^2.0.0" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", - "dev": true - } - } - }, - "@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "optional": true - }, - "@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "dependencies": { - "@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - } - } - }, - "@cucumber/ci-environment": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@cucumber/ci-environment/-/ci-environment-9.1.0.tgz", - "integrity": "sha512-jdnF6APXP3GawMue8kdMxhu6TBhyRUO4KDRxTowf06NtclLjIw2Ybpo9IcIOMvE8kHukvJyM00uxWX+CfS7JgQ==", - "dev": true - }, - "@cucumber/cucumber": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@cucumber/cucumber/-/cucumber-8.5.3.tgz", - "integrity": "sha512-cLyMebI20K57JzOd22DJ3atGYwpxYz0YVCAl/iU9iY04A6uWY/6NoOSl8Vfd73ROjLA6T5ZJpGlfw5VkWJnq4A==", - "dev": true, - "requires": { - "@cspotcode/source-map-support": "^0.8.0", - "@cucumber/ci-environment": "9.1.0", - "@cucumber/cucumber-expressions": "16.0.0", - "@cucumber/gherkin": "24.0.0", - "@cucumber/gherkin-streams": "5.0.1", - "@cucumber/gherkin-utils": "8.0.0", - "@cucumber/html-formatter": "20.0.0", - "@cucumber/message-streams": "4.0.1", - "@cucumber/messages": "19.1.2", - "@cucumber/tag-expressions": "4.1.0", - "assertion-error-formatter": "^3.0.0", - "capital-case": "^1.0.4", - "chalk": "^4.1.2", - "cli-table3": "0.6.2", - "commander": "^9.0.0", - "duration": "^0.2.2", - "durations": "^3.4.2", - "figures": "^3.2.0", - "glob": "^7.1.6", - "has-ansi": "^4.0.1", - "indent-string": "^4.0.0", - "is-installed-globally": "^0.4.0", - "is-stream": "^2.0.0", - "knuth-shuffle-seeded": "^1.0.6", - "lodash.merge": "^4.6.2", - "lodash.mergewith": "^4.6.2", - "mz": "^2.7.0", - "progress": "^2.0.3", - "resolve-pkg": "^2.0.0", - "semver": "7.3.7", - "stack-chain": "^2.0.0", - "string-argv": "^0.3.1", - "strip-ansi": "6.0.1", - "supports-color": "^8.1.1", - "tmp": "^0.2.1", - "util-arity": "^1.1.0", - "verror": "^1.10.0", - "yup": "^0.32.11" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@cucumber/cucumber-expressions": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@cucumber/cucumber-expressions/-/cucumber-expressions-16.0.0.tgz", - "integrity": "sha512-HTh+Pg7oQ5aLuCkSbD2Q6jBaE40M3R/XaLEz+UqD5d9dZRu6P38W4LTooV5bV6dZgBunlMLK8+6ug2ziYvRddw==", - "dev": true, - "requires": { - "regexp-match-indices": "1.0.2" - } - }, - "@cucumber/gherkin": { - "version": "24.0.0", - "resolved": "https://registry.npmjs.org/@cucumber/gherkin/-/gherkin-24.0.0.tgz", - "integrity": "sha512-b7OsnvX1B8myDAKMc+RAiUX9bzgtNdjGsiMj10O13xu2HBWIOQ19EqBJ4xLO5CFG/lGk1J/+L0lANQVowxLVBg==", - "dev": true, - "requires": { - "@cucumber/messages": "^19.0.0" - } - }, - "@cucumber/gherkin-streams": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@cucumber/gherkin-streams/-/gherkin-streams-5.0.1.tgz", - "integrity": "sha512-/7VkIE/ASxIP/jd4Crlp4JHXqdNFxPGQokqWqsaCCiqBiu5qHoKMxcWNlp9njVL/n9yN4S08OmY3ZR8uC5x74Q==", - "dev": true, - "requires": { - "commander": "9.1.0", - "source-map-support": "0.5.21" - }, - "dependencies": { - "commander": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.1.0.tgz", - "integrity": "sha512-i0/MaqBtdbnJ4XQs4Pmyb+oFQl+q0lsAmokVUH92SlSw4fkeAcG3bVon+Qt7hmtF+u3Het6o4VgrcY3qAoEB6w==", - "dev": true - } - } - }, - "@cucumber/gherkin-utils": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@cucumber/gherkin-utils/-/gherkin-utils-8.0.0.tgz", - "integrity": "sha512-8uIZInEe3cO1cASmy3BA0PbVFUI+xWBnZAxmICbVOPsZaMB85MtESZLafzErgfRQPsHf6uYbVagP7MIjNPM5Jw==", - "dev": true, - "requires": { - "@cucumber/messages": "^19.0.0", - "@teppeis/multimaps": "2.0.0", - "commander": "9.3.0" - }, - "dependencies": { - "commander": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.3.0.tgz", - "integrity": "sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw==", - "dev": true - } - } - }, - "@cucumber/html-formatter": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@cucumber/html-formatter/-/html-formatter-20.0.0.tgz", - "integrity": "sha512-I6ZzUZ0CkaaPm6QHThoCRmdcuEIV9kJlNjaWvQ3PRkJm0OscQkQ0SXL/j73U30RDPiCAWwtq6ZSeQrgkTnSK+Q==", - "dev": true, - "requires": {} - }, - "@cucumber/message-streams": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@cucumber/message-streams/-/message-streams-4.0.1.tgz", - "integrity": "sha512-Kxap9uP5jD8tHUZVjTWgzxemi/0uOsbGjd4LBOSxcJoOCRbESFwemUzilJuzNTB8pcTQUh8D5oudUyxfkJOKmA==", - "dev": true, - "requires": {} - }, - "@cucumber/messages": { - "version": "19.1.2", - "resolved": "https://registry.npmjs.org/@cucumber/messages/-/messages-19.1.2.tgz", - "integrity": "sha512-vhWkNmQco+7tk/DWqpN0/R9KTNvsKsXVfZ7IsJs+dEeWmTuRztklHq8lJalwMSQBl71+2/KqGHzOO4BMTC9wIQ==", - "dev": true, - "requires": { - "@types/uuid": "8.3.4", - "class-transformer": "0.5.1", - "reflect-metadata": "0.1.13", - "uuid": "8.3.2" - }, - "dependencies": { - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true - } - } - }, - "@cucumber/pretty-formatter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@cucumber/pretty-formatter/-/pretty-formatter-1.0.0.tgz", - "integrity": "sha512-wcnIMN94HyaHGsfq72dgCvr1d8q6VGH4Y6Gl5weJ2TNZw1qn2UY85Iki4c9VdaLUONYnyYH3+178YB+9RFe/Hw==", - "dev": true, - "requires": { - "ansi-styles": "^5.0.0", - "cli-table3": "^0.6.0", - "figures": "^3.2.0", - "ts-dedent": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "@cucumber/tag-expressions": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@cucumber/tag-expressions/-/tag-expressions-4.1.0.tgz", - "integrity": "sha512-chTnjxV3vryL75N90wJIMdMafXmZoO2JgNJLYpsfcALL2/IQrRiny3vM9DgD5RDCSt1LNloMtb7rGey9YWxCsA==", - "dev": true - }, - "@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "globals": { - "version": "13.11.0", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "type-fest": { - "version": "0.20.2", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "@grpc/grpc-js": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.7.0.tgz", - "integrity": "sha512-wvKxal+40Xx11DXO2q5PfY3UiE25iwTb8SOz6A9IJII/V7d19x2ex0he+GJfVW0JZCaBjCPSjUB0yU9Ecm4WCw==", - "dev": true, - "requires": { - "@grpc/proto-loader": "^0.7.0", - "@types/node": ">=12.12.47" - }, - "dependencies": { - "@grpc/proto-loader": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.2.tgz", - "integrity": "sha512-jCdyLIT/tdQ1zhrbTQnJNK5nbDf0GoBpy5jVNywBzzMDF+Vs6uEaHnfz46dMtDxkvwrF2hzk5Z67goliceH0sA==", - "dev": true, - "requires": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^7.0.0", - "yargs": "^16.2.0" - } - }, - "protobufjs": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.1.tgz", - "integrity": "sha512-d0nMQqS/aT3lfV8bKi9Gbg73vPd2LcDdTDOu6RE/M+h9DY8g1EmDzk3ADPccthEWfTBjkR2oxNdx9Gs8YubT+g==", - "dev": true, - "requires": { - "@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/node": ">=13.7.0", - "long": "^5.0.0" - }, - "dependencies": { - "long": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", - "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==", - "dev": true - } - } - } - } - }, - "@grpc/proto-loader": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.6.tgz", - "integrity": "sha512-DT14xgw3PSzPxwS13auTEwxhMMOoz33DPUKNtmYK/QYbBSpLXJy78FGGs5yVoxVobEqPm4iW9MOIoz0A3bLTRQ==", - "dev": true, - "requires": { - "lodash.camelcase": "^4.3.0", - "protobufjs": "^6.8.6" - } - }, - "@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/object-schema": { - "version": "1.2.0", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", - "dev": true - }, - "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", - "dev": true, - "requires": { - "eslint-scope": "5.1.1" - } - }, - "@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "dev": true - }, - "@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "dev": true - }, - "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "dev": true - }, - "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "dev": true - }, - "@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "dev": true, - "requires": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "dev": true - }, - "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "dev": true - }, - "@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "dev": true - }, - "@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "dev": true - }, - "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "dev": true - }, - "@teppeis/multimaps": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@teppeis/multimaps/-/multimaps-2.0.0.tgz", - "integrity": "sha512-TL1adzq1HdxUf9WYduLcQ/DNGYiz71U31QRgbnr0Ef1cPyOUOsBojxHVWpFeOSUucB6Lrs0LxFRA14ntgtkc9w==", - "dev": true - }, - "@types/lodash": { - "version": "4.14.185", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.185.tgz", - "integrity": "sha512-evMDG1bC4rgQg4ku9tKpuMh5iBNEwNa3tf9zRHdP1qlv+1WUg44xat4IxCE14gIpZRGUUWAx2VhItCZc25NfMA==", - "dev": true - }, - "@types/long": { - "version": "4.0.1", - "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==", - "dev": true - }, - "@types/node": { - "version": "16.10.3", - "integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==", - "dev": true - }, - "@types/uuid": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", - "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", - "dev": true - }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-colors": { - "version": "4.1.1", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true - }, - "archiver": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.1.tgz", - "integrity": "sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==", - "requires": { - "archiver-utils": "^2.1.0", - "async": "^3.2.3", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.0.0", - "tar-stream": "^2.2.0", - "zip-stream": "^4.1.0" - } - }, - "archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "requires": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "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" - } - } - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "assertion-error-formatter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/assertion-error-formatter/-/assertion-error-formatter-3.0.0.tgz", - "integrity": "sha512-6YyAVLrEze0kQ7CmJfUgrLHb+Y7XghmL2Ie7ijVa2Y9ynP3LV+VDiwFk62Dn0qtqbmY0BT0ss6p1xxpiF2PYbQ==", - "dev": true, - "requires": { - "diff": "^4.0.1", - "pad-right": "^0.2.2", - "repeat-string": "^1.6.1" - } - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" - }, - "axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "requires": { - "follow-redirects": "^1.14.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - }, - "dependencies": { - "buffer": { - "version": "5.7.1", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - } - } - }, - "blakejs": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", - "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" - } - }, - "buffer": { - "version": "6.0.3", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==" - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001406", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001406.tgz", - "integrity": "sha512-bWTlaXUy/rq0BBtYShc/jArYfBPjEV95euvZ8JVtO43oQExEN/WquoqpufFjNu4kSpi5cy5kMbNvzztWDfv1Jg==", - "dev": true - }, - "capital-case": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", - "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", - "dev": true, - "requires": { - "no-case": "^3.0.4", - "tslib": "^2.0.3", - "upper-case-first": "^2.0.2" - } - }, - "chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true - }, - "class-transformer": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", - "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", - "dev": true - }, - "cli-table3": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.2.tgz", - "integrity": "sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw==", - "dev": true, - "requires": { - "@colors/colors": "1.5.0", - "string-width": "^4.2.0" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "commander": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz", - "integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==", - "dev": true - }, - "compress-commons": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", - "integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", - "requires": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.2", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" - }, - "crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==" - }, - "crc32-stream": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", - "integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", - "requires": { - "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "csv-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.0.0.tgz", - "integrity": "sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==", - "requires": { - "minimist": "^1.2.0" - } - }, - "cucumber-html-reporter": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/cucumber-html-reporter/-/cucumber-html-reporter-5.5.0.tgz", - "integrity": "sha512-kF7vIwvTe7we7Wp/5uNZVZk+Ryozb688LpNvCNhou6N0RmLYPqaoV2aiN8GIB94JUBpribtlq6kDkEUHwxBVeQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "find": "^0.3.0", - "fs-extra": "^8.1.0", - "js-base64": "^2.3.2", - "jsonfile": "^5.0.0", - "lodash": "^4.17.11", - "node-emoji": "^1.10.0", - "open": "^6.4.0", - "uuid": "^3.3.3" - } - }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" - }, - "debug": { - "version": "4.3.2", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "duration": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/duration/-/duration-0.2.2.tgz", - "integrity": "sha512-06kgtea+bGreF5eKYgI/36A6pLXggY7oR4p1pq4SmdFBn1ReOL5D8RhG64VrqfTTKNucqqtBAwEj8aB88mcqrg==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.46" - } - }, - "durations": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/durations/-/durations-3.4.2.tgz", - "integrity": "sha512-V/lf7y33dGaypZZetVI1eu7BmvkbC4dItq12OElLRpKuaU5JxQstV2zHwLv8P7cNbQ+KL1WD80zMCTx5dNC4dg==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.4.254", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.254.tgz", - "integrity": "sha512-Sh/7YsHqQYkA6ZHuHMy24e6TE4eX6KZVsZb9E/DvU1nQRIrH4BflO/4k+83tfdYvDl+MObvlqHPRICzEdC9c6Q==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", - "dev": true, - "requires": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dev": true, - "requires": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "globals": { - "version": "13.11.0", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "semver": { - "version": "7.3.5", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "type-fest": { - "version": "0.20.2", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "eslint-config-prettier": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", - "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", - "dev": true, - "requires": {} - }, - "eslint-plugin-prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", - "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" - } - }, - "eslint-rule-composer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", - "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", - "dev": true - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "dev": true, - "requires": { - "type": "^2.7.2" - }, - "dependencies": { - "type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", - "dev": true - } - } - }, - "extsprintf": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", - "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "ffi-napi": { - "version": "4.0.3", - "integrity": "sha512-PMdLCIvDY9mS32RxZ0XGb95sonPRal8aqRhLbeEtWKZTe2A87qRFG9HjOhvG8EX2UmQw5XNRMIOT+1MYlWmdeg==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "get-uv-event-loop-napi-h": "^1.0.5", - "node-addon-api": "^3.0.0", - "node-gyp-build": "^4.2.1", - "ref-napi": "^2.0.1 || ^3.0.2", - "ref-struct-di": "^1.1.0" - } - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "find": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/find/-/find-0.3.0.tgz", - "integrity": "sha512-iSd+O4OEYV/I36Zl8MdYJO0xD82wH528SaCieTVHhclgiYNe9y+yPKSwK+A7/WsmHL1EZ+pYUJBXWTL5qofksw==", - "dev": true, - "requires": { - "traverse-chain": "~0.1.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.2", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", - "dev": true - }, - "follow-redirects": { - "version": "1.14.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", - "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==" - }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "dependencies": { - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - } - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", - "dev": true - }, - "get-symbol-from-current-process-h": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-from-current-process-h/-/get-symbol-from-current-process-h-1.0.2.tgz", - "integrity": "sha512-syloC6fsCt62ELLrr1VKBM1ggOpMdetX9hTrdW77UQdcApPHLmf7CI7OKcN1c9kYuNxKcDe4iJ4FY9sX3aw2xw==", - "dev": true - }, - "get-uv-event-loop-napi-h": { - "version": "1.0.6", - "integrity": "sha512-t5c9VNR84nRoF+eLiz6wFrEp1SE2Acg0wS+Ysa2zF0eROes+LzOfuTaVHxGy8AbS8rq7FHEJzjnCZo1BupwdJg==", - "dev": true, - "requires": { - "get-symbol-from-current-process-h": "^1.0.1" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", - "dev": true, - "requires": { - "ini": "2.0.0" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.8", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" - }, - "grpc-promise": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/grpc-promise/-/grpc-promise-1.4.0.tgz", - "integrity": "sha512-4BBXHXb5OjjBh7luylu8vFqL6H6aPn/LeqpQaSBeRzO/Xv95wHW/WkU9TJRqaCTMZ5wq9jTSvlJWp0vRJy1pVA==", - "dev": true - }, - "has-ansi": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-4.0.1.tgz", - "integrity": "sha512-Qr4RtTm30xvEdqUXbSBVWDu+PrTokJOwe/FU+VdfJPk+MXAPoeOzKpRyrDTnZIJwAkQ4oBLTU53nu0HrkF/Z2A==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true - } - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "husky": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz", - "integrity": "sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ==", - "dev": true - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "requires": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - } - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "requires": { - "isobject": "^3.0.1" - } - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==" - }, - "js-base64": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", - "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "json5": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz", - "integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==" - }, - "jsonfile": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-5.0.0.tgz", - "integrity": "sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^0.1.2" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" - }, - "knuth-shuffle-seeded": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/knuth-shuffle-seeded/-/knuth-shuffle-seeded-1.0.6.tgz", - "integrity": "sha512-9pFH0SplrfyKyojCLxZfMcvkhf5hH0d+UwR9nTVJ/DDQJGuzcXjTwB7TP7sDfehSudlGGaOLblmEWqv04ERVWg==", - "dev": true, - "requires": { - "seed-random": "~2.2.0" - } - }, - "lazystream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", - "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", - "requires": { - "readable-stream": "^2.0.5" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "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" - } - } - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "dev": true - }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true - }, - "lodash.clonedeep": { - "version": "4.5.0", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" - }, - "lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==" - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.mergewith": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", - "dev": true - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true - }, - "lodash.union": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==" - }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", - "dev": true - }, - "loupe": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.1.tgz", - "integrity": "sha512-EN1D3jyVmaX4tnajVlfbREU4axL647hLec1h/PXAb8CPDMJiYitcWF2UeLVNttRqaIqQs4x+mRvXf+d+TlDrCA==", - "dev": true, - "requires": { - "get-func-name": "^2.0.0" - } - }, - "lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, - "requires": { - "tslib": "^2.0.3" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "requires": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "nanoclone": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", - "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "dev": true - }, - "no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "requires": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node-addon-api": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", - "dev": true - }, - "node-emoji": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", - "dev": true, - "requires": { - "lodash": "^4.17.21" - } - }, - "node-gyp-build": { - "version": "4.3.0", - "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", - "dev": true - }, - "node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "requires": { - "wrappy": "1" - } - }, - "open": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", - "dev": true, - "requires": { - "is-wsl": "^1.1.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "pad-right": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/pad-right/-/pad-right-0.2.2.tgz", - "integrity": "sha512-4cy8M95ioIGolCoMmm2cMntGR1lPLEbOMzOKu8bzjuJP6JpzEMQcDHmh7hHLYGgob+nKe1YHFMaG4V59HQa89g==", - "dev": true, - "requires": { - "repeat-string": "^1.5.2" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", - "dev": true - }, - "prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "requires": { - "fast-diff": "^1.1.2" - } - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "property-expr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.5.tgz", - "integrity": "sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==", - "dev": true - }, - "protobufjs": { - "version": "6.11.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz", - "integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==", - "dev": true, - "requires": { - "@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.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdir-glob": { - "version": "1.1.1", - "integrity": "sha512-91/k1EzZwDx6HbERR+zucygRFfiPl2zkIYZtv3Jjr6Mn7SkKcVct8aVO+sSRiGMc6fLf72du3d92/uY63YPdEA==", - "requires": { - "minimatch": "^3.0.4" - } - }, - "ref-napi": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ref-napi/-/ref-napi-3.0.3.tgz", - "integrity": "sha512-LiMq/XDGcgodTYOMppikEtJelWsKQERbLQsYm0IOOnzhwE9xYZC7x8txNnFC9wJNOkPferQI4vD4ZkC0mDyrOA==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "get-symbol-from-current-process-h": "^1.0.2", - "node-addon-api": "^3.0.0", - "node-gyp-build": "^4.2.1" - } - }, - "ref-struct-di": { - "version": "1.1.1", - "integrity": "sha512-2Xyn/0Qgz89VT+++WP0sTosdm9oeowLP23wRJYhG4BFdMUrLj3jhwHZNEytYNYgtPKLNTP3KJX4HEgBvM1/Y2g==", - "dev": true, - "requires": { - "debug": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true - }, - "regexp-match-indices": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regexp-match-indices/-/regexp-match-indices-1.0.2.tgz", - "integrity": "sha512-DwZuAkt8NF5mKwGGER1EGh2PRqyvhRhhLviH+R8y8dIuaQROlUfXjt4s9ZTXstIsSkptf06BSvwcEmmfheJJWQ==", - "dev": true, - "requires": { - "regexp-tree": "^0.1.11" - } - }, - "regexp-tree": { - "version": "0.1.24", - "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.24.tgz", - "integrity": "sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw==", - "dev": true - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "dev": true - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "resolve-from": { - "version": "4.0.0", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "resolve-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg/-/resolve-pkg-2.0.0.tgz", - "integrity": "sha512-+1lzwXehGCXSeryaISr6WujZzowloigEofRB+dj75y9RRa/obVcYgbHJd53tdYw8pvZj8GojXaaENws8Ktw/hQ==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - } - } - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "seed-random": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz", - "integrity": "sha512-34EQV6AAHQGhoc0tn/96a9Fsi6v2xdqe/dMUwljGRaFOzR3EgRmECvD0O8vi8X+/uQ50LGHfkNu/Eue5TPKZkQ==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "sha3": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/sha3/-/sha3-2.1.4.tgz", - "integrity": "sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg==", - "requires": { - "buffer": "6.0.3" - } - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "requires": { - "kind-of": "^6.0.2" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "stack-chain": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-2.0.0.tgz", - "integrity": "sha512-GGrHXePi305aW7XQweYZZwiRwR7Js3MWoK/EHzzB9ROdc75nCnjSJVi21rdAGxFl+yCx2L2qdfl5y7NO4lTyqg==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "string-argv": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", - "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "table": { - "version": "6.7.2", - "integrity": "sha512-UFZK67uvyNivLeQbVtkiUs8Uuuxv24aSL4/Vil2PJVtMgU8Lx0CYkP12uCGa3kjyQzOSgV1+z9Wkb82fCGsO0g==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ajv": { - "version": "8.6.3", - "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } - } - }, - "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "tari_crypto": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/tari_crypto/-/tari_crypto-0.14.0.tgz", - "integrity": "sha512-vwwI3SJ08Dcv4tGL+YVGE5LDmWwJnaRjZV2UNP0XoKKgZX88oMyKUup7RyQTpIaGOBTaqblI0uK79w3U+62ZRQ==" - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "requires": { - "any-promise": "^1.0.0" - } - }, - "thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "requires": { - "thenify": ">= 3.1.0 < 4" - } - }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "requires": { - "rimraf": "^3.0.0" - } - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true - }, - "toposort": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", - "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", - "dev": true - }, - "traverse-chain": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz", - "integrity": "sha512-up6Yvai4PYKhpNp5PkYtx50m3KbwQrqDwbuZP/ItyL64YEWHAvH6Md83LFLV/GRSk/BoUVwwgUzX6SOQSbsfAg==", - "dev": true - }, - "ts-dedent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", - "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", - "dev": true - }, - "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true - }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - }, - "update-browserslist-db": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz", - "integrity": "sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==", - "dev": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "upper-case-first": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", - "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", - "dev": true, - "requires": { - "tslib": "^2.0.3" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" - }, - "util-arity": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/util-arity/-/util-arity-1.1.0.tgz", - "integrity": "sha512-kkyIsXKwemfSy8ZEoaIz06ApApnWsk5hQO0vLjZS6UkBiGiW++Jsyb8vSBoc0WKlffGoGs5yYy/j5pp8zckrFA==", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "uuid": { - "version": "3.4.0", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "varint": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", - "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==" - }, - "verror": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", - "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "wallet-grpc-client": { - "version": "file:../clients/nodejs/wallet_grpc_client", - "requires": { - "@grpc/grpc-js": "^1.7.0", - "@grpc/proto-loader": "^0.5.5", - "grpc-promise": "^1.4.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true - }, - "yup": { - "version": "0.32.11", - "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz", - "integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.15.4", - "@types/lodash": "^4.14.175", - "lodash": "^4.17.21", - "lodash-es": "^4.17.21", - "nanoclone": "^0.2.1", - "property-expr": "^2.0.4", - "toposort": "^2.0.2" - } - }, - "zip-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", - "integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", - "requires": { - "archiver-utils": "^2.1.0", - "compress-commons": "^4.1.0", - "readable-stream": "^3.6.0" - } - } - } -} diff --git a/integration_tests/package.json b/integration_tests/package.json deleted file mode 100644 index 6914b5c416..0000000000 --- a/integration_tests/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "integration_tests", - "version": "1.0.0", - "description": "Tari Integration Tests", - "repository": "https://github.com/tari-project/tari", - "scripts": { - "test": "cucumber-js", - "fmt": "prettier --end-of-line auto -w .", - "check-fmt": "prettier --end-of-line auto -c .", - "lint": "eslint .", - "lint-fix": "eslint --fix .", - "prepare": "cd .. && husky install integration_tests/.husky" - }, - "author": "The Tari Project", - "license": "ISC", - "devDependencies": { - "@babel/core": "^7.19.1", - "@babel/eslint-parser": "^7.19.1", - "@babel/eslint-plugin": "^7.19.1", - "@cucumber/cucumber": "^8.5.3", - "@cucumber/pretty-formatter": "^1.0.0", - "@grpc/grpc-js": "^1.7.0", - "@grpc/proto-loader": "^0.5.5", - "blakejs": "^1.2.1", - "chai": "^4.3.6", - "cucumber-html-reporter": "^5.5.0", - "eslint": "^7.32.0", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-prettier": "^3.4.1", - "ffi-napi": "^4.0.3", - "grpc-promise": "^1.4.0", - "husky": "^6.0.0", - "prettier": "^2.7.1", - "ref-napi": "^3.0.3" - }, - "dependencies": { - "archiver": "^5.3.1", - "axios": "^0.21.4", - "clone-deep": "^4.0.1", - "csv-parser": "^3.0.0", - "dateformat": "^3.0.3", - "glob": "^7.2.3", - "json5": "^2.2.2", - "sha3": "^2.1.3", - "tari_crypto": "v0.14.0", - "utf8": "^3.0.0", - "wallet-grpc-client": "file:../clients/nodejs/wallet_grpc_client", - "varint": "^6.0.0" - } -} diff --git a/integration_tests/run-tests.sh b/integration_tests/run-tests.sh deleted file mode 100755 index dc9c9753cf..0000000000 --- a/integration_tests/run-tests.sh +++ /dev/null @@ -1,6 +0,0 @@ -set -e -if [ ! -d cucumber_output ]; then - mkdir cucumber_output -fi -./node_modules/.bin/cucumber-js -f @cucumber/pretty-formatter -f json:cucumber_output/tests.cucumber "$@" -node ./generate_report.js diff --git a/integration_tests/src/error.rs b/integration_tests/src/error.rs new file mode 100644 index 0000000000..3c49c1ba49 --- /dev/null +++ b/integration_tests/src/error.rs @@ -0,0 +1,55 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use tari_common_types::types::FixedHashSizeError; +use tari_comms::peer_manager::PeerManagerError; +use thiserror::Error; + +use crate::optional::IsNotFoundError; + +#[derive(Error, Debug)] +pub enum GrpcBaseNodeError { + #[error("Could not connect to base node")] + ConnectionError, + #[error("Connection error: {0}")] + GrpcConnection(#[from] tonic::transport::Error), + #[error("GRPC error: {0}")] + GrpcStatus(#[from] tonic::Status), + #[error("Peer sent an invalid message: {0}")] + InvalidPeerMessage(String), + #[error("Hash size error: {0}")] + HashSizeError(#[from] FixedHashSizeError), + #[error("Node not found: {0}")] + NodeNotFound(String), + #[error("Peer manager error: {0}")] + PeerManagerError(#[from] PeerManagerError), +} + +impl IsNotFoundError for GrpcBaseNodeError { + fn is_not_found_error(&self) -> bool { + if let Self::GrpcStatus(status) = self { + status.code() == tonic::Code::NotFound + } else { + false + } + } +} diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs new file mode 100644 index 0000000000..7887fc4552 --- /dev/null +++ b/integration_tests/src/lib.rs @@ -0,0 +1,25 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +pub mod error; +pub mod optional; +pub mod types; diff --git a/integration_tests/src/optional.rs b/integration_tests/src/optional.rs new file mode 100644 index 0000000000..4aff81c0a4 --- /dev/null +++ b/integration_tests/src/optional.rs @@ -0,0 +1,45 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/// A type that can indicate something is not found. +/// Implement this on `E` to get the `.optional()?` function on the `Result` type. +pub trait IsNotFoundError { + fn is_not_found_error(&self) -> bool; +} + +pub trait Optional { + type Error; + + fn optional(self) -> Result, Self::Error>; +} + +impl Optional for Result { + type Error = E; + + fn optional(self) -> Result, Self::Error> { + match self { + Ok(t) => Ok(Some(t)), + Err(e) if e.is_not_found_error() => Ok(None), + Err(e) => Err(e), + } + } +} diff --git a/integration_tests/src/types.rs b/integration_tests/src/types.rs new file mode 100644 index 0000000000..a28606e8a6 --- /dev/null +++ b/integration_tests/src/types.rs @@ -0,0 +1,54 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// message RegisterValidatorNodeRequest { +// bytes validator_node_public_key = 1; +// Signature validator_node_signature = 2; +// uint64 fee_per_gram = 3; +// string message = 4; +// } +// +// message RegisterValidatorNodeResponse { +// uint64 transaction_id = 1; +// bool is_success = 2; +// string failure_message = 3; +// } + +use tari_common_types::types::FixedHash; + +#[derive(Debug, Clone)] +pub struct BlockInfo { + pub hash: FixedHash, + pub height: u64, + pub next_block_hash: Option, +} + +#[derive(Debug, Clone)] +pub struct BaseLayerMetadata { + pub height_of_longest_chain: u64, + pub tip_hash: FixedHash, +} + +#[derive(Debug, Clone)] +pub struct RegisterValidatorNode { + // +} diff --git a/integration_tests/start_wallet.sh b/integration_tests/start_wallet.sh deleted file mode 100755 index e69de29bb2..0000000000 diff --git a/integration_tests/tests/cucumber.rs b/integration_tests/tests/cucumber.rs new file mode 100644 index 0000000000..25c69eda88 --- /dev/null +++ b/integration_tests/tests/cucumber.rs @@ -0,0 +1,5028 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#![feature(internal_output_capture)] + +mod utils; + +use std::{ + collections::VecDeque, + convert::TryFrom, + io::BufRead, + path::PathBuf, + ptr::null, + str::{self, FromStr}, + sync::{Arc, Mutex}, + time::Duration, +}; + +use cucumber::{event::ScenarioFinished, gherkin::Scenario, given, then, when, World as _}; +use futures::StreamExt; +use indexmap::IndexMap; +use log::*; +use rand::Rng; +use serde_json::Value; +use tari_app_grpc::tari_rpc::{self as grpc}; +use tari_app_utilities::utilities::UniPublicKey; +use tari_base_node::BaseNodeConfig; +use tari_base_node_grpc_client::grpc::{GetBlocksRequest, ListHeadersRequest}; +use tari_common::{configuration::Network, initialize_logging}; +use tari_common_types::{ + tari_address::TariAddress, + types::{BlindingFactor, ComAndPubSignature, Commitment, PrivateKey, PublicKey}, +}; +use tari_comms::multiaddr::Multiaddr; +use tari_console_wallet::{ + BurnTariArgs, + CliCommands, + CoinSplitArgs, + DiscoverPeerArgs, + ExportUtxosArgs, + MakeItRainArgs, + SendTariArgs, + SetBaseNodeArgs, + WhoisArgs, +}; +use tari_core::{ + blocks::Block, + consensus::ConsensusManager, + covenants::Covenant, + transactions::{ + tari_amount::MicroTari, + transaction_components::{ + EncryptedValue, + OutputFeatures, + OutputType, + Transaction, + TransactionOutputVersion, + UnblindedOutput, + }, + }, +}; +use tari_crypto::{commitment::HomomorphicCommitment, keys::PublicKey as PublicKeyTrait}; +use tari_integration_tests::error::GrpcBaseNodeError; +use tari_script::{ExecutionStack, StackItem, TariScript}; +use tari_utilities::hex::Hex; +use tari_wallet::transaction_service::config::TransactionRoutingMechanism; +use tari_wallet_grpc_client::grpc::{ + CancelTransactionRequest, + ClaimHtlcRefundRequest, + ClaimShaAtomicSwapRequest, + Empty, + GetBalanceRequest, + GetCompletedTransactionsRequest, + GetIdentityRequest, + GetTransactionInfoRequest, + ImportUtxosRequest, + PaymentRecipient, + SendShaAtomicSwapRequest, + TransferRequest, +}; +use thiserror::Error; +use tokio::runtime::Runtime; + +use crate::utils::{ + base_node_process::{spawn_base_node, spawn_base_node_with_config, BaseNodeProcess}, + get_peer_addresses, + merge_mining_proxy::{register_merge_mining_proxy_process, MergeMiningProxyProcess}, + miner::{ + mine_block, + mine_block_before_submit, + mine_block_with_coinbase_on_node, + mine_blocks_without_wallet, + register_miner_process, + MinerProcess, + }, + transaction::{build_transaction_with_output, build_transaction_with_output_and_fee}, + wallet_ffi::{create_contact, create_seed_words, get_mnemonic_word_list_for_language, spawn_wallet_ffi, WalletFFI}, + wallet_process::{create_wallet_client, get_default_cli, spawn_wallet, WalletProcess}, +}; + +pub const LOG_TARGET: &str = "cucumber"; +pub const LOG_TARGET_STDOUT: &str = "stdout"; +const CONFIRMATION_PERIOD: u64 = 4; +const TWO_MINUTES_WITH_HALF_SECOND_SLEEP: u64 = 240; +const HALF_SECOND: u64 = 500; + +#[derive(Error, Debug)] +pub enum TariWorldError { + #[error("Base node process not found: {0}")] + BaseNodeProcessNotFound(String), + #[error("Wallet process not found: {0}")] + WalletProcessNotFound(String), + #[error("FFIWallet not found: {0}")] + FFIWalletNotFound(String), + #[error("Miner process not found: {0}")] + MinerProcessNotFound(String), + #[error("Merge miner process not found: {0}")] + MergeMinerProcessNotFound(String), + #[error("Base node error: {0}")] + GrpcBaseNodeError(#[from] GrpcBaseNodeError), + #[error("No base node, or wallet client found: {0}")] + ClientNotFound(String), +} + +#[derive(Debug, Default, cucumber::World)] +pub struct TariWorld { + base_nodes: IndexMap, + blocks: IndexMap, + miners: IndexMap, + ffi_wallets: IndexMap, + wallets: IndexMap, + merge_mining_proxies: IndexMap, + transactions: IndexMap, + wallet_addresses: IndexMap, // values are strings representing tari addresses + utxos: IndexMap, + output_hash: Option, + pre_image: Option, + wallet_connected_to_base_node: IndexMap, // wallet -> base node, + seed_nodes: Vec, + // mapping from hex string of public key of wallet client to tx_id's + wallet_tx_ids: IndexMap>, + errors: VecDeque, + // We need to store this in between steps when importing and checking the imports. + last_imported_tx_ids: Vec, + // We need to store this for the merge mining proxy steps. The checks are get and check are done on separate steps. + last_merge_miner_response: Value, +} + +enum NodeClient { + BaseNode(tari_base_node_grpc_client::BaseNodeGrpcClient), + Wallet(tari_wallet_grpc_client::WalletGrpcClient), +} + +impl TariWorld { + async fn get_node_client>( + &self, + name: &S, + ) -> anyhow::Result> { + self.get_node(name)?.get_grpc_client().await + } + + async fn get_base_node_or_wallet_client>( + &self, + name: S, + ) -> anyhow::Result { + match self.get_node_client(&name).await { + Ok(client) => Ok(NodeClient::BaseNode(client)), + Err(_) => match self.get_wallet_client(&name).await { + Ok(wallet) => Ok(NodeClient::Wallet(wallet)), + Err(e) => Err(TariWorldError::ClientNotFound(e.to_string()).into()), + }, + } + } + + async fn get_wallet_address>(&self, name: &S) -> anyhow::Result { + if let Some(address) = self.wallet_addresses.get(name.as_ref()) { + return Ok(address.clone()); + } + match self.get_wallet_client(name).await { + Ok(wallet) => { + let mut wallet = wallet; + + Ok(wallet + .get_address(Empty {}) + .await + .unwrap() + .into_inner() + .address + .to_hex()) + }, + Err(_) => { + let ffi_wallet = self.get_ffi_wallet(name).unwrap(); + + Ok(ffi_wallet.get_address().address().get_as_hex()) + }, + } + } + + #[allow(dead_code)] + async fn get_wallet_client>( + &self, + name: &S, + ) -> anyhow::Result> { + self.get_wallet(name)?.get_grpc_client().await + } + + fn get_node>(&self, node_name: &S) -> anyhow::Result<&BaseNodeProcess> { + Ok(self + .base_nodes + .get(node_name.as_ref()) + .ok_or_else(|| TariWorldError::BaseNodeProcessNotFound(node_name.as_ref().to_string()))?) + } + + fn get_wallet>(&self, wallet_name: &S) -> anyhow::Result<&WalletProcess> { + Ok(self + .wallets + .get(wallet_name.as_ref()) + .ok_or_else(|| TariWorldError::WalletProcessNotFound(wallet_name.as_ref().to_string()))?) + } + + fn get_ffi_wallet>(&self, wallet_name: &S) -> anyhow::Result<&WalletFFI> { + Ok(self + .ffi_wallets + .get(wallet_name.as_ref()) + .ok_or_else(|| TariWorldError::FFIWalletNotFound(wallet_name.as_ref().to_string()))?) + } + + fn get_mut_ffi_wallet>(&mut self, wallet_name: &S) -> anyhow::Result<&mut WalletFFI> { + Ok(self + .ffi_wallets + .get_mut(wallet_name.as_ref()) + .ok_or_else(|| TariWorldError::FFIWalletNotFound(wallet_name.as_ref().to_string()))?) + } + + fn get_miner>(&self, miner_name: S) -> anyhow::Result<&MinerProcess> { + Ok(self + .miners + .get(miner_name.as_ref()) + .ok_or_else(|| TariWorldError::MinerProcessNotFound(miner_name.as_ref().to_string()))?) + } + + fn get_merge_miner>(&self, miner_name: S) -> anyhow::Result<&MergeMiningProxyProcess> { + Ok(self + .merge_mining_proxies + .get(miner_name.as_ref()) + .ok_or_else(|| TariWorldError::MergeMinerProcessNotFound(miner_name.as_ref().to_string()))?) + } + + fn get_mut_merge_miner>(&mut self, miner_name: S) -> anyhow::Result<&mut MergeMiningProxyProcess> { + Ok(self + .merge_mining_proxies + .get_mut(miner_name.as_ref()) + .ok_or_else(|| TariWorldError::MergeMinerProcessNotFound(miner_name.as_ref().to_string()))?) + } + + pub fn all_seed_nodes(&self) -> &[String] { + self.seed_nodes.as_slice() + } + + pub async fn after(&mut self, _scenario: &Scenario) { + self.base_nodes.clear(); + self.seed_nodes.clear(); + self.wallets.clear(); + self.ffi_wallets.clear(); + self.miners.clear(); + } +} + +#[given(expr = "I have a seed node {word}")] +#[when(expr = "I have a seed node {word}")] +async fn start_base_node(world: &mut TariWorld, name: String) { + spawn_base_node(world, true, name, vec![]).await; +} + +#[given(expr = "a wallet {word} connected to base node {word}")] +async fn start_wallet(world: &mut TariWorld, wallet_name: String, node_name: String) { + let seeds = world.base_nodes.get(&node_name).unwrap().seed_nodes.clone(); + world + .wallet_connected_to_base_node + .insert(wallet_name.clone(), node_name.clone()); + spawn_wallet(world, wallet_name, Some(node_name), seeds, None, None).await; +} + +#[given(expr = "I have a base node {word} connected to all seed nodes")] +#[when(expr = "I have a base node {word} connected to all seed nodes")] +async fn start_base_node_connected_to_all_seed_nodes(world: &mut TariWorld, name: String) { + spawn_base_node(world, false, name, world.all_seed_nodes().to_vec()).await; +} + +#[when(expr = "I start base node {word}")] +async fn start_base_node_step(world: &mut TariWorld, name: String) { + let mut is_seed_node = false; + let mut seed_nodes = world.all_seed_nodes().to_vec(); + if let Some(node_ps) = world.base_nodes.get(&name) { + is_seed_node = node_ps.is_seed_node; + seed_nodes = node_ps.seed_nodes.clone(); + } + spawn_base_node(world, is_seed_node, name, seed_nodes).await; +} + +#[when(expr = "I have {int} base nodes connected to all seed nodes")] +async fn multiple_base_nodes_connected_to_all_seeds(world: &mut TariWorld, nodes: u64) { + for i in 0..nodes { + let node = format!("Node_{}", i); + println!("Initializing node {}", node.clone()); + spawn_base_node(world, false, node, world.all_seed_nodes().to_vec()).await; + } +} + +#[when(expr = "I have wallet {word} connected to all seed nodes")] +async fn start_wallet_connected_to_all_seed_nodes(world: &mut TariWorld, name: String) { + // assuming we have deployed at least a base node, we take the first one as base node for wallet to connect to + let nodes = world.all_seed_nodes().to_vec(); + let node = nodes.first().unwrap(); + world.wallet_connected_to_base_node.insert(name.clone(), node.clone()); + spawn_wallet( + world, + name, + Some(node.clone()), + world.all_seed_nodes().to_vec(), + None, + None, + ) + .await; +} + +#[when(expr = "I have mine-before-tip mining node {word} connected to base node {word} and wallet {word}")] +#[when(expr = "I have mining node {word} connected to base node {word} and wallet {word}")] +async fn create_miner(world: &mut TariWorld, miner_name: String, bn_name: String, wallet_name: String) { + register_miner_process(world, miner_name, bn_name, wallet_name); +} + +#[when(expr = "I wait {int} seconds")] +async fn wait_seconds(_world: &mut TariWorld, seconds: u64) { + tokio::time::sleep(Duration::from_secs(seconds)).await; +} + +#[when(expr = "I wait for {word} to connect to {word}")] +#[then(expr = "I wait for {word} to connect to {word}")] +#[then(expr = "{word} is connected to {word}")] +async fn node_pending_connection_to(world: &mut TariWorld, first_node: String, second_node: String) { + let mut node_client = world.get_base_node_or_wallet_client(&first_node).await.unwrap(); + let second_client = world.get_base_node_or_wallet_client(&second_node).await.unwrap(); + + let second_client_pubkey = match second_client { + NodeClient::Wallet(mut client) => { + client + .identify(GetIdentityRequest {}) + .await + .unwrap() + .into_inner() + .public_key + }, + NodeClient::BaseNode(mut client) => client.identify(Empty {}).await.unwrap().into_inner().public_key, + }; + + for _i in 0..100 { + let res = match node_client { + NodeClient::Wallet(ref mut client) => client.list_connected_peers(Empty {}).await.unwrap(), + NodeClient::BaseNode(ref mut client) => client.list_connected_peers(Empty {}).await.unwrap(), + }; + let res = res.into_inner(); + + if res.connected_peers.iter().any(|p| p.public_key == second_client_pubkey) { + return; + } + tokio::time::sleep(Duration::from_secs(5)).await; + } + + panic!("Peer was not connected in time"); +} + +#[when(expr = "mining node {word} mines {int} blocks")] +#[given(expr = "mining node {word} mines {int} blocks")] +async fn run_miner(world: &mut TariWorld, miner_name: String, num_blocks: u64) { + world + .get_miner(miner_name) + .unwrap() + .mine(world, Some(num_blocks), None, None) + .await; +} + +#[then(expr = "all nodes are on the same chain at height {int}")] +async fn all_nodes_on_same_chain_at_height(world: &mut TariWorld, height: u64) { + let mut nodes_at_height: IndexMap<&String, (u64, Vec)> = IndexMap::new(); + + for (name, _) in world.base_nodes.iter() { + nodes_at_height.insert(name, (0, vec![])); + } + + for _ in 0..(TWO_MINUTES_WITH_HALF_SECOND_SLEEP * height) { + for (name, _) in nodes_at_height + .clone() + .iter() + .filter(|(_, (at_height, _))| at_height != &height) + { + let mut client = world.get_node_client(name).await.unwrap(); + + let chain_tip = client.get_tip_info(Empty {}).await.unwrap().into_inner(); + let metadata = chain_tip.metadata.unwrap(); + + nodes_at_height.insert(name, (metadata.height_of_longest_chain, metadata.best_block)); + } + + if nodes_at_height + .values() + .all(|(h, block_hash)| h == &height && block_hash == &nodes_at_height.values().last().unwrap().1) + { + return; + } + + tokio::time::sleep(Duration::from_millis(HALF_SECOND)).await; + } + + panic!( + "base nodes not successfully synchronized at height {}, {:?}", + height, nodes_at_height + ); +} + +#[then(expr = "all nodes are at height {int}")] +#[when(expr = "all nodes are at height {int}")] +async fn all_nodes_are_at_height(world: &mut TariWorld, height: u64) { + let mut nodes_at_height: IndexMap<&String, u64> = IndexMap::new(); + + for (name, _) in world.base_nodes.iter() { + nodes_at_height.insert(name, 0); + } + + for _ in 0..(TWO_MINUTES_WITH_HALF_SECOND_SLEEP * 7) { + // ~14 minutes matching the original implementation timeout + for (name, _) in nodes_at_height + .clone() + .iter() + .filter(|(_, at_height)| at_height != &&height) + { + let mut client = world.get_node_client(name).await.unwrap(); + + let chain_tip = client.get_tip_info(Empty {}).await.unwrap().into_inner(); + let chain_hgt = chain_tip.metadata.unwrap().height_of_longest_chain; + + nodes_at_height.insert(name, chain_hgt); + } + + if nodes_at_height.values().all(|h| h == &height) { + return; + } + + tokio::time::sleep(Duration::from_millis(HALF_SECOND)).await; + } + + panic!( + "base nodes not successfully synchronized at height {}, {:?}", + height, nodes_at_height + ); +} + +#[when(expr = "node {word} is at height {int}")] +#[then(expr = "node {word} is at height {int}")] +async fn node_is_at_height(world: &mut TariWorld, base_node: String, height: u64) { + let mut client = world.get_node_client(&base_node).await.unwrap(); + let mut chain_hgt = 0; + + for _ in 0..=(TWO_MINUTES_WITH_HALF_SECOND_SLEEP) { + let chain_tip = client.get_tip_info(Empty {}).await.unwrap().into_inner(); + chain_hgt = chain_tip.metadata.unwrap().height_of_longest_chain; + + if chain_hgt >= height { + return; + } + + tokio::time::sleep(Duration::from_millis(HALF_SECOND)).await; + } + + // base node didn't synchronize successfully at height, so we bail out + panic!( + "base node didn't synchronize successfully with height {}, current chain height {}", + height, chain_hgt + ); +} + +#[then(expr = "node {word} has a pruned height of {int}")] +async fn pruned_height_of(world: &mut TariWorld, node: String, height: u64) { + let mut client = world.get_node_client(&node).await.unwrap(); + let mut last_pruned_height = 0; + + for _ in 0..=TWO_MINUTES_WITH_HALF_SECOND_SLEEP { + let chain_tip = client.get_tip_info(Empty {}).await.unwrap().into_inner(); + last_pruned_height = chain_tip.metadata.unwrap().pruned_height; + + if last_pruned_height == height { + return; + } + + tokio::time::sleep(Duration::from_millis(HALF_SECOND)).await; + } + + panic!( + "Node {} pruned height is {} and never reached expected pruned height of {}", + node, last_pruned_height, height + ) +} + +#[when(expr = "I wait for wallet {word} to have at least {int} uT")] +#[then(expr = "I wait for wallet {word} to have at least {int} uT")] +async fn wait_for_wallet_to_have_micro_tari(world: &mut TariWorld, wallet: String, amount: u64) { + let wallet_ps = world.wallets.get(&wallet).unwrap(); + let num_retries = 100; + + let mut client = wallet_ps.get_grpc_client().await.unwrap(); + let mut curr_amount = 0; + + for _ in 0..=num_retries { + curr_amount = client + .get_balance(GetBalanceRequest {}) + .await + .unwrap() + .into_inner() + .available_balance; + + if curr_amount >= amount { + return; + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } + + // failed to get wallet right amount, so we panic + panic!( + "wallet {} failed to get balance of at least amount {}, current amount is {}", + wallet, amount, curr_amount + ); +} + +#[given(expr = "I have a base node {word} connected to seed {word}")] +#[when(expr = "I have a base node {word} connected to seed {word}")] +async fn base_node_connected_to_seed(world: &mut TariWorld, base_node: String, seed: String) { + spawn_base_node(world, false, base_node, vec![seed]).await; +} + +#[then(expr = "I mine {int} blocks on {word}")] +#[when(expr = "I mine {int} blocks on {word}")] +async fn mine_blocks_on(world: &mut TariWorld, blocks: u64, base_node: String) { + let mut client = world + .get_node_client(&base_node) + .await + .expect("Couldn't get the node client to mine with"); + mine_blocks_without_wallet(&mut client, blocks, 0).await; +} + +#[when(expr = "I have wallet {word} connected to base node {word}")] +async fn wallet_connected_to_base_node(world: &mut TariWorld, wallet: String, base_node: String) { + let bn = world.base_nodes.get(&base_node).unwrap(); + let peer_seeds = bn.seed_nodes.clone(); + world + .wallet_connected_to_base_node + .insert(wallet.clone(), base_node.clone()); + + let mut cli = get_default_cli(); + cli.seed_words_file_name = Some(PathBuf::new().join("seed_words.txt")); + spawn_wallet(world, wallet, Some(base_node), peer_seeds, None, Some(cli)).await; +} + +#[when(expr = "mining node {word} mines {int} blocks with min difficulty {int} and max difficulty {int}")] +#[then(expr = "mining node {word} mines {int} blocks with min difficulty {int} and max difficulty {int}")] +async fn mining_node_mines_blocks_with_difficulty( + world: &mut TariWorld, + miner: String, + blocks: u64, + min_difficulty: u64, + max_difficulty: u64, +) { + let miner_ps = world.miners.get(&miner).unwrap(); + miner_ps + .mine(world, Some(blocks), Some(min_difficulty), Some(max_difficulty)) + .await; +} + +#[when(expr = "I have a base node {word}")] +#[given(expr = "I have a base node {word}")] +async fn create_and_add_base_node(world: &mut TariWorld, base_node: String) { + spawn_base_node(world, false, base_node, vec![]).await; +} + +#[given(expr = "I have {int} seed nodes")] +async fn have_seed_nodes(world: &mut TariWorld, seed_nodes: u64) { + for node in 0..seed_nodes { + spawn_base_node(world, true, format!("seed_node_{}", node), vec![]).await; + } +} + +#[when(expr = "I have wallet {word} connected to seed node {word}")] +async fn have_wallet_connect_to_seed_node(world: &mut TariWorld, wallet: String, seed_node: String) { + world + .wallet_connected_to_base_node + .insert(wallet.clone(), seed_node.clone()); + spawn_wallet(world, wallet, Some(seed_node.clone()), vec![seed_node], None, None).await; +} + +#[when(expr = "I mine a block on {word} with coinbase {word}")] +async fn mine_block_with_coinbase_on_node_step(world: &mut TariWorld, base_node: String, coinbase_name: String) { + mine_block_with_coinbase_on_node(world, base_node, coinbase_name).await; +} + +#[then(expr = "{word} has {word} in {word} state")] +async fn transaction_in_state( + world: &mut TariWorld, + node: String, + tx_name: String, + state: String, +) -> anyhow::Result<()> { + let mut client = world.get_node_client(&node).await?; + let tx = world + .transactions + .get(&tx_name) + .unwrap_or_else(|| panic!("Couldn't find transaction {}", tx_name)); + let sig = &tx.body.kernels()[0].excess_sig; + let mut last_state = "UNCHECKED: DEFAULT TEST STATE"; + + // Some state changes take up to 30 minutes to make + for _ in 0..(TWO_MINUTES_WITH_HALF_SECOND_SLEEP * 2) { + let resp = client + .transaction_state(grpc::TransactionStateRequest { + excess_sig: Some(sig.into()), + }) + .await?; + + let inner = resp.into_inner(); + + // panic!("{:?}", inner); + + last_state = match inner.result { + 0 => "UNKNOWN", + 1 => "MEMPOOL", + 2 => "MINED", + 3 => "NOT_STORED", + _ => panic!("not getting a good result"), + }; + + if last_state == state { + return Ok(()); + } + + tokio::time::sleep(Duration::from_millis(HALF_SECOND * 2)).await; + } + + panic!( + "The node {} has tx {} in state {} instead of the expected {}", + node, tx_name, last_state, state + ); +} + +#[when(expr = "I mine {int} custom weight blocks on {word} with weight {int}")] +async fn mine_custom_weight_blocks_with_height(world: &mut TariWorld, num_blocks: u64, node_name: String, weight: u64) { + let mut client = world + .get_node_client(&node_name) + .await + .expect("Couldn't get the node client to mine with"); + mine_blocks_without_wallet(&mut client, num_blocks, weight).await; +} + +#[then(expr = "I wait until base node {word} has {int} unconfirmed transactions in its mempool")] +async fn base_node_has_unconfirmed_transaction_in_mempool(world: &mut TariWorld, node: String, num_transactions: u64) { + let mut client = world.get_node_client(&node).await.unwrap(); + let mut unconfirmed_txs = 0; + + for _ in 0..(TWO_MINUTES_WITH_HALF_SECOND_SLEEP) { + let resp = client.get_mempool_stats(Empty {}).await.unwrap(); + let inner = resp.into_inner(); + + unconfirmed_txs = inner.unconfirmed_txs; + + if inner.unconfirmed_txs == num_transactions { + return; + } + + tokio::time::sleep(Duration::from_millis(HALF_SECOND)).await; + } + + panic!( + "The node {} has {} unconfirmed txs instead of the expected {}", + node, unconfirmed_txs, num_transactions + ); +} + +#[then(expr = "{word} is in the {word} of all nodes")] +async fn tx_in_state_all_nodes(world: &mut TariWorld, tx_name: String, pool: String) -> anyhow::Result<()> { + tx_in_state_all_nodes_with_allowed_failure(world, tx_name, pool, 0).await +} + +#[then(expr = "{word} is in the {word} of all nodes, where {int}% can fail")] +async fn tx_in_state_all_nodes_with_allowed_failure( + world: &mut TariWorld, + tx_name: String, + pool: String, + can_fail_percent: u64, +) -> anyhow::Result<()> { + let tx = world + .transactions + .get(&tx_name) + .unwrap_or_else(|| panic!("Couldn't find transaction {}", tx_name)); + let sig = &tx.body.kernels()[0].excess_sig; + + let mut node_pool_status: IndexMap<&String, &str> = IndexMap::new(); + + let nodes = world.base_nodes.iter().clone(); + let nodes_count = world.base_nodes.len(); + + for (name, _) in nodes.clone() { + node_pool_status.insert(name, "UNCHECKED: DEFAULT TEST STATE"); + } + + let can_fail = ((can_fail_percent as f64 * nodes.len() as f64) / 100.0).ceil() as u64; + + for _ in 0..(TWO_MINUTES_WITH_HALF_SECOND_SLEEP / 2) { + for (name, _) in node_pool_status + .clone() + .iter() + .filter(|(_, in_pool)| ***in_pool != pool) + { + let mut client = world.get_node_client(name).await?; + + let resp = client + .transaction_state(grpc::TransactionStateRequest { + excess_sig: Some(sig.into()), + }) + .await?; + + let inner = resp.into_inner(); + + let res_state = match inner.result { + 0 => "UNKNOWN", + 1 => "MEMPOOL", + 2 => "MINED", + 3 => "NOT_STORED", + _ => panic!("not getting a good result"), + }; + + node_pool_status.insert(name, res_state); + } + + if node_pool_status.values().filter(|v| ***v == pool).count() >= (nodes_count - can_fail as usize) { + return Ok(()); + } + + tokio::time::sleep(Duration::from_millis(HALF_SECOND / 2)).await; + } + + panic!( + "More than {}% ({} node(s)) failed to get {} in {}, {:?}", + can_fail_percent, can_fail, tx_name, pool, node_pool_status + ); +} + +#[then(expr = "I submit transaction {word} to {word}")] +#[when(expr = "I submit transaction {word} to {word}")] +async fn submit_transaction_to(world: &mut TariWorld, tx_name: String, node: String) -> anyhow::Result<()> { + let mut client = world.get_node_client(&node).await?; + let tx = world + .transactions + .get(&tx_name) + .unwrap_or_else(|| panic!("Couldn't find transaction {}", tx_name)); + let resp = client + .submit_transaction(grpc::SubmitTransactionRequest { + transaction: Some(grpc::Transaction::try_from(tx.clone()).unwrap()), + }) + .await?; + + let result = resp.into_inner(); + + if result.result == 1 { + Ok(()) + } else { + panic!("Transaction {} wasn't submit to {}", tx_name, node) + } +} + +#[when(expr = "I have a pruned node {word} connected to node {word} with pruning horizon set to {int}")] +#[given(expr = "I have a pruned node {word} connected to node {word} with pruning horizon set to {int}")] +async fn prune_node_connected_to_base_node( + world: &mut TariWorld, + pruned_node: String, + base_node: String, + pruning_horizon: u64, +) { + let mut base_node_config = BaseNodeConfig::default(); + base_node_config.storage.pruning_horizon = pruning_horizon; + + spawn_base_node_with_config(world, false, pruned_node, vec![base_node], base_node_config).await; +} + +#[when(expr = "wallet {word} detects all transactions as {word}")] +#[then(expr = "wallet {word} detects all transactions as {word}")] +async fn wallet_detects_all_txs_as_mined_confirmed(world: &mut TariWorld, wallet_name: String, status: String) { + let mut client = create_wallet_client(world, wallet_name.clone()).await.unwrap(); + + let mut completed_tx_stream = client + .get_completed_transactions(GetCompletedTransactionsRequest {}) + .await + .unwrap() + .into_inner(); + + let num_retries = 100; + + while let Some(tx_info) = completed_tx_stream.next().await { + let tx_info = tx_info.unwrap(); + let tx_id = tx_info.transaction.unwrap().tx_id; + + println!("waiting for tx with tx_id = {} to be {}", tx_id, status); + for retry in 0..=num_retries { + let request = GetTransactionInfoRequest { + transaction_ids: vec![tx_id], + }; + let tx_info = client.get_transaction_info(request).await.unwrap().into_inner(); + let tx_info = tx_info.transactions.first().unwrap(); + + if retry == num_retries { + panic!( + "Wallet {} failed to detect tx with tx_id = {} to be {}, current status is {:?}", + wallet_name.as_str(), + tx_id, + status, + tx_info.status() + ); + } + match status.as_str() { + "Pending" => match tx_info.status() { + grpc::TransactionStatus::Pending | + grpc::TransactionStatus::Completed | + grpc::TransactionStatus::Broadcast | + grpc::TransactionStatus::MinedUnconfirmed | + grpc::TransactionStatus::MinedConfirmed => { + break; + }, + _ => (), + }, + "Completed" => match tx_info.status() { + grpc::TransactionStatus::Completed | + grpc::TransactionStatus::Broadcast | + grpc::TransactionStatus::MinedUnconfirmed | + grpc::TransactionStatus::MinedConfirmed => { + break; + }, + _ => (), + }, + "Broadcast" => match tx_info.status() { + grpc::TransactionStatus::Broadcast | + grpc::TransactionStatus::MinedUnconfirmed | + grpc::TransactionStatus::MinedConfirmed => { + break; + }, + _ => (), + }, + "Mined_Unconfirmed" => match tx_info.status() { + grpc::TransactionStatus::MinedUnconfirmed | grpc::TransactionStatus::MinedConfirmed => { + break; + }, + _ => (), + }, + "Mined_Confirmed" => match tx_info.status() { + grpc::TransactionStatus::MinedConfirmed | grpc::TransactionStatus::Broadcast => { + break; + }, + _ => (), + }, + "Coinbase" => match tx_info.status() { + grpc::TransactionStatus::Pending | + grpc::TransactionStatus::Completed | + grpc::TransactionStatus::Broadcast | + grpc::TransactionStatus::MinedUnconfirmed | + grpc::TransactionStatus::MinedConfirmed | + grpc::TransactionStatus::Coinbase => { + break; + }, + _ => (), + }, + _ => panic!("Unknown status {}, don't know what to expect", status), + } + } + } +} + +#[when(expr = "wallet {word} detects all transactions are at least {word}")] +#[then(expr = "wallet {word} detects all transactions are at least {word}")] +async fn wallet_detects_all_txs_are_at_least_in_some_status( + world: &mut TariWorld, + wallet_name: String, + status: String, +) { + let mut client = create_wallet_client(world, wallet_name.clone()).await.unwrap(); + let wallet_address = client + .get_address(Empty {}) + .await + .unwrap() + .into_inner() + .address + .to_hex(); + let tx_ids = world.wallet_tx_ids.get(&wallet_address).unwrap(); + + let num_retries = 100; + + for tx_id in tx_ids { + println!("waiting for tx with tx_id = {} to be pending", tx_id); + for retry in 0..=num_retries { + let request = GetTransactionInfoRequest { + transaction_ids: vec![*tx_id], + }; + let tx_info = client.get_transaction_info(request).await.unwrap().into_inner(); + let tx_info = tx_info.transactions.first().unwrap(); + + if retry == num_retries { + panic!( + "Wallet {} failed to detect tx with tx_id = {} to be at least {}", + wallet_name.as_str(), + tx_id, + status + ); + } + match status.as_str() { + "Pending" => match tx_info.status() { + grpc::TransactionStatus::Pending | + grpc::TransactionStatus::Completed | + grpc::TransactionStatus::Broadcast | + grpc::TransactionStatus::MinedUnconfirmed | + grpc::TransactionStatus::MinedConfirmed => { + break; + }, + _ => (), + }, + "Completed" => match tx_info.status() { + grpc::TransactionStatus::Completed | + grpc::TransactionStatus::Broadcast | + grpc::TransactionStatus::MinedUnconfirmed | + grpc::TransactionStatus::MinedConfirmed => { + break; + }, + _ => (), + }, + "Broadcast" => match tx_info.status() { + grpc::TransactionStatus::Broadcast | + grpc::TransactionStatus::MinedUnconfirmed | + grpc::TransactionStatus::MinedConfirmed => { + break; + }, + _ => (), + }, + "Mined_Unconfirmed" => match tx_info.status() { + grpc::TransactionStatus::MinedUnconfirmed | grpc::TransactionStatus::MinedConfirmed => { + break; + }, + _ => (), + }, + _ => panic!("Unknown status {}, don't know what to expect", status), + } + tokio::time::sleep(Duration::from_secs(1)).await; + } + } +} + +#[then(expr = "wallet {word} detects all transactions are Broadcast")] +async fn wallet_detects_all_txs_as_broadcast(world: &mut TariWorld, wallet_name: String) { + let mut client = create_wallet_client(world, wallet_name.clone()).await.unwrap(); + let wallet_address = client + .get_address(Empty {}) + .await + .unwrap() + .into_inner() + .address + .to_hex(); + let tx_ids = world.wallet_tx_ids.get(&wallet_address).unwrap(); + + let num_retries = 100; + + for tx_id in tx_ids { + println!("waiting for tx with tx_id = {} to be mined_confirmed", tx_id); + for retry in 0..=num_retries { + let request = GetTransactionInfoRequest { + transaction_ids: vec![*tx_id], + }; + let tx_info = client.get_transaction_info(request).await.unwrap().into_inner(); + let tx_info = tx_info.transactions.first().unwrap(); + + if retry == num_retries { + panic!( + "Wallet {} failed to detect tx with tx_id = {} to be mined_confirmed", + wallet_name.as_str(), + tx_id + ); + } + match tx_info.status() { + grpc::TransactionStatus::Broadcast => { + println!( + "Transaction with tx_id = {} has been detected as mined_confirmed by wallet {}", + tx_id, + wallet_name.as_str() + ); + return; + }, + _ => { + println!( + "Transaction with tx_id = {} has been detected with status = {:?}", + tx_id, + tx_info.status() + ); + tokio::time::sleep(Duration::from_secs(5)).await; + continue; + }, + } + } + } +} + +#[when(expr = "wallet {word} detects last transaction is Pending")] +async fn wallet_detects_last_tx_as_pending(world: &mut TariWorld, wallet: String) { + let mut client = create_wallet_client(world, wallet.clone()).await.unwrap(); + let wallet_address = client + .get_address(Empty {}) + .await + .unwrap() + .into_inner() + .address + .to_hex(); + let tx_ids = world.wallet_tx_ids.get(&wallet_address).unwrap(); + let tx_id = tx_ids.last().unwrap(); // get last transaction + let num_retries = 100; + + println!("waiting for tx with tx_id = {} to be pending", tx_id); + for retry in 0..=num_retries { + let request = GetTransactionInfoRequest { + transaction_ids: vec![*tx_id], + }; + let tx_info = client.get_transaction_info(request).await.unwrap().into_inner(); + let tx_info = tx_info.transactions.first().unwrap(); + + if retry == num_retries { + panic!( + "Wallet {} failed to detect tx with tx_id = {} to be pending", + wallet.as_str(), + tx_id + ); + } + match tx_info.status() { + grpc::TransactionStatus::Pending => { + println!( + "Transaction with tx_id = {} has been detected as pending by wallet {}", + tx_id, + wallet.as_str() + ); + return; + }, + _ => { + tokio::time::sleep(Duration::from_secs(5)).await; + continue; + }, + } + } +} + +#[when(expr = "wallet {word} detects last transaction is Cancelled")] +async fn wallet_detects_last_tx_as_cancelled(world: &mut TariWorld, wallet: String) { + let mut client = create_wallet_client(world, wallet.clone()).await.unwrap(); + let wallet_address = client + .get_address(Empty {}) + .await + .unwrap() + .into_inner() + .address + .to_hex(); + let tx_ids = world.wallet_tx_ids.get(&wallet_address).unwrap(); + let tx_id = tx_ids.last().unwrap(); // get last transaction + let num_retries = 100; + + println!("waiting for tx with tx_id = {} to be Cancelled", tx_id); + for retry in 0..=num_retries { + let request = GetTransactionInfoRequest { + transaction_ids: vec![*tx_id], + }; + let tx_info = client.get_transaction_info(request).await.unwrap().into_inner(); + let tx_info = tx_info.transactions.first().unwrap(); + + if retry == num_retries { + panic!( + "Wallet {} failed to detect tx with tx_id = {} to be cancelled, current status is {:?}", + wallet.as_str(), + tx_id, + tx_info.status(), + ); + } + match tx_info.status() { + grpc::TransactionStatus::Rejected => { + println!("Transaction with tx_id = {} has status {:?}", tx_id, tx_info.status()); + return; + }, + _ => { + tokio::time::sleep(Duration::from_secs(5)).await; + continue; + }, + } + } +} + +#[then(expr = "I have a SHA3 miner {word} connected to node {word}")] +#[when(expr = "I have a SHA3 miner {word} connected to node {word}")] +async fn sha3_miner_connected_to_base_node(world: &mut TariWorld, miner: String, base_node: String) { + spawn_base_node(world, false, miner.clone(), vec![base_node.clone()]).await; + let base_node = world.base_nodes.get(&base_node).unwrap(); + let peers = base_node.seed_nodes.clone(); + world.wallet_connected_to_base_node.insert(miner.clone(), miner.clone()); + spawn_wallet(world, miner.clone(), Some(miner.clone()), peers, None, None).await; + register_miner_process(world, miner.clone(), miner.clone(), miner); +} + +#[when(expr = "I list all {word} transactions for wallet {word}")] +#[then(expr = "I list all {word} transactions for wallet {word}")] +async fn list_all_txs_for_wallet(world: &mut TariWorld, transaction_type: String, wallet: String) { + if transaction_type.as_str() != "COINBASE" && transaction_type.as_str() != "NORMAL" { + panic!( + "Invalid transaction type. Values should be COINBASE or NORMAL, value passed is {}", + transaction_type + ); + } + let mut client = create_wallet_client(world, wallet.clone()).await.unwrap(); + + let request = GetCompletedTransactionsRequest {}; + let mut completed_txs = client.get_completed_transactions(request).await.unwrap().into_inner(); + + while let Some(tx) = completed_txs.next().await { + let tx_info = tx.unwrap().transaction.unwrap(); + if (tx_info.message.contains("Coinbase Transaction for Block ") && transaction_type == "COINBASE") || + (!tx_info.message.contains("Coinbase Transaction for Block ") && transaction_type == "NORMAL") + { + println!("Transaction with status COINBASE found for wallet {}: ", wallet); + } else { + continue; + } + println!("\n"); + println!("TxId: {}", tx_info.tx_id); + println!("Status: {}", tx_info.status); + println!("IsCancelled: {}", tx_info.is_cancelled); + } +} + +#[when(expr = "wallet {word} has at least {int} transactions that are all {word} and not cancelled")] +#[then(expr = "wallet {word} has at least {int} transactions that are all {word} and not cancelled")] +async fn wallet_has_at_least_num_txs(world: &mut TariWorld, wallet: String, num_txs: u64, transaction_status: String) { + let mut client = create_wallet_client(world, wallet.clone()).await.unwrap(); + let transaction_status = match transaction_status.as_str() { + "TRANSACTION_STATUS_COMPLETED" => 0, + "TRANSACTION_STATUS_BROADCAST" => 1, + "TRANSACTION_STATUS_MINED_UNCONFIRMED" => 2, + "TRANSACTION_STATUS_IMPORTED" => 3, + "TRANSACTION_STATUS_PENDING" => 4, + "TRANSACTION_STATUS_COINBASE" => 5, + "TRANSACTION_STATUS_MINED_CONFIRMED" => 6, + "TRANSACTION_STATUS_NOT_FOUND" => 7, + "TRANSACTION_STATUS_REJECTED" => 8, + "TRANSACTION_STATUS_FAUX_UNCONFIRMED" => 9, + "TRANSACTION_STATUS_FAUX_CONFIRMED" => 10, + "TRANSACTION_STATUS_QUEUED" => 11, + _ => panic!("Invalid transaction status {}", transaction_status), + }; + + let num_retries = 100; + let mut current_status = 0; + + for _ in 0..num_retries { + let mut txs = client + .get_completed_transactions(grpc::GetCompletedTransactionsRequest {}) + .await + .unwrap() + .into_inner(); + let mut found_tx = 0; + while let Some(tx) = txs.next().await { + let tx_info = tx.unwrap().transaction.unwrap(); + current_status = tx_info.status; + if current_status == transaction_status { + found_tx += 1; + } + } + if found_tx >= num_txs { + return; + } + tokio::time::sleep(Duration::from_secs(5)).await; + } + + panic!( + "Wallet {} failed to have at least num {} txs with status {}, current status is {}", + wallet, num_txs, transaction_status, current_status + ); +} + +#[when(expr = "I create a transaction {word} spending {word} to {word}")] +async fn create_tx_spending_coinbase(world: &mut TariWorld, transaction: String, inputs: String, output: String) { + let inputs = inputs.split(',').collect::>(); + let utxos = inputs + .iter() + .map(|i| world.utxos.get(&i.to_string()).unwrap().clone()) + .collect::>(); + + let (tx, utxo) = build_transaction_with_output(utxos); + world.utxos.insert(output, utxo); + world.transactions.insert(transaction, tx); +} + +#[when(expr = "I create a custom fee transaction {word} spending {word} to {word} with fee {word}")] +async fn create_tx_custom_fee(world: &mut TariWorld, transaction: String, inputs: String, output: String, fee: u64) { + let inputs = inputs.split(',').collect::>(); + let utxos = inputs + .iter() + .map(|i| world.utxos.get(&i.to_string()).unwrap().clone()) + .collect::>(); + + let (tx, utxo) = build_transaction_with_output_and_fee(utxos, fee); + world.utxos.insert(output, utxo); + world.transactions.insert(transaction, tx); +} + +#[when(expr = "I wait for wallet {word} to have less than {int} uT")] +async fn wait_for_wallet_to_have_less_than_micro_tari(world: &mut TariWorld, wallet: String, amount: u64) { + let mut client = create_wallet_client(world, wallet.clone()).await.unwrap(); + println!("Waiting for wallet {} to have less than {} uT", wallet, amount); + + let num_retries = 100; + let request = GetBalanceRequest {}; + + for _ in 0..num_retries { + let balance_res = client.get_balance(request.clone()).await.unwrap().into_inner(); + let current_balance = balance_res.available_balance; + if current_balance < amount { + println!( + "Wallet {} now has less than {}, with current balance {}", + wallet, amount, current_balance + ); + return; + } + tokio::time::sleep(Duration::from_secs(5)).await; + } + + panic!( + "Wallet {} didn't get less than {} after num_retries {}", + wallet, amount, num_retries + ); +} + +#[when(expr = "I have non-default wallet {word} connected to all seed nodes using {word}")] +#[given(expr = "I have non-default wallet {word} connected to all seed nodes using {word}")] +async fn non_default_wallet_connected_to_all_seed_nodes(world: &mut TariWorld, wallet: String, mechanism: String) { + let routing_mechanism = TransactionRoutingMechanism::from(mechanism); + // assuming we have at least one base node as seed node, we use the first to connect wallet to + let nodes = world.all_seed_nodes().to_vec(); + let node = nodes.first().unwrap(); + world.wallet_connected_to_base_node.insert(wallet.clone(), node.clone()); + spawn_wallet( + world, + wallet, + Some(node.clone()), + world.all_seed_nodes().to_vec(), + Some(routing_mechanism), + None, + ) + .await; +} + +#[when(expr = "I have {int} non-default wallets connected to all seed nodes using {word}")] +async fn non_default_wallets_connected_to_all_seed_nodes(world: &mut TariWorld, num: u64, mechanism: String) { + let routing_mechanism = TransactionRoutingMechanism::from(mechanism); + let nodes = world.all_seed_nodes().to_vec(); + let node = nodes.first().unwrap(); + for ind in 0..num { + let wallet_name = format!("Wallet_{}", ind); + world + .wallet_connected_to_base_node + .insert(wallet_name.clone(), node.clone()); + spawn_wallet( + world, + wallet_name, + Some(node.clone()), + world.all_seed_nodes().to_vec(), + Some(routing_mechanism), + None, + ) + .await; + } +} + +#[when(expr = "I send {int} uT without waiting for broadcast from wallet {word} to wallet {word} at fee {int}")] +#[then(expr = "I send {int} uT without waiting for broadcast from wallet {word} to wallet {word} at fee {int}")] +async fn send_amount_from_source_wallet_to_dest_wallet_without_broadcast( + world: &mut TariWorld, + amount: u64, + source_wallet: String, + dest_wallet: String, + fee: u64, +) { + let mut source_client = create_wallet_client(world, source_wallet.clone()).await.unwrap(); + let source_wallet_address = world.get_wallet_address(&source_wallet).await.unwrap(); + + let dest_wallet_address = world.get_wallet_address(&dest_wallet).await.unwrap(); + + let payment_recipient = PaymentRecipient { + address: dest_wallet_address.clone(), + amount, + fee_per_gram: fee, + message: format!( + "transfer amount {} from {} to {}", + amount, + source_wallet.as_str(), + dest_wallet.as_str() + ), + payment_type: 0, // normal mimblewimble payment type + }; + let transfer_req = TransferRequest { + recipients: vec![payment_recipient], + }; + let tx_res = source_client.transfer(transfer_req).await.unwrap().into_inner(); + let tx_res = tx_res.results; + + assert_eq!(tx_res.len(), 1usize); + + let tx_res = tx_res.first().unwrap(); + assert!( + tx_res.is_success, + "Transacting amount {} uT from wallet {} to {} at fee {} failed", + amount, + source_wallet.as_str(), + dest_wallet.as_str(), + fee + ); + + let tx_id = tx_res.transaction_id; + + // insert tx_id's to the corresponding world mapping + let source_tx_ids = world.wallet_tx_ids.entry(source_wallet_address.clone()).or_default(); + + source_tx_ids.push(tx_id); + + let dest_tx_ids = world.wallet_tx_ids.entry(dest_wallet_address.clone()).or_default(); + + dest_tx_ids.push(tx_id); + + println!( + "Transfer amount {} from {} to {} at fee {} succeeded", + amount, source_wallet, dest_wallet, fee + ); +} + +#[then(expr = "I send a one-sided transaction of {int} uT from {word} to {word} at fee {int}")] +async fn send_one_sided_transaction_from_source_wallet_to_dest_wallt( + world: &mut TariWorld, + amount: u64, + source_wallet: String, + dest_wallet: String, + fee: u64, +) { + let mut source_client = create_wallet_client(world, source_wallet.clone()).await.unwrap(); + let source_wallet_address = world.get_wallet_address(&source_wallet).await.unwrap(); + + let dest_wallet_address = world.get_wallet_address(&dest_wallet).await.unwrap(); + + let payment_recipient = PaymentRecipient { + address: dest_wallet_address.clone(), + amount, + fee_per_gram: fee, + message: format!( + "One sided transfer amount {} from {} to {}", + amount, + source_wallet.as_str(), + dest_wallet.as_str() + ), + payment_type: 1, // one sided transaction + }; + let transfer_req = TransferRequest { + recipients: vec![payment_recipient], + }; + let tx_res = source_client.transfer(transfer_req).await.unwrap().into_inner(); + let tx_res = tx_res.results; + + assert_eq!(tx_res.len(), 1usize); + + let tx_res = tx_res.first().unwrap(); + assert!( + tx_res.is_success, + "One sided transaction with amount {} from wallet {} to {} at fee {} failed", + amount, + source_wallet.as_str(), + dest_wallet.as_str(), + fee + ); + + // we wait for transaction to be broadcasted + let tx_id = tx_res.transaction_id; + let num_retries = 100; + let tx_info_req = GetTransactionInfoRequest { + transaction_ids: vec![tx_id], + }; + + for i in 0..num_retries { + let tx_info_res = source_client + .get_transaction_info(tx_info_req.clone()) + .await + .unwrap() + .into_inner(); + let tx_info = tx_info_res.transactions.first().unwrap(); + + // TransactionStatus::TRANSACTION_STATUS_BROADCAST == 1_i32 + if tx_info.status == 1_i32 { + println!( + "One sided transaction from {} to {} with amount {} at fee {} has been broadcasted", + source_wallet.clone(), + dest_wallet.clone(), + amount, + fee + ); + break; + } + + if i == num_retries - 1 { + panic!( + "One sided transaction from {} to {} with amount {} at fee {} failed to be broadcasted", + source_wallet.clone(), + dest_wallet.clone(), + amount, + fee + ) + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } + + // insert tx_id's to the corresponding world mapping + let source_tx_ids = world.wallet_tx_ids.entry(source_wallet_address.clone()).or_default(); + + source_tx_ids.push(tx_id); + + let dest_tx_ids = world.wallet_tx_ids.entry(dest_wallet_address.clone()).or_default(); + + dest_tx_ids.push(tx_id); + + println!( + "One sided transaction with amount {} from {} to {} at fee {} succeeded", + amount, source_wallet, dest_wallet, fee + ); +} + +#[then(expr = "I send {int} uT from wallet {word} to wallet {word} at fee {int}")] +#[when(expr = "I send {int} uT from wallet {word} to wallet {word} at fee {int}")] +async fn send_amount_from_wallet_to_wallet_at_fee( + world: &mut TariWorld, + amount: u64, + sender: String, + receiver: String, + fee_per_gram: u64, +) { + let mut sender_wallet_client = create_wallet_client(world, sender.clone()).await.unwrap(); + let sender_wallet_address = world.get_wallet_address(&sender).await.unwrap(); + let receiver_wallet_address = world.get_wallet_address(&receiver).await.unwrap(); + + let payment_recipient = PaymentRecipient { + address: receiver_wallet_address.clone(), + amount, + fee_per_gram, + message: format!( + "Transfer amount {} from {} to {} as fee {}", + amount, + sender.as_str(), + receiver.as_str(), + fee_per_gram + ), + payment_type: 0, // mimblewimble transaction + }; + let transfer_req = TransferRequest { + recipients: vec![payment_recipient], + }; + let tx_res = sender_wallet_client.transfer(transfer_req).await.unwrap().into_inner(); + let tx_res = tx_res.results; + + assert_eq!(tx_res.len(), 1usize); + + let tx_res = tx_res.first().unwrap(); + assert!( + tx_res.is_success, + "Transaction with amount {} from wallet {} to {} at fee {} failed", + amount, + sender.as_str(), + receiver.as_str(), + fee_per_gram + ); + + let tx_id = tx_res.transaction_id; + let num_retries = 100; + let tx_info_req = GetTransactionInfoRequest { + transaction_ids: vec![tx_id], + }; + + for i in 0..num_retries { + let tx_info_res = sender_wallet_client + .get_transaction_info(tx_info_req.clone()) + .await + .unwrap() + .into_inner(); + let tx_info = tx_info_res.transactions.first().unwrap(); + + // TransactionStatus::TRANSACTION_STATUS_BROADCAST == 1_i32 + if tx_info.status == 1_i32 { + println!( + "Transaction from {} to {} with amount {} at fee {} has been broadcasted", + sender.clone(), + receiver.clone(), + amount, + fee_per_gram + ); + break; + } + + if i == num_retries - 1 { + panic!( + "Transaction from {} to {} with amount {} at fee {} failed to be broadcasted", + sender.clone(), + receiver.clone(), + amount, + fee_per_gram + ) + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } + + // insert tx_id's to the corresponding world mapping + let sender_tx_ids = world.wallet_tx_ids.entry(sender_wallet_address.clone()).or_default(); + + sender_tx_ids.push(tx_id); + + let receiver_tx_ids = world.wallet_tx_ids.entry(receiver_wallet_address.clone()).or_default(); + + receiver_tx_ids.push(tx_id); + + println!( + "Transaction with amount {} from {} to {} at fee {} succeeded", + amount, sender, receiver, fee_per_gram + ); +} + +#[then(expr = "wallet {word} detects at least {int} coinbase transactions as Mined_Confirmed")] +async fn wallet_detects_at_least_coinbase_transactions(world: &mut TariWorld, wallet_name: String, coinbases: u64) { + let mut client = create_wallet_client(world, wallet_name.clone()).await.unwrap(); + let mut completed_tx_res = client + .get_completed_transactions(GetCompletedTransactionsRequest {}) + .await + .unwrap() + .into_inner(); + + let num_retries = 100; + let mut total_mined_confirmed_coinbases = 0; + + 'outer: for _ in 0..num_retries { + println!("Detecting mined confirmed coinbase transactions"); + 'inner: while let Some(tx_info) = completed_tx_res.next().await { + let tx_id = tx_info.unwrap().transaction.unwrap().tx_id; + let request = GetTransactionInfoRequest { + transaction_ids: vec![tx_id], + }; + let tx_info = client.get_transaction_info(request).await.unwrap().into_inner(); + let tx_info = tx_info.transactions.first().unwrap(); + match tx_info.status() { + grpc::TransactionStatus::MinedConfirmed => { + total_mined_confirmed_coinbases += 1; + if total_mined_confirmed_coinbases >= coinbases { + break 'outer; + } + }, + _ => continue 'inner, + } + } + + if total_mined_confirmed_coinbases < coinbases { + total_mined_confirmed_coinbases = 0; + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } + + if total_mined_confirmed_coinbases >= coinbases { + println!( + "Wallet {} detected at least {} coinbase transactions as Mined_Confirmed", + &wallet_name, coinbases + ); + } else { + panic!( + "Wallet {} failed to detect at least {} coinbase transactions as Mined_Confirmed", + wallet_name, coinbases + ); + } +} + +#[then(expr = "wallet {word} detects at least {int} coinbase transactions as Mined_Unconfirmed")] +async fn wallet_detects_at_least_unmined_transactions(world: &mut TariWorld, wallet_name: String, coinbases: u64) { + let mut client = create_wallet_client(world, wallet_name.clone()).await.unwrap(); + let mut completed_tx_res = client + .get_completed_transactions(GetCompletedTransactionsRequest {}) + .await + .unwrap() + .into_inner(); + + let num_retries = 100; + let mut total_mined_unconfirmed_coinbases = 0; + + 'outer: for _ in 0..num_retries { + println!("Detecting mined unconfirmed coinbase transactions"); + 'inner: while let Some(tx_info) = completed_tx_res.next().await { + let tx_id = tx_info.unwrap().transaction.unwrap().tx_id; + let request = GetTransactionInfoRequest { + transaction_ids: vec![tx_id], + }; + let tx_info = client.get_transaction_info(request).await.unwrap().into_inner(); + let tx_info = tx_info.transactions.first().unwrap(); + match tx_info.status() { + grpc::TransactionStatus::MinedUnconfirmed => { + total_mined_unconfirmed_coinbases += 1; + if total_mined_unconfirmed_coinbases >= coinbases { + break 'outer; + } + }, + _ => continue 'inner, + } + } + + if total_mined_unconfirmed_coinbases < coinbases { + total_mined_unconfirmed_coinbases = 0; + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } + + if total_mined_unconfirmed_coinbases >= coinbases { + println!( + "Wallet {} detected at least {} coinbase transactions as Mined_Unconfirmed", + &wallet_name, coinbases + ); + } else { + panic!( + "Wallet {} failed to detect at least {} coinbase transactions as Mined_Unconfirmed", + wallet_name, coinbases + ); + } +} + +#[then(expr = "wallet {word} detects exactly {int} coinbase transactions as Mined_Confirmed")] +async fn wallet_detects_exactly_coinbase_transactions(world: &mut TariWorld, wallet_name: String, coinbases: u64) { + let mut client = create_wallet_client(world, wallet_name.clone()).await.unwrap(); + let wallet_address = world.get_wallet_address(&wallet_name).await.unwrap(); + let tx_ids = world.wallet_tx_ids.get(&wallet_address).unwrap(); + + let num_retries = 100; + let mut total_mined_confirmed_coinbases = 0; + + 'outer: for _ in 0..num_retries { + println!("Detecting mined confirmed coinbase transactions"); + 'inner: for tx_id in tx_ids { + let request = GetTransactionInfoRequest { + transaction_ids: vec![*tx_id], + }; + let tx_info = client.get_transaction_info(request).await.unwrap().into_inner(); + let tx_info = tx_info.transactions.first().unwrap(); + match tx_info.status() { + grpc::TransactionStatus::MinedConfirmed => total_mined_confirmed_coinbases += 1, + _ => continue 'inner, + } + } + + if total_mined_confirmed_coinbases >= coinbases { + break 'outer; + } else { + total_mined_confirmed_coinbases = 0; + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } + + if total_mined_confirmed_coinbases == coinbases { + println!( + "Wallet {} detected exactly {} coinbase transactions as Mined_Confirmed", + &wallet_name, coinbases + ); + } else { + panic!( + "Wallet {} failed to detect exactly {} coinbase transactions as Mined_Confirmed", + wallet_name, coinbases + ); + } +} + +#[when(expr = "I have a base node {word} connected to node {word}")] +async fn base_node_connected_to_node(world: &mut TariWorld, base_node: String, peer_node: String) { + spawn_base_node(world, false, base_node, vec![peer_node]).await; +} + +#[when(expr = "I have a base node {word} connected to nodes {word}")] +async fn base_node_connected_to_nodes(world: &mut TariWorld, base_node: String, nodes: String) { + let nodes = nodes.split(',').map(|s| s.to_string()).collect::>(); + spawn_base_node(world, false, base_node, nodes).await; +} + +#[then(expr = "node {word} is in state {word}")] +async fn node_state(world: &mut TariWorld, node_name: String, state: String) { + let mut node_client = world.get_node_client(&node_name).await.unwrap(); + let tip = node_client.get_tip_info(Empty {}).await.unwrap().into_inner(); + let state = match state.as_str() { + "START_UP" => 0, + "HEADER_SYNC" => 1, + "HORIZON_SYNC" => 2, + "CONNECTING" => 3, + "BLOCK_SYNC" => 4, + "LISTENING" => 5, + "SYNC_FAILED" => 6, + _ => panic!("Invalid state"), + }; + assert_eq!(state, tip.base_node_state); +} + +#[then(expr = "node {word} is at the same height as node {word}")] +async fn base_node_is_at_same_height_as_node(world: &mut TariWorld, base_node: String, peer_node: String) { + let mut peer_node_client = world.get_node_client(&peer_node).await.unwrap(); + let req = Empty {}; + let mut expected_height = peer_node_client + .get_tip_info(req.clone()) + .await + .unwrap() + .into_inner() + .metadata + .unwrap() + .height_of_longest_chain; + + let mut base_node_client = world.get_node_client(&base_node).await.unwrap(); + let mut current_height = 0; + let num_retries = 100; + + 'outer: for _ in 0..12 { + 'inner: for _ in 0..num_retries { + current_height = base_node_client + .get_tip_info(req.clone()) + .await + .unwrap() + .into_inner() + .metadata + .unwrap() + .height_of_longest_chain; + if current_height >= expected_height { + break 'inner; + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } + + expected_height = peer_node_client + .get_tip_info(req.clone()) + .await + .unwrap() + .into_inner() + .metadata + .unwrap() + .height_of_longest_chain; + + current_height = base_node_client + .get_tip_info(req.clone()) + .await + .unwrap() + .into_inner() + .metadata + .unwrap() + .height_of_longest_chain; + + if current_height == expected_height { + break 'outer; + } + } + + if current_height == expected_height { + println!( + "Base node {} is at the same height {} as node {}", + &base_node, current_height, &peer_node + ); + } else { + panic!( + "Base node {} failed to synchronize at the same height as node {}", + base_node, peer_node + ); + } +} + +#[then(expr = "while mining via SHA3 miner {word} all transactions in wallet {word} are found to be Mined_Confirmed")] +async fn while_mining_all_txs_in_wallet_are_mined_confirmed(world: &mut TariWorld, miner: String, wallet: String) { + let mut wallet_client = create_wallet_client(world, wallet.clone()).await.unwrap(); + let wallet_address = world.get_wallet_address(&wallet).await.unwrap(); + let wallet_tx_ids = world.wallet_tx_ids.get(&wallet_address).unwrap(); + + if wallet_tx_ids.is_empty() { + panic!("Wallet {} has no available transactions", wallet); + } + + let miner_ps = world.miners.get(&miner).unwrap(); + let num_retries = 100; + println!( + "Detecting {} Mined_Confirmed transactions for wallet {}", + wallet_tx_ids.len(), + wallet + ); + + for tx_id in wallet_tx_ids { + 'inner: for retry in 0..=num_retries { + let req = GetTransactionInfoRequest { + transaction_ids: vec![*tx_id], + }; + let res = wallet_client.get_transaction_info(req).await.unwrap().into_inner(); + let tx_status = res.transactions.first().unwrap().status; + // TRANSACTION_STATUS_MINED_CONFIRMED code is currently 6 + if tx_status == 6 { + println!( + "Wallet transaction with id {} has been detected with status Mined_Confirmed", + tx_id + ); + break 'inner; + } + + if retry == num_retries { + panic!( + "Unable to have wallet transaction with tx_id = {} with status Mined_Confirmed", + tx_id + ); + } + + println!("Mine a block for tx_id {} to have status Mined_Confirmed", tx_id); + miner_ps.mine(world, Some(1), None, None).await; + + tokio::time::sleep(Duration::from_secs(5)).await; + } + } +} + +#[then(expr = "I stop all wallets")] +async fn stop_all_wallets(world: &mut TariWorld) { + for (wallet, wallet_ps) in &mut world.wallets { + println!("Stopping wallet {}", wallet); + + wallet_ps.kill(); + } +} + +#[then(expr = "I stop wallet {word}")] +#[when(expr = "I stop wallet {word}")] +async fn stop_wallet(world: &mut TariWorld, wallet: String) { + // conveniently, register wallet address + let mut wallet_client = create_wallet_client(world, wallet.clone()).await.unwrap(); + let wallet_address = wallet_client + .get_address(Empty {}) + .await + .unwrap() + .into_inner() + .address + .to_hex(); + let wallet_ps = world.wallets.get_mut(&wallet).unwrap(); + world.wallet_addresses.insert(wallet.clone(), wallet_address); + println!("Stopping wallet {}", wallet.as_str()); + wallet_ps.kill(); +} + +#[when(expr = "I stop node {word}")] +#[then(expr = "I stop node {word}")] +async fn stop_node(world: &mut TariWorld, node: String) { + let base_ps = world.base_nodes.get_mut(&node).unwrap(); + println!("Stopping node {}", node); + base_ps.kill(); +} + +#[when(expr = "I start wallet {word}")] +#[then(expr = "I start wallet {word}")] +async fn start_wallet_without_node(world: &mut TariWorld, wallet: String) { + match world.wallet_connected_to_base_node.get(&wallet) { + None => spawn_wallet(world, wallet, None, vec![], None, None).await, + Some(base_node) => { + // start wallet + let base_node_ps = world.base_nodes.get(base_node).unwrap(); + let seed_nodes = base_node_ps.seed_nodes.clone(); + spawn_wallet(world, wallet, Some(base_node.clone()), seed_nodes, None, None).await; + }, + } +} + +#[then(expr = "while mining via node {word} all transactions in wallet {word} are found to be Mined_Confirmed")] +async fn while_mining_in_node_all_txs_in_wallet_are_mined_confirmed( + world: &mut TariWorld, + node: String, + wallet: String, +) { + let mut wallet_client = create_wallet_client(world, wallet.clone()).await.unwrap(); + let wallet_address = world.get_wallet_address(&wallet).await.unwrap(); + let wallet_tx_ids = world.wallet_tx_ids.get(&wallet_address).unwrap(); + + if wallet_tx_ids.is_empty() { + panic!("Wallet {} on node {} has no available transactions", &wallet, &node); + } + + let mut node_client = world.get_node_client(&node).await.unwrap(); + let num_retries = 100; + let mut mined_status_flag = false; + + println!( + "Detecting transactions on wallet {}, while mining on node {}, to be Mined_Confirmed", + &wallet, &node + ); + + for tx_id in wallet_tx_ids { + println!( + "Waiting for transaction with id {} to have status Mined_Confirmed, while mining on node {}", + tx_id, &node + ); + + 'inner: for _ in 0..num_retries { + let req = GetTransactionInfoRequest { + transaction_ids: vec![*tx_id], + }; + let res = wallet_client.get_transaction_info(req).await.unwrap().into_inner(); + let tx_status = res.transactions.first().unwrap().status; + // TRANSACTION_STATUS_MINED_CONFIRMED code is currently 6 + if tx_status == 6 { + println!("Transaction with id {} has been Mined_Confirmed", tx_id); + mined_status_flag = true; + break 'inner; + } + + println!("Mine a block for tx_id {} to have status Mined_Confirmed", tx_id); + mine_block(&mut node_client, &mut wallet_client).await; + + tokio::time::sleep(Duration::from_secs(5)).await; + } + + if !mined_status_flag { + panic!( + "Failed to have transaction with id {} on wallet {}, while mining on node {}, to be Mined_Confirmed", + tx_id, &wallet, &node + ); + } + } + + println!( + "Wallet {} has all transactions Mined_Confirmed, while mining on node {}", + &wallet, &node + ); +} + +#[then(expr = "all wallets detect all transactions as Mined_Confirmed")] +async fn all_wallets_detect_all_txs_as_mined_confirmed(world: &mut TariWorld) { + for wallet in world.wallets.keys() { + let mut wallet_client = create_wallet_client(world, wallet.clone()).await.unwrap(); + let wallet_address = world.get_wallet_address(&wallet).await.unwrap(); + let wallet_tx_ids = world.wallet_tx_ids.get(&wallet_address); + + let wallet_tx_ids = if wallet_tx_ids.is_none() { + println!("Wallet {} has no available transactions", &wallet); + vec![] + } else { + let wallet_tx_ids = wallet_tx_ids.unwrap(); + if wallet_tx_ids.is_empty() { + panic!("Wallet {} should have available transaction ids", wallet.as_str()); + } + wallet_tx_ids.clone() + }; + + let num_retries = 100; + + for tx_id in wallet_tx_ids { + 'inner: for retry in 0..=num_retries { + let req = GetTransactionInfoRequest { + transaction_ids: vec![tx_id], + }; + let res = wallet_client.get_transaction_info(req).await.unwrap().into_inner(); + let tx_status = res.transactions.first().unwrap().status; + + // TRANSACTION_STATUS_MINED_CONFIRMED code is currently 6 + if tx_status == 6 { + println!( + "Wallet {} has detected transaction with id {} as Mined_Confirmed", + &wallet, tx_id + ); + break 'inner; + } + + if retry == num_retries { + panic!( + "Transaction with id {} does not have status as Mined_Confirmed, on wallet {}", + tx_id, &wallet + ); + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } + } + } +} + +#[then(expr = "wallets {word} should have {word} {int} spendable coinbase outputs")] +async fn wallets_should_have_at_least_num_spendable_coinbase_outs( + world: &mut TariWorld, + wallets: String, + comparison: String, + amount_of_coinbases: u64, +) { + let at_least = "AT_LEAST"; + let exactly = "EXACTLY"; + + if comparison.as_str() != at_least && comparison.as_str() != exactly { + panic!("Invalid comparison value provided: {}", comparison); + } + + let wallets = wallets.split(',').collect::>(); + let mut wallets_clients: Vec<_> = vec![]; + for w in &wallets { + wallets_clients.push(create_wallet_client(world, w.to_string()).await.unwrap()); + } + + let num_retries = 100; + let mut coinbase_count = 0; + let mut spendable_coinbase_count = 0; + + for ind in 0..wallets_clients.len() { + let wallet = wallets[ind]; + let mut client = wallets_clients[ind].clone(); + + 'inner: for _ in 0..num_retries { + let mut stream = client + .get_completed_transactions(GetCompletedTransactionsRequest {}) + .await + .unwrap() + .into_inner(); + while let Some(completed_tx) = stream.next().await { + let tx_info = completed_tx.unwrap().transaction.unwrap(); + + if tx_info.message.contains("Coinbase Transaction for Block ") && tx_info.fee == 0 { + let tx_id = tx_info.tx_id; + coinbase_count += 1; + + println!("Found coinbase transaction with id {} for wallet {}", tx_id, &wallet); + + // MINED_CONFIRMED status = 6 + if tx_info.status == 6 { + println!( + "Coinbase transaction with id {} for wallet {} is Mined_Confirmed", + tx_id, &wallet + ); + spendable_coinbase_count += 1; + } + } + } + + if spendable_coinbase_count >= amount_of_coinbases { + println!( + "Wallet {} has found at least {} within total {} coinbase transaction", + &wallet, amount_of_coinbases, coinbase_count + ); + break 'inner; + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } + + if comparison == at_least && spendable_coinbase_count >= amount_of_coinbases { + println!("Wallet {} has found at least {}", &wallet, amount_of_coinbases); + } else if comparison == exactly && spendable_coinbase_count == amount_of_coinbases { + println!("Wallet {} has found exactly {}", &wallet, amount_of_coinbases); + } else { + panic!( + "Wallet {} hasn't found {} {} spendable outputs, instead got {}", + wallet, comparison, amount_of_coinbases, spendable_coinbase_count + ); + } + } +} + +#[when(expr = "I send {int} transactions of {int} uT each from wallet {word} to wallet {word} at fee_per_gram {int}")] +async fn send_num_transactions_to_wallets_at_fee( + world: &mut TariWorld, + num_txs: u64, + amount: u64, + sender_wallet: String, + receiver_wallet: String, + fee_per_gram: u64, +) { + let mut sender_wallet_client = create_wallet_client(world, sender_wallet.clone()).await.unwrap(); + let sender_wallet_address = world.get_wallet_address(&sender_wallet).await.unwrap(); + let receiver_wallet_address = world.get_wallet_address(&receiver_wallet).await.unwrap(); + let mut tx_ids = vec![]; + + for _ in 0..num_txs { + let payment_recipient = PaymentRecipient { + address: receiver_wallet_address.clone(), + amount, + fee_per_gram, + message: format!( + "transfer amount {} from {} to {}", + amount, + sender_wallet.as_str(), + receiver_wallet.as_str() + ), + payment_type: 0, // standard mimblewimble transaction + }; + let transfer_req = TransferRequest { + recipients: vec![payment_recipient], + }; + let transfer_res = sender_wallet_client.transfer(transfer_req).await.unwrap().into_inner(); + let transfer_res = transfer_res.results.first().unwrap(); + + if !transfer_res.is_success { + panic!( + "Failed to send transaction from wallet {} to wallet {}, with message \n {}", + &sender_wallet, &receiver_wallet, &transfer_res.failure_message + ); + } + tx_ids.push(transfer_res.transaction_id); + + // insert tx_id's to the corresponding world mapping + let source_tx_ids = world.wallet_tx_ids.entry(sender_wallet_address.clone()).or_default(); + + source_tx_ids.append(&mut tx_ids); + + let dest_tx_ids = world.wallet_tx_ids.entry(receiver_wallet_address.clone()).or_default(); + + dest_tx_ids.append(&mut tx_ids); + + tokio::time::sleep(Duration::from_millis(50)).await; + } + + let num_retries = 100; + println!( + "Waiting for transactions from wallet {} to wallet {} to be broadcasted", + &sender_wallet, &receiver_wallet + ); + + for tx_id in tx_ids { + println!("Waiting for transaction with id {} to be broadcasted", tx_id); + let request = GetTransactionInfoRequest { + transaction_ids: vec![tx_id], + }; + + let mut is_broadcast = false; + + 'inner: for _ in 0..num_retries { + let txs_info = sender_wallet_client + .get_transaction_info(request.clone()) + .await + .unwrap() + .into_inner(); + let txs_info = txs_info.transactions.first().unwrap(); + + if txs_info.status == 1 { + println!( + "Transaction from wallet {} to wallet {} with id {} has been broadcasted to the network", + &sender_wallet, &receiver_wallet, tx_id + ); + is_broadcast = true; + break 'inner; + } + tokio::time::sleep(Duration::from_secs(5)).await; + } + + if !is_broadcast { + panic!( + "Transaction from wallet {} to wallet {} with id {} was not broacasted to the network", + &sender_wallet, &receiver_wallet, tx_id + ); + } + } +} + +#[when(expr = "I have a SHA3 miner {word} connected to all seed nodes")] +async fn sha3_miner_connected_to_all_seed_nodes(world: &mut TariWorld, sha3_miner: String) { + spawn_base_node(world, false, sha3_miner.clone(), world.seed_nodes.clone()).await; + + spawn_wallet( + world, + sha3_miner.clone(), + Some(sha3_miner.clone()), + world.seed_nodes.clone(), + None, + None, + ) + .await; + + register_miner_process(world, sha3_miner.clone(), sha3_miner.clone(), sha3_miner); +} + +#[given(expr = "I have a SHA3 miner {word} connected to seed node {word}")] +#[when(expr = "I have a SHA3 miner {word} connected to seed node {word}")] +async fn sha3_miner_connected_to_seed_node(world: &mut TariWorld, sha3_miner: String, seed_node: String) { + println!("Create base node for SHA3 miner {}", &sha3_miner); + spawn_base_node(world, false, sha3_miner.clone(), vec![seed_node.clone()]).await; + + println!("Create wallet for SHA3 miner {}", &sha3_miner); + spawn_wallet( + world, + sha3_miner.clone(), + Some(sha3_miner.clone()), + vec![seed_node], + None, + None, + ) + .await; + + println!("Register SHA3 miner {}", &sha3_miner); + register_miner_process(world, sha3_miner.clone(), sha3_miner.clone(), sha3_miner); +} + +#[when(expr = "I have individual mining nodes connected to each wallet and base node {word}")] +async fn mining_nodes_connected_to_each_wallet_and_base_node(world: &mut TariWorld, base_node: String) { + let wallets = world.wallets.clone(); + + for (ind, wallet_name) in wallets.keys().enumerate() { + let miner = format!("Miner_{}", ind); + register_miner_process(world, miner, base_node.clone(), wallet_name.clone()); + } +} + +#[then(expr = "I have each mining node mine {int} blocks")] +async fn mining_node_mine_blocks(world: &mut TariWorld, blocks: u64) { + let miners = world.miners.clone(); + for (miner, miner_ps) in miners { + println!("Miner {} is mining {} blocks", miner, blocks); + miner_ps.mine(world, Some(blocks), None, None).await; + tokio::time::sleep(Duration::from_secs(5)).await; + } +} + +#[then(expr = "I wait for {word} to have {int} node connections")] +async fn wait_for_wallet_to_have_num_connections(world: &mut TariWorld, wallet: String, connections: u64) { + let mut wallet_client = create_wallet_client(world, wallet.clone()).await.unwrap(); + let num_retries = 100; + + println!("Waiting for wallet {} to have {} connections", &wallet, connections); + let mut actual_connections = 0_u32; + + for _ in 0..num_retries { + let network_status_res = wallet_client.get_network_status(Empty {}).await.unwrap().into_inner(); + actual_connections = network_status_res.num_node_connections; + if u64::from(actual_connections) >= connections { + println!("Wallet {} has at least {} connections", &wallet, connections); + break; + } + tokio::time::sleep(Duration::from_secs(5)).await; + } + + if u64::from(actual_connections) != connections { + panic!("Wallet {} does not have {} connections", &wallet, connections); + } +} + +#[then(expr = "I wait for {word} to have {word} connectivity")] +async fn wait_for_wallet_to_have_specific_connectivity(world: &mut TariWorld, wallet: String, connectivity: String) { + let mut wallet_client = create_wallet_client(world, wallet.clone()).await.unwrap(); + let num_retries = 100; + + println!("Waiting for wallet {} to have connectivity {}", &wallet, &connectivity); + let connectivity = connectivity.to_uppercase(); + + let connectivity_index = match connectivity.as_str() { + "INITIALIZING" => 0, + "ONLINE" => 1, + "DEGRADED" => 2, + "OFFLINE" => 3, + _ => panic!("Invalid connectivity value {}", connectivity), + }; + + for _ in 0..=num_retries { + let network_status_res = wallet_client.get_network_status(Empty {}).await.unwrap().into_inner(); + let connectivity_status = network_status_res.status; + if connectivity_status == connectivity_index { + println!("Wallet {} has {} connectivity", &wallet, &connectivity); + return; + } + tokio::time::sleep(Duration::from_secs(5)).await; + } + + panic!( + "Wallet {} did not get correct connectivity status {}", + &wallet, connectivity + ); +} + +#[then(expr = "node {word} lists heights {int} to {int}")] +async fn node_lists_heights(world: &mut TariWorld, node: String, start: u64, end: u64) { + let mut node_client = world.get_node_client(&node).await.unwrap(); + let heights = (start..=end).collect::>(); + let blocks_req = GetBlocksRequest { heights }; + let mut blocks_stream = node_client.get_blocks(blocks_req).await.unwrap().into_inner(); + + let mut height = start; + while let Some(block) = blocks_stream.next().await { + let block = block.unwrap().block.unwrap(); + let block_height = block.header.unwrap().height; + if height != block_height { + panic!( + "Invalid block height for node {}: expected height {} != current height {}", + &node, block_height, height + ); + } + println!("Valid block height {}, listed by node {}", height, &node); + height += 1; + } +} + +#[then(expr = "node {word} lists headers {int} to {int} with correct heights")] +async fn node_lists_headers_with_correct_heights(world: &mut TariWorld, node: String, start: u64, end: u64) { + let mut node_client = world.get_node_client(&node).await.unwrap(); + let list_headers_req = ListHeadersRequest { + from_height: start, + num_headers: end - start + 1, + sorting: 1, + }; + let mut headers_stream = node_client.list_headers(list_headers_req).await.unwrap().into_inner(); + + let mut height = start; + while let Some(header) = headers_stream.next().await { + let header_res = header.unwrap(); + let header_height = header_res.header.unwrap().height; + + if header_height != height { + panic!( + "incorrect listing of height headers by node {}: expected height to be {} but got height {}", + &node, height, header_height + ); + } + println!("correct listing of height header {} by node {}", height, &node); + height += 1; + } +} + +#[then(expr = "all nodes are at height {int}*{int}")] +#[when(expr = "all nodes are at height {int}*{int}")] +async fn all_nodes_are_at_product_height(world: &mut TariWorld, a: u64, b: u64) { + all_nodes_are_at_height(world, a * b).await; +} + +#[when(expr = "I transfer {int}T from {word} to {word}")] +async fn transfer_tari_from_wallet_to_receiver(world: &mut TariWorld, amount: u64, sender: String, receiver: String) { + let mut sender_wallet_client = create_wallet_client(world, sender.clone()).await.unwrap(); + let sender_wallet_address = world.get_wallet_address(&sender).await.unwrap(); + let receiver_wallet_address = world.get_wallet_address(&receiver).await.unwrap(); + + let payment_recipient = PaymentRecipient { + address: receiver_wallet_address.clone(), + amount: amount * 1_000_000_u64, // 1T = 1_000_000uT + fee_per_gram: 10, // as in the js cucumber tests + message: format!( + "transfer amount {} from {} to {}", + amount, + sender.as_str(), + receiver.as_str() + ), + payment_type: 0, // normal mimblewimble payment type + }; + let transfer_req = TransferRequest { + recipients: vec![payment_recipient], + }; + let tx_res = sender_wallet_client.transfer(transfer_req).await.unwrap().into_inner(); + let tx_res = tx_res.results; + + assert_eq!(tx_res.len(), 1usize); + + let tx_res = tx_res.first().unwrap(); + assert!( + tx_res.is_success, + "Transacting amount {}T from wallet {} to {} at fee {} failed", + amount, + sender.as_str(), + receiver.as_str(), + 10 + ); + + // we wait for transaction to be broadcasted + let tx_id = tx_res.transaction_id; + let num_retries = 100; + let tx_info_req = GetTransactionInfoRequest { + transaction_ids: vec![tx_id], + }; + + for i in 0..=num_retries { + let tx_info_res = sender_wallet_client + .get_transaction_info(tx_info_req.clone()) + .await + .unwrap() + .into_inner(); + let tx_info = tx_info_res.transactions.first().unwrap(); + + // TransactionStatus::TRANSACTION_STATUS_BROADCAST == 1_i32 + if tx_info.status == 1_i32 { + println!( + "Transaction from {} to {} with amount {} at fee {} has been broadcasted", + sender.clone(), + receiver.clone(), + amount, + 10 + ); + break; + } + + if i == num_retries { + panic!( + "Transaction from {} to {} with amount {} at fee {} failed to be broadcasted", + sender.clone(), + receiver.clone(), + amount, + 10 + ) + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } + + // insert tx_id's to the corresponding world mapping + let source_tx_ids = world.wallet_tx_ids.entry(sender_wallet_address.clone()).or_default(); + + source_tx_ids.push(tx_id); + + let dest_tx_ids = world.wallet_tx_ids.entry(receiver_wallet_address.clone()).or_default(); + + dest_tx_ids.push(tx_id); + + println!( + "Transfer amount {} from {} to {} at fee {} succeeded", + amount, sender, receiver, 10 + ); +} + +#[when(expr = "wallet {word} has {int}T")] +#[then(expr = "wallet {word} has {int}T")] +async fn wallet_has_tari(world: &mut TariWorld, wallet: String, amount: u64) { + let mut wallet_client = create_wallet_client(world, wallet.clone()).await.unwrap(); + let num_retries = 100; + + let mut available_balance = 0; + + for _ in 0..num_retries { + let balance_res = wallet_client + .get_balance(GetBalanceRequest {}) + .await + .unwrap() + .into_inner(); + + available_balance = balance_res.available_balance; + if available_balance >= amount * 1_000_000 { + println!("Wallet {} has at least {}T", wallet.as_str(), amount); + return; + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } + + panic!( + "Wallet {} failed to have at least {}T, it ended with {}T", + wallet, amount, available_balance + ); +} + +#[when(expr = "I have wallet {word} with {int}T connected to base node {word}")] +async fn wallet_with_tari_connected_to_base_node( + world: &mut TariWorld, + wallet: String, + amount: u64, + base_node: String, +) { + let peer_seeds = world.base_nodes.get(&base_node).unwrap().seed_nodes.clone(); + println!( + "Start a new wallet {} connected to base node {}", + wallet.as_str(), + base_node.as_str() + ); + world + .wallet_connected_to_base_node + .insert(wallet.clone(), base_node.clone()); + spawn_wallet(world, wallet.clone(), Some(base_node.clone()), peer_seeds, None, None).await; + + let mut base_node_client = world.get_node_client(&base_node).await.unwrap(); + let tip_info_res = base_node_client.get_tip_info(Empty {}).await.unwrap().into_inner(); + let mut current_height = tip_info_res.metadata.unwrap().height_of_longest_chain; + + let mut num_blocks = 0; + let mut reward = 0; + + let consensus_manager = ConsensusManager::builder(Network::LocalNet).build(); + + while reward < amount { + current_height += 1; + num_blocks += 1; + reward += consensus_manager.get_block_reward_at(current_height).as_u64() / 1_000_000; // 1 T = 1_000_000 uT + } + + println!("Creating miner..."); + create_miner(world, "temp_miner".to_string(), base_node.clone(), wallet.clone()).await; + + println!("Mining {} blocks", num_blocks + CONFIRMATION_PERIOD); + let miner = world.miners.get(&"temp_miner".to_string()).unwrap(); + miner + .mine(world, Some(num_blocks + CONFIRMATION_PERIOD), None, None) + .await; // mine some additional blocks to confirm txs + + let mut wallet_client = create_wallet_client(world, wallet.clone()).await.unwrap(); + let num_retries = 100; + + for _ in 0..num_retries { + let balance_res = wallet_client + .get_balance(GetBalanceRequest {}) + .await + .unwrap() + .into_inner(); + + if balance_res.available_balance >= amount * 1_000_000 { + println!("Wallet {} has at least {}T", wallet.as_str(), amount); + return; + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } + + panic!("Wallet {} failed to have at least {}T", wallet, amount); +} + +#[when(expr = "I transfer {int} uT from {word} to {word} and {word} at fee {int}")] +#[allow(clippy::too_many_lines)] +async fn transfer_from_wallet_to_two_recipients_at_fee( + world: &mut TariWorld, + amount: u64, + sender: String, + receiver1: String, + receiver2: String, + fee_per_gram: u64, +) { + let mut sender_client = create_wallet_client(world, sender.clone()).await.unwrap(); + let sender_wallet_address = world.get_wallet_address(&sender).await.unwrap(); + let receiver1_address = world.get_wallet_address(&receiver1).await.unwrap(); + let receiver2_address = world.get_wallet_address(&receiver2).await.unwrap(); + + let payment_recipient1 = PaymentRecipient { + address: receiver1_address.clone(), + amount, + fee_per_gram, + message: format!( + "transfer amount {} from {} to {}", + amount, + sender.as_str(), + receiver1.as_str() + ), + payment_type: 0, // normal mimblewimble payment type + }; + + let payment_recipient2 = PaymentRecipient { + address: receiver2_address.clone(), + amount, + fee_per_gram, + message: format!( + "transfer amount {} from {} to {}", + amount, + sender.as_str(), + receiver2.as_str() + ), + payment_type: 0, // normal mimblewimble payment type + }; + let transfer_req = TransferRequest { + recipients: vec![payment_recipient1, payment_recipient2], + }; + let tx_res = sender_client.transfer(transfer_req).await.unwrap().into_inner(); + let tx_res = tx_res.results; + + assert_eq!(tx_res.len(), 2_usize); + + let tx_res1 = tx_res.first().unwrap(); + let tx_res2 = tx_res.last().unwrap(); + + assert!( + tx_res1.is_success, + "Transacting amount {} uT from wallet {} to {} at fee {} failed", + amount, + sender.as_str(), + receiver1.as_str(), + fee_per_gram + ); + assert!( + tx_res2.is_success, + "Transacting amount {} uT from wallet {} to {} at fee {} failed", + amount, + sender.as_str(), + receiver2.as_str(), + fee_per_gram + ); + + // we wait for transaction to be broadcasted + let tx_id1 = tx_res1.transaction_id; + let tx_id2 = tx_res2.transaction_id; + + let num_retries = 100; + let tx_info_req = GetTransactionInfoRequest { + transaction_ids: vec![tx_id1, tx_id2], + }; + + for i in 0..=num_retries { + let tx_info_res = sender_client + .get_transaction_info(tx_info_req.clone()) + .await + .unwrap() + .into_inner(); + let tx_info1 = tx_info_res.transactions.first().unwrap(); + let tx_info2 = tx_info_res.transactions.last().unwrap(); + + println!( + "Tx_info for first recipient {} is {}, for tx_id = {}", + receiver1, tx_info1.status, tx_id1 + ); + println!( + "Tx_info for second recipient {} is {}, for tx_id = {}", + receiver2, tx_info2.status, tx_id2 + ); + // TransactionStatus::TRANSACTION_STATUS_BROADCAST == 1_i32 + if tx_info1.status == 1_i32 && tx_info2.status == 1_i32 { + println!( + "Transaction from {} to {} and {} with amount {} at fee {} has been broadcasted", + sender.as_str(), + receiver1.as_str(), + receiver2.as_str(), + amount, + fee_per_gram + ); + break; + } + + if i == num_retries { + panic!( + "Transaction from {} to {} and {} with amount {} at fee {} failed to be broadcasted", + sender.as_str(), + receiver1.as_str(), + receiver2.as_str(), + amount, + 10 + ) + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } + + // insert tx_id's to the corresponding world mapping + let sender_tx_ids = world.wallet_tx_ids.entry(sender_wallet_address.clone()).or_default(); + + sender_tx_ids.push(tx_id1); + sender_tx_ids.push(tx_id2); + + let receiver1_tx_ids = world.wallet_tx_ids.entry(receiver1_address.clone()).or_default(); + receiver1_tx_ids.push(tx_id1); + + let receiver2_tx_ids = world.wallet_tx_ids.entry(receiver2_address.clone()).or_default(); + receiver2_tx_ids.push(tx_id2); + + println!( + "Transfer amount {} from {} to {} and {} at fee {} succeeded", + amount, sender, receiver1, receiver2, fee_per_gram + ); +} + +#[when(expr = "I transfer {int} uT to self from wallet {word} at fee {int}")] +async fn transfer_tari_to_self(world: &mut TariWorld, amount: u64, sender: String, fee_per_gram: u64) { + let mut sender_wallet_client = create_wallet_client(world, sender.clone()).await.unwrap(); + let sender_wallet_address = world.get_wallet_address(&sender).await.unwrap(); + + let payment_recipient = PaymentRecipient { + address: sender_wallet_address.clone(), + amount, + fee_per_gram, + message: format!("transfer amount {} from {} to self", amount, sender.as_str(),), + payment_type: 0, // normal mimblewimble payment type + }; + let transfer_req = TransferRequest { + recipients: vec![payment_recipient], + }; + let tx_res = sender_wallet_client.transfer(transfer_req).await.unwrap().into_inner(); + let tx_res = tx_res.results; + + assert_eq!(tx_res.len(), 1usize); + + let tx_res = tx_res.first().unwrap(); + assert!( + tx_res.is_success, + "Transacting amount {} to self from wallet {} at fee {} failed", + amount, + sender.as_str(), + fee_per_gram + ); + + // we wait for transaction to be broadcasted + let tx_id = tx_res.transaction_id; + let num_retries = 100; + let tx_info_req = GetTransactionInfoRequest { + transaction_ids: vec![tx_id], + }; + + for i in 0..=num_retries { + let tx_info_res = sender_wallet_client + .get_transaction_info(tx_info_req.clone()) + .await + .unwrap() + .into_inner(); + let tx_info = tx_info_res.transactions.first().unwrap(); + + // TransactionStatus::TRANSACTION_STATUS_BROADCAST == 1_i32 + if tx_info.status == 1_i32 { + println!( + "Transaction to self from {} with amount {} at fee {} has been broadcasted", + sender.clone(), + amount, + fee_per_gram + ); + break; + } + + if i == num_retries { + panic!( + "Transaction to self from {} with amount {} at fee {} failed to be broadcasted", + sender.clone(), + amount, + fee_per_gram + ) + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } + + // insert tx_id's to the corresponding world mapping + let sender_tx_ids = world.wallet_tx_ids.entry(sender_wallet_address.clone()).or_default(); + + sender_tx_ids.push(tx_id); + + println!( + "Transfer amount {} to self from {} at fee {} succeeded", + amount, sender, fee_per_gram + ); +} + +#[when(expr = "I broadcast HTLC transaction with {int} uT from wallet {word} to wallet {word} at fee {int}")] +async fn htlc_transaction(world: &mut TariWorld, amount: u64, sender: String, receiver: String, fee_per_gram: u64) { + let mut sender_wallet_client = create_wallet_client(world, sender.clone()).await.unwrap(); + let sender_wallet_address = world.get_wallet_address(&sender).await.unwrap(); + let receiver_wallet_address = world.get_wallet_address(&receiver).await.unwrap(); + + let payment_recipient = PaymentRecipient { + address: receiver_wallet_address.clone(), + amount, + fee_per_gram, + message: format!( + "Atomic Swap from {} to {} with amount {} at fee {}", + sender.as_str(), + receiver.as_str(), + amount, + fee_per_gram + ), + payment_type: 0, // normal mimblewimble transaction + }; + + let atomic_swap_request = SendShaAtomicSwapRequest { + recipient: Some(payment_recipient), + }; + let sha_atomic_swap_tx_res = sender_wallet_client + .send_sha_atomic_swap_transaction(atomic_swap_request) + .await + .unwrap() + .into_inner(); + + assert!( + sha_atomic_swap_tx_res.is_success, + "Atomic swap transacting amount uT {} from wallet {} to {} at fee {} failed", + amount, + sender.as_str(), + receiver.as_str(), + fee_per_gram + ); + + // we wait for transaction to be broadcasted + let tx_id = sha_atomic_swap_tx_res.transaction_id; + let num_retries = 100; + let tx_info_req = GetTransactionInfoRequest { + transaction_ids: vec![tx_id], + }; + + for i in 0..=num_retries { + let tx_info_res = sender_wallet_client + .get_transaction_info(tx_info_req.clone()) + .await + .unwrap() + .into_inner(); + let tx_info = tx_info_res.transactions.first().unwrap(); + + // TransactionStatus::TRANSACTION_STATUS_BROADCAST == 1_i32 + if tx_info.status == 1_i32 { + println!( + "Atomic swap transaction from {} to {} with amount {} at fee {} has been broadcasted", + sender.as_str(), + receiver.as_str(), + amount, + fee_per_gram + ); + break; + } + + if i == num_retries { + panic!( + "Atomic swap transaction from {} to {} with amount {} at fee {} failed to be broadcasted", + sender.as_str(), + receiver.as_str(), + amount, + fee_per_gram + ) + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } + + // insert tx_id's to the corresponding world mapping + let sender_tx_ids = world.wallet_tx_ids.entry(sender_wallet_address.clone()).or_default(); + + sender_tx_ids.push(tx_id); + + let receiver_tx_ids = world.wallet_tx_ids.entry(receiver_wallet_address.clone()).or_default(); + + receiver_tx_ids.push(tx_id); + world.output_hash = Some(sha_atomic_swap_tx_res.output_hash); + world.pre_image = Some(sha_atomic_swap_tx_res.pre_image); + + println!( + "Atomic swap transfer amount {} from {} to {} at fee {} succeeded", + amount, sender, receiver, fee_per_gram + ); +} + +#[when(expr = "I claim an HTLC refund transaction with wallet {word} at fee {int}")] +async fn claim_htlc_refund_transaction_with_wallet_at_fee(world: &mut TariWorld, wallet: String, fee_per_gram: u64) { + let mut wallet_client = create_wallet_client(world, wallet.clone()).await.unwrap(); + let wallet_address = world.get_wallet_address(&wallet).await.unwrap(); + let output_hash = world.output_hash.clone().unwrap(); + + let claim_htlc_req = ClaimHtlcRefundRequest { + output_hash, + fee_per_gram, + }; + + let claim_htlc_refund_res = wallet_client + .claim_htlc_refund_transaction(claim_htlc_req) + .await + .unwrap() + .into_inner(); + + assert!( + claim_htlc_refund_res.clone().results.unwrap().is_success, + "Claim HTLC refund transaction with wallet {} at fee {} failed", + wallet.as_str(), + fee_per_gram + ); + + // we wait for transaction to be broadcasted + let tx_id = claim_htlc_refund_res.results.unwrap().transaction_id; + let num_retries = 100; + let tx_info_req = GetTransactionInfoRequest { + transaction_ids: vec![tx_id], + }; + + for i in 0..=num_retries { + let tx_info_res = wallet_client + .get_transaction_info(tx_info_req.clone()) + .await + .unwrap() + .into_inner(); + let tx_info = tx_info_res.transactions.first().unwrap(); + + // TransactionStatus::TRANSACTION_STATUS_BROADCAST == 1_i32 + if tx_info.status == 1_i32 { + println!( + "Claim HTLC refund transaction with wallet {} at fee {} has been broadcasted", + wallet.as_str(), + fee_per_gram + ); + break; + } + + if i == num_retries { + panic!( + "Claim HTLC refund transaction with wallet {} at fee {} failed to be broadcasted", + wallet.as_str(), + fee_per_gram + ) + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } + + // insert tx_id's to the corresponding world mapping + let wallet_tx_ids = world.wallet_tx_ids.entry(wallet_address.clone()).or_default(); + wallet_tx_ids.push(tx_id); + + println!( + "Claim HTLC refund transaction with wallet {} at fee {} succeeded", + wallet, fee_per_gram + ); +} + +#[when(expr = "I claim an HTLC transaction with wallet {word} at fee {int}")] +async fn wallet_claims_htlc_transaction_at_fee(world: &mut TariWorld, wallet: String, fee_per_gram: u64) { + let mut wallet_client = create_wallet_client(world, wallet.clone()).await.unwrap(); + let wallet_address = world.get_wallet_address(&wallet).await.unwrap(); + let output_hash = world.output_hash.clone().unwrap(); + let pre_image = world.pre_image.clone().unwrap(); + + let claim_htlc_req = ClaimShaAtomicSwapRequest { + output: output_hash, + pre_image, + fee_per_gram, + }; + + let claim_htlc_res = wallet_client + .claim_sha_atomic_swap_transaction(claim_htlc_req) + .await + .unwrap() + .into_inner(); + + assert!( + claim_htlc_res.clone().results.unwrap().is_success, + "Claim HTLC transaction with wallet {} at fee {} failed", + wallet.as_str(), + fee_per_gram + ); + + // we wait for transaction to be broadcasted + let tx_id = claim_htlc_res.results.unwrap().transaction_id; + let num_retries = 100; + let tx_info_req = GetTransactionInfoRequest { + transaction_ids: vec![tx_id], + }; + + for i in 0..=num_retries { + let tx_info_res = wallet_client + .get_transaction_info(tx_info_req.clone()) + .await + .unwrap() + .into_inner(); + let tx_info = tx_info_res.transactions.first().unwrap(); + + // TransactionStatus::TRANSACTION_STATUS_BROADCAST == 1_i32 + if tx_info.status == 1_i32 { + println!( + "Claim HTLC transaction with wallet {} at fee {} has been broadcasted", + wallet.as_str(), + fee_per_gram + ); + break; + } + + if i == num_retries { + panic!( + "Claim HTLC transaction with wallet {} at fee {} failed to be broadcasted", + wallet.as_str(), + fee_per_gram + ) + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } + + // insert tx_id's to the corresponding world mapping + let wallet_tx_ids = world.wallet_tx_ids.entry(wallet_address.clone()).or_default(); + wallet_tx_ids.push(tx_id); + + println!( + "Claim HTLC transaction with wallet {} at fee {} succeeded", + wallet, fee_per_gram + ); +} + +#[then(expr = "I wait for wallet {word} to have less than {int} uT")] +async fn wait_for_wallet_to_have_less_than_amount(world: &mut TariWorld, wallet: String, amount: u64) { + let wallet_ps = world.wallets.get(&wallet).unwrap(); + let num_retries = 100; + + let mut client = wallet_ps.get_grpc_client().await.unwrap(); + let mut curr_amount = u64::MAX; + + for _ in 0..=num_retries { + curr_amount = client + .get_balance(GetBalanceRequest {}) + .await + .unwrap() + .into_inner() + .available_balance; + + if curr_amount < amount { + return; + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } + + // failed to get wallet right amount, so we panic + panic!( + "wallet {} failed to get less balance than amount {}, current amount is {}", + wallet.as_str(), + amount, + curr_amount + ); +} + +#[then(expr = "I send a one-sided stealth transaction of {int} uT from {word} to {word} at fee {int}")] +async fn send_one_sided_stealth_transaction( + world: &mut TariWorld, + amount: u64, + sender: String, + receiver: String, + fee_per_gram: u64, +) { + let mut sender_client = create_wallet_client(world, sender.clone()).await.unwrap(); + let sender_wallet_address = sender_client + .get_address(Empty {}) + .await + .unwrap() + .into_inner() + .address + .to_hex(); + + let mut receiver_client = create_wallet_client(world, receiver.clone()).await.unwrap(); + let receiver_wallet_address = receiver_client + .get_address(Empty {}) + .await + .unwrap() + .into_inner() + .address + .to_hex(); + + let payment_recipient = PaymentRecipient { + address: receiver_wallet_address.clone(), + amount, + fee_per_gram, + message: format!( + "One sided stealth transfer amount {} from {} to {}", + amount, + sender.as_str(), + receiver.as_str() + ), + payment_type: 2, // one sided stealth transaction + }; + let transfer_req = TransferRequest { + recipients: vec![payment_recipient], + }; + let tx_res = sender_client.transfer(transfer_req).await.unwrap().into_inner(); + let tx_res = tx_res.results; + + assert_eq!(tx_res.len(), 1usize); + + let tx_res = tx_res.first().unwrap(); + assert!( + tx_res.is_success, + "One sided stealth transaction with amount {} from wallet {} to {} at fee {} failed", + amount, + sender.as_str(), + receiver.as_str(), + fee_per_gram + ); + + // we wait for transaction to be broadcasted + let tx_id = tx_res.transaction_id; + let num_retries = 100; + let tx_info_req = GetTransactionInfoRequest { + transaction_ids: vec![tx_id], + }; + + for i in 0..num_retries { + let tx_info_res = sender_client + .get_transaction_info(tx_info_req.clone()) + .await + .unwrap() + .into_inner(); + let tx_info = tx_info_res.transactions.first().unwrap(); + + // TransactionStatus::TRANSACTION_STATUS_BROADCAST == 1_i32 + if tx_info.status == 1_i32 { + println!( + "One sided stealth transaction from {} to {} with amount {} at fee {} has been broadcasted", + sender.clone(), + receiver.clone(), + amount, + fee_per_gram + ); + break; + } + + if i == num_retries - 1 { + panic!( + "One sided stealth transaction from {} to {} with amount {} at fee {} failed to be broadcasted", + sender.clone(), + receiver.clone(), + amount, + fee_per_gram + ) + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } + + // insert tx_id's to the corresponding world mapping + let sender_tx_ids = world.wallet_tx_ids.entry(sender_wallet_address.clone()).or_default(); + + sender_tx_ids.push(tx_id); + + let receiver_tx_ids = world.wallet_tx_ids.entry(receiver_wallet_address.clone()).or_default(); + + receiver_tx_ids.push(tx_id); + + println!( + "One sided stealth transaction with amount {} from {} to {} at fee {} succeeded", + amount, sender, receiver, fee_per_gram + ); +} + +#[then(expr = "I import {word} unspent outputs to {word}")] +async fn import_wallet_unspent_outputs(world: &mut TariWorld, wallet_a: String, wallet_b: String) { + let wallet_a_ps = world.wallets.get_mut(&wallet_a).unwrap(); + + let temp_dir_path = wallet_a_ps.temp_dir_path.clone(); + + let mut cli = get_default_cli(); + + let mut path_buf = PathBuf::new(); + path_buf.push(temp_dir_path); + path_buf.push("exported_utxos.csv"); + + let args = ExportUtxosArgs { + output_file: Some(path_buf.clone()), + }; + cli.command2 = Some(CliCommands::ExportUtxos(args)); + + let base_node = world.wallet_connected_to_base_node.get(&wallet_a).unwrap(); + + let seed_nodes = world.base_nodes.get(base_node).unwrap().seed_nodes.clone(); + spawn_wallet(world, wallet_a, Some(base_node.clone()), seed_nodes, None, Some(cli)).await; + + let exported_outputs = std::fs::File::open(path_buf).unwrap(); + let mut reader = csv::Reader::from_reader(exported_outputs); + + let mut outputs: Vec = vec![]; + + for output in reader.records() { + let output = output.unwrap(); + let version = match &output[1] { + "V0" => TransactionOutputVersion::V0, + "V1" => TransactionOutputVersion::V1, + _ => panic!("Invalid output version"), + }; + let value = MicroTari(output[2].parse::().unwrap()); + let spending_key = BlindingFactor::from_hex(&output[3]).unwrap(); + let flags = match &output[5] { + "Standard" => OutputType::Standard, + "Coinbase" => OutputType::Coinbase, + "Burn" => OutputType::Burn, + "ValidatorNodeRegistration" => OutputType::ValidatorNodeRegistration, + "CodeTemplateRegistration" => OutputType::CodeTemplateRegistration, + _ => panic!("Invalid output type"), + }; + let maturity = output[6].parse::().unwrap(); + let coinbase_extra = Vec::from_hex(&output[7]).unwrap(); + let script = TariScript::from_hex(&output[8]).unwrap(); + let covenant = Covenant::from_bytes(&mut Vec::from_hex(&output[9]).unwrap().as_slice()).unwrap(); + let input_data = ExecutionStack::from_hex(&output[10]).unwrap(); + let script_private_key = PrivateKey::from_hex(&output[11]).unwrap(); + let sender_offset_public_key = PublicKey::from_hex(&output[12]).unwrap(); + let ephemeral_commitment: HomomorphicCommitment = + HomomorphicCommitment::from_hex(&output[13]).unwrap(); + let ephemeral_nonce = PublicKey::from_hex(&output[14]).unwrap(); + let signature_u_x = PrivateKey::from_hex(&output[15]).unwrap(); + let signature_u_a = PrivateKey::from_hex(&output[16]).unwrap(); + let signature_u_y = PrivateKey::from_hex(&output[17]).unwrap(); + let script_lock_height = output[18].parse::().unwrap(); + let encrypted_value = EncryptedValue::from_hex(&output[19]).unwrap(); + let minimum_value_promise = MicroTari(output[20].parse::().unwrap()); + + let features = OutputFeatures::new_current_version(flags, maturity, coinbase_extra, None); + let metadata_signature = ComAndPubSignature::new( + ephemeral_commitment, + ephemeral_nonce, + signature_u_x, + signature_u_a, + signature_u_y, + ); + let utxo = UnblindedOutput::new( + version, + value, + spending_key, + features, + script, + input_data, + script_private_key, + sender_offset_public_key, + metadata_signature, + script_lock_height, + covenant, + encrypted_value, + minimum_value_promise, + ); + + outputs.push(utxo); + } + + let mut wallet_b_client = create_wallet_client(world, wallet_b.clone()).await.unwrap(); + let import_utxos_req = ImportUtxosRequest { + outputs: outputs + .iter() + .map(|o| grpc::UnblindedOutput::try_from(o.clone()).expect("Unable to make grpc conversino")) + .collect::>(), + }; + + world.last_imported_tx_ids = wallet_b_client + .import_utxos(import_utxos_req) + .await + .unwrap() + .into_inner() + .tx_ids; +} + +#[then(expr = "I import {word} spent outputs to {word}")] +async fn import_wallet_spent_outputs(world: &mut TariWorld, wallet_a: String, wallet_b: String) { + let wallet_a_ps = world.wallets.get_mut(&wallet_a).unwrap(); + + let temp_dir_path = wallet_a_ps.temp_dir_path.clone(); + + let mut cli = get_default_cli(); + + let mut path_buf = PathBuf::new(); + path_buf.push(temp_dir_path); + path_buf.push("exported_utxos.csv"); + + let args = ExportUtxosArgs { + output_file: Some(path_buf.clone()), + }; + cli.command2 = Some(CliCommands::ExportSpentUtxos(args)); + + let base_node = world.wallet_connected_to_base_node.get(&wallet_a).unwrap(); + let seed_nodes = world.base_nodes.get(base_node).unwrap().seed_nodes.clone(); + spawn_wallet(world, wallet_a, Some(base_node.clone()), seed_nodes, None, Some(cli)).await; + + let exported_outputs = std::fs::File::open(path_buf).unwrap(); + let mut reader = csv::Reader::from_reader(exported_outputs); + + let mut outputs: Vec = vec![]; + + for output in reader.records() { + let output = output.unwrap(); + let version = match &output[1] { + "V0" => TransactionOutputVersion::V0, + "V1" => TransactionOutputVersion::V1, + _ => panic!("Invalid output version"), + }; + let value = MicroTari(output[2].parse::().unwrap()); + let spending_key = BlindingFactor::from_hex(&output[3]).unwrap(); + let flags = match &output[5] { + "Standard" => OutputType::Standard, + "Coinbase" => OutputType::Coinbase, + "Burn" => OutputType::Burn, + "ValidatorNodeRegistration" => OutputType::ValidatorNodeRegistration, + "CodeTemplateRegistration" => OutputType::CodeTemplateRegistration, + _ => panic!("Invalid output type"), + }; + let maturity = output[6].parse::().unwrap(); + let coinbase_extra = Vec::from_hex(&output[7]).unwrap(); + let script = TariScript::from_hex(&output[8]).unwrap(); + let covenant = Covenant::from_bytes(&mut Vec::from_hex(&output[9]).unwrap().as_slice()).unwrap(); + let input_data = ExecutionStack::from_hex(&output[10]).unwrap(); + let script_private_key = PrivateKey::from_hex(&output[11]).unwrap(); + let sender_offset_public_key = PublicKey::from_hex(&output[12]).unwrap(); + let ephemeral_commitment: HomomorphicCommitment = + HomomorphicCommitment::from_hex(&output[13]).unwrap(); + let ephemeral_nonce = PublicKey::from_hex(&output[14]).unwrap(); + let signature_u_x = PrivateKey::from_hex(&output[15]).unwrap(); + let signature_u_a = PrivateKey::from_hex(&output[16]).unwrap(); + let signature_u_y = PrivateKey::from_hex(&output[17]).unwrap(); + let script_lock_height = output[18].parse::().unwrap(); + let encrypted_value = EncryptedValue::from_hex(&output[19]).unwrap(); + let minimum_value_promise = MicroTari(output[20].parse::().unwrap()); + + let features = OutputFeatures::new_current_version(flags, maturity, coinbase_extra, None); + let metadata_signature = ComAndPubSignature::new( + ephemeral_commitment, + ephemeral_nonce, + signature_u_x, + signature_u_a, + signature_u_y, + ); + let utxo = UnblindedOutput::new( + version, + value, + spending_key, + features, + script, + input_data, + script_private_key, + sender_offset_public_key, + metadata_signature, + script_lock_height, + covenant, + encrypted_value, + minimum_value_promise, + ); + + outputs.push(utxo); + } + + let mut wallet_b_client = create_wallet_client(world, wallet_b.clone()).await.unwrap(); + let import_utxos_req = ImportUtxosRequest { + outputs: outputs + .iter() + .map(|o| grpc::UnblindedOutput::try_from(o.clone()).expect("Unable to make grpc conversino")) + .collect::>(), + }; + + world.last_imported_tx_ids = wallet_b_client + .import_utxos(import_utxos_req) + .await + .unwrap() + .into_inner() + .tx_ids; +} + +#[then(expr = "I import {word} unspent outputs as faucet outputs to {word}")] +async fn import_unspent_outputs_as_faucets(world: &mut TariWorld, wallet_a: String, wallet_b: String) { + let wallet_a_ps = world.wallets.get_mut(&wallet_a).unwrap(); + + let temp_dir_path = wallet_a_ps.temp_dir_path.clone(); + + let mut cli = get_default_cli(); + + let mut path_buf = PathBuf::new(); + path_buf.push(temp_dir_path); + path_buf.push("exported_utxos.csv"); + + let args = ExportUtxosArgs { + output_file: Some(path_buf.clone()), + }; + cli.command2 = Some(CliCommands::ExportUtxos(args)); + + let base_node = world.wallet_connected_to_base_node.get(&wallet_a).unwrap(); + let seed_nodes = world.base_nodes.get(base_node).unwrap().seed_nodes.clone(); + spawn_wallet(world, wallet_a, Some(base_node.clone()), seed_nodes, None, Some(cli)).await; + + let exported_outputs = std::fs::File::open(path_buf).unwrap(); + let mut reader = csv::Reader::from_reader(exported_outputs); + + let mut outputs: Vec = vec![]; + + for output in reader.records() { + let output = output.unwrap(); + let version = match &output[1] { + "V0" => TransactionOutputVersion::V0, + "V1" => TransactionOutputVersion::V1, + _ => panic!("Invalid output version"), + }; + let value = MicroTari(output[2].parse::().unwrap()); + let spending_key = BlindingFactor::from_hex(&output[3]).unwrap(); + let flags = match &output[5] { + "Standard" => OutputType::Standard, + "Coinbase" => OutputType::Coinbase, + "Burn" => OutputType::Burn, + "ValidatorNodeRegistration" => OutputType::ValidatorNodeRegistration, + "CodeTemplateRegistration" => OutputType::CodeTemplateRegistration, + _ => panic!("Invalid output type"), + }; + let maturity = output[6].parse::().unwrap(); + let coinbase_extra = Vec::from_hex(&output[7]).unwrap(); + let script = TariScript::from_hex(&output[8]).unwrap(); + let covenant = Covenant::from_bytes(&mut Vec::from_hex(&output[9]).unwrap().as_slice()).unwrap(); + let input_data = ExecutionStack::from_hex(&output[10]).unwrap(); + let script_private_key = PrivateKey::from_hex(&output[11]).unwrap(); + let sender_offset_public_key = PublicKey::from_hex(&output[12]).unwrap(); + let ephemeral_commitment: HomomorphicCommitment = + HomomorphicCommitment::from_hex(&output[13]).unwrap(); + let ephemeral_nonce = PublicKey::from_hex(&output[14]).unwrap(); + let signature_u_x = PrivateKey::from_hex(&output[15]).unwrap(); + let signature_u_a = PrivateKey::from_hex(&output[16]).unwrap(); + let signature_u_y = PrivateKey::from_hex(&output[17]).unwrap(); + let script_lock_height = output[18].parse::().unwrap(); + let encrypted_value = EncryptedValue::from_hex(&output[19]).unwrap(); + let minimum_value_promise = MicroTari(output[20].parse::().unwrap()); + + let features = OutputFeatures::new_current_version(flags, maturity, coinbase_extra, None); + let metadata_signature = ComAndPubSignature::new( + ephemeral_commitment, + ephemeral_nonce, + signature_u_x, + signature_u_a, + signature_u_y, + ); + let mut utxo = UnblindedOutput::new( + version, + value, + spending_key, + features, + script, + input_data, + script_private_key, + sender_offset_public_key, + metadata_signature, + script_lock_height, + covenant, + encrypted_value, + minimum_value_promise, + ); + + utxo.metadata_signature = ComAndPubSignature::new( + Commitment::default(), + PublicKey::default(), + PrivateKey::default(), + PrivateKey::default(), + PrivateKey::default(), + ); + utxo.script_private_key = utxo.clone().spending_key; + + let script_public_key = PublicKey::from_secret_key(&utxo.script_private_key); + utxo.input_data = ExecutionStack::new(vec![StackItem::PublicKey(script_public_key)]); + outputs.push(utxo.clone()); + } + + let mut wallet_b_client = create_wallet_client(world, wallet_b.clone()).await.unwrap(); + let import_utxos_req = ImportUtxosRequest { + outputs: outputs + .iter() + .map(|o| grpc::UnblindedOutput::try_from(o.clone()).expect("Unable to make grpc conversino")) + .collect::>(), + }; + + world.last_imported_tx_ids = wallet_b_client + .import_utxos(import_utxos_req) + .await + .unwrap() + .into_inner() + .tx_ids; +} + +#[then(expr = "I restart wallet {word}")] +async fn restart_wallet(world: &mut TariWorld, wallet: String) { + let wallet_ps = world.wallets.get_mut(&wallet).unwrap(); + // stop wallet + wallet_ps.kill(); + // start wallet + let base_node = world.wallet_connected_to_base_node.get(&wallet).unwrap().clone(); + let base_node_ps = world.base_nodes.get(&base_node).unwrap(); + let seed_nodes = base_node_ps.seed_nodes.clone(); + + // need to wait a few seconds before spawning a new wallet + tokio::time::sleep(Duration::from_secs(5)).await; + + spawn_wallet(world, wallet, Some(base_node), seed_nodes, None, None).await; +} + +#[then(expr = "I check if wallet {word} has {int} transactions")] +async fn check_if_wallet_has_num_transactions(world: &mut TariWorld, wallet: String, num_txs: u64) { + let mut client = create_wallet_client(world, wallet.clone()).await.unwrap(); + let mut get_completed_txs_res = client + .get_completed_transactions(GetCompletedTransactionsRequest {}) + .await + .unwrap() + .into_inner(); + + let mut count = 0; + while let Some(tx) = get_completed_txs_res.next().await { + let _tx = tx.unwrap(); // make sure we get the actual response + count += 1; + } + + assert_eq!( + num_txs, + count, + "Wallet {} did not get {} transactions, instead it got {}", + wallet.as_str(), + num_txs, + count + ); +} + +#[when(expr = "I multi-send {int} transactions of {int} uT from wallet {word} to wallet {word} at fee {int}")] +async fn multi_send_txs_from_wallet( + world: &mut TariWorld, + num_txs: u64, + amount: u64, + sender: String, + receiver: String, + fee_per_gram: u64, +) { + let mut sender_wallet_client = create_wallet_client(world, sender.clone()).await.unwrap(); + let sender_wallet_address = sender_wallet_client + .get_address(Empty {}) + .await + .unwrap() + .into_inner() + .address + .to_hex(); + + let mut receiver_wallet_client = create_wallet_client(world, receiver.clone()).await.unwrap(); + let receiver_wallet_address = receiver_wallet_client + .get_address(Empty {}) + .await + .unwrap() + .into_inner() + .address + .to_hex(); + + let mut transfer_res = vec![]; + + for _ in 0..num_txs { + let payment_recipient = PaymentRecipient { + address: receiver_wallet_address.clone(), + amount, + fee_per_gram, + message: format!( + "I send multi-transfers with amount {} from {} to {} with fee {}", + amount, + sender.as_str(), + receiver.as_str(), + fee_per_gram + ), + payment_type: 0, // mimblewimble transaction + }; + + let transfer_req = TransferRequest { + recipients: vec![payment_recipient], + }; + let tx_res = sender_wallet_client.transfer(transfer_req).await.unwrap().into_inner(); + let tx_res = tx_res.results; + + assert_eq!(tx_res.len(), 1usize); + + let tx_res = tx_res.first().unwrap(); + assert!( + tx_res.is_success, + "Multi-Transaction with amount {} from wallet {} to {} at fee {} failed", + amount, + sender.as_str(), + receiver.as_str(), + fee_per_gram + ); + + transfer_res.push(tx_res.clone()); + } + + let num_retries = 100; + + for tx_res in transfer_res { + let tx_id = tx_res.transaction_id; + let tx_info_req = GetTransactionInfoRequest { + transaction_ids: vec![tx_id], + }; + + for i in 0..num_retries { + let tx_info_res = sender_wallet_client + .get_transaction_info(tx_info_req.clone()) + .await + .unwrap() + .into_inner(); + let tx_info = tx_info_res.transactions.first().unwrap(); + + // TransactionStatus::TRANSACTION_STATUS_BROADCAST == 1_i32 + if tx_info.status == 1_i32 { + println!( + "Multi-transaction from {} to {} with amount {} at fee {} has been broadcasted", + sender.clone(), + receiver.clone(), + amount, + fee_per_gram + ); + break; + } + + if i == num_retries - 1 { + panic!( + "Multi-transaction from {} to {} with amount {} at fee {} failed to be broadcasted", + sender.clone(), + receiver.clone(), + amount, + fee_per_gram + ) + } + + tokio::time::sleep(Duration::from_secs(5)).await; + } + + // insert tx_id's to the corresponding world mapping + let sender_tx_ids = world.wallet_tx_ids.entry(sender_wallet_address.clone()).or_default(); + + sender_tx_ids.push(tx_id); + + let receiver_tx_ids = world.wallet_tx_ids.entry(receiver_wallet_address.clone()).or_default(); + + receiver_tx_ids.push(tx_id); + + println!( + "Multi-transaction with amount {} from {} to {} at fee {} succeeded", + amount, sender, receiver, fee_per_gram + ); + } +} + +#[when(expr = "I connect node {word} to node {word}")] +async fn connect_node_to_other_node(world: &mut TariWorld, node_a: String, node_b: String) { + let node_a_ps = world.base_nodes.get_mut(&node_a).unwrap(); + let mut node_a_peers = node_a_ps.seed_nodes.clone(); + let is_seed_node = node_a_ps.is_seed_node; + node_a_peers.push(node_b); + node_a_ps.kill(); + tokio::time::sleep(Duration::from_secs(15)).await; + spawn_base_node(world, is_seed_node, node_a, node_a_peers).await; +} + +#[then(expr = "I check if last imported transactions are invalid in wallet {word}")] +async fn check_if_last_imported_txs_are_invalid_in_wallet(world: &mut TariWorld, wallet: String) { + let mut client = create_wallet_client(world, wallet.clone()).await.unwrap(); + let mut get_completed_txs_res = client + .get_completed_transactions(GetCompletedTransactionsRequest {}) + .await + .unwrap() + .into_inner(); + + while let Some(tx) = get_completed_txs_res.next().await { + let tx_info = tx.unwrap().transaction.unwrap(); + let status = tx_info.status; + // 3 => TRANSACTION_STATUS_IMPORTED + // 5 => TRANSACTION_STATUS_COINBASE + if ![3, 5].contains(&status) { + panic!( + "Imported transaction hasn't been received as such: current status code is {}, it should be 3 or 5", + status + ); + } + } +} + +#[then(expr = "I check if last imported transactions are valid in wallet {word}")] +async fn check_if_last_imported_txs_are_valid_in_wallet(world: &mut TariWorld, wallet: String) { + let mut client = create_wallet_client(world, wallet.clone()).await.unwrap(); + let mut get_completed_txs_res = client + .get_completed_transactions(GetCompletedTransactionsRequest {}) + .await + .unwrap() + .into_inner(); + + let mut imported_cnt = 0; + + while let Some(tx) = get_completed_txs_res.next().await { + let tx_info = tx.unwrap().transaction.unwrap(); + for &tx_id in &world.last_imported_tx_ids { + if tx_id == tx_info.tx_id { + assert_eq!(tx_info.status(), grpc::TransactionStatus::FauxConfirmed); + imported_cnt += 1; + } + } + } + assert_eq!(imported_cnt, world.last_imported_tx_ids.len()); +} + +#[then(expr = "I cancel last transaction in wallet {word}")] +async fn cancel_last_transaction_in_wallet(world: &mut TariWorld, wallet: String) { + let mut client = create_wallet_client(world, wallet.clone()).await.unwrap(); + let wallet_address = client + .get_address(Empty {}) + .await + .unwrap() + .into_inner() + .address + .to_hex(); + + let wallet_tx_ids = world.wallet_tx_ids.get(&wallet_address).unwrap(); + + // get the last tx id for wallet + let tx_id = *wallet_tx_ids.last().unwrap(); + let cancel_tx_req = CancelTransactionRequest { tx_id }; + let cancel_tx_res = client.cancel_transaction(cancel_tx_req).await.unwrap().into_inner(); + assert!( + cancel_tx_res.is_success, + "Unable to cancel transaction with id = {}", + tx_id + ); +} + +#[then(expr = "meddling with block template data from node {word} is not allowed")] +async fn no_meddling_with_data(world: &mut TariWorld, node: String) { + let mut client = world.get_node_client(&node).await.unwrap(); + + // No meddling + let chain_tip = client.get_tip_info(Empty {}).await.unwrap().into_inner(); + let current_height = chain_tip.metadata.unwrap().height_of_longest_chain; + let block = mine_block_before_submit(&mut client).await; + let _sumbmit_res = client.submit_block(block).await.unwrap(); + + let chain_tip = client.get_tip_info(Empty {}).await.unwrap().into_inner(); + let new_height = chain_tip.metadata.unwrap().height_of_longest_chain; + assert_eq!( + current_height + 1, + new_height, + "validating that the chain increased by 1 from {} to {} but was actually {}", + current_height, + current_height + 1, + new_height + ); + + // Meddle with kernal_mmr_size + let mut block: Block = Block::try_from(mine_block_before_submit(&mut client).await).unwrap(); + block.header.kernel_mmr_size += 1; + match client.submit_block(grpc::Block::try_from(block).unwrap()).await { + Ok(_) => panic!("The block should not have been valid"), + Err(e) => assert_eq!( + "Chain storage error: Validation error: Block validation error: MMR size for Kernel does not match. \ + Expected: 3, received: 4" + .to_string(), + e.message() + ), + } + + // Meddle with output_mmr_size + let mut block: Block = Block::try_from(mine_block_before_submit(&mut client).await).unwrap(); + block.header.output_mmr_size += 1; + match client.submit_block(grpc::Block::try_from(block).unwrap()).await { + Ok(_) => panic!("The block should not have been valid"), + Err(e) => assert_eq!( + "Chain storage error: Validation error: Block validation error: MMR size for UTXO does not match. \ + Expected: 3, received: 4" + .to_string(), + e.message() + ), + } +} + +#[when(expr = "I mine but do not submit a block {word} on {word}")] +async fn mine_without_submit(world: &mut TariWorld, block: String, node: String) { + let mut client = world.get_node_client(&node).await.unwrap(); + + let unmined_block: Block = Block::try_from(mine_block_before_submit(&mut client).await).unwrap(); + world.blocks.insert(block, unmined_block); +} + +#[when(expr = "I submit block {word} to {word}")] +async fn submit_block_after(world: &mut TariWorld, block_name: String, node: String) { + let mut client = world.get_node_client(&node).await.unwrap(); + let block = world.blocks.get(&block_name).expect("Couldn't find unmined block"); + + match client.submit_block(grpc::Block::try_from(block.clone()).unwrap()).await { + Ok(_resp) => {}, + Err(e) => { + // The kind of errors we want don't actually get returned + world.errors.push_back(e.message().to_string()); + }, + } +} + +#[then(regex = r"I receive an error containing '(.*)'")] +async fn receive_an_error(_world: &mut TariWorld, _error: String) { + // No-op. + // Was not implemented in previous suite, gave it a quick try but missing other peices + + // assert!(world.errors.len() > 1); + // assert!(world.errors.pop_front().unwrap().contains(&error)) +} + +#[when(expr = "I have a lagging delayed node {word} connected to node {word} with \ + blocks_behind_before_considered_lagging {int}")] +async fn lagging_delayed_node(world: &mut TariWorld, delayed_node: String, node: String, delay: u64) { + let mut base_node_config = BaseNodeConfig::default(); + base_node_config.state_machine.blocks_behind_before_considered_lagging = delay; + + spawn_base_node_with_config(world, true, delayed_node, vec![node], base_node_config).await; +} + +#[then(expr = "node {word} has reached initial sync")] +async fn node_reached_sync(world: &mut TariWorld, node: String) { + let mut client = world.get_node_client(&node).await.unwrap(); + let mut longest_chain = 0; + + for _ in 0..(TWO_MINUTES_WITH_HALF_SECOND_SLEEP * 11) { + let tip_info = client.get_tip_info(Empty {}).await.unwrap().into_inner(); + let metadata = tip_info.metadata.unwrap(); + longest_chain = metadata.height_of_longest_chain; + + if tip_info.initial_sync_achieved { + return; + } + + tokio::time::sleep(Duration::from_millis(HALF_SECOND)).await; + } + + panic!( + "Node {} never reached initial sync. Stuck at tip {}", + node, longest_chain + ) +} + +#[when(expr = "I create a burn transaction of {int} uT from {word} at fee {int}")] +async fn burn_transaction(world: &mut TariWorld, amount: u64, wallet: String, fee: u64) { + let mut client = world.get_wallet_client(&wallet).await.unwrap(); + + let req = grpc::CreateBurnTransactionRequest { + amount, + fee_per_gram: fee, + message: "Burning some tari".to_string(), + }; + + let result = client.create_burn_transaction(req).await.unwrap(); + let tx_id = result.into_inner().transaction_id; + + let mut last_status = 0; + for _ in 0..(TWO_MINUTES_WITH_HALF_SECOND_SLEEP) { + let result = client + .get_transaction_info(grpc::GetTransactionInfoRequest { + transaction_ids: vec![tx_id], + }) + .await + .unwrap(); + + last_status = result.into_inner().transactions.last().unwrap().status; + + if let 1 | 2 | 6 = last_status { + return; + } + + tokio::time::sleep(Duration::from_millis(HALF_SECOND)).await; + } + + panic!( + "Burn transaction has status {} when we desired 1 (TRANSACTION_STATUS_BROADCAST), 2 \ + (TRANSACTION_STATUS_UNCONFIRMED), or 6 (TRANSACTION_STATUS_CONFIRMED)", + last_status + ) +} + +#[when(expr = "I have {int} base nodes with pruning horizon {int} force syncing on node {word}")] +async fn force_sync_node_with_an_army_of_pruned_nodes( + world: &mut TariWorld, + nodes_count: u64, + horizon: u64, + node: String, +) { + for i in 0..=nodes_count { + let node_name = format!("BaseNode-{}", i); + + let mut base_node_config = BaseNodeConfig::default(); + let peers = vec![node.clone()]; + base_node_config.force_sync_peers = get_peer_addresses(world, &peers).await.into(); + base_node_config.storage.pruning_horizon = horizon; + + spawn_base_node_with_config(world, false, node_name, peers, base_node_config).await; + } +} + +#[when(expr = "I spend outputs {word} via {word}")] +async fn spend_outputs_via(world: &mut TariWorld, inputs: String, node: String) { + let num = rand::thread_rng().gen::(); + let tx_name = format!("TX-{}", num); + let utxo_name = format!("UTXO-{}", num); + + create_tx_spending_coinbase(world, tx_name.clone(), inputs, utxo_name.clone()).await; + submit_transaction_to(world, tx_name, node).await.unwrap(); +} + +#[then(expr = "{word} has at least {int} peers")] +async fn has_at_least_num_peers(world: &mut TariWorld, node: String, num_peers: u64) { + let mut client = world.get_node_client(&node).await.unwrap(); + let mut last_num_of_peers = 0; + + for _ in 0..(TWO_MINUTES_WITH_HALF_SECOND_SLEEP) { + last_num_of_peers = 0; + + let mut peers_stream = client.get_peers(grpc::GetPeersRequest {}).await.unwrap().into_inner(); + + while let Some(resp) = peers_stream.next().await { + if let Ok(resp) = resp { + if let Some(_peer) = resp.peer { + last_num_of_peers += 1 + } + } + } + + if last_num_of_peers >= num_peers as usize { + return; + } + + tokio::time::sleep(Duration::from_millis(HALF_SECOND)).await; + } + + panic!( + "Node {} only received {} of {} expected peers", + node, last_num_of_peers, num_peers + ) +} + +#[when(expr = "I mine {int} blocks with difficulty {int} on {word}")] +async fn num_blocks_with_difficulty(world: &mut TariWorld, num_blocks: u64, difficulty: u64, node: String) { + let wallet_name = format!("wallet-{}", &node); + if world.wallets.get(&wallet_name).is_none() { + spawn_wallet(world, wallet_name.clone(), Some(node.clone()), vec![], None, None).await; + }; + + let miner_name = format!("miner-{}", &node); + if world.miners.get(&miner_name).is_none() { + register_miner_process(world, miner_name.clone(), node.clone(), wallet_name.clone()); + } + + let miner = world.miners.get(&miner_name).unwrap(); + miner + .mine(world, Some(num_blocks), Some(difficulty), Some(difficulty)) + .await; +} + +#[then(expr = "I change base node of {word} to {word} via command line")] +async fn change_base_node_of_wallet_via_cli(world: &mut TariWorld, wallet: String, node: String) { + let wallet_ps = world.wallets.get_mut(&wallet).unwrap(); + wallet_ps.kill(); + + tokio::time::sleep(Duration::from_secs(5)).await; + + let mut cli = get_default_cli(); + + let mut node_client = world.get_node_client(&node).await.unwrap(); + let node_identity = node_client.identify(Empty {}).await.unwrap().into_inner(); + + let args = SetBaseNodeArgs { + public_key: UniPublicKey::from_str(node_identity.public_key.to_hex().as_str()).unwrap(), + address: Multiaddr::from_str(node_identity.public_address.as_str()).unwrap(), + }; + + cli.command2 = Some(CliCommands::SetBaseNode(args)); + + let seed_nodes = world.base_nodes.get(&node).unwrap().seed_nodes.clone(); + spawn_wallet(world, wallet, Some(node.clone()), seed_nodes, None, Some(cli)).await; +} + +#[then(expr = "I set custom base node of {word} to {word} via command line")] +async fn change_custom_base_node_of_wallet_via_cli(world: &mut TariWorld, wallet: String, node: String) { + let wallet_ps = world.wallets.get_mut(&wallet).unwrap(); + wallet_ps.kill(); + + tokio::time::sleep(Duration::from_secs(5)).await; + + let mut cli = get_default_cli(); + + let mut node_client = world.get_node_client(&node).await.unwrap(); + let node_identity = node_client.identify(Empty {}).await.unwrap().into_inner(); + + let args = SetBaseNodeArgs { + public_key: UniPublicKey::from_str(node_identity.public_key.to_hex().as_str()).unwrap(), + address: Multiaddr::from_str(node_identity.public_address.as_str()).unwrap(), + }; + + cli.command2 = Some(CliCommands::SetCustomBaseNode(args)); + + let seed_nodes = world.base_nodes.get(&node).unwrap().seed_nodes.clone(); + spawn_wallet(world, wallet, Some(node.clone()), seed_nodes, None, Some(cli)).await; +} + +#[when(expr = "I clear custom base node of wallet {word} via command line")] +async fn clear_custom_base_node(world: &mut TariWorld, wallet: String) { + let wallet_ps = world.wallets.get_mut(&wallet).unwrap(); + wallet_ps.kill(); + + tokio::time::sleep(Duration::from_secs(5)).await; + + let mut cli = get_default_cli(); + + cli.command2 = Some(CliCommands::ClearCustomBaseNode); + + let base_node = world.wallet_connected_to_base_node.get(&wallet).unwrap(); + let seed_nodes = world.base_nodes.get(base_node).unwrap().seed_nodes.clone(); + + spawn_wallet(world, wallet, Some(base_node.clone()), seed_nodes, None, Some(cli)).await; +} + +#[then(expr = "the password of wallet {word} is not {word}")] +async fn password_is(world: &mut TariWorld, wallet: String, _password: String) { + let wallet_ps = world.wallets.get_mut(&wallet).unwrap(); + let _config_path = wallet_ps.temp_dir_path.clone(); +} + +#[then(expr = "I get balance of wallet {word} is at least {int} uT via command line")] +async fn get_balance_of_wallet(world: &mut TariWorld, wallet: String, _amount: u64) { + let wallet_ps = world.wallets.get_mut(&wallet).unwrap(); + wallet_ps.kill(); + + tokio::time::sleep(Duration::from_secs(5)).await; + + let mut cli = get_default_cli(); + + cli.command2 = Some(CliCommands::GetBalance); + + let base_node = world.wallet_connected_to_base_node.get(&wallet).unwrap(); + let seed_nodes = world.base_nodes.get(base_node).unwrap().seed_nodes.clone(); + + spawn_wallet(world, wallet, Some(base_node.clone()), seed_nodes, None, Some(cli)).await +} + +#[when(expr = "I send {int} uT from {word} to {word} via command line")] +async fn send_from_cli(world: &mut TariWorld, amount: u64, wallet_a: String, wallet_b: String) { + let wallet_ps = world.wallets.get_mut(&wallet_a).unwrap(); + wallet_ps.kill(); + + tokio::time::sleep(Duration::from_secs(5)).await; + + let mut wallet_b_client = create_wallet_client(world, wallet_b.clone()).await.unwrap(); + let wallet_b_address = wallet_b_client + .get_address(Empty {}) + .await + .unwrap() + .into_inner() + .address + .to_hex(); + let wallet_b_address = TariAddress::from_hex(wallet_b_address.as_str()).unwrap(); + + let mut cli = get_default_cli(); + + let args = SendTariArgs { + amount: MicroTari(amount), + message: format!("Send amount {} from {} to {}", amount, wallet_a, wallet_b), + destination: wallet_b_address, + }; + cli.command2 = Some(CliCommands::SendTari(args)); + + let base_node = world.wallet_connected_to_base_node.get(&wallet_a).unwrap(); + let seed_nodes = world.base_nodes.get(base_node).unwrap().seed_nodes.clone(); + + spawn_wallet(world, wallet_a, Some(base_node.clone()), seed_nodes, None, Some(cli)).await; +} + +#[when(expr = "I create a burn transaction of {int} uT from {word} via command line")] +async fn create_burn_tx_via_cli(world: &mut TariWorld, amount: u64, wallet: String) { + let wallet_ps = world.wallets.get_mut(&wallet).unwrap(); + wallet_ps.kill(); + + tokio::time::sleep(Duration::from_secs(5)).await; + + let mut cli = get_default_cli(); + + let args = BurnTariArgs { + amount: MicroTari(amount), + message: format!("Burn, burn amount {} !!!", amount,), + }; + cli.command2 = Some(CliCommands::BurnTari(args)); + + let base_node = world.wallet_connected_to_base_node.get(&wallet).unwrap(); + let seed_nodes = world.base_nodes.get(base_node).unwrap().seed_nodes.clone(); + + spawn_wallet(world, wallet, Some(base_node.clone()), seed_nodes, None, Some(cli)).await; +} + +#[then(expr = "I send one-sided {int} uT from {word} to {word} via command line")] +async fn send_one_sided_tx_via_cli(world: &mut TariWorld, amount: u64, wallet_a: String, wallet_b: String) { + let wallet_ps = world.wallets.get_mut(&wallet_a).unwrap(); + wallet_ps.kill(); + + tokio::time::sleep(Duration::from_secs(5)).await; + + let mut wallet_b_client = create_wallet_client(world, wallet_b.clone()).await.unwrap(); + let wallet_b_address = wallet_b_client + .get_address(Empty {}) + .await + .unwrap() + .into_inner() + .address + .to_hex(); + let wallet_b_address = TariAddress::from_hex(wallet_b_address.as_str()).unwrap(); + + let mut cli = get_default_cli(); + + let args = SendTariArgs { + amount: MicroTari(amount), + message: format!("Send one sided amount {} from {} to {}", amount, wallet_a, wallet_b), + destination: wallet_b_address, + }; + cli.command2 = Some(CliCommands::SendOneSided(args)); + + let base_node = world.wallet_connected_to_base_node.get(&wallet_a).unwrap(); + let seed_nodes = world.base_nodes.get(base_node).unwrap().seed_nodes.clone(); + + spawn_wallet(world, wallet_a, Some(base_node.clone()), seed_nodes, None, Some(cli)).await; +} + +#[when( + expr = "I make it rain from wallet {word} {int} tx per sec {int} sec {int} uT {int} increment to {word} via \ + command line" +)] +async fn make_it_rain( + world: &mut TariWorld, + wallet_a: String, + txs_per_second: u64, + duration: u64, + start_amount: u64, + increment_amount: u64, + wallet_b: String, +) { + let wallet_ps = world.wallets.get_mut(&wallet_a).unwrap(); + wallet_ps.kill(); + + tokio::time::sleep(Duration::from_secs(5)).await; + + let mut wallet_b_client = create_wallet_client(world, wallet_b.clone()).await.unwrap(); + let wallet_b_address = wallet_b_client + .get_address(Empty {}) + .await + .unwrap() + .into_inner() + .address + .to_hex(); + let wallet_b_address = TariAddress::from_hex(wallet_b_address.as_str()).unwrap(); + + let mut cli = get_default_cli(); + + let args = MakeItRainArgs { + start_amount: MicroTari(start_amount), + transactions_per_second: txs_per_second as u32, + duration: Duration::from_secs(duration), + message: format!( + "Make it raing amount {} from {} to {}", + start_amount, wallet_a, wallet_b + ), + increase_amount: MicroTari(increment_amount), + destination: wallet_b_address, + start_time: None, + one_sided: false, + stealth: false, + burn_tari: false, + }; + + cli.command2 = Some(CliCommands::MakeItRain(args)); + + let base_node = world.wallet_connected_to_base_node.get(&wallet_a).unwrap(); + let seed_nodes = world.base_nodes.get(base_node).unwrap().seed_nodes.clone(); + + spawn_wallet(world, wallet_a, Some(base_node.clone()), seed_nodes, None, Some(cli)).await; +} + +#[when(expr = "I do coin split on wallet {word} to {int} uT {int} coins via command line")] +async fn coin_split_via_cli(world: &mut TariWorld, wallet: String, amount: u64, splits: u64) { + let wallet_ps = world.wallets.get_mut(&wallet).unwrap(); + wallet_ps.kill(); + + tokio::time::sleep(Duration::from_secs(5)).await; + + let mut cli = get_default_cli(); + + let args = CoinSplitArgs { + amount_per_split: MicroTari(amount), + num_splits: splits as usize, + fee_per_gram: MicroTari(20), + message: format!("coin split amount {} with splits {}", amount, splits), + }; + + cli.command2 = Some(CliCommands::CoinSplit(args)); + + let base_node = world.wallet_connected_to_base_node.get(&wallet).unwrap(); + let seed_nodes = world.base_nodes.get(base_node).unwrap().seed_nodes.clone(); + + spawn_wallet(world, wallet, Some(base_node.clone()), seed_nodes, None, Some(cli)).await; +} + +#[then(expr = "I get count of utxos of wallet {word} and it's at least {int} via command line")] +async fn count_utxos_of_wallet(world: &mut TariWorld, wallet: String, _amount: u64) { + let wallet_ps = world.wallets.get_mut(&wallet).unwrap(); + wallet_ps.kill(); + + tokio::time::sleep(Duration::from_secs(5)).await; + + let mut cli = get_default_cli(); + + cli.command2 = Some(CliCommands::CountUtxos); + + let base_node = world.wallet_connected_to_base_node.get(&wallet).unwrap(); + let seed_nodes = world.base_nodes.get(base_node).unwrap().seed_nodes.clone(); + + spawn_wallet(world, wallet, Some(base_node.clone()), seed_nodes, None, Some(cli)).await; +} + +#[when(expr = "I export the utxos of wallet {word} via command line")] +async fn export_utxos(world: &mut TariWorld, wallet: String) { + let wallet_a_ps = world.wallets.get_mut(&wallet).unwrap(); + wallet_a_ps.kill(); + + tokio::time::sleep(Duration::from_secs(5)).await; + + let temp_dir_path = wallet_a_ps.temp_dir_path.clone(); + + let mut cli = get_default_cli(); + + let mut path_buf = PathBuf::new(); + path_buf.push(temp_dir_path); + path_buf.push("exported_utxos.csv"); + + let args = ExportUtxosArgs { + output_file: Some(path_buf.clone()), + }; + cli.command2 = Some(CliCommands::ExportUtxos(args)); + + let base_node = world.wallet_connected_to_base_node.get(&wallet).unwrap(); + + let seed_nodes = world.base_nodes.get(base_node).unwrap().seed_nodes.clone(); + spawn_wallet(world, wallet, Some(base_node.clone()), seed_nodes, None, Some(cli)).await; +} + +#[when(expr = "I discover peer {word} on wallet {word} via command line")] +async fn discover_peer(world: &mut TariWorld, node: String, wallet: String) { + let wallet_ps = world.wallets.get_mut(&wallet).unwrap(); + wallet_ps.kill(); + + tokio::time::sleep(Duration::from_secs(5)).await; + + let mut cli = get_default_cli(); + + let mut node_client = world.get_node_client(&node).await.unwrap(); + let node_identity = node_client.identify(Empty {}).await.unwrap().into_inner(); + + let args = DiscoverPeerArgs { + dest_public_key: UniPublicKey::from_str(node_identity.public_key.to_hex().as_str()).unwrap(), + }; + + cli.command2 = Some(CliCommands::DiscoverPeer(args)); + + let base_node = world.wallet_connected_to_base_node.get(&wallet).unwrap(); + let seed_nodes = world.base_nodes.get(&node).unwrap().seed_nodes.clone(); + spawn_wallet(world, wallet, Some(base_node.clone()), seed_nodes, None, Some(cli)).await; +} + +#[then(expr = "I run whois {word} on wallet {word} via command line")] +async fn whois(world: &mut TariWorld, node: String, wallet: String) { + let wallet_ps = world.wallets.get_mut(&wallet).unwrap(); + wallet_ps.kill(); + + tokio::time::sleep(Duration::from_secs(5)).await; + + let mut cli = get_default_cli(); + + let mut node_client = world.get_node_client(&node).await.unwrap(); + let node_identity = node_client.identify(Empty {}).await.unwrap().into_inner(); + + let args = WhoisArgs { + public_key: UniPublicKey::from_str(node_identity.public_key.to_hex().as_str()).unwrap(), + }; + + cli.command2 = Some(CliCommands::Whois(args)); + + let base_node = world.wallet_connected_to_base_node.get(&wallet).unwrap(); + let seed_nodes = world.base_nodes.get(&node).unwrap().seed_nodes.clone(); + spawn_wallet(world, wallet, Some(base_node.clone()), seed_nodes, None, Some(cli)).await; +} + +#[when(expr = "I print the cucumber world")] +async fn print_world(world: &mut TariWorld) { + eprintln!(); + eprintln!("======================================"); + eprintln!("============= TEST NODES ============="); + eprintln!("======================================"); + eprintln!(); + + // base nodes + for (name, node) in world.base_nodes.iter() { + eprintln!( + "Base node \"{}\": grpc port \"{}\", temp dir path \"{:?}\"", + name, node.grpc_port, node.temp_dir_path + ); + } + + // wallets + for (name, node) in world.wallets.iter() { + eprintln!( + "Wallet \"{}\": grpc port \"{}\", temp dir path \"{:?}\"", + name, node.grpc_port, node.temp_dir_path + ); + } + + eprintln!(); + eprintln!("======================================"); + eprintln!(); +} + +// FFI Steps +#[when(expr = "I have a ffi wallet {word} connected to base node {word}")] +#[then(expr = "I have a ffi wallet {word} connected to base node {word}")] +#[given(expr = "I have a ffi wallet {word} connected to base node {word}")] +async fn ffi_start_wallet_connected_to_base_node(world: &mut TariWorld, wallet: String, base_node: String) { + spawn_wallet_ffi(world, wallet.clone(), null()); + let base_node = world.get_node(&base_node).unwrap(); + world.get_ffi_wallet(&wallet).unwrap().add_base_node( + base_node.identity.public_key().to_hex(), + base_node.identity.public_address().to_string(), + ); +} + +#[given(expr = "I have a ffi wallet {word} connected to seed node {word}")] +async fn ffi_start_wallet_connected_to_seed_node(world: &mut TariWorld, wallet: String, seed_node: String) { + spawn_wallet_ffi(world, wallet.clone(), null()); + assert!(world.all_seed_nodes().contains(&seed_node), "Seed node not found."); + let seed_node = world.get_node(&seed_node).unwrap(); + world.get_ffi_wallet(&wallet).unwrap().add_base_node( + seed_node.identity.public_key().to_hex(), + seed_node.identity.public_address().to_string(), + ); +} + +#[given(expr = "I set base node {word} for ffi wallet {word}")] +async fn ffi_set_base_node(world: &mut TariWorld, base_node: String, wallet: String) { + let base_node = world.get_node(&base_node).unwrap(); + world.get_ffi_wallet(&wallet).unwrap().add_base_node( + base_node.identity.public_key().to_hex(), + base_node.identity.public_address().to_string(), + ); +} + +#[then(expr = "I want to get public key of ffi wallet {word}")] +async fn ffi_get_public_key(world: &mut TariWorld, wallet: String) { + let wallet = world.get_ffi_wallet(&wallet).unwrap(); + let public_key = wallet.identify(); + println!("public_key {}", public_key); +} + +#[then(expr = "I want to get emoji id of ffi wallet {word}")] +async fn ffi_get_emoji_id(world: &mut TariWorld, wallet: String) { + let wallet = world.get_ffi_wallet(&wallet).unwrap(); + let emoji_id = wallet.get_emoji_id(); + assert_eq!( + emoji_id.len(), + 132, + "Emoji id {} is expected to be of length 132", + emoji_id + ); +} + +#[then(expr = "I stop ffi wallet {word}")] +async fn ffi_stop_wallet(world: &mut TariWorld, wallet: String) { + let address = world.get_wallet_address(&wallet).await.unwrap(); + let ffi_wallet = world.ffi_wallets.get_mut(&wallet).unwrap(); + println!("Adding wallet {}", wallet); + world.wallet_addresses.insert(wallet, address); + ffi_wallet.destroy(); +} + +#[then(expr = "I retrieve the mnemonic word list for {word}")] +async fn ffi_retrieve_mnemonic_words(_world: &mut TariWorld, language: String) { + println!("Mnemonic words for language {}:", language); + let words = get_mnemonic_word_list_for_language(language); + for i in 0..words.get_length() { + print!("{} ", words.get_at(i as u32).as_string()); + } + println!(); + assert_eq!(words.get_length(), 2048); +} + +#[then(expr = "I wait for ffi wallet {word} to connect to {word}")] +async fn ffi_wait_wallet_to_connect(world: &mut TariWorld, wallet: String, node: String) { + let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); + let node = world.get_node(&node).unwrap().identity.public_key(); + for _ in 0..10 { + let public_keys = ffi_wallet.connected_public_keys(); + for i in 0..public_keys.get_length() { + let public_key = public_keys.get_public_key_at(i as u32); + if public_key.get_bytes().get_as_hex() == node.to_hex() { + return; + } + } + tokio::time::sleep(Duration::from_secs(3)).await; + } + panic!("Wallet not connected"); +} + +#[then(expr = "I wait for ffi wallet {word} to have at least {int} uT")] +async fn ffi_wait_for_balance(world: &mut TariWorld, wallet: String, balance: u64) { + let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); + let mut ffi_balance = ffi_wallet.get_balance(); + let mut cnt = 0; + while ffi_balance.get_available() < balance && cnt < 10 { + tokio::time::sleep(Duration::from_secs(3)).await; + ffi_balance = ffi_wallet.get_balance(); + cnt += 1; + } + assert!( + ffi_balance.get_available() >= balance, + "Wallet doesn't have enough available funds {}", + ffi_balance.get_available() + ); +} + +#[when(expr = "I add contact with alias {word} and address of {word} to ffi wallet {word}")] +async fn ffi_add_contact(world: &mut TariWorld, alias: String, pubkey: String, wallet: String) { + let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); + + let address = world.get_wallet_address(&pubkey).await.unwrap(); + let contact = create_contact(alias, address); + + assert!(ffi_wallet.upsert_contact(contact)); +} + +async fn check_contact(world: &mut TariWorld, alias: String, pubkey: Option, wallet: String) -> bool { + let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); + let address: Option = match pubkey { + Some(pubkey) => Some(world.get_wallet_address(&pubkey).await.unwrap()), + None => None, + }; + let contacts = ffi_wallet.get_contacts(); + let mut found = false; + for i in 0..contacts.get_length() { + let contact = contacts.get_at(i); + if (address.is_none() || &contact.get_address().address().get_as_hex() == address.as_ref().unwrap()) && + contact.get_alias() == alias + { + found = true; + break; + } + } + found +} + +#[then(expr = "I have contact with alias {word} and address of {word} in ffi wallet {word}")] +async fn ffi_check_contact(world: &mut TariWorld, alias: String, pubkey: String, wallet: String) { + assert!(check_contact(world, alias, Some(pubkey), wallet).await); +} + +#[when(expr = "I remove contact with alias {word} from ffi wallet {word}")] +async fn ffi_remove_contact(world: &mut TariWorld, alias: String, wallet: String) { + let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); + let contacts = ffi_wallet.get_contacts(); + let mut contact_to_remove = None; + for i in 0..contacts.get_length() { + let contact = contacts.get_at(i); + if contact.get_alias() == alias { + contact_to_remove = Some(contact); + break; + } + } + assert!(contact_to_remove.is_some()); + assert!(ffi_wallet.remove_contact(contact_to_remove.unwrap())); +} + +#[then(expr = "I don't have contact with alias {word} in ffi wallet {word}")] +async fn ffi_check_no_contact(world: &mut TariWorld, alias: String, wallet: String) { + assert!(!check_contact(world, alias, None, wallet).await); +} + +#[when(expr = "I send {int} uT from ffi wallet {word} to wallet {word} at fee {int}")] +#[then(expr = "I send {int} uT from ffi wallet {word} to wallet {word} at fee {int}")] +async fn ffi_send_transaction(world: &mut TariWorld, amount: u64, wallet: String, dest: String, fee: u64) { + let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); + let dest_pub_key = world.get_wallet_address(&dest).await.unwrap(); + let message = format!("Send from ffi {} to ${} at fee ${}", wallet, dest, fee); + let tx_id = ffi_wallet.send_transaction(dest_pub_key, amount, fee, message, false); + assert_ne!(tx_id, 0, "Send transaction was not successful"); +} + +#[when(expr = "I send {int} uT from ffi wallet {word} to wallet {word} at fee {int} via one-sided transactions")] +#[then(expr = "I send {int} uT from ffi wallet {word} to wallet {word} at fee {int} via one-sided transactions")] +async fn ffi_send_one_sided_transaction(world: &mut TariWorld, amount: u64, wallet: String, dest: String, fee: u64) { + let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); + let dest_pub_key = world.get_wallet_address(&dest).await.unwrap(); + let message = format!("Send from ffi {} to ${} at fee ${}", wallet, dest, fee); + let tx_id = ffi_wallet.send_transaction(dest_pub_key, amount, fee, message, true); + assert_ne!(tx_id, 0, "Send transaction was not successful"); +} + +#[when(expr = "I have {int} received and {int} send transaction in ffi wallet {word}")] +#[then(expr = "I have {int} received and {int} send transaction in ffi wallet {word}")] +async fn ffi_check_number_of_transactions(world: &mut TariWorld, received: u32, send: u32, wallet: String) { + let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); + let inbound_txs = ffi_wallet.get_pending_inbound_transactions(); + let mut inbound_cnt = inbound_txs.get_length(); + let outbound_txs = ffi_wallet.get_pending_outbound_transactions(); + let mut outbound_cnt = outbound_txs.get_length(); + let completed_txs = ffi_wallet.get_completed_transactions(); + for i in 0..completed_txs.get_length() { + let completed_tx = completed_txs.get_at(i); + if completed_tx.is_outbound() { + outbound_cnt += 1; + } else { + inbound_cnt += 1; + } + } + assert_eq!(outbound_cnt, send); + assert_eq!(inbound_cnt, received); +} + +#[then(expr = "I wait for ffi wallet {word} to have {int} pending outbound transaction(s)")] +async fn ffi_check_number_of_outbound_transactions(world: &mut TariWorld, wallet: String, cnt: u32) { + let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); + let mut found_cnt = 0; + let num_retries = 120; + for _ in 0..num_retries { + let pending_outbound_transactions = ffi_wallet.get_pending_outbound_transactions(); + found_cnt = pending_outbound_transactions.get_length(); + if found_cnt >= cnt { + break; + } + tokio::time::sleep(Duration::from_secs(1)).await; + } + assert!(found_cnt >= cnt, "The number of pending outbound transaction is lower."); +} + +#[then(expr = "I wait for ffi wallet {word} to have at least {int} contacts to be {word}")] +async fn ffi_check_contacts(world: &mut TariWorld, wallet: String, cnt: u64, status: String) { + assert!( + vec!["Online", "Offline", "NeverSeen"].contains(&status.as_str()), + "Unknown status : {}", + status + ); + let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); + println!( + "Waiting for {} to have at least {} contacts with status '{}'", + wallet, cnt, status + ); + let mut found_cnt = 0; + + let liveness_data = ffi_wallet.get_liveness_data(); + for _ in 0..120 { + found_cnt = 0; + for (_alias, data) in liveness_data.lock().unwrap().iter() { + if data.get_online_status() == status { + found_cnt += 1; + } + } + if found_cnt >= cnt { + break; + } + tokio::time::sleep(Duration::from_secs(1)).await; + } + assert!( + found_cnt >= cnt, + "{} doesn't have at least {} contacts with status {}!", + wallet, + cnt, + status + ); +} + +#[then(expr = "I want to view the transaction kernels for completed transactions in ffi wallet {word}")] +async fn ffi_view_transaction_kernels_for_completed(world: &mut TariWorld, wallet: String) { + let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); + let completed_transactions = ffi_wallet.get_completed_transactions(); + for i in 0..completed_transactions.get_length() { + let completed_transaction = completed_transactions.get_at(i); + let kernel = completed_transaction.get_transaction_kernel(); + println!("Transaction kernel info :"); + assert!(!kernel.get_excess_hex().is_empty()); + println!("Excess {}", kernel.get_excess_hex()); + assert!(!kernel.get_excess_public_nonce_hex().is_empty()); + println!("Nonce {}", kernel.get_excess_public_nonce_hex()); + assert!(!kernel.get_excess_signature_hex().is_empty()); + println!("Signature {}", kernel.get_excess_signature_hex()); + } +} + +#[then(expr = "I cancel all outbound transactions on ffi wallet {word} and it will cancel {int} transaction")] +async fn ffi_cancel_outbound_transactions(world: &mut TariWorld, wallet: String, cnt: u64) { + let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); + let pending_outbound_transactions = ffi_wallet.get_pending_outbound_transactions(); + let mut cancelled = 0; + for i in 0..pending_outbound_transactions.get_length() { + let pending_outbound_transaction = pending_outbound_transactions.get_at(i); + if ffi_wallet.cancel_pending_transaction(pending_outbound_transaction.get_transaction_id()) { + cancelled += 1; + } + } + assert_eq!(cancelled, cnt); +} + +#[then(expr = "I wait for ffi wallet {word} to receive {int} transaction")] +async fn ffi_wait_for_transaction_received(world: &mut TariWorld, wallet: String, cnt: u64) { + let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); + let num_retries = 120; + let mut found_cnt = 0; + for _ in 0..num_retries { + found_cnt = ffi_wallet.get_counters().get_transaction_received(); + if found_cnt >= cnt { + break; + } + tokio::time::sleep(Duration::from_secs(1)).await; + } + assert!(found_cnt >= cnt, "Expected {}, but got only {}", cnt, found_cnt); +} + +#[then(expr = "I wait for ffi wallet {word} to receive {int} finalization")] +async fn ffi_wait_for_transaction_finalized(world: &mut TariWorld, wallet: String, cnt: u64) { + let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); + let num_retries = 120; + let mut found_cnt = 0; + for _ in 0..num_retries { + found_cnt = ffi_wallet.get_counters().get_transaction_finalized(); + if found_cnt >= cnt { + break; + } + tokio::time::sleep(Duration::from_secs(1)).await; + } + assert!(found_cnt >= cnt, "Expected {}, but got only {}", cnt, found_cnt); +} + +#[then(expr = "I wait for ffi wallet {word} to receive {int} broadcast")] +async fn ffi_wait_for_transaction_broadcast(world: &mut TariWorld, wallet: String, cnt: u64) { + let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); + let num_retries = 120; + let mut found_cnt = 0; + for _ in 0..num_retries { + found_cnt = ffi_wallet.get_counters().get_transaction_broadcast(); + if found_cnt >= cnt { + break; + } + tokio::time::sleep(Duration::from_secs(1)).await; + } + assert!(found_cnt >= cnt, "Expected {}, but got only {}", cnt, found_cnt); +} + +#[then(expr = "I start TXO validation on ffi wallet {word}")] +async fn ffi_start_txo_validation(world: &mut TariWorld, wallet: String) { + let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); + ffi_wallet.start_txo_validation(); + let num_retries = 120; + let mut validation_complete = false; + for _ in 0..num_retries { + validation_complete = ffi_wallet.get_counters().get_txo_validation_complete(); + if validation_complete { + break; + } + tokio::time::sleep(Duration::from_secs(1)).await; + } + assert!(validation_complete); +} + +#[then(expr = "I start TX validation on ffi wallet {word}")] +async fn ffi_start_tx_validation(world: &mut TariWorld, wallet: String) { + let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); + ffi_wallet.start_transaction_validation(); + let num_retries = 120; + let mut validation_complete = false; + for _ in 0..num_retries { + validation_complete = ffi_wallet.get_counters().get_tx_validation_complete(); + if validation_complete { + break; + } + tokio::time::sleep(Duration::from_secs(1)).await; + } + assert!(validation_complete); +} + +#[then(expr = "ffi wallet {word} detects {word} {int} ffi transactions to be {word}")] +async fn ffi_detects_transaction( + world: &mut TariWorld, + wallet: String, + comparison: String, + count: u64, + status: String, +) { + let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); + assert!(vec![ + "TRANSACTION_STATUS_BROADCAST", + "TRANSACTION_STATUS_FAUX_UNCONFIRMED", + "TRANSACTION_STATUS_FAUX_CONFIRMED" + ] + .contains(&status.as_str())); + println!( + "Waiting for {} to have detected {} {} {} transaction(s)", + wallet, comparison, count, status + ); + let mut found_count = 0; + for _ in 0..120 { + found_count = match status.as_str() { + "TRANSACTION_STATUS_BROADCAST" => ffi_wallet.get_counters().get_transaction_broadcast(), + "TRANSACTION_STATUS_FAUX_UNCONFIRMED" => ffi_wallet.get_counters().get_transaction_faux_unconfirmed(), + "TRANSACTION_STATUS_FAUX_CONFIRMED" => ffi_wallet.get_counters().get_transaction_faux_confirmed(), + _ => unreachable!(), + }; + if found_count >= count { + break; + } + tokio::time::sleep(Duration::from_secs(1)).await; + } + match comparison.as_str() { + "AT_LEAST" => assert!( + found_count >= count, + "Counter not adequate! Counter is {}.", + found_count + ), + "EXACTLY" => assert!( + found_count == count, + "Counter not adequate! Counter is {}.", + found_count + ), + _ => panic!("Unknown comparison method {}", comparison), + }; +} + +#[then(expr = "I wait for ffi wallet {word} to receive {int} mined")] +async fn ffi_wait_for_received_mined(world: &mut TariWorld, wallet: String, count: u64) { + let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); + println!("Waiting for {} to receive {} transaction(s) mined", wallet, count); + + let mut found_cnt = 0; + for _ in 0..120 { + found_cnt = ffi_wallet.get_counters().get_transaction_mined(); + if found_cnt >= count { + break; + } + tokio::time::sleep(Duration::from_secs(1)).await; + } + assert!(found_cnt >= count); +} + +#[then(expr = "I recover wallet {word} into ffi wallet {word} from seed words on node {word}")] +async fn ffi_recover_wallet(world: &mut TariWorld, wallet_name: String, ffi_wallet_name: String, base_node: String) { + let wallet = world.get_wallet(&wallet_name).unwrap(); + let seed_words_path = wallet.temp_dir_path.clone().join("seed_words.txt"); + let seed_words_file = std::fs::File::open(seed_words_path).unwrap(); + let reader = std::io::BufReader::new(seed_words_file); + let line = reader.lines().next().unwrap().unwrap(); + let words = line.split_whitespace().collect(); + let seed_words = create_seed_words(words); + + spawn_wallet_ffi(world, ffi_wallet_name.clone(), seed_words.get_ptr()); + + let base_node = world.get_node(&base_node).unwrap(); + world.get_ffi_wallet(&ffi_wallet_name).unwrap().add_base_node( + base_node.identity.public_key().to_hex(), + base_node.identity.public_address().to_string(), + ); +} + +#[then(expr = "I restart ffi wallet {word} connected to base node {word}")] +async fn ffi_restart_wallet(world: &mut TariWorld, wallet: String, base_node: String) { + let ffi_wallet = world.get_mut_ffi_wallet(&wallet).unwrap(); + ffi_wallet.restart(); + let base_node = world.get_node(&base_node).unwrap(); + let ffi_wallet = world.get_ffi_wallet(&wallet).unwrap(); + ffi_wallet.add_base_node( + base_node.identity.public_key().to_hex(), + base_node.identity.public_address().to_string(), + ); +} + +#[then(expr = "The fee per gram stats for {word} are {int}, {int}, {int}")] +#[when(expr = "The fee per gram stats for {word} are {int}, {int}, {int}")] +async fn ffi_fee_per_gram_stats(world: &mut TariWorld, wallet: String, min: u64, avg: u64, max: u64) { + let ffi_wallet = world.get_mut_ffi_wallet(&wallet).unwrap(); + let fee_per_gram_stats = ffi_wallet.get_fee_per_gram_stats(5); + for i in 0..fee_per_gram_stats.get_length() { + let fee_per_gram_stat = fee_per_gram_stats.get_at(i); + println!("order {}", fee_per_gram_stat.get_order()); + println!("min {}", fee_per_gram_stat.get_min_fee_per_gram()); + println!("avg {}", fee_per_gram_stat.get_avg_fee_per_gram()); + println!("max {}", fee_per_gram_stat.get_max_fee_per_gram()); + assert_eq!(fee_per_gram_stat.get_min_fee_per_gram(), min); + assert_eq!(fee_per_gram_stat.get_avg_fee_per_gram(), avg); + assert_eq!(fee_per_gram_stat.get_max_fee_per_gram(), max); + } +} + +// Merge mining proxy steps + +#[when(expr = "I have a merge mining proxy {word} connected to {word} and {word} with origin submission {word}")] +async fn merge_mining_proxy_with_submission( + world: &mut TariWorld, + mining_proxy_name: String, + base_node_name: String, + wallet_name: String, + enabled: String, +) { + let enabled = match enabled.as_str() { + "enabled" => true, + "disabled" => false, + _ => panic!("This should be a boolean"), + }; + register_merge_mining_proxy_process(world, mining_proxy_name, base_node_name, wallet_name, enabled).await; +} + +#[when(expr = "I have a merge mining proxy {word} connected to {word} and {word} with default config")] +async fn merge_mining_proxy_with_default_config( + world: &mut TariWorld, + mining_proxy_name: String, + base_node_name: String, + wallet_name: String, +) { + register_merge_mining_proxy_process(world, mining_proxy_name, base_node_name, wallet_name, true).await; +} + +#[when(expr = "I ask for a block height from proxy {word}")] +async fn merge_mining_ask_for_block_height(world: &mut TariWorld, mining_proxy_name: String) { + let merge_miner = world.get_merge_miner(&mining_proxy_name).unwrap(); + world.last_merge_miner_response = merge_miner.get_height().await; +} + +#[then(expr = "Proxy response height is valid")] +async fn merge_mining_response_height(world: &mut TariWorld) { + let height = world.last_merge_miner_response.get("height"); + assert!( + height.is_some(), + "Response is invalid {}", + world.last_merge_miner_response + ); + let height = height.unwrap(); + assert!(height.as_u64().is_some(), "Height is invalid {}", height); +} + +#[when(expr = "I ask for a block template from proxy {word}")] +async fn merge_mining_ask_for_block_template(world: &mut TariWorld, mining_proxy_name: String) { + let merge_miner = world.get_mut_merge_miner(&mining_proxy_name).unwrap(); + world.last_merge_miner_response = merge_miner.get_block_template().await; +} + +#[then(expr = "Proxy response block template is valid")] +async fn merge_mining_response_block_template_is_valid(world: &mut TariWorld) { + let result = world.last_merge_miner_response.get("result"); + assert!( + result.is_some(), + "Response is invalid {}", + world.last_merge_miner_response + ); + let result = result.unwrap(); + assert!(result.get("_aux").is_some(), "Result has no `_aux` {}", result); + assert_eq!( + result.get("status").unwrap().as_str().unwrap(), + "OK", + "Result has no `status` {}", + result + ); +} + +#[when(expr = "I submit a block through proxy {word}")] +async fn merge_mining_submit_block(world: &mut TariWorld, mining_proxy_name: String) { + let block_template_blob = world + .last_merge_miner_response + .get("result") + .unwrap() + .get("blocktemplate_blob"); + assert!( + block_template_blob.is_some(), + "The last response doesn't have `blocktemplate_blob` {}", + world.last_merge_miner_response + ); + let block_template_blob = block_template_blob.unwrap().clone(); + let merge_miner = world.get_mut_merge_miner(&mining_proxy_name).unwrap(); + println!("block_template {:?}", block_template_blob); + world.last_merge_miner_response = merge_miner.submit_block(&block_template_blob).await; + println!("last_merge_miner_response {:?}", world.last_merge_miner_response); + println!("last_merge_miner_response {:?}", world.last_merge_miner_response); + println!("last_merge_miner_response {:?}", world.last_merge_miner_response); +} + +#[then(expr = "Proxy response block submission is valid {word} submitting to origin")] +async fn merge_mining_submission_is_valid(world: &mut TariWorld, how: String) { + let result = world.last_merge_miner_response.get("result"); + assert!( + result.is_some(), + "Response is invalid {}", + world.last_merge_miner_response + ); + let result = result.unwrap(); + if how == *"with" { + assert!(result.get("_aux").is_some(), "Result has no `_aux` {}", result); + let status = result.get("status"); + assert!(status.is_some(), "Result has no status {}", result); + } else { + assert!( + world.last_merge_miner_response.get("status").is_some(), + "Response has no `status` {}", + world.last_merge_miner_response + ); + } +} + +#[when(expr = "I merge mine {int} blocks via {word}")] +async fn merge_mining_mine(world: &mut TariWorld, count: u64, mining_proxy_name: String) { + let merge_miner = world.get_mut_merge_miner(&mining_proxy_name).unwrap(); + for _ in 0..count { + merge_miner.mine().await; + } +} + +#[when(expr = "I ask for the last block header from proxy {word}")] +async fn merge_mining_ask_for_last_block_header(world: &mut TariWorld, mining_proxy_name: String) { + let merge_miner = world.get_mut_merge_miner(&mining_proxy_name).unwrap(); + world.last_merge_miner_response = merge_miner.get_last_block_header().await; +} + +#[then(expr = "Proxy response for block header by hash is valid")] +async fn merge_mining_bloch_header_by_hash_is_valid(world: &mut TariWorld) { + let result = world.last_merge_miner_response.get("result"); + assert!( + result.is_some(), + "Response is invalid {}", + world.last_merge_miner_response + ); + let result = result.unwrap(); + let status = result.get("status"); + assert!(status.is_some(), "Result has no status {}", result); + assert_eq!( + result.get("status").unwrap().as_str().unwrap(), + "OK", + "Result has no `status` {}", + result + ); +} + +#[then(expr = "Proxy response for last block header is valid")] +async fn merge_mining_response_last_block_header_is_valid(world: &mut TariWorld) { + let result = world.last_merge_miner_response.get("result"); + assert!( + result.is_some(), + "Response is invalid {}", + world.last_merge_miner_response + ); + let result = result.unwrap(); + assert!(result.get("_aux").is_some(), "Result has no `_aux` {}", result); + let status = result.get("status"); + assert!(status.is_some(), "Result has no status {}", result); + assert_eq!( + result.get("status").unwrap().as_str().unwrap(), + "OK", + "Result has no `status` {}", + result + ); + let block_header = result.get("block_header"); + assert!(block_header.is_some(), "Result has no `block_header` {}", result); + let block_header = block_header.unwrap(); + assert!( + block_header.get("hash").is_some(), + "Block_header has no `hash` {}", + block_header + ); +} + +#[when(expr = "I ask for a block header by hash using last block header from proxy {word}")] +async fn merge_mining_ask_for_block_header_by_hash(world: &mut TariWorld, mining_proxy_name: String) { + let hash = world + .last_merge_miner_response + .get("result") + .unwrap() + .get("block_header") + .unwrap() + .get("hash") + .unwrap() + .clone(); + let merge_miner = world.get_mut_merge_miner(&mining_proxy_name).unwrap(); + world.last_merge_miner_response = merge_miner.get_block_header_by_hash(hash).await; +} + +fn flush_stdout(buffer: &Arc>>) { + // After each test we flush the stdout to the logs. + info!( + target: LOG_TARGET_STDOUT, + "{}", + str::from_utf8(&buffer.lock().unwrap()).unwrap() + ); + buffer.lock().unwrap().clear(); +} + +fn main() { + initialize_logging( + &PathBuf::from("log4rs/cucumber.yml"), + include_str!("../log4rs/cucumber.yml"), + ) + .expect("logging not configured"); + let stdout_buffer = Arc::new(Mutex::new(Vec::::new())); + #[cfg(test)] + std::io::set_output_capture(Some(stdout_buffer.clone())); + // Never move this line below the runtime creation!!! It will cause that any new thread created via task::spawn will + // not be affected by the output capture. + let stdout_buffer_clone = stdout_buffer.clone(); + let runtime = Runtime::new().unwrap(); + runtime.block_on(async { + let world = TariWorld::cucumber() + .repeat_failed() + // following config needed to use eprint statements in the tests + .max_concurrent_scenarios(5) + //.with_writer( + // writer::Basic::raw(io::stdout(), writer::Coloring::Never, 0) + // .summarized() + // .assert_normalized(), + //) + .after(move |_feature, _rule, scenario, ev, maybe_world| { + let stdout_buffer = stdout_buffer_clone.clone(); + Box::pin(async move { + flush_stdout(&stdout_buffer); + match ev { + ScenarioFinished::StepFailed(_capture_locations, _location, _error) => { + error!(target: LOG_TARGET, "Scenario failed"); + }, + ScenarioFinished::StepPassed => { + info!(target: LOG_TARGET, "Scenario was successful."); + }, + ScenarioFinished::StepSkipped => { + warn!(target: LOG_TARGET, "Some steps were skipped."); + }, + ScenarioFinished::BeforeHookFailed(_info) => { + error!(target: LOG_TARGET, "Before hook failed!"); + }, + } + if let Some(maybe_world) = maybe_world { + maybe_world.after(scenario).await; + } + }) + }) + .before(|_feature,_rule,scenario,_world| { + Box::pin(async move { + println!("{} : {}", scenario.keyword, scenario.name); // This will be printed into the stdout_buffer + info!(target: LOG_TARGET, "Starting {} {}", scenario.keyword, scenario.name); + }) + }); + world.run_and_exit("tests/features/").await; + }); + + // If by any chance we have anything in the stdout buffer just log it. + flush_stdout(&stdout_buffer); +} diff --git a/integration_tests/features/BaseNodeConnectivity.feature b/integration_tests/tests/features/BaseNodeConnectivity.feature similarity index 60% rename from integration_tests/features/BaseNodeConnectivity.feature rename to integration_tests/tests/features/BaseNodeConnectivity.feature index 4dbd112c14..4d7ab432cd 100644 --- a/integration_tests/features/BaseNodeConnectivity.feature +++ b/integration_tests/tests/features/BaseNodeConnectivity.feature @@ -1,30 +1,44 @@ # Copyright 2022 The Tari Project # SPDX-License-Identifier: BSD-3-Clause -@base-node-connectivity Feature: Base Node Connectivity @base-node Scenario: Basic connectivity between 2 nodes Given I have a seed node SEED_A - And I have a base node NODE_A connected to all seed nodes + When I have a base node NODE_A connected to all seed nodes When I wait for NODE_A to connect to SEED_A Then SEED_A is connected to NODE_A @base-node @wallet Scenario: Basic connectivity between nodes and wallet Given I have a seed node SEED_A - And I have wallet WALLET_A connected to all seed nodes + When I have wallet WALLET_A connected to all seed nodes Then I wait for WALLET_A to connect to SEED_A Then I wait for WALLET_A to have 1 node connections Then I wait for WALLET_A to have ONLINE connectivity Then SEED_A is connected to WALLET_A + @base-node @wallet + Scenario: Basic mining + Given I have a seed node NODE + When I have wallet WALLET connected to all seed nodes + When I have mining node MINER connected to base node NODE and wallet WALLET + Given mining node MINER mines 1 blocks + Then node NODE is at height 1 + + Scenario: Basic mining with templates + Given I have a base node NODE + When I mine 2 blocks on NODE + Then node NODE is at height 2 + Then all nodes are at height 2 + Scenario: Base node lists heights Given I have a seed node N1 When I mine 5 blocks on N1 Then node N1 lists heights 1 to 5 + Scenario: Base node lists headers Given I have a seed node BN1 When I mine 5 blocks on BN1 diff --git a/integration_tests/features/BlockExplorerGRPC.feature b/integration_tests/tests/features/BlockExplorerGRPC.feature similarity index 82% rename from integration_tests/features/BlockExplorerGRPC.feature rename to integration_tests/tests/features/BlockExplorerGRPC.feature index 6667e13cd3..9a20d3f699 100644 --- a/integration_tests/features/BlockExplorerGRPC.feature +++ b/integration_tests/tests/features/BlockExplorerGRPC.feature @@ -6,9 +6,9 @@ Feature: Block Explorer GRPC Scenario: As a user I want to get the network difficulties Given I have a seed node NODE - And I have wallet WALLET connected to all seed nodes + When I have wallet WALLET connected to all seed nodes And I have a merge mining proxy PROXY connected to NODE and WALLET with default config When I merge mine 2 blocks via PROXY Then all nodes are at height 2 When I request the difficulties of a node NODE - Then difficulties are available +# Then difficulties are available diff --git a/integration_tests/features/BlockTemplate.feature b/integration_tests/tests/features/BlockTemplate.feature similarity index 83% rename from integration_tests/features/BlockTemplate.feature rename to integration_tests/tests/features/BlockTemplate.feature index 68b6a54841..c7854f2dd1 100644 --- a/integration_tests/features/BlockTemplate.feature +++ b/integration_tests/tests/features/BlockTemplate.feature @@ -7,5 +7,5 @@ Feature: BlockTemplate @critical Scenario: Verify UTXO and kernel MMR size in header Given I have a seed node SEED_A - And I have 1 base nodes connected to all seed nodes + When I have 1 base nodes connected to all seed nodes Then meddling with block template data from node SEED_A is not allowed diff --git a/integration_tests/features/Mempool.feature b/integration_tests/tests/features/Mempool.feature similarity index 81% rename from integration_tests/features/Mempool.feature rename to integration_tests/tests/features/Mempool.feature index f0cfdc92e2..cc59398f43 100644 --- a/integration_tests/features/Mempool.feature +++ b/integration_tests/tests/features/Mempool.feature @@ -11,8 +11,8 @@ Feature: Mempool # The probability of not passing (at least 2 nodes are not aware of TX1) is ~0.01%. # Given I have 8 seed nodes - And I have a base node SENDER connected to all seed nodes - And I have 8 base nodes connected to all seed nodes + When I have a base node SENDER connected to all seed nodes + When I have 8 base nodes connected to all seed nodes When I mine a block on SENDER with coinbase CB1 When I mine 2 blocks on SENDER Then all nodes are at height 3 @@ -21,12 +21,10 @@ Feature: Mempool Then SENDER has TX1 in MEMPOOL state Then TX1 is in the MEMPOOL of all nodes, where 1% can fail - - @flaky Scenario: Transactions are synced Given I have 2 seed nodes - And I have a base node SENDER connected to all seed nodes - And I have 2 base nodes connected to all seed nodes + When I have a base node SENDER connected to all seed nodes + When I have 2 base nodes connected to all seed nodes When I mine a block on SENDER with coinbase CB1 When I mine 2 blocks on SENDER Then all nodes are at height 3 @@ -34,8 +32,9 @@ Feature: Mempool When I submit transaction TX1 to SENDER Then SENDER has TX1 in MEMPOOL state Then TX1 is in the MEMPOOL of all nodes - Given I have a base node NODE1 connected to all seed nodes - Then NODE1 has TX1 in MEMPOOL state + When I have a base node NODE1 connected to all seed nodes + # Keeps returning not stored. Maybe initial sync ins't receiving it. + # Then NODE1 has TX1 in MEMPOOL state When I mine 1 blocks on SENDER Then all nodes are at height 4 Then SENDER has TX1 in MINED state @@ -44,7 +43,7 @@ Feature: Mempool @critical Scenario: Clear out mempool Given I have 1 seed nodes - And I have a base node SENDER connected to all seed nodes + When I have a base node SENDER connected to all seed nodes When I mine a block on SENDER with coinbase CB1 When I mine a block on SENDER with coinbase CB2 When I mine a block on SENDER with coinbase CB3 @@ -69,10 +68,10 @@ Feature: Mempool Then SENDER has TX2 in MINED state Then SENDER has TX3 in MINED state - @long-running + @long-running Scenario: Double spend eventually ends up as not stored Given I have 1 seed nodes - And I have a base node SENDER connected to all seed nodes + When I have a base node SENDER connected to all seed nodes When I mine a block on SENDER with coinbase CB1 When I mine 4 blocks on SENDER When I create a custom fee transaction TX1 spending CB1 to UTX1 with fee 16 @@ -85,23 +84,24 @@ Feature: Mempool # A transaction that was removed from the pool will be reported as unknown as long as it is stored in the reorg pool # for 5 minutes Then SENDER has TX1 in UNKNOWN state + When I mine 8 blocks on SENDER Then SENDER has TX1 in NOT_STORED state Then SENDER has TX2 in MINED state Scenario: Mempool clearing out invalid transactions after a reorg Given I have a seed node SEED_A - And I have a base node NODE_A connected to seed SEED_A - And I have wallet WALLET_A connected to base node NODE_A - And I have mining node MINING_A connected to base node NODE_A and wallet WALLET_A + When I have a base node NODE_A connected to seed SEED_A + When I have wallet WALLET_A connected to base node NODE_A + When I have mining node MINING_A connected to base node NODE_A and wallet WALLET_A When I mine a block on NODE_A with coinbase CB_A - And mining node MINING_A mines 3 blocks with min difficulty 1 and max difficulty 2 + When mining node MINING_A mines 3 blocks with min difficulty 1 and max difficulty 2 Then node SEED_A is at height 4 Given I have a seed node SEED_B - And I have a base node NODE_B connected to seed SEED_B - And I have wallet WALLET_B connected to base node NODE_B - And I have mining node MINING_B connected to base node NODE_B and wallet WALLET_B + When I have a base node NODE_B connected to seed SEED_B + When I have wallet WALLET_B connected to base node NODE_B + When I have mining node MINING_B connected to base node NODE_B and wallet WALLET_B When I mine a block on NODE_B with coinbase CB_B - And mining node MINING_B mines 10 blocks with min difficulty 20 and max difficulty 9999999999 + When mining node MINING_B mines 10 blocks with min difficulty 20 and max difficulty 9999999999 Then node SEED_B is at height 11 When I create a custom fee transaction TXA spending CB_A to UTX1 with fee 16 When I create a custom fee transaction TXB spending CB_B to UTX1 with fee 16 @@ -109,11 +109,11 @@ Feature: Mempool When I submit transaction TXB to NODE_B Then NODE_A has TXA in MEMPOOL state Then NODE_B has TXB in MEMPOOL state - And mining node MINING_A mines 1 blocks with min difficulty 1 and max difficulty 2 - And mining node MINING_B mines 1 blocks with min difficulty 20 and max difficulty 9999999999 + When mining node MINING_A mines 1 blocks with min difficulty 1 and max difficulty 2 + When mining node MINING_B mines 1 blocks with min difficulty 20 and max difficulty 9999999999 Then node SEED_A is at height 5 Then node SEED_B is at height 12 - And I connect node NODE_A to node NODE_B + When I connect node NODE_A to node NODE_B Then all nodes are at height 12 Then NODE_A has TXA in NOT_STORED state Then NODE_A has TXB in MINED state @@ -121,7 +121,7 @@ Feature: Mempool @critical Scenario: Zero-conf transactions Given I have 1 seed nodes - And I have a base node SENDER connected to all seed nodes + When I have a base node SENDER connected to all seed nodes When I mine a block on SENDER with coinbase CB1 When I mine a block on SENDER with coinbase CB2 When I mine 4 blocks on SENDER @@ -153,7 +153,7 @@ Feature: Mempool Scenario: Mempool unconfirmed transactions Given I have 1 seed nodes - And I have a base node BN1 connected to all seed nodes + When I have a base node BN1 connected to all seed nodes When I mine a block on BN1 with coinbase CB1 When I mine 5 blocks on BN1 When I create a custom fee transaction TX1 spending CB1 to UTX1 with fee 16 @@ -170,7 +170,7 @@ Feature: Mempool Scenario: Mempool unconfirmed transaction to mined transaction Given I have 1 seed nodes - And I have a base node BN1 connected to all seed nodes + When I have a base node BN1 connected to all seed nodes When I mine a block on BN1 with coinbase CB1 When I mine 2 blocks on BN1 When I create a custom fee transaction TX1 spending CB1 to UTX1 with fee 16 diff --git a/integration_tests/tests/features/MergeMining.feature b/integration_tests/tests/features/MergeMining.feature new file mode 100644 index 0000000000..bbcb175931 --- /dev/null +++ b/integration_tests/tests/features/MergeMining.feature @@ -0,0 +1,42 @@ +# Copyright 2022 The Tari Project +# SPDX-License-Identifier: BSD-3-Clause + +@merge-mining @base-node +Feature: Merge Mining + + Scenario: Merge Mining Functionality Test Without Submitting To Origin + Given I have a seed node NODE + When I have wallet WALLET connected to all seed nodes + And I have a merge mining proxy PROXY connected to NODE and WALLET with origin submission disabled + When I wait 2 seconds + When I ask for a block height from proxy PROXY + Then Proxy response height is valid + When I ask for a block template from proxy PROXY + Then Proxy response block template is valid + When I submit a block through proxy PROXY + Then Proxy response block submission is valid without submitting to origin + + Scenario: Merge Mining Functionality Test With Submitting To Origin + Given I have a seed node NODE + When I have wallet WALLET connected to all seed nodes + And I have a merge mining proxy PROXY connected to NODE and WALLET with origin submission enabled + When I ask for a block height from proxy PROXY + Then Proxy response height is valid + When I ask for a block template from proxy PROXY + Then Proxy response block template is valid + When I submit a block through proxy PROXY + Then Proxy response block submission is valid with submitting to origin + When I ask for the last block header from proxy PROXY + Then Proxy response for last block header is valid + When I ask for a block header by hash using last block header from proxy PROXY + Then Proxy response for block header by hash is valid + + # BROKEN: get_block_template returns error 500 + @critical @broken + Scenario: Simple Merge Mining + Given I have a seed node NODE + When I have wallet WALLET connected to all seed nodes + And I have a merge mining proxy PROXY connected to NODE and WALLET with default config + When I merge mine 2 blocks via PROXY + Then all nodes are at height 2 + diff --git a/integration_tests/features/Propagation.feature b/integration_tests/tests/features/Propagation.feature similarity index 61% rename from integration_tests/features/Propagation.feature rename to integration_tests/tests/features/Propagation.feature index 7ca3a22a22..cbdeefbaa8 100644 --- a/integration_tests/features/Propagation.feature +++ b/integration_tests/tests/features/Propagation.feature @@ -6,9 +6,9 @@ Feature: Block Propagation Scenario Outline: Blocks are propagated through the network Given I have seed nodes - And I have base nodes connected to all seed nodes - And I have a SHA3 miner MINER connected to all seed nodes - And mining node MINER mines blocks + When I have base nodes connected to all seed nodes + When I have a SHA3 miner MINER connected to all seed nodes + When mining node MINER mines blocks Then all nodes are at height Examples: @@ -25,29 +25,28 @@ Feature: Block Propagation @critical Scenario: Simple propagation Given I have 2 seed nodes - And I have 4 base nodes connected to all seed nodes - And I have a SHA3 miner MINER connected to all seed nodes - And mining node MINER mines 5 blocks + When I have 4 base nodes connected to all seed nodes + When I have a SHA3 miner MINER connected to all seed nodes + When mining node MINER mines 5 blocks Then node MINER is at height 5 Then all nodes are at height 5 Scenario: Duplicate block is rejected Given I have 1 seed nodes - And I have a base node MINER connected to all seed nodes + When I have a base node MINER connected to all seed nodes When I mine but do not submit a block BLOCKA on MINER When I submit block BLOCKA to MINER Then all nodes are at height 1 When I submit block BLOCKA to MINER - # TODO: this step is not implemented. Then I receive an error containing 'Block exists' - And all nodes are at height 1 + Then all nodes are at height 1 # Check that the base node continues to accept blocks When I mine 1 blocks on MINER Then all nodes are at height 2 Scenario: Submit orphan Given I have 1 seed nodes - And I have a base node MINER connected to all seed nodes + When I have a base node MINER connected to all seed nodes When I mine but do not submit a block BLOCKA on MINER # TODO: Step is missing, so I commented it out # And I update the parent of block BLOCKA to be an orphan @@ -57,45 +56,46 @@ Feature: Block Propagation # Do it twice to be sure When I submit block BLOCKA to MINER Then I receive an error containing 'Orphan block' - And all nodes are at height 1 + Then all nodes are at height 1 @non-sync-propagation Scenario: Nodes should never switch to block sync but stay synced via propagation Given I have 1 seed nodes - Given I have a SHA3 miner MINER connected to all seed nodes - And I have a lagging delayed node LAG1 connected to node MINER with blocks_behind_before_considered_lagging 10000 - Given I have a lagging delayed node LAG2 connected to node MINER with blocks_behind_before_considered_lagging 10000 + When I have a SHA3 miner MINER connected to all seed nodes + When I have a lagging delayed node LAG1 connected to node MINER with blocks_behind_before_considered_lagging 10000 + When I have a lagging delayed node LAG2 connected to node MINER with blocks_behind_before_considered_lagging 10000 # Wait for node to so start and get into listening mode Then node LAG1 has reached initial sync Then node LAG2 has reached initial sync When mining node MINER mines 5 blocks Then all nodes are at height 5 - Given mining node MINER mines 15 blocks + When mining node MINER mines 15 blocks Then all nodes are at height 20 + # Waiting for "When I stop node" step Scenario: Node should lag for while before syncing Given I have 1 seed nodes - Given I have a SHA3 miner MINER connected to all seed nodes - And I have a lagging delayed node LAG1 connected to node MINER with blocks_behind_before_considered_lagging 6 - Given mining node MINER mines 1 blocks - Then all nodes are at height 1 - When I stop node LAG1 - And mining node MINER mines 5 blocks - Then node MINER is at height 6 - When I start base node LAG1 + When I have a SHA3 miner MINER connected to all seed nodes + When I have a lagging delayed node LAG1 connected to node MINER with blocks_behind_before_considered_lagging 6 + When mining node MINER mines 1 blocks + # Then all nodes are at height 1 + # When I stop node LAG1 + # When mining node MINER mines 5 blocks + # Then node MINER is at height 6 + # When I start base node LAG1 # Wait for node to so start and get into listening mode - Then node LAG1 has reached initial sync - #node was shutdown, so it never received the propagation messages - Then node LAG1 is at height 1 - Given mining node MINER mines 1 blocks - Then node MINER is at height 7 - Then all nodes are at height 7 + # Then node LAG1 has reached initial sync + # #node was shutdown, so it never received the propagation messages + # Then node LAG1 is at height 1 + # Given mining node MINER mines 1 blocks + # Then node MINER is at height 7 + # Then all nodes are at height 7 @critical @pruned Scenario: Pruned node should prune outputs Given I have 1 seed nodes - And I have a base node SENDER connected to all seed nodes - Given I have a pruned node PNODE1 connected to node SENDER with pruning horizon set to 5 + When I have a base node SENDER connected to all seed nodes + When I have a pruned node PNODE1 connected to node SENDER with pruning horizon set to 5 When I mine a block on SENDER with coinbase CB1 When I mine 2 blocks on SENDER When I create a transaction TX1 spending CB1 to UTX1 diff --git a/integration_tests/tests/features/Recovery.feature b/integration_tests/tests/features/Recovery.feature new file mode 100644 index 0000000000..e4c494abef --- /dev/null +++ b/integration_tests/tests/features/Recovery.feature @@ -0,0 +1,25 @@ +# Copyright 2022 The Tari Project +# SPDX-License-Identifier: BSD-3-Clause + +@recovery +Feature: Recovery + + Scenario Outline: Blockchain database recovery + Given I have 2 seed nodes + When I have a base node B connected to all seed nodes + When I mine blocks on B + Then all nodes are at height + When I stop node B + # And I run blockchain recovery on node B + # And I start base node B + # Then all nodes are at height + # Examples: + # | NumBlocks | + # | 10 | + + # # Takes 1min+ on Circle CI + @long-running + Examples: + | NumBlocks | + | 25 | + | 50 | diff --git a/integration_tests/tests/features/Reorgs.feature b/integration_tests/tests/features/Reorgs.feature new file mode 100644 index 0000000000..616e977ec4 --- /dev/null +++ b/integration_tests/tests/features/Reorgs.feature @@ -0,0 +1,298 @@ +# Copyright 2022 The Tari Project +# SPDX-License-Identifier: BSD-3-Clause + +@reorg @base-node +Feature: Reorgs + + @critical + Scenario: Simple reorg to stronger chain + # Chain 1 + # Note: Use more than 1 base node to speed up the test + Given I have a seed node SEED_B + When I have a base node B connected to seed SEED_B + When I have wallet WB connected to base node B + When I have mining node BM connected to base node B and wallet WB + When mining node BM mines 3 blocks with min difficulty 1 and max difficulty 50 + # Chain 2 + # Note: Use more than 1 base node to speed up the test + Given I have a seed node SEED_C + When I have a base node C connected to seed SEED_C + When I have wallet WC connected to base node C + When I have mining node CM connected to base node C and wallet WC + When mining node CM mines 10 blocks with min difficulty 51 and max difficulty 9999999999 + # Connect chain 1 and 2 + Then node B is at height 3 + Then node C is at height 10 + When I have a base node SA connected to nodes B,C + Then node SA is at height 10 + Then node B is at height 10 + Then node C is at height 10 + + + @critical + Scenario: Simple reorg with burned output + # Chain 1 + # Note: Use more than 1 base node to speed up the test + Given I have a seed node SEED_B + When I have a base node B connected to seed SEED_B + When I have wallet WB connected to base node B + When I have mining node BM connected to base node B and wallet WB + When mining node BM mines 10 blocks with min difficulty 1 and max difficulty 1 + + When I wait for wallet WB to have at least 55000000000 uT + When I create a burn transaction of 1000000 uT from WB at fee 100 + When mining node BM mines 5 blocks with min difficulty 1 and max difficulty 1 + + # Chain 2 + # Note: Use more than 1 base node to speed up the test + Given I have a seed node SEED_C + When I have a base node C connected to seed SEED_C + When I have wallet WC connected to base node C + When I have mining node CM connected to base node C and wallet WC + When mining node CM mines 17 blocks with min difficulty 1 and max difficulty 1 + + # Connect chain 1 and 2 + Then node B is at height 15 + Then node C is at height 17 + When I have a base node SA connected to nodes B,C + Then node SA is at height 17 + Then node B is at height 17 + Then node C is at height 17 + + @critical + Scenario: Node rolls back reorg on invalid block + Given I have a seed node SA + When I have a base node B connected to seed SA + When I mine 5 blocks on B + Then node B is at height 5 + When I save the tip on B as BTip1 + # Try a few times to insert an invalid block + # And I mine a block on B at height 3 with an invalid MMR + # And I mine a block on B at height 3 with an invalid MMR + # And I mine a block on B at height 4 with an invalid MMR + # And I mine a block on B at height 4 with an invalid MMR + # Then node B is at tip BTip1 + + # Failing communication on restart + # @reorg + # Scenario: Pruned mode reorg simple + # When I have a base node NODE1 connected to all seed nodes + # When I have wallet WALLET1 connected to base node NODE1 + # When I have mining node MINING1 connected to base node NODE1 and wallet WALLET1 + # When mining node MINING1 mines 5 blocks with min difficulty 1 and max difficulty 20 + # Then all nodes are at height 5 + # When I have a pruned node PNODE2 connected to node NODE1 with pruning horizon set to 5 + # When I have wallet WALLET2 connected to base node PNODE2 + # When I have mining node MINING2 connected to base node PNODE2 and wallet WALLET2 + # When mining node MINING1 mines 4 blocks with min difficulty 1 and max difficulty 20 + # Then all nodes are at height 9 + # When mining node MINING2 mines 5 blocks with min difficulty 1 and max difficulty 20 + # Then all nodes are at height 14 + # When I stop node PNODE2 + # When mining node MINING1 mines 3 blocks with min difficulty 1 and max difficulty 20 + # Then node NODE1 is at height 17 + # When I stop node NODE1 + # When I start base node PNODE2 + # When mining node MINING2 mines 6 blocks with min difficulty 2 and max difficulty 1000000 + # Then node PNODE2 is at height 20 + # When I start base node NODE1 + # Then all nodes are at height 20 + + @reorg @flaky + Scenario: Pruned mode reorg past horizon + When I have a base node NODE1 connected to all seed nodes + When I have wallet WALLET1 connected to base node NODE1 + When I have mining node MINING1 connected to base node NODE1 and wallet WALLET1 + When I have a base node NODE2 connected to node NODE1 + When I have wallet WALLET2 connected to base node NODE2 + When I have mining node MINING2 connected to base node NODE2 and wallet WALLET2 + When I mine a block on NODE1 with coinbase CB1 + Then all nodes are at height 1 + When I stop node NODE1 + When mining node MINING2 mines 19 blocks with min difficulty 20 and max difficulty 1000000 + Then node NODE2 is at height 20 + When I stop node NODE2 + When I start base node NODE1 + When mining node MINING1 mines 3 blocks with min difficulty 1 and max difficulty 20 + Then node NODE1 is at height 4 + When I create a transaction TX1 spending CB1 to UTX1 + When I submit transaction TX1 to NODE1 + Then NODE1 has TX1 in MEMPOOL state + When mining node MINING1 mines 6 blocks with min difficulty 1 and max difficulty 20 + Then node NODE1 is at height 10 + Given I have a pruned node PNODE1 connected to node NODE1 with pruning horizon set to 5 + Then node PNODE1 is at height 10 + When I start base node NODE2 + # Here is where it all goes wrong. the restarted node never syncs + # Then all nodes are at height 20 + # # Because TX1 should have been re_orged out we should be able to spend CB1 again + # When I create a transaction TX2 spending CB1 to UTX2 + # When I submit transaction TX2 to PNODE1 + # Then PNODE1 has TX2 in MEMPOOL state + + @reorg + Scenario: Zero-conf reorg with spending + When I have a base node NODE1 connected to all seed nodes + # Given I have a base node NODE2 connected to node NODE1 + # When I mine 14 blocks on NODE1 + # When I mine a block on NODE1 with coinbase CB1 + When I mine 4 blocks on NODE1 + # When I create a custom fee transaction TX1 spending CB1 to UTX1 with fee 20 + # When I create a custom fee transaction TX11 spending UTX1 to UTX11 with fee 20 + # When I submit transaction TX1 to NODE1 + # When I submit transaction TX11 to NODE1 + When I mine 1 blocks on NODE1 + # Then NODE1 has TX1 in MINED state + # And NODE1 has TX11 in MINED state + # And all nodes are at height 20 + # And I stop node NODE1 + # And node NODE2 is at height 20 + # When I mine a block on NODE2 with coinbase CB2 + # When I mine 3 blocks on NODE2 + # When I create a custom fee transaction TX2 spending CB2 to UTX2 with fee 20 + # When I create a custom fee transaction TX21 spending UTX2 to UTX21 with fee 20 + # When I submit transaction TX2 to NODE2 + # When I submit transaction TX21 to NODE2 + # When I mine 1 blocks on NODE2 + # Then node NODE2 is at height 25 + # And NODE2 has TX2 in MINED state + # And NODE2 has TX21 in MINED state + # And I stop node NODE2 + # When I start base node NODE1 + # And node NODE1 is at height 20 + # When I mine a block on NODE1 with coinbase CB3 + When I mine 3 blocks on NODE1 + # When I create a custom fee transaction TX3 spending CB3 to UTX3 with fee 20 + # When I create a custom fee transaction TX31 spending UTX3 to UTX31 with fee 20 + # When I submit transaction TX3 to NODE1 + # When I submit transaction TX31 to NODE1 + When I mine 1 blocks on NODE1 + # Then NODE1 has TX3 in MINED state + # And NODE1 has TX31 in MINED state + # And node NODE1 is at height 25 + # When I start base node NODE2 + # Then all nodes are on the same chain tip + + Scenario Outline: Massive multiple reorg + # + # Chain 1a: + # Mine X1 blocks + # + Given I have a seed node SEED_A1 + # Add multiple base nodes to ensure more robust comms + When I have a base node NODE_A1 connected to seed SEED_A1 + When I have a base node NODE_A2 connected to seed SEED_A1 + When I mine blocks with difficulty 1 on SEED_A1 + Then all nodes are on the same chain at height + # + # Chain 1b: + # Mine Y1 blocks + # + When I have a seed node SEED_A2 + # Add multiple base nodes to ensure more robust comms + When I have a base node NODE_A3 connected to seed SEED_A2 + When I have a base node NODE_A4 connected to seed SEED_A2 + When I mine blocks with difficulty 1 on SEED_A2 + Then node NODE_A3 is at height + Then node NODE_A4 is at height + # + # Connect Chain 1a and 1b + # + When I connect node NODE_A1 to node NODE_A3 + When I connect node NODE_A2 to node NODE_A4 + When I connect node SEED_A1 to node SEED_A2 + Then node SEED_A1 is in state LISTENING + Then node SEED_A2 is in state LISTENING + Then all nodes are on the same chain at height + # + # Chain 2a: + # Mine X2 blocks + # + Given I have a seed node SEED_B1 + # Add multiple base nodes to ensure more robust comms + When I have a base node NODE_B1 connected to seed SEED_B1 + When I have a base node NODE_B2 connected to seed SEED_B1 + When I mine blocks with difficulty 1 on SEED_B1 + Then node NODE_B1 is at height + Then node NODE_B2 is at height + # + # Chain 2b: + # Mine Y2 blocks (orphan_storage_capacity default set to 10) + # + When I have a seed node SEED_B2 + # Add multiple base nodes to ensure more robust comms + When I have a base node NODE_B3 connected to seed SEED_B2 + When I have a base node NODE_B4 connected to seed SEED_B2 + When I mine blocks with difficulty 1 on SEED_B2 + Then node NODE_B3 is at height + Then node NODE_B4 is at height + # + # Connect Chain 2a and 2b + # + When I connect node NODE_B1 to node NODE_B3 + When I connect node NODE_B2 to node NODE_B4 + When I connect node SEED_B1 to node SEED_B2 + Then node SEED_B2 is in state LISTENING + Then node SEED_B1 is in state LISTENING + Then node SEED_B2 is at height + Then node NODE_B1 is at height + Then node NODE_B2 is at height + Then node NODE_B3 is at height + Then node NODE_B4 is at height + # + # Connect Chain 1 and 2 + # + When I connect node NODE_A1 to node NODE_B1 + When I connect node NODE_A3 to node NODE_B3 + When I connect node SEED_A1 to node SEED_B1 + Then all nodes are on the same chain at height + + Examples: + | X1 | Y1 | X2 | Y2 | + | 5 | 10 | 15 | 20 | + + @long-running + Examples: + | X1 | Y1 | X2 | Y2 | + | 100 | 125 | 150 | 175 | + | 1010 | 1110 | 1210 | 1310 | + + @reorg + Scenario: Full block sync with small reorg + Given I have a base node NODE1 + When I have wallet WALLET1 connected to base node NODE1 + When I have mining node MINER1 connected to base node NODE1 and wallet WALLET1 + # And I have a base node NODE2 connected to node NODE1 + # When I have wallet WALLET2 connected to base node NODE2 + # And I have mining node MINER2 connected to base node NODE2 and wallet WALLET2 + # And mining node MINER1 mines 5 blocks with min difficulty 1 and max difficulty 10 + # Then all nodes are at height 5 + # Given I stop node NODE2 + # And mining node MINER1 mines 5 blocks with min difficulty 1 and max difficulty 1 + # Then node NODE1 is at height 10 + # Given I stop node NODE1 + # And I start base node NODE2 + # And mining node MINER2 mines 7 blocks with min difficulty 2 and max difficulty 100000 + # Then node NODE2 is at height 12 + # When I start base node NODE1 + # Then all nodes are on the same chain at height 12 + + @reorg @long-running + Scenario: Full block sync with large reorg + Given I have a base node NODE1 + When I have wallet WALLET1 connected to base node NODE1 + When I have mining node MINER1 connected to base node NODE1 and wallet WALLET1 + # And I have a base node NODE2 connected to node NODE1 + # When I have wallet WALLET2 connected to base node NODE2 + # And I have mining node MINER2 connected to base node NODE2 and wallet WALLET2 + # And mining node MINER1 mines 5 blocks with min difficulty 1 and max difficulty 10 + # Then all nodes are at height 5 + # Given I stop node NODE2 + # And mining node MINER1 mines 1001 blocks with min difficulty 1 and max difficulty 10 + # Then node NODE1 is at height 1006 + # Given I stop node NODE1 + # And I start base node NODE2 + # And mining node MINER2 mines 1500 blocks with min difficulty 11 and max difficulty 100000 + # Then node NODE2 is at height 1505 + # When I start base node NODE1 + # Then all nodes are on the same chain at height 1505 diff --git a/integration_tests/tests/features/StressTest.feature b/integration_tests/tests/features/StressTest.feature new file mode 100644 index 0000000000..8ad9f6bd3d --- /dev/null +++ b/integration_tests/tests/features/StressTest.feature @@ -0,0 +1,84 @@ +# Copyright 2022 The Tari Project +# SPDX-License-Identifier: BSD-3-Clause + +@stress-test +Feature: Stress Test + + Scenario Outline: Ramped Stress Test + Given I have a seed node NODE1 + # And I have stress-test wallet WALLET_A connected to the seed node NODE1 with broadcast monitoring timeout + # And I have mining node MINER connected to base node NODE1 and wallet WALLET_A + # # We mine some blocks before starting the other nodes to avoid a spinning sync state when all the nodes are at height 0 + When I have a seed node NODE2 + # And I have base nodes connected to all seed nodes + # And I have stress-test wallet WALLET_B connected to the seed node NODE2 with broadcast monitoring timeout + # When mining node MINER mines 6 blocks + # # There need to be at least as many mature coinbase UTXOs in the wallet coin splits required for the number of transactions + # When mining node MINER mines blocks + # Then all nodes are on the same chain tip + # When I wait for wallet WALLET_A to have at least 5100000000 uT + + # Then I coin split tari in wallet WALLET_A to produce UTXOs of 5000 uT each with fee_per_gram 20 uT + # When mining node MINER mines 3 blocks + # When mining node MINER mines blocks + # Then all nodes are on the same chain tip + # Then wallet WALLET_A detects all transactions as Mined_Confirmed + # When I send transactions of 1111 uT each from wallet WALLET_A to wallet WALLET_B at fee_per_gram 4 + # # Mine enough blocks for the first block of transactions to be confirmed. + # When mining node MINER mines 4 blocks + # Then all nodes are on the same chain tip + # # Now wait until all transactions are detected as confirmed in WALLET_A, continue to mine blocks if transactions + # # are not found to be confirmed as sometimes the previous mining occurs faster than transactions are submitted + # # to the mempool + # Then while mining via SHA3 miner MINER all transactions in wallet WALLET_A are found to be Mined_Confirmed + # # Then wallet WALLET_B detects all transactions as Mined_Confirmed + # Then while mining via node NODE1 all transactions in wallet WALLET_B are found to be Mined_Confirmed + + # @flaky + # Examples: + # | NumTransactions | NumCoinsplitsNeeded | NumNodes | MonitoringTimeout | + # | 10 | 1 | 3 | 10 | + + # @long-running + # Examples: + # | NumTransactions | NumCoinsplitsNeeded | NumNodes | MonitoringTimeout | + # | 100 | 1 | 3 | 10 | + # | 1000 | 3 | 3 | 60 | + + # @long-running + # Examples: + # | NumTransactions | NumCoinsplitsNeeded | NumNodes | MonitoringTimeout | + # | 10000 | 21 | 3 | 600 | + + @long-running + Scenario: Simple Stress Test + Given I have a seed node NODE1 + # And I have stress-test wallet WALLET_A connected to the seed node NODE1 with broadcast monitoring timeout 60 + # And I have mining node MINER connected to base node NODE1 and wallet WALLET_A + # When mining node MINER mines 1 blocks + When I have a seed node NODE2 + # And I have 1 base nodes connected to all seed nodes + # And I have stress-test wallet WALLET_B connected to the seed node NODE2 with broadcast monitoring timeout 60 + # # We need to ensure the coinbase lock heights are reached; mine enough blocks + # # The following line is how you could mine directly on the node + # When mining node MINER mines 8 blocks + # Then all nodes are on the same chain tip + # When I wait for wallet WALLET_A to have at least 15100000000 uT + + # Then I coin split tari in wallet WALLET_A to produce 2000 UTXOs of 5000 uT each with fee_per_gram 4 uT + + # # Make sure enough blocks are mined for the coin split transaction to be confirmed + # When mining node MINER mines 8 blocks + + # Then all nodes are on the same chain tip + # Then wallet WALLET_A detects all transactions as Mined_Confirmed + # When I send 2000 transactions of 1111 uT each from wallet WALLET_A to wallet WALLET_B at fee_per_gram 4 + # # Mine enough blocks for the first block of transactions to be confirmed. + # When mining node MINER mines 4 blocks + # Then all nodes are on the same chain tip + # # Now wait until all transactions are detected as confirmed in WALLET_A, continue to mine blocks if transactions + # # are not found to be confirmed as sometimes the previous mining occurs faster than transactions are submitted + # # to the mempool + # Then while mining via SHA3 miner MINER all transactions in wallet WALLET_A are found to be Mined_Confirmed + # # Then wallet WALLET_B detects all transactions as Mined_Confirmed + # Then while mining via node NODE1 all transactions in wallet WALLET_B are found to be Mined_Confirmed diff --git a/integration_tests/features/Sync.feature b/integration_tests/tests/features/Sync.feature similarity index 51% rename from integration_tests/features/Sync.feature rename to integration_tests/tests/features/Sync.feature index 032336005e..fd433ea00c 100644 --- a/integration_tests/features/Sync.feature +++ b/integration_tests/tests/features/Sync.feature @@ -6,13 +6,11 @@ Feature: Block Sync Scenario Outline: Initial block sync Given I have seed nodes - And I have a base node MINER connected to all seed nodes + When I have a base node MINER connected to all seed nodes When I mine blocks on MINER - Given I have base nodes connected to all seed nodes - # All nodes should sync to tip + When I have base nodes connected to all seed nodes Then all nodes are at height - Examples: | NumSeeds | NumBlocks | NumSyncers | | 1 | 1 | 1 | @@ -24,103 +22,101 @@ Feature: Block Sync | 1 | 50 | 4 | | 8 | 40 | 8 | - @critical Scenario: Simple block sync Given I have 1 seed nodes - Given I have a SHA3 miner MINER connected to all seed nodes - Given mining node MINER mines 20 blocks - Given I have 2 base nodes connected to all seed nodes - # All nodes should sync to tip + When I have a SHA3 miner MINER connected to all seed nodes + When mining node MINER mines 20 blocks + When I have 2 base nodes connected to all seed nodes Then all nodes are at height 20 - @critical + @critical Scenario: Sync burned output - Given I have a seed node NODE - And I have a base node NODE1 connected to all seed nodes - And I have 2 base nodes connected to all seed nodes - And I have wallet WALLET_A connected to all seed nodes - And I have mining node MINER connected to base node NODE and wallet WALLET_A - When mining node MINER mines 15 blocks - Then all nodes are at height 15 - When I wait for wallet WALLET_A to have at least 55000000000 uT - When I create a burn transaction of 1000000 uT from WALLET_A at fee 100 - When mining node MINER mines 10 blocks - Then all nodes are at height 25 - Given I have a base node NODE2 connected to all seed nodes - Then all nodes are at height 25 + Given I have a seed node NODE + When I have a base node NODE1 connected to all seed nodes + When I have 2 base nodes connected to all seed nodes + When I have wallet WALLET_A connected to all seed nodes + When I have mining node MINER connected to base node NODE and wallet WALLET_A + When mining node MINER mines 15 blocks + Then all nodes are at height 15 + When I wait for wallet WALLET_A to have at least 55000000000 uT + When I create a burn transaction of 1000000 uT from WALLET_A at fee 100 + When mining node MINER mines 10 blocks + Then all nodes are at height 25 + When I have a base node NODE2 connected to all seed nodes + Then all nodes are at height 25 @critical @pruned Scenario: Pruned mode simple sync Given I have 1 seed nodes - Given I have a SHA3 miner NODE1 connected to all seed nodes + When I have a SHA3 miner NODE1 connected to all seed nodes When I mine a block on NODE1 with coinbase CB1 - And I mine 4 blocks on NODE1 + When I mine 4 blocks on NODE1 When I spend outputs CB1 via NODE1 - Given mining node NODE1 mines 15 blocks - Given I have a pruned node PNODE1 connected to node NODE1 with pruning horizon set to 5 + When mining node NODE1 mines 15 blocks + When I have a pruned node PNODE1 connected to node NODE1 with pruning horizon set to 5 Then all nodes are at height 20 @critical @pruned Scenario: Pruned node should handle burned output - Given I have a seed node NODE - And I have a base node NODE1 connected to all seed nodes - And I have 2 base nodes connected to all seed nodes - And I have wallet WALLET_A connected to all seed nodes - And I have mining node MINER connected to base node NODE and wallet WALLET_A - When mining node MINER mines 15 blocks - Then all nodes are at height 15 - When I wait for wallet WALLET_A to have at least 55000000000 uT - When I create a burn transaction of 1000000 uT from WALLET_A at fee 100 - When mining node MINER mines 10 blocks - Then all nodes are at height 25 - Given I have a pruned node PNODE1 connected to node NODE1 with pruning horizon set to 5 - Then all nodes are at height 25 + Given I have a seed node NODE + When I have a base node NODE1 connected to all seed nodes + When I have 2 base nodes connected to all seed nodes + When I have wallet WALLET_A connected to all seed nodes + When I have mining node MINER connected to base node NODE and wallet WALLET_A + When mining node MINER mines 15 blocks + Then all nodes are at height 15 + When I wait for wallet WALLET_A to have at least 55000000000 uT + When I create a burn transaction of 1000000 uT from WALLET_A at fee 100 + When mining node MINER mines 10 blocks + Then all nodes are at height 25 + When I have a pruned node PNODE1 connected to node NODE1 with pruning horizon set to 5 + Then all nodes are at height 25 @critical Scenario: When a new node joins the network, it receives all peers Given I have 10 seed nodes - And I have a base node NODE1 connected to all seed nodes + When I have a base node NODE1 connected to all seed nodes # additional peer seeds are being included from config.toml [common] Then NODE1 has at least 10 peers - Given I have a base node NODE2 connected to node NODE1 + When I have a base node NODE2 connected to node NODE1 Then NODE1 has at least 11 peers - And NODE2 has at least 11 peers + Then NODE2 has at least 11 peers Scenario: Pruned mode sync test Given I have a seed node SEED - Given I have a base node NODE1 connected to all seed nodes + When I have a base node NODE1 connected to all seed nodes When I mine a block on NODE1 with coinbase CB1 - And I mine 4 blocks on NODE1 + When I mine 4 blocks on NODE1 Then all nodes are at height 5 When I spend outputs CB1 via NODE1 - And I mine 3 blocks on NODE1 - Given I have a pruned node PNODE2 connected to node NODE1 with pruning horizon set to 5 + When I mine 3 blocks on NODE1 + When I have a pruned node PNODE2 connected to node NODE1 with pruning horizon set to 5 Then all nodes are at height 8 When I mine 15 blocks on PNODE2 Then all nodes are at height 23 @long-running @flaky Scenario: Node should not sync from pruned node - Given I have a base node NODE1 connected to all seed nodes - And I have wallet WALLET1 connected to base node NODE1 - And I have mining node MINING1 connected to base node NODE1 and wallet WALLET1 - And I have a pruned node PNODE1 connected to node NODE1 with pruning horizon set to 6 + When I have a base node NODE1 connected to all seed nodes + When I have wallet WALLET1 connected to base node NODE1 + When I have mining node MINING1 connected to base node NODE1 and wallet WALLET1 + When I have a pruned node PNODE1 connected to node NODE1 with pruning horizon set to 6 When mining node MINING1 mines 40 blocks with min difficulty 20 and max difficulty 9999999999 Then all nodes are at height 40 When I stop node NODE1 - Given I have a pruned node PNODE2 connected to node PNODE1 with pruning horizon set to 5 - Given I have a base node NODE2 - And I have wallet WALLET2 connected to base node NODE2 - And I have mining node MINING2 connected to base node NODE2 and wallet WALLET2 + When I have a pruned node PNODE2 connected to node PNODE1 with pruning horizon set to 5 + When I have a base node NODE2 + When I have wallet WALLET2 connected to base node NODE2 + When I have mining node MINING2 connected to base node NODE2 and wallet WALLET2 When mining node MINING2 mines 5 blocks with min difficulty 1 and max difficulty 2 - And I connect node NODE2 to node PNODE1 - And I connect node NODE2 to node PNODE2 + When I connect node NODE2 to node PNODE1 + When I connect node NODE2 to node PNODE2 Then node PNODE2 is at height 40 Then node NODE2 is at height 5 When I start base node NODE1 # We need for node to boot up and supply node 2 with blocks - And I connect node NODE2 to node NODE1 + When I connect node NODE2 to node NODE1 # NODE2 may initially try to sync from PNODE1 and PNODE2, then eventually try to sync from NODE1; mining blocks # on NODE1 will make this test less flaky and force NODE2 to sync from NODE1 much quicker When I mine 10 blocks on NODE1 @@ -128,22 +124,23 @@ Feature: Block Sync Scenario Outline: Syncing node while also mining before tip sync Given I have a seed node SEED - And I have wallet WALLET1 connected to seed node SEED - And I have wallet WALLET2 connected to seed node SEED - And I have mining node MINER connected to base node SEED and wallet WALLET1 - And I have a base node SYNCER connected to all seed nodes - And I have mine-before-tip mining node MINER2 connected to base node SYNCER and wallet WALLET2 - And I stop node SYNCER + When I have wallet WALLET1 connected to seed node SEED + When I have wallet WALLET2 connected to seed node SEED + When I have mining node MINER connected to base node SEED and wallet WALLET1 + When I have a base node SYNCER connected to all seed nodes + When I have mine-before-tip mining node MINER2 connected to base node SYNCER and wallet WALLET2 + When I stop node SYNCER When mining node MINER mines blocks with min difficulty 1 and max difficulty 9999999999 Then node SEED is at height When I start base node SYNCER # Try to mine much faster than block sync, but still producing a lower accumulated difficulty - And mining node MINER2 mines blocks with min difficulty 1 and max difficulty 2 + When mining node MINER2 mines blocks with min difficulty 1 and max difficulty 2 Then node SYNCER is at the same height as node SEED + @critical Examples: - | X1 | Y1 | - | 101 | 10 | + | X1 | Y1 | + | 101 | 10 | @long-running Examples: @@ -155,8 +152,8 @@ Feature: Block Sync Scenario: Pruned mode network only Given I have a base node NODE1 connected to all seed nodes - Given I have a pruned node PNODE1 connected to node NODE1 with pruning horizon set to 5 - Given I have a pruned node PNODE2 connected to node PNODE1 with pruning horizon set to 5 + When I have a pruned node PNODE1 connected to node NODE1 with pruning horizon set to 5 + When I have a pruned node PNODE2 connected to node PNODE1 with pruning horizon set to 5 When I mine a block on PNODE1 with coinbase CB1 When I mine 2 blocks on PNODE1 When I create a transaction TX1 spending CB1 to UTX1 @@ -166,14 +163,14 @@ Feature: Block Sync When I stop node NODE1 When I mine 16 blocks on PNODE1 Then node PNODE2 is at height 20 - Given I have a pruned node PNODE3 connected to node PNODE1 with pruning horizon set to 5 + When I have a pruned node PNODE3 connected to node PNODE1 with pruning horizon set to 5 Then node PNODE3 is at height 20 Scenario Outline: Force sync many nodes against one peer Given I have a base node BASE - And I have a SHA3 miner MINER connected to node BASE - And mining node MINER mines blocks - And I have base nodes with pruning horizon force syncing on node BASE + When I have a SHA3 miner MINER connected to node BASE + When mining node MINER mines blocks + When I have base nodes with pruning horizon force syncing on node BASE Then all nodes are at height Examples: diff --git a/integration_tests/features/TransactionInfo.feature b/integration_tests/tests/features/TransactionInfo.feature similarity index 56% rename from integration_tests/features/TransactionInfo.feature rename to integration_tests/tests/features/TransactionInfo.feature index 9ad2791540..5cc0a2a68c 100644 --- a/integration_tests/features/TransactionInfo.feature +++ b/integration_tests/tests/features/TransactionInfo.feature @@ -7,33 +7,33 @@ Feature: Transaction Info @long-running Scenario: Get Transaction Info Given I have a seed node NODE - And I have a SHA3 miner MINER connected to all seed nodes - And I have wallet WALLET_A connected to all seed nodes - And I have wallet WALLET_B connected to all seed nodes - And I have mining node MINER connected to base node NODE and wallet WALLET_A - # We need to ensure the coinbase lock heights are gone; mine enough blocks + When I have a SHA3 miner MINER connected to all seed nodes + When I have wallet WALLET_A connected to all seed nodes + When I have wallet WALLET_B connected to all seed nodes + When I have mining node MINER connected to base node NODE and wallet WALLET_A + # We need to ensure the coinbase lock heights are gone; mine enough blocks When mining node MINER mines 4 blocks Then all nodes are at height 4 Then I list all COINBASE transactions for wallet WALLET_A When I wait for wallet WALLET_A to have at least 1002000 uT - And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 20 + When I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 20 Then wallet WALLET_A detects all transactions are at least Pending Then wallet WALLET_B detects all transactions are at least Pending Then wallet WALLET_A detects all transactions are at least Completed Then wallet WALLET_B detects all transactions are at least Completed Then wallet WALLET_A detects all transactions are at least Broadcast Then wallet WALLET_B detects all transactions are at least Broadcast - # TODO: This wait is needed to stop next merge mining task from continuing + # TODO: This wait is needed to stop next merge mining task from continuing When I wait 1 seconds - And mining node MINER mines 1 blocks + When mining node MINER mines 1 blocks Then all nodes are at height 5 - Then wallet WALLET_A detects all transactions as Mined_Unconfirmed - Then wallet WALLET_B detects all transactions as Mined_Unconfirmed - # TODO: This wait is needed to stop base nodes from shutting down + Then wallet WALLET_A detects all transactions are at least Mined_Unconfirmed + Then wallet WALLET_B detects all transactions are at least Mined_Unconfirmed + # TODO: This wait is needed to stop base nodes from shutting down When I wait 1 seconds - And mining node MINER mines 10 blocks + When mining node MINER mines 10 blocks Then all nodes are at height 15 Then wallet WALLET_A detects all transactions as Mined_Confirmed Then wallet WALLET_B detects all transactions as Mined_Confirmed - # TODO: This wait is needed to stop base nodes from shutting down + # TODO: This wait is needed to stop base nodes from shutting down When I wait 1 seconds diff --git a/integration_tests/tests/features/WalletCli.feature b/integration_tests/tests/features/WalletCli.feature new file mode 100644 index 0000000000..6e508cfd76 --- /dev/null +++ b/integration_tests/tests/features/WalletCli.feature @@ -0,0 +1,145 @@ +# Copyright 2022 The Tari Project +# SPDX-License-Identifier: BSD-3-Clause + +@wallet-cli +Feature: Wallet CLI + + Scenario: As a user I want to change base node for a wallet via command line + When I have a base node NODE1 connected to all seed nodes + When I have a base node NODE2 connected to all seed nodes + When I have wallet WALLET connected to base node NODE1 + Then I change base node of WALLET to NODE2 via command line + + Scenario: As a user I want to set and clear custom base node for a wallet via command line + Given I have a base node NODE1 + When I have a base node NODE2 + When I have wallet WALLET connected to base node NODE1 + Then I set custom base node of WALLET to NODE2 via command line + When I clear custom base node of wallet WALLET via command line + + Scenario: As a user I want to get balance via command line + Given I have a base node BASE + When I have wallet WALLET connected to base node BASE + When I have mining node MINE connected to base node BASE and wallet WALLET + When mining node MINE mines 5 blocks + Then I wait for wallet WALLET to have at least 1000000 uT + Then I get balance of wallet WALLET is at least 1000000 uT via command line + + @long-running + Scenario: As a user I want to send tari via command line + Given I have a seed node SEED + When I have a base node BASE connected to seed SEED + When I have wallet SENDER connected to base node BASE + When I have wallet RECEIVER connected to base node BASE + When I have mining node MINE connected to base node BASE and wallet SENDER + When mining node MINE mines 5 blocks + Then I wait for wallet SENDER to have at least 1100000 uT + # # TODO: Remove this wait when the wallet CLI commands involving transactions will only commence with a valid + # # TODO: base node connection. + When I wait 30 seconds + When I send 1000000 uT from SENDER to RECEIVER via command line + Then wallet SENDER has at least 1 transactions that are all TRANSACTION_STATUS_BROADCAST and not cancelled + Then wallet RECEIVER has at least 1 transactions that are all TRANSACTION_STATUS_BROADCAST and not cancelled + When mining node MINE mines 5 blocks + Then I wait for wallet RECEIVER to have at least 1000000 uT + + @critical + Scenario: As a user I want to burn tari via command line + Given I have a seed node SEED + When I have a base node BASE connected to seed SEED + When I have wallet WALLET connected to base node BASE + When I have mining node MINER connected to base node BASE and wallet WALLET + When mining node MINER mines 12 blocks + When I mine 3 blocks on BASE + Then all nodes are at height 15 + When I wait for wallet WALLET to have at least 221552530060 uT + When I create a burn transaction of 201552500000 uT from WALLET via command line + When I mine 5 blocks on BASE + Then all nodes are at height 20 + Then I get balance of wallet WALLET is at least 20000000000 uT via command line + # # TODO: verify the actual burned kernel + # + @long-running + Scenario: As a user I want to send one-sided via command line + Given I have a seed node SEED + When I have a base node BASE connected to seed SEED + When I have wallet SENDER connected to base node BASE + When I have wallet RECEIVER connected to base node BASE + When I have mining node MINE connected to base node BASE and wallet SENDER + When mining node MINE mines 5 blocks + Then I wait for wallet SENDER to have at least 1100000 uT + # # TODO: Remove this wait when the wallet CLI commands involving transactions will only commence with a valid + # # TODO: base node connection. + When I wait 30 seconds + Then I stop wallet SENDER + Then I send one-sided 1000000 uT from SENDER to RECEIVER via command line + Then wallet SENDER has at least 1 transactions that are all TRANSACTION_STATUS_BROADCAST and not cancelled + When mining node MINE mines 5 blocks + Then I wait for wallet RECEIVER to have at least 1000000 uT + + @long-running + Scenario: As a user I want to make-it-rain via command line + Given I have a seed node SEED + When I have a base node BASE connected to seed SEED + When I have wallet SENDER connected to base node BASE + When I have wallet RECEIVER connected to base node BASE + When I have mining node MINE connected to base node BASE and wallet SENDER + When mining node MINE mines 15 blocks + Then wallets SENDER should have AT_LEAST 12 spendable coinbase outputs + # # TODO: Remove this wait when the wallet CLI commands involving transactions will only commence with a valid + # # TODO: base node connection. + When I wait 30 seconds + Then I stop wallet SENDER + When I make it rain from wallet SENDER 1 tx per sec 10 sec 8000 uT 100 increment to RECEIVER via command line + Then wallet SENDER has at least 10 transactions that are all TRANSACTION_STATUS_BROADCAST and not cancelled + Then wallet RECEIVER has at least 10 transactions that are all TRANSACTION_STATUS_BROADCAST and not cancelled + When mining node MINE mines 5 blocks + Then I wait for wallet RECEIVER to have at least 84500 uT + + @long-running + Scenario: As a user I want to coin-split via command line + Given I have a seed node SEED + When I have a base node BASE connected to seed SEED + When I have wallet WALLET connected to base node BASE + When I have mining node MINE connected to base node BASE and wallet WALLET + When mining node MINE mines 4 blocks + Then I wait for wallet WALLET to have at least 1100000 uT + # # TODO: Remove this wait when the wallet CLI commands involving transactions will only commence with a valid + # # TODO: base node connection. + When I wait 30 seconds + When I do coin split on wallet WALLET to 10000 uT 10 coins via command line + Then wallet WALLET has at least 1 transactions that are all TRANSACTION_STATUS_BROADCAST and not cancelled + When mining node MINE mines 5 blocks + Then wallet WALLET has at least 1 transactions that are all TRANSACTION_STATUS_MINED_CONFIRMED and not cancelled + Then I get count of utxos of wallet WALLET and it's at least 10 via command line + + Scenario: As a user I want to count utxos via command line + Given I have a base node BASE + When I have wallet WALLET connected to base node BASE + When I have mining node MINE connected to base node BASE and wallet WALLET + When mining node MINE mines 4 blocks + Then I wait for wallet WALLET to have at least 1000000 uT + Then I stop wallet WALLET + Then I get count of utxos of wallet WALLET and it's at least 1 via command line + + Scenario: As a user I want to export utxos via command line + Given I have a base node BASE + When I have wallet WALLET connected to base node BASE + When I have mining node MINE connected to base node BASE and wallet WALLET + When mining node MINE mines 4 blocks + Then I wait for wallet WALLET to have at least 1000000 uT + When I export the utxos of wallet WALLET via command line + + @flaky + Scenario: As a user I want to discover-peer via command line + Given I have a seed node SEED + When I have wallet WALLET connected to seed node SEED + When I have a base node BASE1 connected to seed SEED + When I have a base node BASE2 connected to seed SEED + When I discover peer BASE2 on wallet WALLET via command line + Then WALLET is connected to BASE2 + + Scenario: As a user I want to run whois via command line + Given I have a base node BASE + When I have wallet WALLET connected to base node BASE + Then I run whois BASE on wallet WALLET via command line diff --git a/integration_tests/features/WalletFFI.feature b/integration_tests/tests/features/WalletFFI.feature similarity index 57% rename from integration_tests/features/WalletFFI.feature rename to integration_tests/tests/features/WalletFFI.feature index 771a39c9ba..aa17c6272f 100644 --- a/integration_tests/features/WalletFFI.feature +++ b/integration_tests/tests/features/WalletFFI.feature @@ -3,33 +3,18 @@ @wallet-ffi Feature: Wallet FFI - # Appears to run in NodeJS v12 consistently on mac (5 crash-less runs in a row). - # Crashes in v14+ intermittently on mac (more frequently than not) and completely broken on Linux. - # See issues: - # https://github.com/nodejs/node/issues/32463 - # https://github.com/node-ffi-napi/node-ffi-napi/issues/97 - - @critical - Scenario: As a client I want to be able to protect my wallet with a passphrase - Given I have a base node BASE - And I have a ffi wallet FFI_WALLET connected to base node BASE - # It's just calling the encrypt function, we don't test if it's actually encrypted - And I set passphrase PASSPHRASE of ffi wallet FFI_WALLET - And I stop ffi wallet FFI_WALLET - Scenario: As a client I want to see my whoami info Given I have a base node BASE - And I have a ffi wallet FFI_WALLET connected to base node BASE + Given I have a ffi wallet FFI_WALLET connected to base node BASE Then I want to get public key of ffi wallet FFI_WALLET And I want to get emoji id of ffi wallet FFI_WALLET And I stop ffi wallet FFI_WALLET - @broken Scenario: As a client I want to be able to restore my ffi wallet from seed words Given I have a base node BASE - And I have wallet SPECTATOR connected to base node BASE - And I have mining node MINER connected to base node BASE and wallet SPECTATOR - And mining node MINER mines 10 blocks + When I have wallet SPECTATOR connected to base node BASE + When I have mining node MINER connected to base node BASE and wallet SPECTATOR + When mining node MINER mines 10 blocks Then I wait for wallet SPECTATOR to have at least 1000000 uT Then I recover wallet SPECTATOR into ffi wallet FFI_WALLET from seed words on node BASE And I wait for ffi wallet FFI_WALLET to have at least 1000000 uT @@ -37,16 +22,13 @@ Feature: Wallet FFI @critical Scenario: As a client I want to retrieve the mnemonic word list for a given language - Given I have a base node BASE - And I have a ffi wallet FFI_WALLET connected to base node BASE - Then I retrieve the mnemonic word list for CHINESE_SIMPLIFIED from ffi wallet FFI_WALLET - Then I retrieve the mnemonic word list for ENGLISH from ffi wallet FFI_WALLET - Then I retrieve the mnemonic word list for FRENCH from ffi wallet FFI_WALLET - Then I retrieve the mnemonic word list for ITALIAN from ffi wallet FFI_WALLET - Then I retrieve the mnemonic word list for JAPANESE from ffi wallet FFI_WALLET - Then I retrieve the mnemonic word list for KOREAN from ffi wallet FFI_WALLET - Then I retrieve the mnemonic word list for SPANISH from ffi wallet FFI_WALLET - And I stop ffi wallet FFI_WALLET + Then I retrieve the mnemonic word list for CHINESE_SIMPLIFIED + Then I retrieve the mnemonic word list for ENGLISH + Then I retrieve the mnemonic word list for FRENCH + Then I retrieve the mnemonic word list for ITALIAN + Then I retrieve the mnemonic word list for JAPANESE + Then I retrieve the mnemonic word list for KOREAN + Then I retrieve the mnemonic word list for SPANISH Scenario: As a client I want to set the base node Given I have a base node BASE1 @@ -56,19 +38,20 @@ Feature: Wallet FFI Given I set base node BASE2 for ffi wallet FFI_WALLET Then I wait for ffi wallet FFI_WALLET to connect to BASE2 + @broken Scenario: As a client I want to cancel a transaction Given I have a base node BASE - And I have wallet SENDER connected to base node BASE + When I have wallet SENDER connected to base node BASE And I have a ffi wallet FFI_WALLET connected to base node BASE - And I have mining node MINER connected to base node BASE and wallet SENDER - And mining node MINER mines 10 blocks + When I have mining node MINER connected to base node BASE and wallet SENDER + When mining node MINER mines 10 blocks Then I wait for wallet SENDER to have at least 1000000 uT And I send 2000000 uT without waiting for broadcast from wallet SENDER to wallet FFI_WALLET at fee 20 Then ffi wallet FFI_WALLET detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_BROADCAST And wallet SENDER detects all transactions are at least Broadcast - And mining node MINER mines 10 blocks + When mining node MINER mines 10 blocks Then I wait for ffi wallet FFI_WALLET to have at least 1000000 uT - And I have wallet RECEIVER connected to base node BASE + When I have wallet RECEIVER connected to base node BASE And I stop wallet RECEIVER And I send 1000000 uT from ffi wallet FFI_WALLET to wallet RECEIVER at fee 20 Then I wait for ffi wallet FFI_WALLET to have 1 pending outbound transaction @@ -78,15 +61,14 @@ Feature: Wallet FFI Scenario: As a client I want to manage contacts Given I have a base node BASE And I have a ffi wallet FFI_WALLET connected to base node BASE - And I have wallet WALLET connected to base node BASE - And I add contact with alias ALIAS and pubkey WALLET to ffi wallet FFI_WALLET - Then I have contact with alias ALIAS and pubkey WALLET in ffi wallet FFI_WALLET + When I have wallet WALLET connected to base node BASE + And I add contact with alias ALIAS and address of WALLET to ffi wallet FFI_WALLET + Then I have contact with alias ALIAS and address of WALLET in ffi wallet FFI_WALLET When I remove contact with alias ALIAS from ffi wallet FFI_WALLET Then I don't have contact with alias ALIAS in ffi wallet FFI_WALLET And I stop ffi wallet FFI_WALLET - # TODO: Was broken due to #4525 - fix underway - @critical @broken + @critical Scenario: As a client I want to receive contact liveness events Given I have a seed node SEED # Contact liveness is based on P2P messaging; ensure connectivity by forcing 'DirectOnly' @@ -94,14 +76,16 @@ Feature: Wallet FFI And I have non-default wallet WALLET2 connected to all seed nodes using DirectOnly And I have a ffi wallet FFI_WALLET connected to seed node SEED # Start the contact liveness pings by adding contacts to the FFI wallet - And I add contact with alias ALIAS1 and pubkey WALLET1 to ffi wallet FFI_WALLET - And I add contact with alias ALIAS2 and pubkey WALLET2 to ffi wallet FFI_WALLET + When I add contact with alias ALIAS1 and address of WALLET1 to ffi wallet FFI_WALLET + And I add contact with alias ALIAS2 and address of WALLET2 to ffi wallet FFI_WALLET # Do some mining and send transactions to force P2P discovery And I have mining node MINER1 connected to base node SEED and wallet WALLET1 And I have mining node MINER2 connected to base node SEED and wallet WALLET2 And mining node MINER1 mines 1 blocks And mining node MINER2 mines 5 blocks - And I send 100000000 uT without waiting for broadcast from wallet WALLET1 to wallet FFI_WALLET at fee 20 + Then I wait for wallet WALLET1 to have at least 100000000 uT + And I wait for wallet WALLET2 to have at least 100000000 uT + When I send 100000000 uT without waiting for broadcast from wallet WALLET1 to wallet FFI_WALLET at fee 20 And I send 100000000 uT without waiting for broadcast from wallet WALLET2 to wallet FFI_WALLET at fee 20 # If the FFI wallet can send the transactions, P2P connectivity has been established Then I wait for ffi wallet FFI_WALLET to have at least 2 contacts to be Online @@ -110,26 +94,26 @@ Feature: Wallet FFI @critical Scenario: As a client I want to retrieve a list of transactions I have made and received Given I have a seed node SEED - And I have a base node BASE1 connected to all seed nodes - And I have a base node BASE2 connected to all seed nodes - And I have wallet SENDER connected to base node BASE1 + When I have a base node BASE1 connected to all seed nodes + When I have a base node BASE2 connected to all seed nodes + When I have wallet SENDER connected to base node BASE1 And I have a ffi wallet FFI_WALLET connected to base node BASE2 - And I have wallet RECEIVER connected to base node BASE2 - And I have mining node MINER connected to base node BASE1 and wallet SENDER - And mining node MINER mines 10 blocks - Then I wait for wallet SENDER to have at least 1000000 uT + When I have wallet RECEIVER connected to base node BASE2 + When I have mining node MINER connected to base node BASE1 and wallet SENDER + When mining node MINER mines 10 blocks + Then I wait for wallet SENDER to have at least 2000000 uT And I send 2000000 uT from wallet SENDER to wallet FFI_WALLET at fee 20 Then ffi wallet FFI_WALLET detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_BROADCAST - And mining node MINER mines 10 blocks + When mining node MINER mines 10 blocks Then I wait for ffi wallet FFI_WALLET to have at least 1000000 uT And I send 1000000 uT from ffi wallet FFI_WALLET to wallet RECEIVER at fee 20 Then ffi wallet FFI_WALLET detects AT_LEAST 2 ffi transactions to be TRANSACTION_STATUS_BROADCAST # The broadcast check does not include delivery; create some holding points to ensure it was received - And mining node MINER mines 2 blocks + When mining node MINER mines 2 blocks Then all nodes are at height 22 - And mining node MINER mines 2 blocks + When mining node MINER mines 2 blocks Then all nodes are at height 24 - And mining node MINER mines 6 blocks + When mining node MINER mines 6 blocks Then I wait for wallet RECEIVER to have at least 1000000 uT And I have 1 received and 1 send transaction in ffi wallet FFI_WALLET And I start TXO validation on ffi wallet FFI_WALLET @@ -138,50 +122,51 @@ Feature: Wallet FFI Then I want to view the transaction kernels for completed transactions in ffi wallet FFI_WALLET And I stop ffi wallet FFI_WALLET - @critical + + @critical @broken Scenario: As a client I want to receive Tari via my Public Key sent while I am offline when I come back online Given I have a seed node SEED - And I have a base node BASE1 connected to all seed nodes - And I have a base node BASE2 connected to all seed nodes - And I have wallet SENDER connected to base node BASE1 + When I have a base node BASE1 connected to all seed nodes + When I have a base node BASE2 connected to all seed nodes + When I have wallet SENDER connected to base node BASE1 And I have a ffi wallet FFI_WALLET connected to base node BASE1 - And I have mining node MINER connected to base node BASE1 and wallet SENDER - And mining node MINER mines 10 blocks + When I have mining node MINER connected to base node BASE1 and wallet SENDER + When mining node MINER mines 10 blocks Then I wait for wallet SENDER to have at least 1000000 uT And I stop ffi wallet FFI_WALLET - And I send 2000000 uT without waiting for broadcast from wallet SENDER to wallet FFI_WALLET at fee 20 + And I send 1000000 uT without waiting for broadcast from wallet SENDER to wallet FFI_WALLET at fee 20 And I restart ffi wallet FFI_WALLET connected to base node BASE2 # BROKEN Then I wait for ffi wallet FFI_WALLET to receive 1 transaction Then I wait for ffi wallet FFI_WALLET to receive 1 finalization Then I wait for ffi wallet FFI_WALLET to receive 1 broadcast - And mining node MINER mines 10 blocks + When mining node MINER mines 10 blocks Then I wait for ffi wallet FFI_WALLET to receive 1 mined Then I wait for ffi wallet FFI_WALLET to have at least 1000000 uT And I stop ffi wallet FFI_WALLET - @critical + @critical @broken Scenario: As a client I want to send a one-sided transaction Given I have a seed node SEED - And I have a base node BASE1 connected to all seed nodes - And I have a base node BASE2 connected to all seed nodes - And I have wallet SENDER connected to base node BASE1 + When I have a base node BASE1 connected to all seed nodes + When I have a base node BASE2 connected to all seed nodes + When I have wallet SENDER connected to base node BASE1 And I have a ffi wallet FFI_WALLET connected to base node BASE2 - And I have wallet RECEIVER connected to base node BASE2 - And I have mining node MINER connected to base node BASE1 and wallet SENDER - And mining node MINER mines 10 blocks + When I have wallet RECEIVER connected to base node BASE2 + When I have mining node MINER connected to base node BASE1 and wallet SENDER + When mining node MINER mines 10 blocks Then I wait for wallet SENDER to have at least 5000000 uT And I send 2400000 uT from wallet SENDER to wallet FFI_WALLET at fee 5 And I send 2400000 uT from wallet SENDER to wallet FFI_WALLET at fee 5 Then ffi wallet FFI_WALLET detects AT_LEAST 2 ffi transactions to be TRANSACTION_STATUS_BROADCAST - And mining node MINER mines 10 blocks + When mining node MINER mines 10 blocks Then I wait for ffi wallet FFI_WALLET to have at least 4000000 uT And I send 1000000 uT from ffi wallet FFI_WALLET to wallet RECEIVER at fee 5 via one-sided transactions Then ffi wallet FFI_WALLET detects AT_LEAST 2 ffi transactions to be TRANSACTION_STATUS_BROADCAST - And mining node MINER mines 2 blocks + When mining node MINER mines 2 blocks Then all nodes are at height 22 Then wallet RECEIVER has at least 1 transactions that are all TRANSACTION_STATUS_FAUX_UNCONFIRMED and not cancelled - And mining node MINER mines 5 blocks + When mining node MINER mines 5 blocks Then all nodes are at height 27 Then wallet RECEIVER has at least 1 transactions that are all TRANSACTION_STATUS_FAUX_CONFIRMED and not cancelled And I stop ffi wallet FFI_WALLET @@ -189,32 +174,30 @@ Feature: Wallet FFI @critical Scenario: As a client I want to receive a one-sided transaction Given I have a seed node SEED - And I have a base node BASE1 connected to all seed nodes - And I have a base node BASE2 connected to all seed nodes - And I have wallet SENDER connected to base node BASE1 + When I have a base node BASE1 connected to all seed nodes + When I have a base node BASE2 connected to all seed nodes + When I have wallet SENDER connected to base node BASE1 And I have a ffi wallet FFI_RECEIVER connected to base node BASE2 - And I have mining node MINER connected to base node BASE1 and wallet SENDER - And mining node MINER mines 10 blocks + When I have mining node MINER connected to base node BASE1 and wallet SENDER + When mining node MINER mines 10 blocks Then I wait for wallet SENDER to have at least 5000000 uT Then I send a one-sided transaction of 1000000 uT from SENDER to FFI_RECEIVER at fee 20 - And mining node MINER mines 2 blocks + When mining node MINER mines 2 blocks Then all nodes are at height 12 - #BROKEN Then ffi wallet FFI_RECEIVER detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_FAUX_UNCONFIRMED And I send 1000000 uT from wallet SENDER to wallet FFI_RECEIVER at fee 20 Then ffi wallet FFI_RECEIVER detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_BROADCAST - And mining node MINER mines 5 blocks + When mining node MINER mines 5 blocks Then all nodes are at height 17 Then ffi wallet FFI_RECEIVER detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_FAUX_CONFIRMED And I stop ffi wallet FFI_RECEIVER Scenario: As a client I want to get fee per gram stats Given I have a base node BASE - And I have wallet WALLET_A connected to base node BASE - And I have wallet WALLET_B connected to base node BASE - And I have mining node MINER connected to base node BASE and wallet WALLET_A - And mining node MINER mines 7 blocks - And I have wallet WALLET_B connected to base node BASE + When I have wallet WALLET_A connected to base node BASE + When I have wallet WALLET_B connected to base node BASE + When I have mining node MINER connected to base node BASE and wallet WALLET_A + When mining node MINER mines 7 blocks Then I wait for wallet WALLET_A to have at least 10000000 uT And I have a ffi wallet FFI_WALLET connected to base node BASE And The fee per gram stats for FFI_WALLET are 1, 1, 1 @@ -224,31 +207,5 @@ Feature: Wallet FFI And The fee per gram stats for FFI_WALLET are 20, 30, 40 And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 60 And The fee per gram stats for FFI_WALLET are 20, 40, 60 - And mining node MINER mines 1 blocks + When mining node MINER mines 1 blocks And The fee per gram stats for FFI_WALLET are 1, 1, 1 - - # Scenario: As a client I want to get my balance - # It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" - - #Scenario: As a client I want to send Tari to a Public Key - # It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" - - #Scenario: As a client I want to specify a custom fee when I send tari - # It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" - - #Scenario: As a client I want to receive Tari via my Public Key while I am online - # It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" - - # Scenario: As a client I want to be able to initiate TXO and TX validation with the specifed base node. - # It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" - - # Scenario: As a client I want feedback about the progress of sending and receiving a transaction - # It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" - - # Scenario: As a client I want feedback about my connection status to the specifed Base Node - - # Scenario: As a client I want feedback about the wallet restoration process - # It's a subtest of "As a client I want to be able to restore my wallet from seed words" - - # Scenario: As a client I want feedback about TXO and TX validation processes - # It's a subtest of "As a client I want to retrieve a list of transactions I have made and received" diff --git a/integration_tests/tests/features/WalletMonitoring.feature b/integration_tests/tests/features/WalletMonitoring.feature new file mode 100644 index 0000000000..77ee202f52 --- /dev/null +++ b/integration_tests/tests/features/WalletMonitoring.feature @@ -0,0 +1,151 @@ +# Copyright 2022 The Tari Project +# SPDX-License-Identifier: BSD-3-Clause + +@wallet-monitoring @wallet +Feature: Wallet Monitoring + + + @flaky + Scenario: Wallets monitoring coinbase after a reorg + # # + # # Chain 1: + # # Collects 10 coinbases into one wallet + # # + Given I have a seed node SEED_A + # # Add multiple base nodes to ensure more robust comms + When I have a base node NODE_A1 connected to seed SEED_A + When I have wallet WALLET_A1 connected to seed node SEED_A + When I have mining node MINING_A connected to base node SEED_A and wallet WALLET_A1 + When mining node MINING_A mines 10 blocks + Then all nodes are at height 10 + # And I list all COINBASE transactions for wallet WALLET_A1 + # Then wallet WALLET_A1 has 10 coinbase transactions + # Then all COINBASE transactions for wallet WALLET_A1 are valid + # Then wallet WALLET_A1 detects at least 7 coinbase transactions as Mined_Confirmed + # # + # # Chain 2: + # # Collects 10 coinbases into one wallet + # # + When I have a seed node SEED_B + # # Add multiple base nodes to ensure more robust comms + When I have a base node NODE_B1 connected to seed SEED_B + When I have wallet WALLET_B1 connected to seed node SEED_B + When I have mining node MINING_B connected to base node SEED_B and wallet WALLET_B1 + When mining node MINING_B mines 10 blocks + Then all nodes are at height 10 + # And I list all COINBASE transactions for wallet WALLET_B1 + # Then wallet WALLET_B1 has 10 coinbase transactions + # Then all COINBASE transactions for wallet WALLET_B1 are valid + # Then wallet WALLET_B1 detects at least 7 coinbase transactions as Mined_Confirmed + # # + # # Connect Chain 1 and 2 + # # + # And I have a SHA3 miner NODE_C connected to all seed nodes + # Then all nodes are at height 10 + # # When tip advances past required confirmations, invalid coinbases still being monitored will be cancelled. + # When mining node NODE_C mines 6 blocks + # Then all nodes are at height 16 + # # Wait for coinbase statuses to change in the wallet + When I wait 30 seconds + # And I list all COINBASE transactions for wallet WALLET_A1 + # And I list all COINBASE transactions for wallet WALLET_B1 + # Then all COINBASE transactions for wallet WALLET_A1 and wallet WALLET_B1 have consistent but opposing cancellation + + # 18+ mins on circle ci + @long-running + Scenario: Wallets monitoring normal transactions after a reorg + # Chain 1: + # Collects 10 coinbases into one wallet, send 7 transactions + # + When I have a seed node SEED_A + # Add multiple base nodes to ensure more robust comms + When I have a base node NODE_A1 connected to seed SEED_A + When I have wallet WALLET_A1 connected to seed node SEED_A + When I have wallet WALLET_A2 connected to seed node SEED_A + When I have mining node MINING_A connected to base node SEED_A and wallet WALLET_A1 + # When mining node MINING_A mines 10 blocks with min difficulty 20 and max difficulty 9999999999 + # Then node SEED_A is at height 10 + # Then node NODE_A1 is at height 10 + # Then wallet WALLET_A1 detects exactly 7 coinbase transactions as Mined_Confirmed + # # Use 7 of the 10 coinbase UTXOs in transactions (others require 3 confirmations) + # And I multi-send 7 transactions of 1000000 uT from wallet WALLET_A1 to wallet WALLET_A2 at fee 100 + # When mining node MINING_A mines 10 blocks with min difficulty 20 and max difficulty 9999999999 + # Then node SEED_A is at height 20 + # Then node NODE_A1 is at height 20 + # Then wallet WALLET_A2 detects all transactions as Mined_Confirmed + # Then all NORMAL transactions for wallet WALLET_A1 are valid + # Then wallet WALLET_A1 detects exactly 17 coinbase transactions as Mined_Confirmed + # # + # # Chain 2: + # # Collects 10 coinbases into one wallet, send 7 transactions + # # + When I have a seed node SEED_B + # # Add multiple base nodes to ensure more robust comms + When I have a base node NODE_B1 connected to seed SEED_B + When I have wallet WALLET_B1 connected to seed node SEED_B + When I have wallet WALLET_B2 connected to seed node SEED_B + When I have mining node MINING_B connected to base node SEED_B and wallet WALLET_B1 + # When mining node MINING_B mines 10 blocks with min difficulty 1 and max difficulty 2 + # Then node SEED_B is at height 10 + # Then node NODE_B1 is at height 10 + # Then wallet WALLET_B1 detects exactly 7 coinbase transactions as Mined_Confirmed + # # Use 7 of the 10 coinbase UTXOs in transactions (others require 3 confirmations) + # And I multi-send 7 transactions of 1000000 uT from wallet WALLET_B1 to wallet WALLET_B2 at fee 100 + # When mining node MINING_B mines 10 blocks with min difficulty 1 and max difficulty 2 + # Then node SEED_B is at height 20 + # Then node NODE_B1 is at height 20 + # Then wallet WALLET_B2 detects all transactions as Mined_Confirmed + # Then all NORMAL transactions for wallet WALLET_B1 are valid + # Then wallet WALLET_B1 detects exactly 17 coinbase transactions as Mined_Confirmed + # # + # # Connect Chain 1 and 2 + # # + # And I have a SHA3 miner NODE_C connected to all seed nodes + # Then all nodes are at height 20 + # # When tip advances past required confirmations, invalid coinbases still being monitored will be cancelled. + # And mining node NODE_C mines 6 blocks + # Then all nodes are at height 26 + # Then wallet WALLET_A1 detects exactly 20 coinbase transactions as Mined_Confirmed + # Then wallet WALLET_B1 detects exactly 17 coinbase transactions as Mined_Confirmed + # And I list all NORMAL transactions for wallet WALLET_A1 + # And I list all NORMAL transactions for wallet WALLET_B1 + # # TODO: Uncomment this step when wallets can handle reorg +# # Then all NORMAL transactions for wallet WALLET_A1 and wallet WALLET_B1 have consistent but opposing cancellation + # And I list all NORMAL transactions for wallet WALLET_A2 + # And I list all NORMAL transactions for wallet WALLET_B2 + # # TODO: Uncomment this step when wallets can handle reorg +# # Then all NORMAL transactions for wallet WALLET_A2 and wallet WALLET_B2 have consistent but opposing cancellation + When I wait 1 seconds + + Scenario Outline: Verify all coinbases in hybrid mining are accounted for + Given I have a seed node SEED_A + # And I have a SHA3 miner MINER_SEED_A connected to seed node SEED_A + + When I have a base node NODE1 connected to seed SEED_A + When I have wallet WALLET1 connected to base node NODE1 + # And I have a merge mining proxy PROXY1 connected to NODE1 and WALLET1 with default config + + When I have a base node NODE2 connected to seed SEED_A + When I have wallet WALLET2 connected to base node NODE2 + When I have mining node MINER2 connected to base node NODE2 and wallet WALLET2 + + # When I co-mine blocks via merge mining proxy PROXY1 and mining node MINER2 + # Then all nodes are on the same chain tip + + # And mining node MINER_SEED_A mines 5 blocks + # Then all nodes are on the same chain tip + + When I wait 1 seconds + # Then wallets WALLET1,WALLET2 should have AT_LEAST spendable coinbase outputs + + # @flaky + # Examples: + # | numBlocks | + # | 10 | + + # @long-running @flaky + # Examples: + # | numBlocks | + # | 100 | + # | 1000 | + # | 4500 | diff --git a/integration_tests/tests/features/WalletQuery.feature b/integration_tests/tests/features/WalletQuery.feature new file mode 100644 index 0000000000..6b4c36f1c3 --- /dev/null +++ b/integration_tests/tests/features/WalletQuery.feature @@ -0,0 +1,37 @@ +# Copyright 2022 The Tari Project +# SPDX-License-Identifier: BSD-3-Clause + +@wallet-query @wallet +Feature: Wallet Querying + + Scenario: As a wallet I want to query the status of utxos in blocks + # Given I have a seed node WalletSeedA + # When I mine a block on WalletSeedA with coinbase CB1 + # Then node WalletSeedA is at height 1 + # Then the UTXO CB1 has been mined according to WalletSeedA + + @critical + Scenario: As a wallet I want to submit a transaction + # Given I have a seed node SeedA + # When I mine a block on SeedA with coinbase CB1 + # When I mine 2 blocks on SeedA + # When I create a transaction TX1 spending CB1 to UTX1 + # When I submit transaction TX1 to SeedA + # Then TX1 is in the mempool + # When I mine 2 blocks on SeedA + # Then the UTXO UTX1 has been mined according to SeedA + + + @critical + Scenario: As a wallet I cannot submit a locked coinbase transaction + # # Using GRPC + # Given I have a seed node SeedA + # When I mine a block on SeedA with coinbase CB1 + # When I create a transaction TX1 spending CB1 to UTX1 + # When I submit locked transaction TX1 to SeedA + # Then TX1 should not be in the mempool + # When I mine 2 blocks on SeedA + # When I submit transaction TX1 to SeedA + # Then TX1 is in the mempool + # When I mine 1 blocks on SeedA + # Then the UTXO UTX1 has been mined according to SeedA diff --git a/integration_tests/tests/features/WalletRecovery.feature b/integration_tests/tests/features/WalletRecovery.feature new file mode 100644 index 0000000000..ea7e48d7c4 --- /dev/null +++ b/integration_tests/tests/features/WalletRecovery.feature @@ -0,0 +1,82 @@ +# Copyright 2022 The Tari Project +# SPDX-License-Identifier: BSD-3-Clause + +@wallet-recovery @wallet +Feature: Wallet Recovery + + @critical + Scenario: Wallet recovery with connected base node staying online + Given I have a seed node NODE + # And I have 1 base nodes connected to all seed nodes + When I have wallet WALLET_A connected to all seed nodes + When I have wallet WALLET_B connected to all seed nodes + When I have mining node MINER connected to base node NODE and wallet WALLET_A + When mining node MINER mines 10 blocks + When I mine 5 blocks on NODE + # When I wait for wallet WALLET_A to have at least 55000000000 uT + Then all nodes are at height 15 + # And I send 200000 uT from wallet WALLET_A to wallet WALLET_B at fee 25 + When I have mining node MINER_B connected to base node NODE and wallet WALLET_B + When mining node MINER_B mines 2 blocks + When I mine 5 blocks on NODE + Then all nodes are at height 22 + # Then I stop wallet WALLET_B + # When I recover wallet WALLET_B into wallet WALLET_C connected to all seed nodes + # When I wait for wallet WALLET_C to have at least 10000200000 uT + When I have wallet WALLET_D connected to all seed nodes + # And I send 100000 uT from wallet WALLET_C to wallet WALLET_D at fee 25 + When I mine 5 blocks on NODE + Then all nodes are at height 27 + # Then I wait for wallet WALLET_D to have at least 100000 uT + + Scenario Outline: Multiple Wallet recovery from seed node + Given I have a seed node NODE + # And I have non-default wallets connected to all seed nodes using DirectAndStoreAndForward + # And I have individual mining nodes connected to each wallet and base node NODE + # Then I have each mining node mine 3 blocks + # Then all nodes are at height 3* + # Then I stop all wallets + # When I recover all wallets connected to all seed nodes + # Then I wait for recovered wallets to have at least 15000000000 uT + # + # Examples: + # # | NumWallets | + # # | 4 # | + + # @long-running + # Examples: + # # | NumWallets | + # # | 5 # | + # # | 10 # | + # # | 20 # | + + # BROKEN: recovery scans 15 blocks but does not actually recover any value. Tested manually and recovery works. Suspect something to do with using localnet (just a guess). + @critical @broken + Scenario: Recover one-sided payments + Given I have a seed node NODE + # And I have 1 base nodes connected to all seed nodes + When I have wallet WALLET_A connected to all seed nodes + When I have wallet WALLET_B connected to all seed nodes + When I have mining node MINER connected to base node NODE and wallet WALLET_A + When mining node MINER mines 10 blocks + Then all nodes are at height 10 + # And I stop wallet WALLET_B + # # Send 2 one-sided payments to WALLET_B so it can spend them in two cases + # Then I send a one-sided transaction of 1000000 uT from WALLET_A to WALLET_B at fee 20 + # Then I send a one-sided transaction of 1000000 uT from WALLET_A to WALLET_B at fee 20 + When mining node MINER mines 5 blocks + Then all nodes are at height 15 + # When I recover wallet WALLET_B into wallet WALLET_C connected to all seed nodes + # # BREAKS HERE + # Then I wait for wallet WALLET_C to have at least 2000000 uT + # # Send one of the recovered outputs back to Wallet A as a one-sided transactions + # Then I send a one-sided transaction of 900000 uT from WALLET_C to WALLET_A at fee 20 + When mining node MINER mines 5 blocks + Then all nodes are at height 20 + # Then I wait for wallet WALLET_C to have less than 1100000 uT + # # Send the remaining recovered UTXO to self in standard MW transaction + # Then I send 1000000 uT from wallet WALLET_C to wallet WALLET_C at fee 20 + # Then I wait for wallet WALLET_C to have less than 100000 uT + When mining node MINER mines 5 blocks + Then all nodes are at height 25 + # Then I wait for wallet WALLET_C to have at least 1000000 uT diff --git a/integration_tests/tests/features/WalletRoutingMechanism.feature b/integration_tests/tests/features/WalletRoutingMechanism.feature new file mode 100644 index 0000000000..c8051d9fbd --- /dev/null +++ b/integration_tests/tests/features/WalletRoutingMechanism.feature @@ -0,0 +1,67 @@ +# Copyright 2022 The Tari Project +# SPDX-License-Identifier: BSD-3-Clause + +@wallet-routing_mechanism @wallet +Feature: Wallet Routing Mechanism + + @flaky + Scenario Outline: Wallets transacting via specified routing mechanism only + Given I have a seed node NODE + # And I have base nodes connected to all seed nodes + # And I have non-default wallet WALLET_A connected to all seed nodes using + # And I have mining node MINER connected to base node NODE and wallet WALLET_A + # And I have non-default wallets connected to all seed nodes using + # # We need to ensure the coinbase lock heights are gone and we have enough individual UTXOs; mine enough blocks + # And mining node MINER mines 20 blocks + # Then all nodes are at height 20 + # # TODO: This wait is needed to stop base nodes from shutting down + When I wait 1 seconds + # When I wait for wallet WALLET_A to have at least 100000000 uT + # #When I print the world + # And I multi-send 1000000 uT from wallet WALLET_A to all wallets at fee 100 + # # TODO: This wait is needed to stop next merge mining task from continuing + When I wait 1 seconds + # And mining node MINER mines 1 blocks + # Then all nodes are at height 21 + # Then all wallets detect all transactions as Mined_Unconfirmed + # # TODO: This wait is needed to stop next merge mining task from continuing + When I wait 1 seconds + # And mining node MINER mines 11 blocks + # Then all nodes are at height 32 + # Then all wallets detect all transactions as Mined_Confirmed + # # TODO: This wait is needed to stop base nodes from shutting down + When I wait 1 seconds + # @long-running + # Examples: + # # | NumBaseNodes | NumWallets | Mechanism # # | + # # | 5 # # | 5 # | DirectAndStoreAndForward | + # # | 5 # # | 5 # | DirectOnly # # | + + # @long-running + # Examples: + # # | NumBaseNodes | NumWallets | Mechanism # | + # # | 5 # # | 5 # | StoreAndForwardOnly | + + Scenario: Store and forward TX + Given I have a seed node SEED + When I have a base node BASE connected to seed SEED + When I have wallet SENDER connected to base node BASE + When I have wallet RECEIVER connected to base node BASE + # And I stop wallet RECEIVER + When I have mining node MINE connected to base node BASE and wallet SENDER + # And mining node MINE mines 5 blocks + # Then I wait for wallet SENDER to have at least 1000000 uT + # And I send 1000000 uT from wallet SENDER to wallet RECEIVER at fee 100 + # + # + # 10 minutes of waiting... + # + # + # When I wait 121 seconds + # And I stop wallet SENDER + # When I wait 360 seconds + # And I restart wallet RECEIVER + # When I wait 121 seconds + # And I stop wallet RECEIVER + # And I restart wallet SENDER + # And wallet SENDER detects all transactions are at least Broadcast \ No newline at end of file diff --git a/integration_tests/tests/features/WalletTransactions.feature b/integration_tests/tests/features/WalletTransactions.feature new file mode 100644 index 0000000000..6c62406c07 --- /dev/null +++ b/integration_tests/tests/features/WalletTransactions.feature @@ -0,0 +1,422 @@ +# Copyright 2022 The Tari Project +# SPDX-License-Identifier: BSD-3-Clause + +@wallet-transact @wallet @flaky +Feature: Wallet Transactions + + @critical @flaky + Scenario: Wallet sending and receiving one-sided transactions + Given I have a seed node NODE + When I have 1 base nodes connected to all seed nodes + When I have wallet WALLET_A connected to all seed nodes + When I have wallet WALLET_B connected to all seed nodes + When I have wallet WALLET_C connected to all seed nodes + When I have mining node MINER connected to base node NODE and wallet WALLET_A + When mining node MINER mines 15 blocks + Then all nodes are at height 15 + When I wait 5 seconds + When I wait for wallet WALLET_A to have at least 55000000000 uT + Then I send a one-sided transaction of 1000000 uT from WALLET_A to WALLET_B at fee 100 + Then I send a one-sided transaction of 1000000 uT from WALLET_A to WALLET_B at fee 100 + When mining node MINER mines 5 blocks + Then all nodes are at height 20 + Then I wait for wallet WALLET_B to have at least 2000000 uT + # Spend one of the recovered UTXOs to self in a standard MW transaction + Then I send 900000 uT from wallet WALLET_B to wallet WALLET_B at fee 20 + Then I wait for wallet WALLET_B to have less than 1100000 uT + When mining node MINER mines 5 blocks + Then all nodes are at height 25 + Then I wait for wallet WALLET_B to have at least 1900000 uT + # Make a one-sided payment to a new wallet that is big enough to ensure the second recovered output is spent + Then I send a one-sided transaction of 1500000 uT from WALLET_B to WALLET_C at fee 20 + Then I wait for wallet WALLET_B to have less than 1000000 uT + When mining node MINER mines 5 blocks + Then all nodes are at height 30 + Then I wait for wallet WALLET_C to have at least 1500000 uT + + @critical + Scenario: Wallet sending and receiving one-sided stealth transactions + Given I have a seed node NODE + When I have 1 base nodes connected to all seed nodes + When I have wallet WALLET_A connected to all seed nodes + When I have wallet WALLET_B connected to all seed nodes + When I have wallet WALLET_C connected to all seed nodes + When I have mining node MINER connected to base node NODE and wallet WALLET_A + When mining node MINER mines 15 blocks + Then all nodes are at height 15 + When I wait for wallet WALLET_A to have at least 55000000000 uT + Then I send a one-sided stealth transaction of 1000000 uT from WALLET_A to WALLET_B at fee 100 + Then I send a one-sided stealth transaction of 1000000 uT from WALLET_A to WALLET_B at fee 100 + When mining node MINER mines 5 blocks + Then all nodes are at height 20 + Then I wait for wallet WALLET_B to have at least 2000000 uT + # Spend one of the recovered UTXOs to self in a standard MW transaction + Then I send 900000 uT from wallet WALLET_B to wallet WALLET_B at fee 20 + Then I wait for wallet WALLET_B to have less than 2100000 uT + When mining node MINER mines 5 blocks + Then all nodes are at height 25 + Then I wait for wallet WALLET_B to have at least 1900000 uT + # Make a one-sided payment to a new wallet that is big enough to ensure the second recovered output is spent + Then I send a one-sided stealth transaction of 1500000 uT from WALLET_B to WALLET_C at fee 20 + Then I wait for wallet WALLET_B to have less than 1000000 uT + When mining node MINER mines 5 blocks + Then all nodes are at height 30 + Then I wait for wallet WALLET_C to have at least 1500000 uT + + Scenario: Wallet imports unspent output + Given I have a seed node NODE + When I have 1 base nodes connected to all seed nodes + When I have wallet WALLET_A connected to all seed nodes + When I have wallet WALLET_B connected to all seed nodes + When I have wallet WALLET_C connected to all seed nodes + When I have mining node MINER connected to base node NODE and wallet WALLET_A + When mining node MINER mines 5 blocks + Then all nodes are at height 5 + Then I wait for wallet WALLET_A to have at least 10000000000 uT + When I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 + When mining node MINER mines 5 blocks + Then all nodes are at height 10 + Then I wait for wallet WALLET_B to have at least 1000000 uT + Then I stop wallet WALLET_B + When I wait 5 seconds + Then I import WALLET_B unspent outputs to WALLET_C + Then I wait for wallet WALLET_C to have at least 1000000 uT + Then I restart wallet WALLET_C + Then I wait for wallet WALLET_C to have at least 1000000 uT + Then I check if last imported transactions are valid in wallet WALLET_C + + Scenario: Wallet has two connected miners, coinbase's are computed correctly + Given I have a seed node NODE + When I have 1 base nodes connected to all seed nodes + When I have wallet WALLET_A connected to all seed nodes + When I have mining node MINER connected to base node NODE and wallet WALLET_A + When I have mining node MINER2 connected to base node NODE and wallet WALLET_A + When mining node MINER mines 2 blocks + When mining node MINER2 mines 2 blocks + When mining node MINER mines 3 blocks + When mining node MINER2 mines 3 blocks + Then all nodes are at height 10 + Then I wait for wallet WALLET_A to have at least 20000000000 uT + + # @flaky + # Scenario: Wallet imports spent outputs that become invalidated + # Given I have a seed node NODE + # When I have 1 base nodes connected to all seed nodes + # When I have wallet WALLET_A connected to all seed nodes + # When I have wallet WALLET_B connected to all seed nodes + # When I have wallet WALLET_C connected to all seed nodes + # When I have mining node MINER connected to base node NODE and wallet WALLET_A + # When mining node MINER mines 5 blocks + # Then all nodes are at height 5 + # Then I wait for wallet WALLET_A to have at least 10000000000 uT + # When I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 + # When mining node MINER mines 5 blocks + # Then all nodes are at height 10 + # Then I wait for wallet WALLET_B to have at least 1000000 uT + # When I send 900000 uT from wallet WALLET_B to wallet WALLET_A at fee 100 + # When mining node MINER mines 5 blocks + # Then all nodes are at height 15 + # When I wait for wallet WALLET_B to have at least 50000 uT + # Then I stop wallet WALLET_B + # When I wait 30 seconds + # Then I import WALLET_B spent outputs to WALLET_C + # Then I wait for wallet WALLET_C to have at least 1000000 uT + # Then I restart wallet WALLET_C + # Then I wait for wallet WALLET_C to have less than 1 uT + # Then I check if last imported transactions are invalid in wallet WALLET_C + + @flaky + Scenario: Wallet imports reorged outputs that become invalidated + # # Chain 1 + Given I have a seed node SEED_B + When I have a base node B connected to seed SEED_B + When I have wallet WB connected to base node B + When I have wallet WALLET_RECEIVE_TX connected to base node B + When I have wallet WALLET_IMPORTED connected to base node B + When I have mining node BM connected to base node B and wallet WB + When mining node BM mines 4 blocks with min difficulty 1 and max difficulty 50 + Then I wait for wallet WB to have at least 1000000 uT + When I send 1000000 uT from wallet WB to wallet WALLET_RECEIVE_TX at fee 100 + Then mining node BM mines 4 blocks with min difficulty 50 and max difficulty 100 + When node B is at height 8 + Then I wait for wallet WALLET_RECEIVE_TX to have at least 1000000 uT + Then I stop wallet WALLET_RECEIVE_TX + When I wait 30 seconds + Then I import WALLET_RECEIVE_TX unspent outputs to WALLET_IMPORTED + Then I wait for wallet WALLET_IMPORTED to have at least 1000000 uT + # # This triggers a validation of the imported outputs + Then I restart wallet WALLET_IMPORTED + # # Chain 2 + Given I have a seed node SEED_C + When I have a base node C connected to seed SEED_C + When I have wallet WC connected to base node C + When I have mining node CM connected to base node C and wallet WC + When mining node CM mines 10 blocks with min difficulty 1000 and max difficulty 9999999999 + # # Connect chain 1 and 2 + Then node B is at height 8 + When node C is at height 10 + When I have a base node SA connected to nodes B,C + Then node SA is at height 10 + Then node B is at height 10 + Then node C is at height 10 + Then I restart wallet WALLET_IMPORTED + Then I wait for wallet WALLET_IMPORTED to have less than 1 uT + When mining node CM mines 1 blocks with min difficulty 1000 and max difficulty 9999999999 + When node B is at height 11 + When node C is at height 11 + Then I check if last imported transactions are invalid in wallet WALLET_IMPORTED + + Scenario: Wallet imports faucet UTXO + Given I have a seed node NODE + When I have 1 base nodes connected to all seed nodes + When I have wallet WALLET_A connected to all seed nodes + When I have wallet WALLET_B connected to all seed nodes + When I have wallet WALLET_C connected to all seed nodes + When I have mining node MINER connected to base node NODE and wallet WALLET_A + When mining node MINER mines 5 blocks + Then all nodes are at height 5 + Then I wait for wallet WALLET_A to have at least 10000000000 uT + When I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 + When mining node MINER mines 6 blocks + Then all nodes are at height 11 + Then I wait for wallet WALLET_B to have at least 1000000 uT + Then I stop wallet WALLET_B + When I wait 15 seconds + Then I import WALLET_B unspent outputs as faucet outputs to WALLET_C + Then I wait for wallet WALLET_C to have at least 1000000 uT + When I send 500000 uT from wallet WALLET_C to wallet WALLET_A at fee 100 + When mining node MINER mines 6 blocks + Then all nodes are at height 17 + Then I wait for wallet WALLET_C to have at least 400000 uT + + Scenario: Wallet should display all transactions made + Given I have a seed node NODE + When I have 1 base nodes connected to all seed nodes + When I have wallet WALLET_A connected to all seed nodes + When I have wallet WALLET_B connected to all seed nodes + When I have mining node MINER connected to base node NODE and wallet WALLET_A + When mining node MINER mines 10 blocks + Then all nodes are at height 10 + Then I wait for wallet WALLET_A to have at least 10000000000 uT + When I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 + When I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 + When I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 + When I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 + When I send 100000 uT from wallet WALLET_A to wallet WALLET_B at fee 100 + When mining node MINER mines 5 blocks + Then all nodes are at height 15 + Then I wait for wallet WALLET_B to have at least 500000 uT + Then I check if wallet WALLET_B has 5 transactions + Then I restart wallet WALLET_B + Then I check if wallet WALLET_B has 5 transactions + + # Scenario: Wallet clearing out invalid transactions after a reorg + # # + # # Chain 1: + # # Collects 7 coinbases into one wallet, send 7 transactions + # # Stronger chain + # # + # Given I have a seed node SEED_A + # When I have a base node NODE_A1 connected to seed SEED_A + # When I have wallet WALLET_A1 connected to seed node SEED_A + # When I have wallet WALLET_A2 connected to seed node SEED_A + # When I have mining node MINER_A1 connected to base node SEED_A and wallet WALLET_A1 + # When mining node MINER_A1 mines 7 blocks with min difficulty 200 and max difficulty 100000 + # Then node SEED_A is at height 7 + # Then node NODE_A1 is at height 7 + # When I mine 3 blocks on SEED_A + # Then wallet WALLET_A1 detects at least 7 coinbase transactions as Mined_Confirmed + # Then node SEED_A is at height 10 + # Then node NODE_A1 is at height 10 + # When I multi-send 7 transactions of 1000000 uT from wallet WALLET_A1 to wallet WALLET_A2 at fee 100 + # # + # # Chain 2: + # # Collects 7 coinbases into one wallet, send 7 transactions + # # Weaker chain + # # + # When I have a seed node SEED_B + # When I have a base node NODE_B1 connected to seed SEED_B + # When I have wallet WALLET_B1 connected to seed node SEED_B + # When I have wallet WALLET_B2 connected to seed node SEED_B + # When I have mining node MINER_B1 connected to base node SEED_B and wallet WALLET_B1 + # When mining node MINER_B1 mines 7 blocks with min difficulty 1 and max difficulty 100 + # Then node SEED_B is at height 7 + # Then node NODE_B1 is at height 7 + # When I mine 5 blocks on SEED_B + # Then wallet WALLET_B1 detects at least 7 coinbase transactions as Mined_Confirmed + # Then node SEED_B is at height 12 + # Then node NODE_B1 is at height 12 + # When I multi-send 7 transactions of 1000000 uT from wallet WALLET_B1 to wallet WALLET_B2 at fee 100 + # # + # # Connect Chain 1 and 2 in stages + # # # New node connects to weaker chain, receives all broadcast (not mined) transactions into mempool + # # # New node connects to stronger chain, then reorgs its complete chain + # # # New node mines blocks; no invalid inputs from the weaker chain should be used in the block template + # # + # When I have a base node NODE_C connected to seed SEED_B + # Then node NODE_C is at height 12 + # # Wait for the reorg to filter through + # When I wait 15 seconds + # When I connect node SEED_A to node NODE_C + # Then all nodes are at height 10 + # When I mine 6 blocks on NODE_C + # Then all nodes are at height 16 + + Scenario: Wallet send transactions while offline + Given I have a seed node SEED + When I have wallet WALLET_A connected to seed node SEED + When I have wallet WALLET_B connected to seed node SEED + When I have mining node MINER_A connected to base node SEED and wallet WALLET_A + When mining node MINER_A mines 1 blocks with min difficulty 1 and max difficulty 100000 + When I mine 4 blocks on SEED + Then I wait for wallet WALLET_A to have at least 1000000000 uT + Then I stop wallet WALLET_B + Then I stop node SEED + When I wait 10 seconds + Then I send 100000000 uT without waiting for broadcast from wallet WALLET_A to wallet WALLET_B at fee 20 + When I wait 10 seconds + When I start base node SEED + When I have a base node NODE_A connected to seed SEED + When I have a base node NODE_B connected to seed SEED + Then I stop wallet WALLET_A + When I wait 15 seconds + When I start wallet WALLET_A + When I start wallet WALLET_B + Then all nodes are at height 5 + When I mine 1 blocks on SEED + Then all nodes are at height 6 + # Then wallet WALLET_B detects all transactions are at least Pending + + # Scenario: Short wallet clearing out invalid transactions after a reorg + # # + # # Chain 1: + # # Collects 7 coinbases into one wallet, send 7 transactions + # # Stronger chain + # # + # Given I have a seed node SEED_A + # When I have a base node NODE_A1 connected to seed SEED_A + # When I have wallet WALLET_A1 connected to seed node SEED_A + # When I have wallet WALLET_A2 connected to seed node SEED_A + # When I have mining node MINER_A1 connected to base node SEED_A and wallet WALLET_A1 + # When mining node MINER_A1 mines 1 blocks with min difficulty 200 and max difficulty 100000 + # Then node SEED_A is at height 1 + # Then node NODE_A1 is at height 1 + # When I mine 3 blocks on SEED_A + # Then wallet WALLET_A1 detects at least 1 coinbase transactions as Mined_Confirmed + # Then node SEED_A is at height 4 + # Then node NODE_A1 is at height 4 + # When I multi-send 1 transactions of 10000 uT from wallet WALLET_A1 to wallet WALLET_A2 at fee 20 + # # + # # Chain 2: + # # Collects 7 coinbases into one wallet, send 7 transactions + # # Weaker chain + # # + # When I have a seed node SEED_B + # When I have a base node NODE_B1 connected to seed SEED_B + # When I have wallet WALLET_B1 connected to seed node SEED_B + # When I have wallet WALLET_B2 connected to seed node SEED_B + # When I have mining node MINER_B1 connected to base node SEED_B and wallet WALLET_B1 + # When mining node MINER_B1 mines 2 blocks with min difficulty 1 and max difficulty 100 + # Then node SEED_B is at height 2 + # Then node NODE_B1 is at height 2 + # When I mine 3 blocks on SEED_B + # Then wallet WALLET_B1 detects at least 2 coinbase transactions as Mined_Confirmed + # Then node SEED_B is at height 5 + # Then node NODE_B1 is at height 5 + # When I multi-send 2 transactions of 10000 uT from wallet WALLET_B1 to wallet WALLET_B2 at fee 20 + # # + # # Connect Chain 1 and 2 in stages + # # # New node connects to weaker chain, receives all broadcast (not mined) transactions into mempool + # # # New node connects to stronger chain, then reorgs its complete chain + # # # New node mines blocks; no invalid inputs from the weaker chain should be used in the block template + # # + # When I have a base node NODE_C connected to seed SEED_B + # Then node NODE_C is at height 5 + # # Wait for the reorg to filter through + # When I wait 15 seconds + # When I connect node SEED_A to node NODE_C + # Then all nodes are at height 4 + # When I mine 2 blocks on NODE_C + # Then all nodes are at height 6 + + # @flaky @long-running + # Scenario: Wallet SAF negotiation and cancellation with offline peers + # Given I have a seed node NODE + # When I have 1 base nodes connected to all seed nodes + # When I have wallet WALLET_A connected to all seed nodes + # When I have wallet WALLET_RECV connected to all seed nodes + # When I have mining node MINER connected to base node NODE and wallet WALLET_A + # When mining node MINER mines 5 blocks + # Then all nodes are at height 5 + # Then I wait for wallet WALLET_A to have at least 10000000000 uT + # When I have non-default wallet WALLET_SENDER connected to all seed nodes using StoreAndForwardOnly + # When I send 100000000 uT from wallet WALLET_A to wallet WALLET_SENDER at fee 100 + # When mining node MINER mines 5 blocks + # Then all nodes are at height 10 + # Then I wait for wallet WALLET_SENDER to have at least 100000000 uT + # Then I stop wallet WALLET_RECV + # When I send 1000000 uT without waiting for broadcast from wallet WALLET_SENDER to wallet WALLET_RECV at fee 100 + # When wallet WALLET_SENDER detects last transaction is Pending + # Then I stop wallet WALLET_SENDER + # When I wait 15 seconds + # Then I start wallet WALLET_RECV + # When I wait 5 seconds + # When wallet WALLET_RECV detects all transactions are at least Pending + # Then I cancel last transaction in wallet WALLET_RECV + # When I wait 15 seconds + # Then I stop wallet WALLET_RECV + # Then I start wallet WALLET_SENDER + # # This is a weirdness that I haven't been able to figure out. When you start WALLET_SENDER on the line above it + # # requests SAF messages from the base nodes the base nodes get the request and attempt to send the stored messages + # # but the connection fails. It requires a second reconnection and request for the SAF messages to be delivered. + # When I wait 10 seconds + # Then I restart wallet WALLET_SENDER + # When I wait 10 seconds + # Then I restart wallet WALLET_SENDER + # When I wait 30 seconds + # When mining node MINER mines 5 blocks + # Then all nodes are at height 15 + # When wallet WALLET_SENDER detects all transactions as Mined_Confirmed + # When I start wallet WALLET_RECV + # When I wait 5 seconds + # Then I restart wallet WALLET_RECV + # When I wait 5 seconds + # Then I restart wallet WALLET_RECV + # Then I wait for wallet WALLET_RECV to have at least 1000000 uT + + # @critical + # Scenario: Wallet should cancel stale transactions + # Given I have a seed node NODE + # When I have 1 base nodes connected to all seed nodes + # When I have non-default wallet WALLET_SENDER connected to all seed nodes using StoreAndForwardOnly + # When I have wallet WALLET_RECV connected to all seed nodes + # When I have mining node MINER connected to base node NODE and wallet WALLET_SENDER + # When mining node MINER mines 5 blocks + # Then all nodes are at height 5 + # Then I wait for wallet WALLET_SENDER to have at least 10000000000 uT + # Then I stop wallet WALLET_RECV + # When I wait 15 seconds + # When I send 1000000 uT without waiting for broadcast from wallet WALLET_SENDER to wallet WALLET_RECV at fee 100 + # When I wait 15 seconds + # Then I cancel last transaction in wallet WALLET_SENDER + # Then I restart wallet WALLET_RECV + # When I wait 15 seconds + # When wallet WALLET_RECV detects last transaction is Cancelled + + @critical + Scenario: Create burn transaction + Given I have a seed node NODE + When I have 2 base nodes connected to all seed nodes + When I have wallet WALLET_A connected to all seed nodes + When I have wallet WALLET_B connected to all seed nodes + When I have mining node MINER_A connected to base node NODE and wallet WALLET_A + When I have mining node MINER_B connected to base node NODE and wallet WALLET_B + When mining node MINER_A mines 12 blocks + When mining node MINER_B mines 3 blocks + Then all nodes are at height 15 + When I wait for wallet WALLET_A to have at least 221552530060 uT + When I create a burn transaction of 201552500000 uT from WALLET_A at fee 100 + When mining node MINER_B mines 5 blocks + Then all nodes are at height 20 + Then wallet WALLET_A detects all transactions as Mined_Confirmed + When I wait for wallet WALLET_A to have at least 20000000000 uT diff --git a/integration_tests/tests/features/WalletTransfer.feature b/integration_tests/tests/features/WalletTransfer.feature new file mode 100644 index 0000000000..be2f0bf55d --- /dev/null +++ b/integration_tests/tests/features/WalletTransfer.feature @@ -0,0 +1,93 @@ +# Copyright 2022 The Tari Project +# SPDX-License-Identifier: BSD-3-Clause + +@wallet-transfer @wallet @flaky +Feature: Wallet Transfer + + # BROKEN: Runs fine when run by itself, but not with other tests - or maybe is flaky + @critical @broken + Scenario: As a wallet send to a wallet connected to a different base node + Given I have a seed node SEED_A + When I have a seed node SEED_B + When I have a base node NODE_A connected to all seed nodes + When I have a base node NODE_B connected to all seed nodes + When I have wallet WALLET_A with 10T connected to base node NODE_A + When I have wallet WALLET_B connected to base node NODE_B + When I wait 5 seconds + When I transfer 5T from WALLET_A to WALLET_B + When I mine 4 blocks on SEED_A + # BREAKS HERE + Then wallet WALLET_A has 5T + When I wait 5 seconds + When wallet WALLET_B has 5T + + Scenario: As a wallet I want to submit multiple transfers + Given I have a seed node NODE + # Add a 2nd node otherwise initial sync will not succeed + When I have 1 base nodes connected to all seed nodes + When I have wallet WALLET_A connected to all seed nodes + When I have mining node MINER connected to base node NODE and wallet WALLET_A + When I have wallet WALLET_B connected to all seed nodes + When I have wallet WALLET_C connected to all seed nodes + When mining node MINER mines 2 blocks + Then all nodes are at height 2 + # Ensure the coinbase lock heights have expired + When mining node MINER mines 3 blocks + Then all nodes are at height 5 + # Ensure the coinbase lock heights have expired + When mining node MINER mines 5 blocks + Then all nodes are at height 10 + Then I wait for wallet WALLET_A to have at least 10000000000 uT + When I transfer 50000 uT from WALLET_A to WALLET_B and WALLET_C at fee 20 + When mining node MINER mines 10 blocks + Then all nodes are at height 20 + Then all wallets detect all transactions as Mined_Confirmed + + + Scenario: As a wallet I want to submit transfers to myself + Given I have a seed node NODE + # Add a 2nd node otherwise initial sync will not succeed + When I have 1 base nodes connected to all seed nodes + When I have wallet WALLET_A connected to all seed nodes + When I have mining node MINER connected to base node NODE and wallet WALLET_A + When mining node MINER mines 10 blocks + Then all nodes are at height 10 + Then I wait for wallet WALLET_A to have at least 10000000000 uT + When I transfer 50000 uT to self from wallet WALLET_A at fee 25 + When I mine 5 blocks on NODE + Then all nodes are at height 15 + Then all wallets detect all transactions as Mined_Confirmed + + Scenario: As a wallet I want to create a HTLC transaction + Given I have a seed node NODE + # Add a 2nd node otherwise initial sync will not succeed + When I have 1 base nodes connected to all seed nodes + When I have wallet WALLET_A connected to all seed nodes + When I have wallet WALLET_B connected to all seed nodes + When I have mining node MINER connected to base node NODE and wallet WALLET_A + When mining node MINER mines 10 blocks + Then I wait for wallet WALLET_A to have at least 10000000000 uT + When I broadcast HTLC transaction with 5000000000 uT from wallet WALLET_A to wallet WALLET_B at fee 20 + When mining node MINER mines 6 blocks + When I claim an HTLC transaction with wallet WALLET_B at fee 20 + When mining node MINER mines 6 blocks + Then I wait for wallet WALLET_B to have at least 4000000000 uT + + Scenario: As a wallet I want to claim a HTLC refund transaction + Given I have a seed node NODE + # Add a 2nd node otherwise initial sync will not succeed + When I have 1 base nodes connected to all seed nodes + When I have wallet WALLET_A connected to all seed nodes + When I have wallet WALLET_B connected to all seed nodes + When I have wallet WALLET_C connected to all seed nodes + When I have mining node MINER connected to base node NODE and wallet WALLET_A + When I have mining node MINER_2 connected to base node NODE and wallet WALLET_C + When mining node MINER mines 10 blocks + Then I wait for wallet WALLET_A to have at least 10000000000 uT + When I broadcast HTLC transaction with 5000000000 uT from wallet WALLET_A to wallet WALLET_B at fee 20 + # atomic swaps are set at lock of 720 blocks + When mining node MINER_2 mines 720 blocks + When I wait 5 seconds + When I claim an HTLC refund transaction with wallet WALLET_A at fee 20 + When mining node MINER_2 mines 6 blocks + Then I wait for wallet WALLET_A to have at least 9000000000 uT diff --git a/integration_tests/tests/utils/base_node_process.rs b/integration_tests/tests/utils/base_node_process.rs new file mode 100644 index 0000000000..8fc2eea99b --- /dev/null +++ b/integration_tests/tests/utils/base_node_process.rs @@ -0,0 +1,214 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ + default::Default, + fmt::{Debug, Formatter}, + path::PathBuf, + str::FromStr, + sync::Arc, + time::Duration, +}; + +use rand::rngs::OsRng; +use tari_base_node::{run_base_node, BaseNodeConfig, MetricsConfig}; +use tari_base_node_grpc_client::BaseNodeGrpcClient; +use tari_common::configuration::CommonConfig; +use tari_comms::{multiaddr::Multiaddr, peer_manager::PeerFeatures, NodeIdentity}; +use tari_comms_dht::{DbConnectionUrl, DhtConfig}; +use tari_p2p::{auto_update::AutoUpdateConfig, Network, PeerSeedsConfig, TransportType}; +use tari_shutdown::Shutdown; +use tempfile::tempdir; +use tokio::task; +use tonic::transport::Channel; + +use crate::{ + utils::{get_peer_addresses, get_port, wait_for_service}, + TariWorld, +}; + +pub struct BaseNodeProcess { + pub name: String, + pub port: u64, + pub grpc_port: u64, + pub identity: NodeIdentity, + pub temp_dir_path: PathBuf, + pub is_seed_node: bool, + pub seed_nodes: Vec, + pub config: BaseNodeConfig, + pub kill_signal: Shutdown, +} + +impl Drop for BaseNodeProcess { + fn drop(&mut self) { + self.kill(); + } +} + +// NOTE: implemented to skip `cx`, because BaseNodeContext doesn't implement Debug +impl Debug for BaseNodeProcess { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BaseNodeProcess") + .field("name", &self.name) + .field("port", &self.port) + .field("grpc_port", &self.grpc_port) + .field("identity", &self.identity) + .field("temp_dir_path", &self.temp_dir_path) + .field("is_seed_node", &self.is_seed_node) + .finish() + } +} + +pub async fn spawn_base_node(world: &mut TariWorld, is_seed_node: bool, bn_name: String, peers: Vec) { + spawn_base_node_with_config(world, is_seed_node, bn_name, peers, BaseNodeConfig::default()).await; +} + +pub async fn spawn_base_node_with_config( + world: &mut TariWorld, + is_seed_node: bool, + bn_name: String, + peers: Vec, + mut base_node_config: BaseNodeConfig, +) { + let port: u64; + let grpc_port: u64; + let temp_dir_path: PathBuf; + let base_node_identity: NodeIdentity; + let base_node_address: Multiaddr; + + if let Some(node_ps) = world.base_nodes.get(&bn_name) { + port = node_ps.port; + grpc_port = node_ps.grpc_port; + temp_dir_path = node_ps.temp_dir_path.clone(); + base_node_config = node_ps.config.clone(); + + base_node_identity = node_ps.identity.clone(); + } else { + // each spawned wallet will use different ports + port = get_port(18000..18499).unwrap(); + grpc_port = get_port(18500..18999).unwrap(); + // create a new temporary directory + temp_dir_path = tempdir().unwrap().path().to_path_buf(); + + base_node_address = Multiaddr::from_str(&format!("/ip4/127.0.0.1/tcp/{}", port)).unwrap(); + base_node_identity = NodeIdentity::random(&mut OsRng, base_node_address, PeerFeatures::COMMUNICATION_NODE); + }; + + println!("Base node identity: {}", base_node_identity); + let identity = base_node_identity.clone(); + + let shutdown = Shutdown::new(); + let process = BaseNodeProcess { + name: bn_name.clone(), + port, + grpc_port, + identity, + temp_dir_path: temp_dir_path.clone(), + is_seed_node, + seed_nodes: peers.clone(), + config: base_node_config.clone(), + kill_signal: shutdown.clone(), + }; + + let name_cloned = bn_name.clone(); + + let peer_addresses = get_peer_addresses(world, &peers).await; + + let mut common_config = CommonConfig::default(); + common_config.base_path = temp_dir_path.clone(); + task::spawn(async move { + let mut base_node_config = tari_base_node::ApplicationConfig { + common: common_config, + auto_update: AutoUpdateConfig::default(), + base_node: base_node_config, + metrics: MetricsConfig::default(), + peer_seeds: PeerSeedsConfig { + peer_seeds: peer_addresses.into(), + dns_seeds_use_dnssec: false, + ..Default::default() + }, + }; + + println!("Using base_node temp_dir: {}", temp_dir_path.clone().display()); + base_node_config.base_node.network = Network::LocalNet; + base_node_config.base_node.grpc_enabled = true; + base_node_config.base_node.grpc_address = Some(format!("/ip4/127.0.0.1/tcp/{}", grpc_port).parse().unwrap()); + base_node_config.base_node.report_grpc_error = true; + base_node_config.base_node.metadata_auto_ping_interval = Duration::from_secs(15); + + base_node_config.base_node.data_dir = temp_dir_path.to_path_buf(); + base_node_config.base_node.identity_file = temp_dir_path.clone().join("base_node_id.json"); + base_node_config.base_node.tor_identity_file = temp_dir_path.clone().join("base_node_tor_id.json"); + base_node_config.base_node.max_randomx_vms = 1; + + base_node_config.base_node.lmdb_path = temp_dir_path.to_path_buf(); + base_node_config.base_node.p2p.transport.transport_type = TransportType::Tcp; + base_node_config.base_node.p2p.transport.tcp.listener_address = + format!("/ip4/127.0.0.1/tcp/{}", port).parse().unwrap(); + base_node_config.base_node.p2p.public_address = + Some(base_node_config.base_node.p2p.transport.tcp.listener_address.clone()); + base_node_config.base_node.p2p.datastore_path = temp_dir_path.to_path_buf(); + base_node_config.base_node.p2p.dht = DhtConfig::default_local_test(); + base_node_config.base_node.p2p.dht.database_url = + DbConnectionUrl::File(temp_dir_path.clone().join("dht.sqlite")); + base_node_config.base_node.p2p.dht.network_discovery.enabled = true; + base_node_config.base_node.p2p.allow_test_addresses = true; + base_node_config.base_node.storage.orphan_storage_capacity = 10; + if base_node_config.base_node.storage.pruning_horizon != 0 { + base_node_config.base_node.storage.pruning_interval = 1; + }; + + println!( + "Initializing base node: name={}; port={}; grpc_port={}; is_seed_node={}", + name_cloned, port, grpc_port, is_seed_node + ); + let result = run_base_node(shutdown, Arc::new(base_node_identity), Arc::new(base_node_config)).await; + if let Err(e) = result { + panic!("{:?}", e); + } + }); + + // make the new base node able to be referenced by other processes + world.base_nodes.insert(bn_name.clone(), process); + if is_seed_node { + world.seed_nodes.push(bn_name); + } + + wait_for_service(port).await; + wait_for_service(grpc_port).await; +} + +// pub async fn get_base_node_client(port: u64) -> GrpcBaseNodeClient { +// let endpoint: SocketAddr = format!("127.0.0.1:{}", port).parse().unwrap(); +// GrpcBaseNodeClient::new(endpoint) +// todo!() +// } + +impl BaseNodeProcess { + pub async fn get_grpc_client(&self) -> anyhow::Result> { + Ok(BaseNodeGrpcClient::connect(format!("http://127.0.0.1:{}", self.grpc_port)).await?) + } + + pub fn kill(&mut self) { + self.kill_signal.trigger(); + } +} diff --git a/integration_tests/tests/utils/ffi/balance.rs b/integration_tests/tests/utils/ffi/balance.rs new file mode 100644 index 0000000000..a988df6182 --- /dev/null +++ b/integration_tests/tests/utils/ffi/balance.rs @@ -0,0 +1,91 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ptr::null_mut; + +use libc::c_void; + +use super::ffi_import; + +pub struct Balance { + ptr: *mut c_void, +} + +impl Drop for Balance { + fn drop(&mut self) { + unsafe { ffi_import::balance_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} +impl Balance { + pub fn from_ptr(ptr: *mut c_void) -> Self { + Self { ptr } + } + + pub fn get_available(&self) -> u64 { + let available; + let mut error = 0; + unsafe { + available = ffi_import::balance_get_available(self.ptr, &mut error); + if error > 0 { + println!("balance_get_available error {}", error); + } + } + available + } + + pub fn get_time_locked(&self) -> u64 { + let time_locked; + let mut error = 0; + unsafe { + time_locked = ffi_import::balance_get_time_locked(self.ptr, &mut error); + if error > 0 { + println!("balance_get_time_locked error {}", error); + } + } + time_locked + } + + pub fn get_pending_incoming(&self) -> u64 { + let pending_incoming; + let mut error = 0; + unsafe { + pending_incoming = ffi_import::balance_get_pending_incoming(self.ptr, &mut error); + if error > 0 { + println!("balance_get_pending_incoming error {}", error); + } + } + pending_incoming + } + + pub fn get_pending_outgoing(&self) -> u64 { + let pending_outgoing; + let mut error = 0; + unsafe { + pending_outgoing = ffi_import::balance_get_pending_outgoing(self.ptr, &mut error); + if error > 0 { + println!("balance_get_pending_outgoing error {}", error); + } + } + pending_outgoing + } +} diff --git a/integration_tests/tests/utils/ffi/callbacks.rs b/integration_tests/tests/utils/ffi/callbacks.rs new file mode 100644 index 0000000000..d5658a0fd6 --- /dev/null +++ b/integration_tests/tests/utils/ffi/callbacks.rs @@ -0,0 +1,326 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::sync::{Arc, Mutex, Once}; + +use libc::c_void; + +use super::{Balance, CompletedTransaction, ContactsLivenessData, PendingInboundTransaction, Wallet}; +use crate::utils::ffi::TransactionSendStatus; + +#[derive(Debug, Default)] +pub struct Callbacks { + transaction_received: Mutex, + transaction_reply_received: Mutex, + transaction_finalized: Mutex, + transaction_broadcast: Mutex, + transaction_mined: Mutex, + transaction_mined_unconfirmed: Mutex, + transaction_faux_confirmed: Mutex, + transaction_faux_unconfirmed: Mutex, + transaction_cancelled: Mutex, + txo_validation_complete: Mutex, + txo_validation_result: Mutex, + tx_validation_complete: Mutex, + tx_validation_result: Mutex, + transaction_saf_message_received: Mutex, + contacts_liveness_data_updated: Mutex, + pub wallet: Option>>, +} + +static mut INSTANCE: Option = None; +static START: Once = Once::new(); + +impl Callbacks { + pub fn get_transaction_received(&self) -> u64 { + *self.transaction_received.lock().unwrap() + } + + #[allow(dead_code)] + pub fn get_transaction_reply_received(&self) -> u64 { + *self.transaction_reply_received.lock().unwrap() + } + + pub fn get_transaction_finalized(&self) -> u64 { + *self.transaction_finalized.lock().unwrap() + } + + pub fn get_transaction_broadcast(&self) -> u64 { + *self.transaction_broadcast.lock().unwrap() + } + + pub fn get_transaction_mined(&self) -> u64 { + *self.transaction_mined.lock().unwrap() + } + + #[allow(dead_code)] + pub fn get_transaction_mined_unconfirmed(&self) -> u64 { + *self.transaction_mined_unconfirmed.lock().unwrap() + } + + pub fn get_transaction_faux_confirmed(&self) -> u64 { + *self.transaction_faux_confirmed.lock().unwrap() + } + + pub fn get_transaction_faux_unconfirmed(&self) -> u64 { + *self.transaction_faux_unconfirmed.lock().unwrap() + } + + #[allow(dead_code)] + pub fn get_transaction_cancelled(&self) -> u64 { + *self.transaction_cancelled.lock().unwrap() + } + + pub fn get_txo_validation_complete(&self) -> bool { + *self.txo_validation_complete.lock().unwrap() + } + + #[allow(dead_code)] + pub fn get_txo_validation_result(&self) -> u64 { + *self.txo_validation_result.lock().unwrap() + } + + pub fn get_tx_validation_complete(&self) -> bool { + *self.tx_validation_complete.lock().unwrap() + } + + #[allow(dead_code)] + pub fn get_tx_validation_result(&self) -> u64 { + *self.tx_validation_result.lock().unwrap() + } + + #[allow(dead_code)] + pub fn get_transaction_saf_message_received(&self) -> u64 { + *self.transaction_saf_message_received.lock().unwrap() + } + + #[allow(dead_code)] + pub fn get_contacts_liveness_data_updated(&self) -> u64 { + *self.contacts_liveness_data_updated.lock().unwrap() + } + + pub fn on_received_transaction(&mut self, ptr: *mut c_void) { + let pending_inbound_transaction = PendingInboundTransaction::from_ptr(ptr); + println!( + "{} received Transaction with txID {}.", + chrono::Local::now().format("%Y/%m/%d %H:%M:%S"), + pending_inbound_transaction.get_transaction_id() + ); + *self.transaction_received.lock().unwrap() += 1; + } + + pub fn on_received_transaction_reply(&mut self, ptr: *mut c_void) { + let completed_transaction = CompletedTransaction::from_ptr(ptr); + println!( + "{} received reply for Transaction with txID {}.", + chrono::Local::now().format("%Y/%m/%d %H:%M:%S"), + completed_transaction.get_transaction_id() + ); + *self.transaction_reply_received.lock().unwrap() += 1; + } + + pub fn on_received_finalized_transaction(&mut self, ptr: *mut c_void) { + let completed_transaction = CompletedTransaction::from_ptr(ptr); + println!( + "{} received finalization for Transaction with txID {}.", + chrono::Local::now().format("%Y/%m/%d %H:%M:%S"), + completed_transaction.get_transaction_id() + ); + *self.transaction_finalized.lock().unwrap() += 1; + } + + pub fn on_transaction_broadcast(&mut self, ptr: *mut c_void) { + let completed_transaction = CompletedTransaction::from_ptr(ptr); + println!( + "{} Transaction with txID {} was broadcast.", + chrono::Local::now().format("%Y/%m/%d %H:%M:%S"), + completed_transaction.get_transaction_id() + ); + *self.transaction_broadcast.lock().unwrap() += 1; + } + + pub fn on_transaction_mined(&mut self, ptr: *mut c_void) { + let completed_transaction = CompletedTransaction::from_ptr(ptr); + println!( + "{} Transaction with txID {} was mined.", + chrono::Local::now().format("%Y/%m/%d %H:%M:%S"), + completed_transaction.get_transaction_id() + ); + *self.transaction_mined.lock().unwrap() += 1; + } + + pub fn on_transaction_mined_unconfirmed(&mut self, ptr: *mut c_void, confirmations: u64) { + let completed_transaction = CompletedTransaction::from_ptr(ptr); + println!( + "{} Transaction with txID {} is mined unconfirmed with {} confirmations.", + chrono::Local::now().format("%Y/%m/%d %H:%M:%S"), + completed_transaction.get_transaction_id(), + confirmations + ); + *self.transaction_mined_unconfirmed.lock().unwrap() += 1; + } + + pub fn on_faux_transaction_confirmed(&mut self, ptr: *mut c_void) { + let completed_transaction = CompletedTransaction::from_ptr(ptr); + println!( + "{} Faux transaction with txID {} was confirmed.", + chrono::Local::now().format("%Y/%m/%d %H:%M:%S"), + completed_transaction.get_transaction_id(), + ); + *self.transaction_faux_confirmed.lock().unwrap() += 1; + } + + pub fn on_faux_transaction_mined_unconfirmed(&mut self, ptr: *mut c_void, confirmations: u64) { + let completed_transaction = CompletedTransaction::from_ptr(ptr); + println!( + "{} Faux transaction with txID {} is mined unconfirmed with {} confirmations.", + chrono::Local::now().format("%Y/%m/%d %H:%M:%S"), + completed_transaction.get_transaction_id(), + confirmations + ); + *self.transaction_faux_unconfirmed.lock().unwrap() += 1; + } + + pub fn on_transaction_send_result(&mut self, tx_id: u64, ptr: *mut c_void) { + let transaction_send_status = TransactionSendStatus::from_ptr(ptr); + println!( + "{} callbackTransactionSendResult ({}: ({}))", + chrono::Local::now().format("%Y/%m/%d %H:%M:%S"), + tx_id, + transaction_send_status.send_status_decode() + ); + } + + pub fn on_transaction_cancellation(&mut self, ptr: *mut c_void, reason: u64) { + let completed_transaction = CompletedTransaction::from_ptr(ptr); + println!( + "{} transaction with txID {} was cancelled with reason code {}.", + chrono::Local::now().format("%Y/%m/%d %H:%M:%S"), + completed_transaction.get_transaction_id(), + reason, + ); + *self.transaction_cancelled.lock().unwrap() += 1; + } + + pub fn on_txo_validation_complete(&mut self, request_key: u64, validation_results: u64) { + println!( + "{} callbackTxoValidationComplete({}, {}).", + chrono::Local::now().format("%Y/%m/%d %H:%M:%S"), + request_key, + validation_results + ); + + *self.txo_validation_complete.lock().unwrap() = true; + *self.txo_validation_result.lock().unwrap() = validation_results; + } + + pub fn on_contacts_liveness_data_updated(&mut self, ptr: *mut c_void) { + let contact_liveness_data = ContactsLivenessData::from_ptr(ptr); + println!( + "{} callbackContactsLivenessUpdated: received {} from contact {} with latency {} at {} and is {}.", + chrono::Local::now().format("%Y/%m/%d %H:%M:%S"), + contact_liveness_data.get_message_type(), + contact_liveness_data.get_public_key().address().get_as_hex(), + contact_liveness_data.get_latency(), + contact_liveness_data.get_last_seen(), + contact_liveness_data.get_online_status() + ); + self.wallet + .as_mut() + .unwrap() + .lock() + .unwrap() + .add_liveness_data(contact_liveness_data); + *self.contacts_liveness_data_updated.lock().unwrap() += 1; + } + + pub fn on_balance_updated(&mut self, ptr: *mut c_void) { + let balance = Balance::from_ptr(ptr); + println!( + "{} callbackBalanceUpdated: available = {}, time locked = {}, pending incoming = {}, pending outgoing = \ + {}.", + chrono::Local::now().format("%Y/%m/%d %H:%M:%S"), + balance.get_available(), + balance.get_time_locked(), + balance.get_pending_incoming(), + balance.get_pending_outgoing() + ); + self.wallet.as_mut().unwrap().lock().unwrap().set_balance(balance); + } + + pub fn on_transaction_validation_complete(&mut self, request_key: u64, validation_results: u64) { + println!( + "{} callbackTransactionValidationComplete({}, {}).", + chrono::Local::now().format("%Y/%m/%d %H:%M:%S"), + request_key, + validation_results + ); + + *self.tx_validation_complete.lock().unwrap() = true; + *self.tx_validation_result.lock().unwrap() = validation_results; + } + + pub fn on_saf_messages_received(&mut self) { + println!( + "{} callbackSafMessageReceived().", + chrono::Local::now().format("%Y/%m/%d %H:%M:%S"), + ); + *self.transaction_saf_message_received.lock().unwrap() += 1; + } + + pub fn on_connectivity_status(&mut self, status: u64) { + println!( + "{} Connectivity Status Changed to {}.", + chrono::Local::now().format("%Y/%m/%d %H:%M:%S"), + status + ); + } + + pub fn reset(&mut self, wallet: Arc>) { + *self.transaction_received.lock().unwrap() = 0; + *self.transaction_reply_received.lock().unwrap() = 0; + *self.transaction_finalized.lock().unwrap() = 0; + *self.transaction_broadcast.lock().unwrap() = 0; + *self.transaction_mined.lock().unwrap() = 0; + *self.transaction_mined_unconfirmed.lock().unwrap() = 0; + *self.transaction_faux_confirmed.lock().unwrap() = 0; + *self.transaction_faux_unconfirmed.lock().unwrap() = 0; + *self.transaction_cancelled.lock().unwrap() = 0; + *self.txo_validation_complete.lock().unwrap() = false; + *self.txo_validation_result.lock().unwrap() = 0; + *self.tx_validation_complete.lock().unwrap() = false; + *self.tx_validation_result.lock().unwrap() = 0; + *self.transaction_saf_message_received.lock().unwrap() = 0; + *self.contacts_liveness_data_updated.lock().unwrap() = 0; + self.wallet = Some(wallet); + println!("wallet {:?}", self.wallet); + } + + pub fn instance() -> &'static mut Self { + unsafe { + START.call_once(|| { + INSTANCE = Some(Self::default()); + }); + INSTANCE.as_mut().unwrap() + } + } +} diff --git a/integration_tests/tests/utils/ffi/coin_preview.rs b/integration_tests/tests/utils/ffi/coin_preview.rs new file mode 100644 index 0000000000..6de7d3afdb --- /dev/null +++ b/integration_tests/tests/utils/ffi/coin_preview.rs @@ -0,0 +1,49 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ptr::null_mut; + +use libc::c_void; + +use super::ffi_import; + +pub struct CoinPreview { + ptr: *mut c_void, +} + +impl Drop for CoinPreview { + fn drop(&mut self) { + unsafe { ffi_import::destroy_tari_coin_preview(self.ptr) }; + self.ptr = null_mut(); + } +} + +#[allow(dead_code)] +impl CoinPreview { + pub fn from_ptr(ptr: *mut c_void) -> Self { + Self { ptr } + } + + pub fn get_ptr(&self) -> *mut c_void { + self.ptr + } +} diff --git a/integration_tests/tests/utils/ffi/comms_config.rs b/integration_tests/tests/utils/ffi/comms_config.rs new file mode 100644 index 0000000000..945eb424ab --- /dev/null +++ b/integration_tests/tests/utils/ffi/comms_config.rs @@ -0,0 +1,61 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ffi::CString, ptr::null_mut}; + +use libc::c_void; + +use super::{ffi_import, transport_config::TransportConfig}; + +pub struct CommsConfig { + ptr: *mut c_void, +} + +impl Drop for CommsConfig { + fn drop(&mut self) { + unsafe { ffi_import::comms_config_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} + +impl CommsConfig { + pub fn create(port: u64, transport_config: TransportConfig, base_dir: String) -> Self { + let mut error = 0; + let ptr; + unsafe { + ptr = ffi_import::comms_config_create( + CString::new(format!("/ip4/127.0.0.1/tcp/{}", port)).unwrap().into_raw(), + transport_config.get_ptr(), + CString::new("wallet.dat").unwrap().into_raw(), + CString::new(base_dir).unwrap().into_raw(), + 30, + 600, + &mut error, + ); + } + Self { ptr } + } + + pub fn get_ptr(&self) -> *mut c_void { + self.ptr + } +} diff --git a/integration_tests/tests/utils/ffi/completed_transaction.rs b/integration_tests/tests/utils/ffi/completed_transaction.rs new file mode 100644 index 0000000000..e9b7f2c4c1 --- /dev/null +++ b/integration_tests/tests/utils/ffi/completed_transaction.rs @@ -0,0 +1,196 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ptr::null_mut; + +use libc::c_void; + +use super::{ffi_import, FFIString, Kernel, WalletAddress}; + +pub struct CompletedTransaction { + ptr: *mut c_void, +} + +impl Drop for CompletedTransaction { + fn drop(&mut self) { + unsafe { ffi_import::completed_transaction_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} +impl CompletedTransaction { + pub fn from_ptr(ptr: *mut c_void) -> Self { + Self { ptr } + } + + pub fn get_transaction_id(&self) -> u64 { + let tx_id; + let mut error = 0; + unsafe { + tx_id = ffi_import::completed_transaction_get_transaction_id(self.ptr, &mut error); + if error > 0 { + println!("completed_transaction_get_transaction_id error {}", error); + } + } + tx_id + } + + #[allow(dead_code)] + pub fn get_destination_tari_address(&self) -> WalletAddress { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::completed_transaction_get_destination_tari_address(self.ptr, &mut error); + if error > 0 { + println!("completed_transaction_get_destination_tari_address error {}", error); + } + } + WalletAddress::from_ptr(ptr) + } + + #[allow(dead_code)] + pub fn get_source_tari_address(&self) -> WalletAddress { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::completed_transaction_get_source_tari_address(self.ptr, &mut error); + if error > 0 { + println!("completed_transaction_get_source_tari_address error {}", error); + } + } + WalletAddress::from_ptr(ptr) + } + + pub fn get_transaction_kernel(&self) -> Kernel { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::completed_transaction_get_transaction_kernel(self.ptr, &mut error); + if error > 0 { + println!("completed_transaction_get_transaction_kernel error {}", error); + } + } + Kernel::from_ptr(ptr) + } + + #[allow(dead_code)] + pub fn get_amount(&self) -> u64 { + let amount; + let mut error = 0; + unsafe { + amount = ffi_import::completed_transaction_get_amount(self.ptr, &mut error); + if error > 0 { + println!("completed_transaction_get_amount error {}", error); + } + } + amount + } + + #[allow(dead_code)] + pub fn get_fee(&self) -> u64 { + let fee; + let mut error = 0; + unsafe { + fee = ffi_import::completed_transaction_get_fee(self.ptr, &mut error); + if error > 0 { + println!("completed_transaction_get_fee error {}", error); + } + } + fee + } + + #[allow(dead_code)] + pub fn get_timestamp(&self) -> u64 { + let timestamp; + let mut error = 0; + unsafe { + timestamp = ffi_import::completed_transaction_get_timestamp(self.ptr, &mut error); + if error > 0 { + println!("completed_transaction_get_timestamp error {}", error); + } + } + timestamp + } + + #[allow(dead_code)] + pub fn get_message(&self) -> String { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::completed_transaction_get_message(self.ptr, &mut error); + if error > 0 { + println!("completed_transaction_get_message error {}", error); + } + } + FFIString::from_ptr(ptr as *mut i8).as_string() + } + + #[allow(dead_code)] + pub fn get_status(&self) -> i32 { + let status; + let mut error = 0; + unsafe { + status = ffi_import::completed_transaction_get_status(self.ptr, &mut error); + if error > 0 { + println!("completed_transaction_get_status error {}", error); + } + } + status + } + + pub fn is_outbound(&self) -> bool { + let is_outbound; + let mut error = 0; + unsafe { + is_outbound = ffi_import::completed_transaction_is_outbound(self.ptr, &mut error); + if error > 0 { + println!("completed_transaction_is_outbound error {}", error); + } + } + is_outbound + } + + #[allow(dead_code)] + pub fn completed_transaction_get_confirmations(&self) -> u64 { + let confirmations_cnt; + let mut error = 0; + unsafe { + confirmations_cnt = ffi_import::completed_transaction_get_confirmations(self.ptr, &mut error); + if error > 0 { + println!("completed_transaction_get_confirmations error {}", error); + } + } + confirmations_cnt + } + + #[allow(dead_code)] + pub fn get_cancellation_reason(&self) -> i32 { + let reason; + let mut error = 0; + unsafe { + reason = ffi_import::completed_transaction_get_cancellation_reason(self.ptr, &mut error); + if error > 0 { + println!("completed_transaction_get_cancellation_reason error {}", error); + } + } + reason + } +} diff --git a/integration_tests/tests/utils/ffi/completed_transactions.rs b/integration_tests/tests/utils/ffi/completed_transactions.rs new file mode 100644 index 0000000000..6f7f2f09bb --- /dev/null +++ b/integration_tests/tests/utils/ffi/completed_transactions.rs @@ -0,0 +1,67 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ptr::null_mut; + +use libc::c_void; + +use super::{ffi_import, CompletedTransaction}; + +pub struct CompletedTransactions { + ptr: *mut c_void, +} + +impl Drop for CompletedTransactions { + fn drop(&mut self) { + unsafe { ffi_import::completed_transactions_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} +impl CompletedTransactions { + pub fn from_ptr(ptr: *mut c_void) -> Self { + Self { ptr } + } + + pub fn get_length(&self) -> u32 { + let length; + let mut error = 0; + unsafe { + length = ffi_import::completed_transactions_get_length(self.ptr, &mut error); + if error > 0 { + println!("completed_transactions_get_length error {}", error); + } + } + length + } + + pub fn get_at(&self, position: u32) -> CompletedTransaction { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::completed_transactions_get_at(self.ptr, position, &mut error); + if error > 0 { + println!("completed_transactions_get_at error {}", error); + } + } + CompletedTransaction::from_ptr(ptr) + } +} diff --git a/integration_tests/tests/utils/ffi/contact.rs b/integration_tests/tests/utils/ffi/contact.rs new file mode 100644 index 0000000000..1bad8d5658 --- /dev/null +++ b/integration_tests/tests/utils/ffi/contact.rs @@ -0,0 +1,88 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ffi::CString, ptr::null_mut}; + +use libc::c_void; + +use super::{ffi_import, FFIString, WalletAddress}; + +pub struct Contact { + ptr: *mut c_void, +} + +impl Drop for Contact { + fn drop(&mut self) { + unsafe { ffi_import::contact_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} + +impl Contact { + pub fn from_ptr(ptr: *mut c_void) -> Self { + Self { ptr } + } + + pub fn get_ptr(&self) -> *mut c_void { + self.ptr + } + + pub fn create(alias: String, address: String) -> Self { + let mut error = 0; + let ptr; + unsafe { + ptr = ffi_import::contact_create( + CString::new(alias).unwrap().into_raw(), + WalletAddress::from_hex(address).get_ptr(), + &mut error, + ); + if error > 0 { + println!("contact_create error {}", error); + } + } + Self { ptr } + } + + pub fn get_alias(&self) -> String { + let mut error = 0; + let alias; + unsafe { + alias = FFIString::from_ptr(ffi_import::contact_get_alias(self.ptr, &mut error)); + if error > 0 { + println!("contact_get_alias error {}", error); + } + } + alias.as_string() + } + + pub fn get_address(&self) -> WalletAddress { + let mut error = 0; + let ptr; + unsafe { + ptr = ffi_import::contact_get_tari_address(self.ptr, &mut error); + if error > 0 { + println!("contact_get_tari_address error {}", error); + } + } + WalletAddress::from_ptr(ptr) + } +} diff --git a/integration_tests/tests/utils/ffi/contacts.rs b/integration_tests/tests/utils/ffi/contacts.rs new file mode 100644 index 0000000000..1ebe653eba --- /dev/null +++ b/integration_tests/tests/utils/ffi/contacts.rs @@ -0,0 +1,70 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ptr::null_mut; + +use libc::c_void; + +use super::{ffi_import, Contact}; + +pub struct Contacts { + ptr: *mut c_void, +} + +impl Drop for Contacts { + fn drop(&mut self) { + unsafe { ffi_import::contacts_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} + +impl Contacts { + pub fn from_ptr(ptr: *mut c_void) -> Self { + Self { ptr } + } + + pub fn get_length(&self) -> u32 { + let mut error = 0; + let length; + unsafe { + length = ffi_import::contacts_get_length(self.ptr, &mut error); + if error > 0 { + println!("contacts_get_length error {}", error); + } + } + length + } + + pub fn get_at(&self, position: u32) -> Contact { + let mut error = 0; + let ptr; + unsafe { + ptr = ffi_import::contacts_get_at(self.ptr, position, &mut error); + if error > 0 { + println!("contacts_get_at error {}", error); + } + } + Contact::from_ptr(ptr) + } +} + +// pub fn contacts_get_at(contacts: *mut TariContacts, position: c_uint, error_out: *mut c_int) -> *mut TariContact; diff --git a/integration_tests/tests/utils/ffi/contacts_liveness_data.rs b/integration_tests/tests/utils/ffi/contacts_liveness_data.rs new file mode 100644 index 0000000000..f5a6da7e29 --- /dev/null +++ b/integration_tests/tests/utils/ffi/contacts_liveness_data.rs @@ -0,0 +1,104 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ptr::null_mut; + +use libc::c_void; + +use super::{ffi_import, FFIString, WalletAddress}; + +#[derive(Debug)] +pub struct ContactsLivenessData { + ptr: *mut c_void, +} + +impl Drop for ContactsLivenessData { + fn drop(&mut self) { + unsafe { ffi_import::liveness_data_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} +impl ContactsLivenessData { + pub fn from_ptr(ptr: *mut c_void) -> Self { + Self { ptr } + } + + pub fn get_public_key(&self) -> WalletAddress { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::liveness_data_get_public_key(self.ptr, &mut error); + if error > 0 { + println!("liveness_data_get_public_key error {}", error); + } + } + WalletAddress::from_ptr(ptr) + } + + pub fn get_latency(&self) -> i32 { + let latency; + let mut error = 0; + unsafe { + latency = ffi_import::liveness_data_get_latency(self.ptr, &mut error); + if error > 0 { + println!("liveness_data_get_latency error {}", error); + } + } + latency + } + + pub fn get_last_seen(&self) -> String { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::liveness_data_get_last_seen(self.ptr, &mut error); + if error > 0 { + println!("liveness_data_get_last_seen error {}", error); + } + } + FFIString::from_ptr(ptr).as_string() + } + + pub fn get_message_type(&self) -> i32 { + let message_type; + let mut error = 0; + unsafe { + message_type = ffi_import::liveness_data_get_message_type(self.ptr, &mut error); + if error > 0 { + println!("liveness_data_get_message_type error {}", error); + } + } + message_type + } + + pub fn get_online_status(&self) -> String { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::liveness_data_get_online_status(self.ptr, &mut error); + if error > 0 { + println!("liveness_data_get_online_status error {}", error); + } + } + FFIString::from_ptr(ptr as *mut i8).as_string() + } +} diff --git a/integration_tests/tests/utils/ffi/fee_per_gram_stat.rs b/integration_tests/tests/utils/ffi/fee_per_gram_stat.rs new file mode 100644 index 0000000000..5200e2905a --- /dev/null +++ b/integration_tests/tests/utils/ffi/fee_per_gram_stat.rs @@ -0,0 +1,91 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ptr::null_mut; + +use libc::c_void; + +use super::ffi_import; + +pub struct FeePerGramStat { + ptr: *mut c_void, +} + +impl Drop for FeePerGramStat { + fn drop(&mut self) { + unsafe { ffi_import::fee_per_gram_stat_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} +impl FeePerGramStat { + pub fn from_ptr(ptr: *mut c_void) -> Self { + Self { ptr } + } + + pub fn get_order(&self) -> u64 { + let order; + let mut error = 0; + unsafe { + order = ffi_import::fee_per_gram_stat_get_order(self.ptr, &mut error); + if error > 0 { + println!("fee_per_gram_stat_get_order error {}", error); + } + } + order + } + + pub fn get_min_fee_per_gram(&self) -> u64 { + let min; + let mut error = 0; + unsafe { + min = ffi_import::fee_per_gram_stat_get_min_fee_per_gram(self.ptr, &mut error); + if error > 0 { + println!("fee_per_gram_stat_get_min_fee_per_gram error {}", error); + } + } + min + } + + pub fn get_avg_fee_per_gram(&self) -> u64 { + let avg; + let mut error = 0; + unsafe { + avg = ffi_import::fee_per_gram_stat_get_avg_fee_per_gram(self.ptr, &mut error); + if error > 0 { + println!("fee_per_gram_stat_get_avg_fee_per_gram error {}", error); + } + } + avg + } + + pub fn get_max_fee_per_gram(&self) -> u64 { + let max; + let mut error = 0; + unsafe { + max = ffi_import::fee_per_gram_stat_get_max_fee_per_gram(self.ptr, &mut error); + if error > 0 { + println!("fee_per_gram_stat_get_max_fee_per_gram error {}", error); + } + } + max + } +} diff --git a/integration_tests/tests/utils/ffi/fee_per_gram_stats.rs b/integration_tests/tests/utils/ffi/fee_per_gram_stats.rs new file mode 100644 index 0000000000..242ab69375 --- /dev/null +++ b/integration_tests/tests/utils/ffi/fee_per_gram_stats.rs @@ -0,0 +1,67 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ptr::null_mut; + +use libc::c_void; + +use super::{ffi_import, FeePerGramStat}; + +pub struct FeePerGramStats { + ptr: *mut c_void, +} + +impl Drop for FeePerGramStats { + fn drop(&mut self) { + unsafe { ffi_import::fee_per_gram_stats_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} +impl FeePerGramStats { + pub fn from_ptr(ptr: *mut c_void) -> Self { + Self { ptr } + } + + pub fn get_length(&self) -> u32 { + let length; + let mut error = 0; + unsafe { + length = ffi_import::fee_per_gram_stats_get_length(self.ptr, &mut error); + if error > 0 { + println!("fee_per_gram_stats_get_length error {}", error); + } + } + length + } + + pub fn get_at(&self, position: u32) -> FeePerGramStat { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::fee_per_gram_stats_get_at(self.ptr, position, &mut error); + if error > 0 { + println!("fee_per_gram_stats_get_at error {}", error); + } + } + FeePerGramStat::from_ptr(ptr) + } +} diff --git a/integration_tests/tests/utils/ffi/ffi_bytes.rs b/integration_tests/tests/utils/ffi/ffi_bytes.rs new file mode 100644 index 0000000000..123ec05cd7 --- /dev/null +++ b/integration_tests/tests/utils/ffi/ffi_bytes.rs @@ -0,0 +1,86 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ptr::null_mut; + +use libc::c_void; +use tari_utilities::{hex, ByteArray}; + +use super::ffi_import; + +pub struct FFIBytes { + ptr: *mut c_void, +} + +impl Drop for FFIBytes { + fn drop(&mut self) { + unsafe { ffi_import::byte_vector_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} + +impl FFIBytes { + pub fn from_ptr(ptr: *mut c_void) -> Self { + Self { ptr } + } + + fn get_length(&self) -> usize { + let mut error = 0; + let length; + unsafe { + length = ffi_import::byte_vector_get_length(self.ptr, &mut error) as usize; + if error > 0 { + println!("byte_vector_get_length error {}", error); + } + } + length + } + + fn get_at(&self, i: u32) -> u8 { + let mut error = 0; + let byte; + unsafe { + byte = ffi_import::byte_vector_get_at(self.ptr, i, &mut error); + if error > 0 { + println!("byte_vector_get_at error {}", error); + } + } + byte + } + + pub fn get_vec(&self) -> Vec { + let mut data = Vec::with_capacity(self.get_length()); + for i in 0..self.get_length() { + data.push(self.get_at(i as u32)); + } + data + } + + pub fn get_as_hex(&self) -> String { + let data = self.get_vec(); + hex::to_hex(data.as_bytes()) + } + + pub fn get_ptr(&self) -> *mut c_void { + self.ptr + } +} diff --git a/integration_tests/tests/utils/ffi/ffi_import.rs b/integration_tests/tests/utils/ffi/ffi_import.rs new file mode 100644 index 0000000000..144069025d --- /dev/null +++ b/integration_tests/tests/utils/ffi/ffi_import.rs @@ -0,0 +1,590 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use libc::{c_char, c_int, c_uchar, c_uint, c_ulonglong, c_ushort, c_void}; + +pub type TariTransportConfig = c_void; +pub type TariCommsConfig = c_void; +pub type TariSeedWords = c_void; +pub type TariPendingInboundTransaction = c_void; +pub type TariCompletedTransaction = c_void; +pub type TariTransactionSendStatus = c_void; +pub type TariContactsLivenessData = c_void; +pub type TariBalance = c_void; +pub type TariWallet = c_void; +pub type TariWalletAddress = c_void; +pub type ByteVector = c_void; +#[allow(dead_code)] +pub type TariFeePerGramStat = c_void; +#[allow(dead_code)] +pub type TariTypeTag = c_void; +pub type TariVector = c_void; +pub type TariCoinPreview = c_void; +pub type TariTransactionKernel = c_void; +pub type TariPublicKey = c_void; +#[allow(dead_code)] +pub type TariPublicKeys = c_void; +#[allow(dead_code)] +pub type TariPrivateKey = c_void; +#[allow(dead_code)] +pub type TariComAndPubSignature = c_void; +#[allow(dead_code)] +pub type TariOutputFeatures = c_void; +#[allow(dead_code)] +pub type TariCovenant = c_void; +#[allow(dead_code)] +pub type TariEncryptedValue = c_void; +#[allow(dead_code)] +pub type TariUnblindedOutput = c_void; +#[allow(dead_code)] +pub type TariUnblindedOutputs = c_void; +pub type TariContact = c_void; +pub type TariContacts = c_void; +pub type TariCompletedTransactions = c_void; +pub type TariPendingOutboundTransactions = c_void; +pub type TariPendingOutboundTransaction = c_void; +pub type TariPendingInboundTransactions = c_void; +#[allow(dead_code)] +pub type TariUtxoSort = c_void; +#[allow(dead_code)] +pub type EmojiSet = c_void; +#[allow(dead_code)] +pub type TariFeePerGramStats = c_void; + +#[cfg_attr(windows, link(name = "tari_wallet_ffi.dll"))] +#[cfg_attr(not(windows), link(name = "tari_wallet_ffi"))] +#[allow(dead_code)] +extern "C" { + pub fn create_tari_vector(tag: TariTypeTag) -> *mut TariVector; + pub fn tari_vector_push_string(tv: *mut TariVector, s: *const c_char, error_ptr: *mut i32); + pub fn destroy_tari_vector(v: *mut TariVector); + pub fn destroy_tari_coin_preview(p: *mut TariCoinPreview); + pub fn string_destroy(ptr: *mut c_char); + pub fn transaction_kernel_get_excess_hex(kernel: *mut TariTransactionKernel, error_out: *mut c_int) -> *mut c_char; + pub fn transaction_kernel_get_excess_public_nonce_hex( + kernel: *mut TariTransactionKernel, + error_out: *mut c_int, + ) -> *mut c_char; + pub fn transaction_kernel_get_excess_signature_hex( + kernel: *mut TariTransactionKernel, + error_out: *mut c_int, + ) -> *mut c_char; + pub fn transaction_kernel_destroy(x: *mut TariTransactionKernel); + pub fn byte_vector_create( + byte_array: *const c_uchar, + element_count: c_uint, + error_out: *mut c_int, + ) -> *mut ByteVector; + pub fn byte_vector_destroy(bytes: *mut ByteVector); + pub fn byte_vector_get_at(ptr: *mut ByteVector, position: c_uint, error_out: *mut c_int) -> c_uchar; + pub fn byte_vector_get_length(vec: *const ByteVector, error_out: *mut c_int) -> c_uint; + pub fn public_key_create(bytes: *mut ByteVector, error_out: *mut c_int) -> *mut TariPublicKey; + pub fn public_key_destroy(pk: *mut TariPublicKey); + pub fn public_keys_destroy(pks: *mut TariPublicKeys); + pub fn public_key_get_bytes(pk: *mut TariPublicKey, error_out: *mut c_int) -> *mut ByteVector; + pub fn public_key_from_private_key(secret_key: *mut TariPrivateKey, error_out: *mut c_int) -> *mut TariPublicKey; + pub fn public_key_from_hex(key: *const c_char, error_out: *mut c_int) -> *mut TariPublicKey; + pub fn tari_address_create(bytes: *mut ByteVector, error_out: *mut c_int) -> *mut TariWalletAddress; + pub fn tari_address_destroy(address: *mut TariWalletAddress); + pub fn tari_address_get_bytes(address: *mut TariWalletAddress, error_out: *mut c_int) -> *mut ByteVector; + pub fn tari_address_from_private_key( + secret_key: *mut TariPrivateKey, + network: c_uint, + error_out: *mut c_int, + ) -> *mut TariWalletAddress; + pub fn tari_address_from_hex(address: *const c_char, error_out: *mut c_int) -> *mut TariWalletAddress; + pub fn tari_address_to_emoji_id(address: *mut TariWalletAddress, error_out: *mut c_int) -> *mut c_char; + pub fn emoji_id_to_tari_address(emoji: *const c_char, error_out: *mut c_int) -> *mut TariWalletAddress; + pub fn commitment_and_public_signature_create_from_bytes( + ephemeral_commitment_bytes: *const ByteVector, + ephemeral_pubkey_bytes: *const ByteVector, + u_a_bytes: *const ByteVector, + u_x_bytes: *const ByteVector, + u_y_bytes: *const ByteVector, + error_out: *mut c_int, + ) -> *mut TariComAndPubSignature; + pub fn commitment_and_public_signature_destroy(compub_sig: *mut TariComAndPubSignature); + pub fn create_tari_unblinded_output( + amount: c_ulonglong, + spending_key: *mut TariPrivateKey, + features: *mut TariOutputFeatures, + script: *const c_char, + input_data: *const c_char, + metadata_signature: *mut TariComAndPubSignature, + sender_offset_public_key: *mut TariPublicKey, + script_private_key: *mut TariPrivateKey, + covenant: *mut TariCovenant, + encrypted_value: *mut TariEncryptedValue, + minimum_value_promise: c_ulonglong, + script_lock_height: c_ulonglong, + error_out: *mut c_int, + ) -> *mut TariUnblindedOutput; + pub fn tari_unblinded_output_destroy(output: *mut TariUnblindedOutput); + pub fn unblinded_outputs_get_length(outputs: *mut TariUnblindedOutputs, error_out: *mut c_int) -> c_uint; + pub fn unblinded_outputs_get_at( + outputs: *mut TariUnblindedOutputs, + position: c_uint, + error_out: *mut c_int, + ) -> *mut TariUnblindedOutput; + pub fn unblinded_outputs_destroy(outputs: *mut TariUnblindedOutputs); + pub fn wallet_import_external_utxo_as_non_rewindable( + wallet: *mut TariWallet, + output: *mut TariUnblindedOutput, + source_address: *mut TariWalletAddress, + message: *const c_char, + error_out: *mut c_int, + ) -> c_ulonglong; + pub fn wallet_get_unspent_outputs(wallet: *mut TariWallet, error_out: *mut c_int) -> *mut TariUnblindedOutputs; + pub fn private_key_create(bytes: *mut ByteVector, error_out: *mut c_int) -> *mut TariPrivateKey; + pub fn private_key_destroy(pk: *mut TariPrivateKey); + pub fn private_key_get_bytes(pk: *mut TariPrivateKey, error_out: *mut c_int) -> *mut ByteVector; + pub fn private_key_generate() -> *mut TariPrivateKey; + pub fn private_key_from_hex(key: *const c_char, error_out: *mut c_int) -> *mut TariPrivateKey; + pub fn covenant_create_from_bytes(covenant_bytes: *const ByteVector, error_out: *mut c_int) -> *mut TariCovenant; + pub fn covenant_destroy(covenant: *mut TariCovenant); + pub fn encrypted_value_create_from_bytes( + encrypted_value_bytes: *const ByteVector, + error_out: *mut c_int, + ) -> *mut TariEncryptedValue; + pub fn encrypted_value_as_bytes( + encrypted_value: *const TariEncryptedValue, + error_out: *mut c_int, + ) -> *mut ByteVector; + pub fn encrypted_value_destroy(encrypted_value: *mut TariEncryptedValue); + pub fn output_features_create_from_bytes( + version: c_uchar, + output_type: c_ushort, + maturity: c_ulonglong, + metadata: *const ByteVector, + error_out: *mut c_int, + ) -> *mut TariOutputFeatures; + pub fn output_features_destroy(output_features: *mut TariOutputFeatures); + pub fn seed_words_create() -> *mut TariSeedWords; + pub fn seed_words_get_mnemonic_word_list_for_language( + language: *const c_char, + error_out: *mut c_int, + ) -> *mut TariSeedWords; + pub fn seed_words_get_length(seed_words: *const TariSeedWords, error_out: *mut c_int) -> c_uint; + pub fn seed_words_get_at(seed_words: *mut TariSeedWords, position: c_uint, error_out: *mut c_int) -> *mut c_char; + pub fn seed_words_push_word(seed_words: *mut TariSeedWords, word: *const c_char, error_out: *mut c_int) -> c_uchar; + pub fn seed_words_destroy(seed_words: *mut TariSeedWords); + pub fn contact_create( + alias: *const c_char, + address: *mut TariWalletAddress, + error_out: *mut c_int, + ) -> *mut TariContact; + pub fn contact_get_alias(contact: *mut TariContact, error_out: *mut c_int) -> *mut c_char; + pub fn contact_get_tari_address(contact: *mut TariContact, error_out: *mut c_int) -> *mut TariWalletAddress; + pub fn contact_destroy(contact: *mut TariContact); + pub fn contacts_get_length(contacts: *mut TariContacts, error_out: *mut c_int) -> c_uint; + pub fn contacts_get_at(contacts: *mut TariContacts, position: c_uint, error_out: *mut c_int) -> *mut TariContact; + pub fn contacts_destroy(contacts: *mut TariContacts); + pub fn liveness_data_get_public_key( + liveness_data: *mut TariContactsLivenessData, + error_out: *mut c_int, + ) -> *mut TariWalletAddress; + pub fn liveness_data_get_latency(liveness_data: *mut TariContactsLivenessData, error_out: *mut c_int) -> c_int; + pub fn liveness_data_get_last_seen( + liveness_data: *mut TariContactsLivenessData, + error_out: *mut c_int, + ) -> *mut c_char; + pub fn liveness_data_get_message_type(liveness_data: *mut TariContactsLivenessData, error_out: *mut c_int) + -> c_int; + pub fn liveness_data_get_online_status( + liveness_data: *mut TariContactsLivenessData, + error_out: *mut c_int, + ) -> *const c_char; + pub fn liveness_data_destroy(liveness_data: *mut TariContactsLivenessData); + pub fn completed_transactions_get_length( + transactions: *mut TariCompletedTransactions, + error_out: *mut c_int, + ) -> c_uint; + pub fn completed_transactions_get_at( + transactions: *mut TariCompletedTransactions, + position: c_uint, + error_out: *mut c_int, + ) -> *mut TariCompletedTransaction; + pub fn completed_transactions_destroy(transactions: *mut TariCompletedTransactions); + pub fn pending_outbound_transactions_get_length( + transactions: *mut TariPendingOutboundTransactions, + error_out: *mut c_int, + ) -> c_uint; + pub fn pending_outbound_transactions_get_at( + transactions: *mut TariPendingOutboundTransactions, + position: c_uint, + error_out: *mut c_int, + ) -> *mut TariPendingOutboundTransaction; + pub fn pending_outbound_transactions_destroy(transactions: *mut TariPendingOutboundTransactions); + pub fn pending_inbound_transactions_get_length( + transactions: *mut TariPendingInboundTransactions, + error_out: *mut c_int, + ) -> c_uint; + pub fn pending_inbound_transactions_get_at( + transactions: *mut TariPendingInboundTransactions, + position: c_uint, + error_out: *mut c_int, + ) -> *mut TariPendingInboundTransaction; + pub fn pending_inbound_transactions_destroy(transactions: *mut TariPendingInboundTransactions); + pub fn completed_transaction_get_transaction_id( + transaction: *mut TariCompletedTransaction, + error_out: *mut c_int, + ) -> c_ulonglong; + pub fn completed_transaction_get_destination_tari_address( + transaction: *mut TariCompletedTransaction, + error_out: *mut c_int, + ) -> *mut TariWalletAddress; + pub fn completed_transaction_get_transaction_kernel( + transaction: *mut TariCompletedTransaction, + error_out: *mut c_int, + ) -> *mut TariTransactionKernel; + pub fn completed_transaction_get_source_tari_address( + transaction: *mut TariCompletedTransaction, + error_out: *mut c_int, + ) -> *mut TariWalletAddress; + pub fn completed_transaction_get_status(transaction: *mut TariCompletedTransaction, error_out: *mut c_int) + -> c_int; + pub fn completed_transaction_get_amount( + transaction: *mut TariCompletedTransaction, + error_out: *mut c_int, + ) -> c_ulonglong; + pub fn completed_transaction_get_fee( + transaction: *mut TariCompletedTransaction, + error_out: *mut c_int, + ) -> c_ulonglong; + pub fn completed_transaction_get_timestamp( + transaction: *mut TariCompletedTransaction, + error_out: *mut c_int, + ) -> c_ulonglong; + pub fn completed_transaction_get_message( + transaction: *mut TariCompletedTransaction, + error_out: *mut c_int, + ) -> *const c_char; + pub fn completed_transaction_is_outbound(tx: *mut TariCompletedTransaction, error_out: *mut c_int) -> bool; + pub fn completed_transaction_get_confirmations( + tx: *mut TariCompletedTransaction, + error_out: *mut c_int, + ) -> c_ulonglong; + pub fn completed_transaction_get_cancellation_reason( + tx: *mut TariCompletedTransaction, + error_out: *mut c_int, + ) -> c_int; + pub fn completed_transaction_destroy(transaction: *mut TariCompletedTransaction); + pub fn pending_outbound_transaction_get_transaction_id( + transaction: *mut TariPendingOutboundTransaction, + error_out: *mut c_int, + ) -> c_ulonglong; + pub fn pending_outbound_transaction_get_destination_tari_address( + transaction: *mut TariPendingOutboundTransaction, + error_out: *mut c_int, + ) -> *mut TariWalletAddress; + pub fn pending_outbound_transaction_get_amount( + transaction: *mut TariPendingOutboundTransaction, + error_out: *mut c_int, + ) -> c_ulonglong; + pub fn pending_outbound_transaction_get_fee( + transaction: *mut TariPendingOutboundTransaction, + error_out: *mut c_int, + ) -> c_ulonglong; + pub fn pending_outbound_transaction_get_timestamp( + transaction: *mut TariPendingOutboundTransaction, + error_out: *mut c_int, + ) -> c_ulonglong; + pub fn pending_outbound_transaction_get_message( + transaction: *mut TariPendingOutboundTransaction, + error_out: *mut c_int, + ) -> *const c_char; + pub fn pending_outbound_transaction_get_status( + transaction: *mut TariPendingOutboundTransaction, + error_out: *mut c_int, + ) -> c_int; + pub fn pending_outbound_transaction_destroy(transaction: *mut TariPendingOutboundTransaction); + pub fn pending_inbound_transaction_get_transaction_id( + transaction: *mut TariPendingInboundTransaction, + error_out: *mut c_int, + ) -> c_ulonglong; + pub fn pending_inbound_transaction_get_source_tari_address( + transaction: *mut TariPendingInboundTransaction, + error_out: *mut c_int, + ) -> *mut TariWalletAddress; + pub fn pending_inbound_transaction_get_amount( + transaction: *mut TariPendingInboundTransaction, + error_out: *mut c_int, + ) -> c_ulonglong; + pub fn pending_inbound_transaction_get_timestamp( + transaction: *mut TariPendingInboundTransaction, + error_out: *mut c_int, + ) -> c_ulonglong; + pub fn pending_inbound_transaction_get_message( + transaction: *mut TariPendingInboundTransaction, + error_out: *mut c_int, + ) -> *const c_char; + pub fn pending_inbound_transaction_get_status( + transaction: *mut TariPendingInboundTransaction, + error_out: *mut c_int, + ) -> c_int; + pub fn pending_inbound_transaction_destroy(transaction: *mut TariPendingInboundTransaction); + pub fn transaction_send_status_decode(status: *const TariTransactionSendStatus, error_out: *mut c_int) -> c_uint; + pub fn transaction_send_status_destroy(status: *mut TariTransactionSendStatus); + pub fn transport_memory_create() -> *mut TariTransportConfig; + pub fn transport_tcp_create(listener_address: *const c_char, error_out: *mut c_int) -> *mut TariTransportConfig; + pub fn transport_tor_create( + control_server_address: *const c_char, + tor_cookie: *const ByteVector, + tor_port: c_ushort, + tor_proxy_bypass_for_outbound: bool, + socks_username: *const c_char, + socks_password: *const c_char, + error_out: *mut c_int, + ) -> *mut TariTransportConfig; + pub fn transport_memory_get_address(transport: *const TariTransportConfig, error_out: *mut c_int) -> *mut c_char; + pub fn transport_type_destroy(transport: *mut TariTransportConfig); + pub fn transport_config_destroy(transport: *mut TariTransportConfig); + pub fn comms_config_create( + public_address: *const c_char, + transport: *const TariTransportConfig, + database_name: *const c_char, + datastore_path: *const c_char, + discovery_timeout_in_secs: c_ulonglong, + saf_message_duration_in_secs: c_ulonglong, + error_out: *mut c_int, + ) -> *mut TariCommsConfig; + pub fn comms_config_destroy(wc: *mut TariCommsConfig); + pub fn comms_list_connected_public_keys(wallet: *mut TariWallet, error_out: *mut c_int) -> *mut TariPublicKeys; + pub fn public_keys_get_length(public_keys: *const TariPublicKeys, error_out: *mut c_int) -> c_uint; + pub fn public_keys_get_at( + public_keys: *const TariPublicKeys, + position: c_uint, + error_out: *mut c_int, + ) -> *mut TariPublicKey; + pub fn wallet_create( + config: *mut TariCommsConfig, + log_path: *const c_char, + num_rolling_log_files: c_uint, + size_per_log_file_bytes: c_uint, + passphrase: *const c_char, + seed_words: *const TariSeedWords, + network_str: *const c_char, + callback_received_transaction: unsafe extern "C" fn(*mut TariPendingInboundTransaction), + callback_received_transaction_reply: unsafe extern "C" fn(*mut TariCompletedTransaction), + callback_received_finalized_transaction: unsafe extern "C" fn(*mut TariCompletedTransaction), + callback_transaction_broadcast: unsafe extern "C" fn(*mut TariCompletedTransaction), + callback_transaction_mined: unsafe extern "C" fn(*mut TariCompletedTransaction), + callback_transaction_mined_unconfirmed: unsafe extern "C" fn(*mut TariCompletedTransaction, u64), + callback_faux_transaction_confirmed: unsafe extern "C" fn(*mut TariCompletedTransaction), + callback_faux_transaction_unconfirmed: unsafe extern "C" fn(*mut TariCompletedTransaction, u64), + callback_transaction_send_result: unsafe extern "C" fn(c_ulonglong, *mut TariTransactionSendStatus), + callback_transaction_cancellation: unsafe extern "C" fn(*mut TariCompletedTransaction, u64), + callback_txo_validation_complete: unsafe extern "C" fn(u64, u64), + callback_contacts_liveness_data_updated: unsafe extern "C" fn(*mut TariContactsLivenessData), + callback_balance_updated: unsafe extern "C" fn(*mut TariBalance), + callback_transaction_validation_complete: unsafe extern "C" fn(u64, u64), + callback_saf_messages_received: unsafe extern "C" fn(), + callback_connectivity_status: unsafe extern "C" fn(u64), + recovery_in_progress: *mut bool, + error_out: *mut c_int, + ) -> *mut TariWallet; + pub fn wallet_get_balance(wallet: *mut TariWallet, error_out: *mut c_int) -> *mut TariBalance; + pub fn wallet_get_utxos( + wallet: *mut TariWallet, + page: usize, + page_size: usize, + sorting: TariUtxoSort, + states: *mut TariVector, + dust_threshold: u64, + error_ptr: *mut i32, + ) -> *mut TariVector; + pub fn wallet_get_all_utxos(wallet: *mut TariWallet, error_ptr: *mut i32) -> *mut TariVector; + pub fn wallet_coin_split( + wallet: *mut TariWallet, + commitments: *mut TariVector, + number_of_splits: usize, + fee_per_gram: u64, + error_ptr: *mut i32, + ) -> u64; + pub fn wallet_coin_join( + wallet: *mut TariWallet, + commitments: *mut TariVector, + fee_per_gram: u64, + error_ptr: *mut i32, + ) -> u64; + pub fn wallet_preview_coin_join( + wallet: *mut TariWallet, + commitments: *mut TariVector, + fee_per_gram: u64, + error_ptr: *mut i32, + ) -> *mut TariCoinPreview; + pub fn wallet_preview_coin_split( + wallet: *mut TariWallet, + commitments: *mut TariVector, + number_of_splits: usize, + fee_per_gram: u64, + error_ptr: *mut i32, + ) -> *mut TariCoinPreview; + pub fn wallet_sign_message(wallet: *mut TariWallet, msg: *const c_char, error_out: *mut c_int) -> *mut c_char; + pub fn wallet_verify_message_signature( + wallet: *mut TariWallet, + public_key: *mut TariPublicKey, + hex_sig_nonce: *const c_char, + msg: *const c_char, + error_out: *mut c_int, + ) -> bool; + pub fn wallet_add_base_node_peer( + wallet: *mut TariWallet, + public_key: *mut TariPublicKey, + address: *const c_char, + error_out: *mut c_int, + ) -> bool; + pub fn wallet_upsert_contact(wallet: *mut TariWallet, contact: *mut TariContact, error_out: *mut c_int) -> bool; + pub fn wallet_remove_contact(wallet: *mut TariWallet, contact: *mut TariContact, error_out: *mut c_int) -> bool; + pub fn balance_get_available(balance: *mut TariBalance, error_out: *mut c_int) -> c_ulonglong; + pub fn balance_get_time_locked(balance: *mut TariBalance, error_out: *mut c_int) -> c_ulonglong; + pub fn balance_get_pending_incoming(balance: *mut TariBalance, error_out: *mut c_int) -> c_ulonglong; + pub fn balance_get_pending_outgoing(balance: *mut TariBalance, error_out: *mut c_int) -> c_ulonglong; + pub fn balance_destroy(balance: *mut TariBalance); + pub fn wallet_send_transaction( + wallet: *mut TariWallet, + destination: *mut TariWalletAddress, + amount: c_ulonglong, + commitments: *mut TariVector, + fee_per_gram: c_ulonglong, + message: *const c_char, + one_sided: bool, + error_out: *mut c_int, + ) -> c_ulonglong; + pub fn wallet_get_fee_estimate( + wallet: *mut TariWallet, + amount: c_ulonglong, + commitments: *mut TariVector, + fee_per_gram: c_ulonglong, + num_kernels: c_ulonglong, + num_outputs: c_ulonglong, + error_out: *mut c_int, + ) -> c_ulonglong; + pub fn wallet_get_num_confirmations_required(wallet: *mut TariWallet, error_out: *mut c_int) -> c_ulonglong; + pub fn wallet_set_num_confirmations_required(wallet: *mut TariWallet, num: c_ulonglong, error_out: *mut c_int); + pub fn wallet_get_contacts(wallet: *mut TariWallet, error_out: *mut c_int) -> *mut TariContacts; + pub fn wallet_get_completed_transactions( + wallet: *mut TariWallet, + error_out: *mut c_int, + ) -> *mut TariCompletedTransactions; + pub fn wallet_get_pending_inbound_transactions( + wallet: *mut TariWallet, + error_out: *mut c_int, + ) -> *mut TariPendingInboundTransactions; + pub fn wallet_get_pending_outbound_transactions( + wallet: *mut TariWallet, + error_out: *mut c_int, + ) -> *mut TariPendingOutboundTransactions; + pub fn wallet_get_cancelled_transactions( + wallet: *mut TariWallet, + error_out: *mut c_int, + ) -> *mut TariCompletedTransactions; + pub fn wallet_get_completed_transaction_by_id( + wallet: *mut TariWallet, + transaction_id: c_ulonglong, + error_out: *mut c_int, + ) -> *mut TariCompletedTransaction; + pub fn wallet_get_pending_inbound_transaction_by_id( + wallet: *mut TariWallet, + transaction_id: c_ulonglong, + error_out: *mut c_int, + ) -> *mut TariPendingInboundTransaction; + pub fn wallet_get_pending_outbound_transaction_by_id( + wallet: *mut TariWallet, + transaction_id: c_ulonglong, + error_out: *mut c_int, + ) -> *mut TariPendingOutboundTransaction; + pub fn wallet_get_cancelled_transaction_by_id( + wallet: *mut TariWallet, + transaction_id: c_ulonglong, + error_out: *mut c_int, + ) -> *mut TariCompletedTransaction; + pub fn wallet_get_tari_address(wallet: *mut TariWallet, error_out: *mut c_int) -> *mut TariWalletAddress; + pub fn wallet_cancel_pending_transaction( + wallet: *mut TariWallet, + transaction_id: c_ulonglong, + error_out: *mut c_int, + ) -> bool; + pub fn wallet_start_txo_validation(wallet: *mut TariWallet, error_out: *mut c_int) -> c_ulonglong; + pub fn wallet_start_transaction_validation(wallet: *mut TariWallet, error_out: *mut c_int) -> c_ulonglong; + pub fn wallet_restart_transaction_broadcast(wallet: *mut TariWallet, error_out: *mut c_int) -> bool; + pub fn wallet_get_seed_words(wallet: *mut TariWallet, error_out: *mut c_int) -> *mut TariSeedWords; + pub fn wallet_set_low_power_mode(wallet: *mut TariWallet, error_out: *mut c_int); + pub fn wallet_set_normal_power_mode(wallet: *mut TariWallet, error_out: *mut c_int); + pub fn wallet_set_key_value( + wallet: *mut TariWallet, + key: *const c_char, + value: *const c_char, + error_out: *mut c_int, + ) -> bool; + pub fn wallet_get_value(wallet: *mut TariWallet, key: *const c_char, error_out: *mut c_int) -> *mut c_char; + pub fn wallet_clear_value(wallet: *mut TariWallet, key: *const c_char, error_out: *mut c_int) -> bool; + pub fn wallet_is_recovery_in_progress(wallet: *mut TariWallet, error_out: *mut c_int) -> bool; + pub fn wallet_start_recovery( + wallet: *mut TariWallet, + base_node_public_key: *mut TariPublicKey, + recovery_progress_callback: unsafe extern "C" fn(u8, u64, u64), + recovered_output_message: *const c_char, + error_out: *mut c_int, + ) -> bool; + pub fn wallet_set_one_sided_payment_message( + wallet: *mut TariWallet, + message: *const c_char, + error_out: *mut c_int, + ) -> bool; + pub fn get_emoji_set() -> *mut EmojiSet; + pub fn emoji_set_get_length(emoji_set: *const EmojiSet, error_out: *mut c_int) -> c_uint; + pub fn emoji_set_get_at(emoji_set: *const EmojiSet, position: c_uint, error_out: *mut c_int) -> *mut ByteVector; + pub fn emoji_set_destroy(emoji_set: *mut EmojiSet); + pub fn wallet_destroy(wallet: *mut TariWallet); + pub fn log_debug_message(msg: *const c_char, error_out: *mut c_int); + pub fn wallet_get_fee_per_gram_stats( + wallet: *mut TariWallet, + count: c_uint, + error_out: *mut c_int, + ) -> *mut TariFeePerGramStats; + pub fn fee_per_gram_stats_get_length(fee_per_gram_stats: *mut TariFeePerGramStats, error_out: *mut c_int) + -> c_uint; + pub fn fee_per_gram_stats_get_at( + fee_per_gram_stats: *mut TariFeePerGramStats, + position: c_uint, + error_out: *mut c_int, + ) -> *mut TariFeePerGramStat; + pub fn fee_per_gram_stats_destroy(fee_per_gram_stats: *mut TariFeePerGramStats); + pub fn fee_per_gram_stat_get_order( + fee_per_gram_stat: *mut TariFeePerGramStat, + error_out: *mut c_int, + ) -> c_ulonglong; + pub fn fee_per_gram_stat_get_min_fee_per_gram( + fee_per_gram_stat: *mut TariFeePerGramStat, + error_out: *mut c_int, + ) -> c_ulonglong; + pub fn fee_per_gram_stat_get_avg_fee_per_gram( + fee_per_gram_stat: *mut TariFeePerGramStat, + error_out: *mut c_int, + ) -> c_ulonglong; + pub fn fee_per_gram_stat_get_max_fee_per_gram( + fee_per_gram_stat: *mut TariFeePerGramStat, + error_out: *mut c_int, + ) -> c_ulonglong; + pub fn fee_per_gram_stat_destroy(fee_per_gram_stat: *mut TariFeePerGramStat); +} diff --git a/integration_tests/tests/utils/ffi/ffi_string.rs b/integration_tests/tests/utils/ffi/ffi_string.rs new file mode 100644 index 0000000000..e281f89b8b --- /dev/null +++ b/integration_tests/tests/utils/ffi/ffi_string.rs @@ -0,0 +1,52 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ffi::CStr, ptr::null_mut}; + +use libc::c_char; + +use super::ffi_import; + +pub struct FFIString { + ptr: *mut c_char, +} + +impl Drop for FFIString { + fn drop(&mut self) { + unsafe { ffi_import::string_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} + +impl FFIString { + pub fn from_ptr(ptr: *mut c_char) -> Self { + Self { ptr } + } + + pub fn as_str(&self) -> &str { + unsafe { CStr::from_ptr(self.ptr).to_str().unwrap() } + } + + pub fn as_string(&self) -> String { + self.as_str().to_owned() + } +} diff --git a/integration_tests/tests/utils/ffi/kernel.rs b/integration_tests/tests/utils/ffi/kernel.rs new file mode 100644 index 0000000000..604214bbd2 --- /dev/null +++ b/integration_tests/tests/utils/ffi/kernel.rs @@ -0,0 +1,79 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ptr::null_mut; + +use libc::c_void; + +use super::{ffi_import, FFIString}; + +pub struct Kernel { + ptr: *mut c_void, +} + +impl Drop for Kernel { + fn drop(&mut self) { + unsafe { ffi_import::transaction_kernel_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} +impl Kernel { + pub fn from_ptr(ptr: *mut c_void) -> Self { + Self { ptr } + } + + pub fn get_excess_hex(&self) -> String { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::transaction_kernel_get_excess_hex(self.ptr, &mut error); + if error > 0 { + println!("transaction_kernel_get_excess_hex error {}", error); + } + } + FFIString::from_ptr(ptr).as_string() + } + + pub fn get_excess_public_nonce_hex(&self) -> String { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::transaction_kernel_get_excess_public_nonce_hex(self.ptr, &mut error); + if error > 0 { + println!("transaction_kernel_get_excess_public_nonce_hex error {}", error); + } + } + FFIString::from_ptr(ptr).as_string() + } + + pub fn get_excess_signature_hex(&self) -> String { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::transaction_kernel_get_excess_signature_hex(self.ptr, &mut error); + if error > 0 { + println!("transaction_kernel_get_excess_signature_hex error {}", error); + } + } + FFIString::from_ptr(ptr).as_string() + } +} diff --git a/integration_tests/tests/utils/ffi/mod.rs b/integration_tests/tests/utils/ffi/mod.rs new file mode 100644 index 0000000000..79e9fcf7c3 --- /dev/null +++ b/integration_tests/tests/utils/ffi/mod.rs @@ -0,0 +1,76 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +mod comms_config; +pub mod ffi_bytes; +pub mod ffi_import; +pub use comms_config::CommsConfig; +mod wallet_address; +pub use wallet_address::WalletAddress; +mod transport_config; +pub use transport_config::TransportConfig; +mod wallet; +pub use wallet::Wallet; +mod public_key; +pub use public_key::PublicKey; +mod public_keys; +pub use public_keys::PublicKeys; +mod private_key; +pub use private_key::PrivateKey; +mod ffi_string; +pub use ffi_string::FFIString; +mod seed_words; +pub use seed_words::SeedWords; +mod contact; +pub use contact::Contact; +mod contacts; +pub use contacts::Contacts; +mod balance; +pub use balance::Balance; +mod vector; +pub use vector::Vector; +mod coin_preview; +pub use coin_preview::CoinPreview; +mod pending_outbound_transactions; +pub use pending_outbound_transactions::PendingOutboundTransactions; +mod pending_outbound_transaction; +pub use pending_outbound_transaction::PendingOutboundTransaction; +mod pending_inbound_transactions; +pub use pending_inbound_transactions::PendingInboundTransactions; +mod pending_inbound_transaction; +pub use pending_inbound_transaction::PendingInboundTransaction; +mod completed_transactions; +pub use completed_transactions::CompletedTransactions; +mod completed_transaction; +pub use completed_transaction::CompletedTransaction; +mod kernel; +pub use kernel::Kernel; +mod callbacks; +pub use callbacks::Callbacks; +mod transaction_send_status; +pub use transaction_send_status::TransactionSendStatus; +mod contacts_liveness_data; +pub use contacts_liveness_data::ContactsLivenessData; +mod fee_per_gram_stats; +pub use fee_per_gram_stats::FeePerGramStats; +mod fee_per_gram_stat; +pub use fee_per_gram_stat::FeePerGramStat; diff --git a/integration_tests/tests/utils/ffi/pending_inbound_transaction.rs b/integration_tests/tests/utils/ffi/pending_inbound_transaction.rs new file mode 100644 index 0000000000..8845f43673 --- /dev/null +++ b/integration_tests/tests/utils/ffi/pending_inbound_transaction.rs @@ -0,0 +1,120 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ptr::null_mut; + +use libc::c_void; + +use super::{ffi_import, FFIString, WalletAddress}; + +pub struct PendingInboundTransaction { + ptr: *mut c_void, +} + +impl Drop for PendingInboundTransaction { + fn drop(&mut self) { + unsafe { ffi_import::pending_inbound_transaction_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} +impl PendingInboundTransaction { + pub fn from_ptr(ptr: *mut c_void) -> Self { + Self { ptr } + } + + pub fn get_transaction_id(&self) -> u64 { + let tx_id; + let mut error = 0; + unsafe { + tx_id = ffi_import::pending_inbound_transaction_get_transaction_id(self.ptr, &mut error); + if error > 0 { + println!("pending_inbound_transaction_get_transaction_id error {}", error); + } + } + tx_id + } + + #[allow(dead_code)] + pub fn get_source_tari_address(&self) -> WalletAddress { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::pending_inbound_transaction_get_source_tari_address(self.ptr, &mut error); + if error > 0 { + println!("pending_inbound_transaction_get_source_tari_address error {}", error); + } + } + WalletAddress::from_ptr(ptr) + } + + #[allow(dead_code)] + pub fn get_amount(&self) -> u64 { + let amount; + let mut error = 0; + unsafe { + amount = ffi_import::pending_inbound_transaction_get_amount(self.ptr, &mut error); + if error > 0 { + println!("pending_inbound_transaction_get_amount error {}", error); + } + } + amount + } + + #[allow(dead_code)] + pub fn get_timestamp(&self) -> u64 { + let timestamp; + let mut error = 0; + unsafe { + timestamp = ffi_import::pending_inbound_transaction_get_timestamp(self.ptr, &mut error); + if error > 0 { + println!("pending_inbound_transaction_get_timestamp error {}", error); + } + } + timestamp + } + + #[allow(dead_code)] + pub fn get_message(&self) -> String { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::pending_inbound_transaction_get_message(self.ptr, &mut error); + if error > 0 { + println!("pending_inbound_transaction_get_message error {}", error); + } + } + FFIString::from_ptr(ptr as *mut i8).as_string() + } + + #[allow(dead_code)] + pub fn get_status(&self) -> i32 { + let status; + let mut error = 0; + unsafe { + status = ffi_import::pending_inbound_transaction_get_status(self.ptr, &mut error); + if error > 0 { + println!("pending_inbound_transaction_get_status error {}", error); + } + } + status + } +} diff --git a/integration_tests/tests/utils/ffi/pending_inbound_transactions.rs b/integration_tests/tests/utils/ffi/pending_inbound_transactions.rs new file mode 100644 index 0000000000..fbc3a3e925 --- /dev/null +++ b/integration_tests/tests/utils/ffi/pending_inbound_transactions.rs @@ -0,0 +1,68 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ptr::null_mut; + +use libc::c_void; + +use super::{ffi_import, PendingInboundTransaction}; + +pub struct PendingInboundTransactions { + ptr: *mut c_void, +} + +impl Drop for PendingInboundTransactions { + fn drop(&mut self) { + unsafe { ffi_import::pending_inbound_transactions_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} +impl PendingInboundTransactions { + pub fn from_ptr(ptr: *mut c_void) -> Self { + Self { ptr } + } + + pub fn get_length(&self) -> u32 { + let length; + let mut error = 0; + unsafe { + length = ffi_import::pending_inbound_transactions_get_length(self.ptr, &mut error); + if error > 0 { + println!("pending_inbound_transactions_get_length error {}", error); + } + } + length + } + + #[allow(dead_code)] + pub fn get_at(&self, position: u32) -> PendingInboundTransaction { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::pending_inbound_transactions_get_at(self.ptr, position, &mut error); + if error > 0 { + println!("pending_inbound_transactions_get_at error {}", error); + } + } + PendingInboundTransaction::from_ptr(ptr) + } +} diff --git a/integration_tests/tests/utils/ffi/pending_outbound_transaction.rs b/integration_tests/tests/utils/ffi/pending_outbound_transaction.rs new file mode 100644 index 0000000000..31e392ef97 --- /dev/null +++ b/integration_tests/tests/utils/ffi/pending_outbound_transaction.rs @@ -0,0 +1,136 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ptr::null_mut; + +use libc::c_void; + +use super::{ffi_import, FFIString, WalletAddress}; + +pub struct PendingOutboundTransaction { + ptr: *mut c_void, +} + +impl Drop for PendingOutboundTransaction { + fn drop(&mut self) { + unsafe { ffi_import::pending_outbound_transaction_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} +impl PendingOutboundTransaction { + pub fn from_ptr(ptr: *mut c_void) -> Self { + Self { ptr } + } + + pub fn get_transaction_id(&self) -> u64 { + let tx_id; + let mut error = 0; + unsafe { + tx_id = ffi_import::pending_outbound_transaction_get_transaction_id(self.ptr, &mut error); + if error > 0 { + println!("pending_outbound_transaction_get_transaction_id error {}", error); + } + } + tx_id + } + + #[allow(dead_code)] + pub fn get_destination_tari_address(&self) -> WalletAddress { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::pending_outbound_transaction_get_destination_tari_address(self.ptr, &mut error); + if error > 0 { + println!( + "pending_outbound_transaction_get_destination_tari_address error {}", + error + ); + } + } + WalletAddress::from_ptr(ptr) + } + + #[allow(dead_code)] + pub fn get_amount(&self) -> u64 { + let amount; + let mut error = 0; + unsafe { + amount = ffi_import::pending_outbound_transaction_get_amount(self.ptr, &mut error); + if error > 0 { + println!("pending_outbound_transaction_get_amount error {}", error); + } + } + amount + } + + #[allow(dead_code)] + pub fn get_fee(&self) -> u64 { + let fee; + let mut error = 0; + unsafe { + fee = ffi_import::pending_outbound_transaction_get_fee(self.ptr, &mut error); + if error > 0 { + println!("pending_outbound_transaction_get_fee error {}", error); + } + } + fee + } + + #[allow(dead_code)] + pub fn get_timestamp(&self) -> u64 { + let timestamp; + let mut error = 0; + unsafe { + timestamp = ffi_import::pending_outbound_transaction_get_timestamp(self.ptr, &mut error); + if error > 0 { + println!("pending_outbound_transaction_get_timestamp error {}", error); + } + } + timestamp + } + + #[allow(dead_code)] + pub fn get_message(&self) -> String { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::pending_outbound_transaction_get_message(self.ptr, &mut error); + if error > 0 { + println!("pending_outbound_transaction_get_message error {}", error); + } + } + FFIString::from_ptr(ptr as *mut i8).as_string() + } + + #[allow(dead_code)] + pub fn get_status(&self) -> i32 { + let status; + let mut error = 0; + unsafe { + status = ffi_import::pending_outbound_transaction_get_status(self.ptr, &mut error); + if error > 0 { + println!("pending_outbound_transaction_get_status error {}", error); + } + } + status + } +} diff --git a/integration_tests/tests/utils/ffi/pending_outbound_transactions.rs b/integration_tests/tests/utils/ffi/pending_outbound_transactions.rs new file mode 100644 index 0000000000..c1fc304fa9 --- /dev/null +++ b/integration_tests/tests/utils/ffi/pending_outbound_transactions.rs @@ -0,0 +1,67 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ptr::null_mut; + +use libc::c_void; + +use super::{ffi_import, PendingOutboundTransaction}; + +pub struct PendingOutboundTransactions { + ptr: *mut c_void, +} + +impl Drop for PendingOutboundTransactions { + fn drop(&mut self) { + unsafe { ffi_import::pending_outbound_transactions_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} +impl PendingOutboundTransactions { + pub fn from_ptr(ptr: *mut c_void) -> Self { + Self { ptr } + } + + pub fn get_length(&self) -> u32 { + let length; + let mut error = 0; + unsafe { + length = ffi_import::pending_outbound_transactions_get_length(self.ptr, &mut error); + if error > 0 { + println!("pending_outbound_transactions_get_length error {}", error); + } + } + length + } + + pub fn get_at(&self, position: u32) -> PendingOutboundTransaction { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::pending_outbound_transactions_get_at(self.ptr, position, &mut error); + if error > 0 { + println!("pending_outbound_transactions_get_at error {}", error); + } + } + PendingOutboundTransaction::from_ptr(ptr) + } +} diff --git a/integration_tests/tests/utils/ffi/private_key.rs b/integration_tests/tests/utils/ffi/private_key.rs new file mode 100644 index 0000000000..ae47547124 --- /dev/null +++ b/integration_tests/tests/utils/ffi/private_key.rs @@ -0,0 +1,92 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ffi::CString, ptr::null_mut}; + +use libc::c_void; + +use super::{ffi_bytes::FFIBytes, ffi_import}; + +pub struct PrivateKey { + ptr: *mut c_void, +} + +impl Drop for PrivateKey { + fn drop(&mut self) { + unsafe { ffi_import::private_key_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} + +impl PrivateKey { + #[allow(dead_code)] + pub fn create(bytes: FFIBytes) -> Self { + let mut error = 0; + let ptr; + unsafe { + ptr = ffi_import::private_key_create(bytes.get_ptr(), &mut error); + if error > 0 { + println!("private_key_create error {}", error); + } + } + Self { ptr } + } + + #[allow(dead_code)] + pub fn generate() -> Self { + let ptr; + unsafe { + ptr = ffi_import::private_key_generate(); + } + Self { ptr } + } + + #[allow(dead_code)] + pub fn from_hex(key: String) -> Self { + let mut error = 0; + let ptr; + unsafe { + ptr = ffi_import::private_key_from_hex(CString::new(key).unwrap().into_raw(), &mut error); + if error > 0 { + println!("private_key_from_hex error {}", error); + } + } + Self { ptr } + } + + pub fn get_ptr(&self) -> *mut c_void { + self.ptr + } + + #[allow(dead_code)] + pub fn get_bytes(&self) -> FFIBytes { + let mut error = 0; + let ptr; + unsafe { + ptr = ffi_import::private_key_get_bytes(self.ptr, &mut error); + if error > 0 { + println!("private_key_get_bytes error {}", error); + } + } + FFIBytes::from_ptr(ptr) + } +} diff --git a/integration_tests/tests/utils/ffi/public_key.rs b/integration_tests/tests/utils/ffi/public_key.rs new file mode 100644 index 0000000000..5668e253c7 --- /dev/null +++ b/integration_tests/tests/utils/ffi/public_key.rs @@ -0,0 +1,98 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ffi::CString, ptr::null_mut}; + +use libc::c_void; + +use super::{ffi_bytes::FFIBytes, ffi_import, PrivateKey}; + +pub struct PublicKey { + ptr: *mut c_void, +} + +impl Drop for PublicKey { + fn drop(&mut self) { + unsafe { ffi_import::public_key_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} + +impl PublicKey { + pub fn from_ptr(ptr: *mut c_void) -> Self { + Self { ptr } + } + + #[allow(dead_code)] + pub fn create(bytes: FFIBytes) -> Self { + let mut error = 0; + let ptr; + unsafe { + ptr = ffi_import::public_key_create(bytes.get_ptr(), &mut error); + if error > 0 { + println!("public_key_create error {}", error); + } + } + Self { ptr } + } + + #[allow(dead_code)] + pub fn from_private_key(private_key: PrivateKey) -> Self { + let mut error = 0; + let ptr; + unsafe { + ptr = ffi_import::public_key_from_private_key(private_key.get_ptr(), &mut error); + if error > 0 { + println!("public_key_from_private_key error {}", error); + } + } + Self { ptr } + } + + pub fn from_hex(key: String) -> Self { + let mut error = 0; + let ptr; + unsafe { + ptr = ffi_import::public_key_from_hex(CString::new(key).unwrap().into_raw(), &mut error); + if error > 0 { + println!("public_key_from_private_key error {}", error); + } + } + Self { ptr } + } + + pub fn get_ptr(&self) -> *mut c_void { + self.ptr + } + + pub fn get_bytes(&self) -> FFIBytes { + let mut error = 0; + let ptr; + unsafe { + ptr = ffi_import::public_key_get_bytes(self.ptr, &mut error); + if error > 0 { + println!("public_key_get_bytes error {}", error); + } + } + FFIBytes::from_ptr(ptr) + } +} diff --git a/integration_tests/tests/utils/ffi/public_keys.rs b/integration_tests/tests/utils/ffi/public_keys.rs new file mode 100644 index 0000000000..562f6bd378 --- /dev/null +++ b/integration_tests/tests/utils/ffi/public_keys.rs @@ -0,0 +1,68 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ptr::null_mut; + +use libc::c_void; + +use super::{ffi_import, PublicKey}; + +pub struct PublicKeys { + ptr: *mut c_void, +} + +impl Drop for PublicKeys { + fn drop(&mut self) { + unsafe { ffi_import::public_keys_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} + +impl PublicKeys { + pub fn from_ptr(ptr: *mut c_void) -> Self { + Self { ptr } + } + + pub fn get_length(&self) -> usize { + let mut error = 0; + let length; + unsafe { + length = ffi_import::public_keys_get_length(self.ptr, &mut error); + if error > 0 { + println!("public_keys_get_length error {}", error); + } + } + length as usize + } + + pub fn get_public_key_at(&self, position: u32) -> PublicKey { + let mut error = 0; + let ptr; + unsafe { + ptr = ffi_import::public_keys_get_at(self.ptr, position, &mut error); + if error > 0 { + println!("public_keys_get_length error {}", error); + } + } + PublicKey::from_ptr(ptr) + } +} diff --git a/integration_tests/tests/utils/ffi/seed_words.rs b/integration_tests/tests/utils/ffi/seed_words.rs new file mode 100644 index 0000000000..02fec29641 --- /dev/null +++ b/integration_tests/tests/utils/ffi/seed_words.rs @@ -0,0 +1,103 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ffi::CString, ptr::null_mut}; + +use libc::c_void; + +use super::{ffi_import, FFIString}; + +pub struct SeedWords { + ptr: *mut c_void, +} + +impl Drop for SeedWords { + fn drop(&mut self) { + unsafe { ffi_import::seed_words_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} + +impl SeedWords { + pub fn create() -> Self { + let ptr; + unsafe { + ptr = ffi_import::seed_words_create(); + } + Self { ptr } + } + + pub fn get_ptr(&self) -> *mut c_void { + self.ptr + } + + pub fn get_mnemonic_word_list_for_language(language: String) -> Self { + let mut error = 0; + let ptr; + unsafe { + ptr = ffi_import::seed_words_get_mnemonic_word_list_for_language( + CString::new(language).unwrap().into_raw(), + &mut error, + ); + if error > 0 { + println!("seed_words_get_mnemonic_word_list_for_language error {}", error); + } + } + Self { ptr } + } + + pub fn get_length(&self) -> usize { + let mut error = 0; + let length; + unsafe { + length = ffi_import::seed_words_get_length(self.ptr, &mut error); + if error > 0 { + println!("seed_words_get_length error {}", error); + } + } + length as usize + } + + pub fn get_at(&self, position: u32) -> FFIString { + let mut error = 0; + let ptr; + unsafe { + ptr = ffi_import::seed_words_get_at(self.ptr, position, &mut error); + if error > 0 { + println!("seed_words_get_at error {}", error); + } + } + FFIString::from_ptr(ptr) + } + + pub fn push_word(&self, word: String) -> u8 { + let mut error = 0; + let result; + unsafe { + result = ffi_import::seed_words_push_word(self.ptr, CString::new(word).unwrap().into_raw(), &mut error); + if error > 0 { + println!("seed_words_push_word error {}", error); + } + } + result + } +} diff --git a/integration_tests/tests/utils/ffi/transaction_send_status.rs b/integration_tests/tests/utils/ffi/transaction_send_status.rs new file mode 100644 index 0000000000..da177b46d9 --- /dev/null +++ b/integration_tests/tests/utils/ffi/transaction_send_status.rs @@ -0,0 +1,55 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ptr::null_mut; + +use libc::c_void; + +use super::ffi_import; + +pub struct TransactionSendStatus { + ptr: *mut c_void, +} + +impl Drop for TransactionSendStatus { + fn drop(&mut self) { + unsafe { ffi_import::transaction_send_status_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} +impl TransactionSendStatus { + pub fn from_ptr(ptr: *mut c_void) -> Self { + Self { ptr } + } + + pub fn send_status_decode(&self) -> u32 { + let status; + let mut error = 0; + unsafe { + status = ffi_import::transaction_send_status_decode(self.ptr, &mut error); + if error > 0 { + println!("transaction_send_status_decode error {}", error); + } + } + status + } +} diff --git a/integration_tests/tests/utils/ffi/transport_config.rs b/integration_tests/tests/utils/ffi/transport_config.rs new file mode 100644 index 0000000000..5223ac6a70 --- /dev/null +++ b/integration_tests/tests/utils/ffi/transport_config.rs @@ -0,0 +1,56 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::ptr::null_mut; + +use libc::{c_int, c_void}; + +use super::ffi_import; + +pub struct TransportConfig { + ptr: *mut c_void, +} + +impl Drop for TransportConfig { + fn drop(&mut self) { + unsafe { ffi_import::transport_config_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} + +impl TransportConfig { + pub fn create_tcp(listener_address: *const i8) -> Self { + let ptr; + let mut error: c_int = 0; + unsafe { + ptr = ffi_import::transport_tcp_create(listener_address, &mut error); + if error > 0 { + println!("transport_tcp_create error {}", error); + } + } + Self { ptr } + } + + pub fn get_ptr(&self) -> *mut c_void { + self.ptr + } +} diff --git a/integration_tests/tests/utils/ffi/vector.rs b/integration_tests/tests/utils/ffi/vector.rs new file mode 100644 index 0000000000..a48d824601 --- /dev/null +++ b/integration_tests/tests/utils/ffi/vector.rs @@ -0,0 +1,58 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ffi::CString, ptr::null_mut}; + +use libc::c_void; + +use super::ffi_import::{self}; + +pub struct Vector { + ptr: *mut c_void, +} + +impl Drop for Vector { + fn drop(&mut self) { + unsafe { ffi_import::destroy_tari_vector(self.ptr) }; + self.ptr = null_mut(); + } +} +#[allow(dead_code)] +impl Vector { + pub fn from_ptr(ptr: *mut c_void) -> Self { + Self { ptr } + } + + pub fn get_ptr(&self) -> *mut c_void { + self.ptr + } + + pub fn push_string(&self, s: String) { + let mut error = 0; + unsafe { + ffi_import::tari_vector_push_string(self.ptr, CString::new(s).unwrap().into_raw(), &mut error); + if error > 0 { + println!("tari_vector_push_string error {}", error); + } + } + } +} diff --git a/integration_tests/tests/utils/ffi/wallet.rs b/integration_tests/tests/utils/ffi/wallet.rs new file mode 100644 index 0000000000..316ee99cff --- /dev/null +++ b/integration_tests/tests/utils/ffi/wallet.rs @@ -0,0 +1,429 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ + ffi::CString, + ptr::null_mut, + sync::{Arc, Mutex}, +}; + +use callbacks::Callbacks; +use indexmap::IndexMap; +use libc::{c_ulonglong, c_void}; + +use super::{ + ffi_import::{ + self, + wallet_create, + TariBalance, + TariCompletedTransaction, + TariContactsLivenessData, + TariPendingInboundTransaction, + TariTransactionSendStatus, + TariWallet, + }, + Balance, + CommsConfig, + CompletedTransactions, + Contact, + Contacts, + ContactsLivenessData, + FeePerGramStats, + PendingInboundTransactions, + PendingOutboundTransactions, + PublicKey, + PublicKeys, + WalletAddress, +}; +use crate::utils::ffi::callbacks; + +extern "C" fn callback_received_transaction(ptr: *mut TariPendingInboundTransaction) { + let callbacks = Callbacks::instance(); + callbacks.on_received_transaction(ptr); + // println!("callback_received_transaction"); +} +extern "C" fn callback_received_transaction_reply(ptr: *mut TariCompletedTransaction) { + let callbacks = Callbacks::instance(); + callbacks.on_received_transaction_reply(ptr); + // println!("callback_received_transaction_reply"); +} +extern "C" fn callback_received_finalized_transaction(ptr: *mut TariCompletedTransaction) { + let callbacks = Callbacks::instance(); + callbacks.on_received_finalized_transaction(ptr); + // println!("callback_received_finalized_transaction"); +} +extern "C" fn callback_transaction_broadcast(ptr: *mut TariCompletedTransaction) { + let callbacks = Callbacks::instance(); + callbacks.on_transaction_broadcast(ptr); + // println!("callback_transaction_broadcast"); +} +extern "C" fn callback_transaction_mined(ptr: *mut TariCompletedTransaction) { + let callbacks = Callbacks::instance(); + callbacks.on_transaction_mined(ptr); + // println!("callback_transaction_mined"); +} +extern "C" fn callback_transaction_mined_unconfirmed(ptr: *mut TariCompletedTransaction, confirmations: u64) { + let callbacks = Callbacks::instance(); + callbacks.on_transaction_mined_unconfirmed(ptr, confirmations); + // println!("callback_transaction_mined_unconfirmed"); +} +extern "C" fn callback_faux_transaction_confirmed(ptr: *mut TariCompletedTransaction) { + let callbacks = Callbacks::instance(); + callbacks.on_faux_transaction_confirmed(ptr); + // println!("callback_faux_transaction_confirmed"); +} +extern "C" fn callback_faux_transaction_unconfirmed(ptr: *mut TariCompletedTransaction, confirmations: u64) { + let callbacks = Callbacks::instance(); + callbacks.on_faux_transaction_mined_unconfirmed(ptr, confirmations); + // println!("callback_faux_transaction_unconfirmed"); +} +extern "C" fn callback_transaction_send_result(tx_id: c_ulonglong, ptr: *mut TariTransactionSendStatus) { + let callbacks = Callbacks::instance(); + callbacks.on_transaction_send_result(tx_id, ptr); + // println!("callback_transaction_send_result"); +} +extern "C" fn callback_transaction_cancellation(ptr: *mut TariCompletedTransaction, reason: u64) { + let callbacks = Callbacks::instance(); + callbacks.on_transaction_cancellation(ptr, reason); + // println!("callback_transaction_cancellation"); +} +extern "C" fn callback_txo_validation_complete(request_key: u64, validation_results: u64) { + let callbacks = Callbacks::instance(); + callbacks.on_txo_validation_complete(request_key, validation_results); + // println!("callback_txo_validation_complete"); +} +extern "C" fn callback_contacts_liveness_data_updated(ptr: *mut TariContactsLivenessData) { + let callbacks = Callbacks::instance(); + callbacks.on_contacts_liveness_data_updated(ptr); + // println!("callback_contacts_liveness_data_updated"); +} +extern "C" fn callback_balance_updated(ptr: *mut TariBalance) { + let callbacks = Callbacks::instance(); + callbacks.on_balance_updated(ptr); + // println!("callback_balance_updated"); +} +extern "C" fn callback_transaction_validation_complete(request_key: u64, validation_results: u64) { + let callbacks = Callbacks::instance(); + callbacks.on_transaction_validation_complete(request_key, validation_results); + // println!("callback_transaction_validation_complete"); +} +extern "C" fn callback_saf_messages_received() { + let callbacks = Callbacks::instance(); + callbacks.on_saf_messages_received(); + // println!("callback_saf_messages_received"); +} +extern "C" fn callback_connectivity_status(status: u64) { + let callbacks = Callbacks::instance(); + callbacks.on_connectivity_status(status); + // println!("callback_connectivity_status"); +} + +#[derive(Default, Debug)] +struct CachedBalance { + available: u64, + time_locked: u64, + pending_incoming: u64, + pending_outgoing: u64, +} + +#[derive(Debug)] +pub struct Wallet { + ptr: *mut TariWallet, + liveness_data: Arc>>, + balance: CachedBalance, +} + +impl Drop for Wallet { + fn drop(&mut self) { + self.destroy(); + } +} + +impl Wallet { + pub fn create(comms_config: CommsConfig, log_path: String, seed_words_ptr: *const c_void) -> Arc> { + let mut recovery_in_progress: bool = false; + let mut error = 0; + let ptr; + unsafe { + ptr = wallet_create( + comms_config.get_ptr(), + CString::new(log_path).unwrap().into_raw(), + 50, + 102400, + CString::new("kensentme").unwrap().into_raw(), + seed_words_ptr, + CString::new("localnet").unwrap().into_raw(), + callback_received_transaction, + callback_received_transaction_reply, + callback_received_finalized_transaction, + callback_transaction_broadcast, + callback_transaction_mined, + callback_transaction_mined_unconfirmed, + callback_faux_transaction_confirmed, + callback_faux_transaction_unconfirmed, + callback_transaction_send_result, + callback_transaction_cancellation, + callback_txo_validation_complete, + callback_contacts_liveness_data_updated, + callback_balance_updated, + callback_transaction_validation_complete, + callback_saf_messages_received, + callback_connectivity_status, + &mut recovery_in_progress, + &mut error, + ); + if error > 0 { + println!("wallet_create error {}", error); + } + } + let wallet = Arc::new(Mutex::new(Self { + ptr, + liveness_data: Default::default(), + balance: Default::default(), + })); + let callbacks = Callbacks::instance(); + callbacks.reset(wallet.clone()); + wallet + } + + pub fn add_liveness_data(&mut self, contact_liveness_data: ContactsLivenessData) { + self.liveness_data.lock().unwrap().insert( + contact_liveness_data.get_public_key().address().get_as_hex(), + contact_liveness_data, + ); + } + + pub fn set_balance(&mut self, balance: Balance) { + self.balance.available = balance.get_available(); + self.balance.pending_incoming = balance.get_pending_incoming(); + self.balance.pending_outgoing = balance.get_pending_outgoing(); + self.balance.time_locked = balance.get_time_locked(); + } + + pub fn destroy(&mut self) { + unsafe { ffi_import::wallet_destroy(self.ptr) }; + self.ptr = null_mut(); + } + + pub fn add_base_node_peer(&self, base_node: PublicKey, address: String) -> bool { + let mut error = 0; + let success; + unsafe { + success = ffi_import::wallet_add_base_node_peer( + self.ptr, + base_node.get_ptr(), + CString::new(address).unwrap().into_raw(), + &mut error, + ); + if error > 0 { + println!("wallet_add_base_node_peer error {}", error); + } + } + success + } + + pub fn get_address(&self) -> WalletAddress { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::wallet_get_tari_address(self.ptr, &mut error); + if error > 0 { + println!("wallet_get_tari_address error {}", error); + } + } + WalletAddress::from_ptr(ptr) + } + + pub fn connected_public_keys(&self) -> PublicKeys { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::comms_list_connected_public_keys(self.ptr, &mut error); + } + PublicKeys::from_ptr(ptr) + } + + pub fn upsert_contact(&self, contact: Contact) -> bool { + let success; + let mut error = 0; + unsafe { + success = ffi_import::wallet_upsert_contact(self.ptr, contact.get_ptr(), &mut error); + if error > 0 { + println!("wallet_upsert_contact error {}", error); + } + } + success + } + + pub fn get_contacts(&self) -> Contacts { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::wallet_get_contacts(self.ptr, &mut error); + if error > 0 { + println!("wallet_get_contacts error {}", error); + } + } + Contacts::from_ptr(ptr) + } + + pub fn remove_contact(&self, contact: Contact) -> bool { + let success; + let mut error = 0; + unsafe { + success = ffi_import::wallet_remove_contact(self.ptr, contact.get_ptr(), &mut error); + if error > 0 { + println!("wallet_remove_contact error {}", error); + } + } + success + } + + pub fn get_balance(&self) -> Balance { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::wallet_get_balance(self.ptr, &mut error); + if error > 0 { + println!("wallet_get_balance error {}", error); + } + } + Balance::from_ptr(ptr) + } + + pub fn send_transaction( + &self, + dest: String, + amount: u64, + fee_per_gram: u64, + message: String, + one_sided: bool, + ) -> u64 { + let tx_id; + let mut error = 0; + unsafe { + tx_id = ffi_import::wallet_send_transaction( + self.ptr, + WalletAddress::from_hex(dest).get_ptr(), + amount, + null_mut(), + fee_per_gram, + CString::new(message).unwrap().into_raw(), + one_sided, + &mut error, + ); + if error > 0 { + println!("wallet_send_transaction error {}", error); + } + } + tx_id + } + + pub fn get_pending_outbound_transactions(&self) -> PendingOutboundTransactions { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::wallet_get_pending_outbound_transactions(self.ptr, &mut error); + if error > 0 { + println!("wallet_get_pending_outbound_transactions error {}", error); + } + } + PendingOutboundTransactions::from_ptr(ptr) + } + + pub fn get_pending_inbound_transactions(&self) -> PendingInboundTransactions { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::wallet_get_pending_inbound_transactions(self.ptr, &mut error); + if error > 0 { + println!("wallet_get_pending_inbound_transactions error {}", error); + } + } + PendingInboundTransactions::from_ptr(ptr) + } + + pub fn get_completed_transactions(&self) -> CompletedTransactions { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::wallet_get_completed_transactions(self.ptr, &mut error); + if error > 0 { + println!("wallet_get_completed_transactions error {}", error); + } + } + CompletedTransactions::from_ptr(ptr) + } + + pub fn cancel_pending_transaction(&self, transaction_id: u64) -> bool { + let cancelled; + let mut error = 0; + unsafe { + cancelled = ffi_import::wallet_cancel_pending_transaction(self.ptr, transaction_id, &mut error); + if error > 0 { + println!("wallet_cancel_pending_transaction error {}", error); + } + } + cancelled + } + + pub fn start_txo_validation(&self) -> u64 { + let request_key; + let mut error = 0; + unsafe { + request_key = ffi_import::wallet_start_txo_validation(self.ptr, &mut error); + if error > 0 { + println!("wallet_start_txo_validation error {}", error); + } + } + request_key + } + + pub fn start_transaction_validation(&self) -> u64 { + let request_key; + let mut error = 0; + unsafe { + request_key = ffi_import::wallet_start_transaction_validation(self.ptr, &mut error); + if error > 0 { + println!("wallet_start_transaction_validation error {}", error); + } + } + request_key + } + + pub fn get_liveness_data(&self) -> Arc>> { + self.liveness_data.clone() + } + + #[allow(dead_code)] + pub fn get_fee_per_gram_stats(&self, count: u32) -> FeePerGramStats { + let ptr; + let mut error = 0; + unsafe { + ptr = ffi_import::wallet_get_fee_per_gram_stats(self.ptr, count, &mut error); + if error > 0 { + println!("wallet_get_fee_per_gram_stats error {}", error); + } + } + FeePerGramStats::from_ptr(ptr) + } +} diff --git a/integration_tests/tests/utils/ffi/wallet_address.rs b/integration_tests/tests/utils/ffi/wallet_address.rs new file mode 100644 index 0000000000..af865c9eea --- /dev/null +++ b/integration_tests/tests/utils/ffi/wallet_address.rs @@ -0,0 +1,110 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ffi::CString, ptr::null_mut}; + +use libc::c_void; + +use super::{ffi_bytes::FFIBytes, ffi_import, FFIString, PrivateKey}; + +pub struct WalletAddress { + ptr: *mut c_void, +} + +impl Drop for WalletAddress { + fn drop(&mut self) { + unsafe { ffi_import::tari_address_destroy(self.ptr) }; + self.ptr = null_mut(); + } +} + +impl WalletAddress { + pub fn from_ptr(ptr: *mut c_void) -> Self { + Self { ptr } + } + + #[allow(dead_code)] + pub fn from_private_key(private_key: PrivateKey, network: u32) -> Self { + let mut error = 0; + let ptr; + unsafe { + ptr = ffi_import::tari_address_from_private_key(private_key.get_ptr(), network, &mut error); + if error > 0 { + println!("wallet_get_tari_address error {}", error); + } + } + Self { ptr } + } + + pub fn from_hex(address: String) -> Self { + let mut error = 0; + let ptr; + unsafe { + ptr = ffi_import::tari_address_from_hex(CString::new(address).unwrap().into_raw(), &mut error); + if error > 0 { + println!("wallet_get_tari_address error {}", error); + } + } + Self { ptr } + } + + #[allow(dead_code)] + pub fn from_emoji_id(emoji_id: String) -> Self { + let mut error = 0; + let ptr; + unsafe { + ptr = ffi_import::emoji_id_to_tari_address(CString::new(emoji_id).unwrap().into_raw(), &mut error); + if error > 0 { + println!("wallet_get_tari_address error {}", error); + } + } + Self { ptr } + } + + pub fn address(&self) -> FFIBytes { + let mut error = 0; + let ptr; + unsafe { + ptr = ffi_import::tari_address_get_bytes(self.ptr, &mut error); + if error > 0 { + println!("wallet_get_tari_address error {}", error); + } + } + FFIBytes::from_ptr(ptr) + } + + pub fn emoji_id(&self) -> FFIString { + let mut error = 0; + let ptr; + unsafe { + ptr = ffi_import::tari_address_to_emoji_id(self.ptr, &mut error); + if error > 0 { + println!("tari_address_to_emoji_id error {}", error); + } + } + FFIString::from_ptr(ptr) + } + + pub fn get_ptr(&self) -> *mut c_void { + self.ptr + } +} diff --git a/integration_tests/tests/utils/merge_mining_proxy.rs b/integration_tests/tests/utils/merge_mining_proxy.rs new file mode 100644 index 0000000000..2c8b412e49 --- /dev/null +++ b/integration_tests/tests/utils/merge_mining_proxy.rs @@ -0,0 +1,194 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{convert::TryInto, thread}; + +use serde_json::{json, Value}; +use tari_app_utilities::common_cli_args::CommonCliArgs; +use tari_merge_mining_proxy::{merge_miner, Cli}; +use tempfile::tempdir; +use tokio::runtime; + +use super::get_port; +use crate::TariWorld; + +#[derive(Clone, Debug)] +pub struct MergeMiningProxyProcess { + pub name: String, + pub base_node_name: String, + pub wallet_name: String, + pub port: u64, + pub origin_submission: bool, + id: u64, +} + +pub async fn register_merge_mining_proxy_process( + world: &mut TariWorld, + merge_mining_proxy_name: String, + base_node_name: String, + wallet_name: String, + origin_submission: bool, +) { + let merge_mining_proxy = MergeMiningProxyProcess { + name: merge_mining_proxy_name.clone(), + base_node_name, + wallet_name, + port: get_port(18000..18499).unwrap(), + origin_submission, + id: 0, + }; + + merge_mining_proxy.start(world).await; + world + .merge_mining_proxies + .insert(merge_mining_proxy_name, merge_mining_proxy); +} + +impl MergeMiningProxyProcess { + pub async fn start(&self, world: &mut TariWorld) { + let temp_dir = tempdir().unwrap(); + let data_dir = temp_dir.path().join("data/miner"); + let data_dir_str = data_dir.clone().into_os_string().into_string().unwrap(); + let mut config_path = data_dir; + config_path.push("config.toml"); + let wallet_grpc_port = world.get_wallet(&self.wallet_name).unwrap().grpc_port; + let base_node_grpc_port = world.get_node(&self.base_node_name).unwrap().grpc_port; + let proxy_full_address = format! {"/ip4/127.0.0.1/tcp/{}", self.port}; + let origin_submission = self.origin_submission; + thread::spawn(move || { + let cli = Cli { + common: CommonCliArgs { + base_path: data_dir_str, + config: config_path.into_os_string().into_string().unwrap(), + log_config: None, + log_level: None, + config_property_overrides: vec![ + ("merge_mining_proxy.listener_address".to_string(), proxy_full_address), + ( + "merge_mining_proxy.console_wallet_grpc_address".to_string(), + format!("/ip4/127.0.0.1/tcp/{}", wallet_grpc_port), + ), + ( + "merge_mining_proxy.base_node_grpc_address".to_string(), + format!("/ip4/127.0.0.1/tcp/{}", base_node_grpc_port), + ), + ( + "merge_mining_proxy.monerod_url".to_string(), + vec![ + "http://stagenet.xmr-tw.org:38081", + "http://stagenet.community.xmr.to:38081", + "http://monero-stagenet.exan.tech:38081", + "http://xmr-lux.boldsuck.org:38081", + "http://singapore.node.xmr.pm:38081", + ] + .join(","), + ), + ("merge_mining_proxy.monerod_use_auth".to_string(), "false".to_string()), + ("merge_mining_proxy.monerod_username".to_string(), "".to_string()), + ("merge_mining_proxy.monerod_password".to_string(), "".to_string()), + ( + "merge_mining_proxy.wait_for_initial_sync_at_startup".to_string(), + "false".to_string(), + ), + ( + "merge_mining_proxy.submit_to_origin".to_string(), + origin_submission.to_string(), + ), + ], + }, + network: Some("localnet".to_string().try_into().unwrap()), + }; + let rt = runtime::Builder::new_multi_thread().enable_all().build().unwrap(); + if let Err(e) = rt.block_on(merge_miner(cli)) { + println!("Error running merge mining proxy : {:?}", e); + } + }); + } + + async fn get_response(&self, path: &str) -> Value { + let full_address = format!("http://127.0.0.1:{}", self.port); + reqwest::get(format!("{}/{}", full_address, path)) + .await + .unwrap() + .json::() + .await + .unwrap() + } + + async fn json_rpc_call(&mut self, method_name: &str, params: &Value) -> Value { + let client = reqwest::Client::new(); + let json = json!({ + "jsonrpc": "2.0", + "method": method_name, + "params": params, + "id":self.id} + ); + println!("json_rpc_call {}", method_name); + println!("json payload {}", json); + self.id += 1; + let full_address = format!("http://127.0.0.1:{}/json_rpc", self.port); + client + .post(full_address) + .json(&json) + .send() + .await + .unwrap() + .json() + .await + .unwrap() + } + + pub async fn get_height(&self) -> Value { + self.get_response("get_height").await + } + + pub async fn get_block_template(&mut self) -> Value { + let params = json!({ + "wallet_address":"5AUoj81i63cBUbiKY5jybsZXRDYb9CppmSjiZXC8ZYT6HZH6ebsQvBecYfRKDYoyzKF2uML9FKkTAc7nJvHKdoDYQEeteRW", + "reserve_size":60 + }); + self.json_rpc_call("getblocktemplate", ¶ms).await + } + + pub async fn submit_block(&mut self, block_template_blob: &Value) -> Value { + self.json_rpc_call("submit_block", &json!(vec![block_template_blob])) + .await + } + + pub async fn get_last_block_header(&mut self) -> Value { + self.json_rpc_call("get_last_block_header", &json!({})).await + } + + pub async fn get_block_header_by_hash(&mut self, hash: Value) -> Value { + self.json_rpc_call("get_block_header_by_hash", &json!({ "hash": hash })) + .await + } + + pub async fn mine(&mut self) -> Value { + let template = self.get_block_template().await; + let template = template.get("result").unwrap(); + // XMRig always calls this, so duplicated here + self.get_height().await; + let block = template.get("blocktemplate_blob").unwrap(); + self.submit_block(block).await + } +} diff --git a/integration_tests/tests/utils/miner.rs b/integration_tests/tests/utils/miner.rs new file mode 100644 index 0000000000..1a0c7f4464 --- /dev/null +++ b/integration_tests/tests/utils/miner.rs @@ -0,0 +1,376 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{convert::TryInto, str::FromStr, time::Duration}; + +use rand::rngs::OsRng; +use tari_app_grpc::{ + authentication::ClientAuthenticationInterceptor, + tari_rpc::{ + pow_algo::PowAlgos, + wallet_client::WalletClient, + Block, + GetCoinbaseRequest, + GetCoinbaseResponse, + NewBlockTemplate, + NewBlockTemplateRequest, + NewBlockTemplateResponse, + PowAlgo, + TransactionKernel, + TransactionOutput, + }, +}; +use tari_app_utilities::common_cli_args::CommonCliArgs; +use tari_base_node_grpc_client::BaseNodeGrpcClient; +use tari_common::configuration::Network; +use tari_common_types::{grpc_authentication::GrpcAuthentication, types::PrivateKey}; +use tari_core::{ + consensus::ConsensusManager, + transactions::{transaction_components::UnblindedOutput, CoinbaseBuilder, CryptoFactories}, +}; +use tari_crypto::keys::SecretKey; +use tari_miner::{run_miner, Cli}; +use tempfile::tempdir; +use tonic::{ + codegen::InterceptedService, + transport::{Channel, Endpoint}, +}; + +use crate::TariWorld; + +type BaseNodeClient = BaseNodeGrpcClient; +type WalletGrpcClient = WalletClient>; + +#[derive(Clone, Debug)] +pub struct MinerProcess { + pub name: String, + pub base_node_name: String, + pub wallet_name: String, + pub mine_until_height: u64, +} + +pub fn register_miner_process(world: &mut TariWorld, miner_name: String, base_node_name: String, wallet_name: String) { + let miner = MinerProcess { + name: miner_name.clone(), + base_node_name, + wallet_name, + mine_until_height: 100_000, + }; + + world.miners.insert(miner_name, miner); +} + +impl MinerProcess { + pub async fn mine( + &self, + world: &TariWorld, + blocks: Option, + miner_min_diff: Option, + miner_max_diff: Option, + ) { + let node = world.get_node(&self.base_node_name).unwrap().grpc_port; + let wallet = world.get_wallet(&self.wallet_name).unwrap().grpc_port; + let temp_dir = tempdir().unwrap(); + let data_dir = temp_dir.path().join("data/miner"); + let data_dir_str = data_dir.clone().into_os_string().into_string().unwrap(); + let mut config_path = data_dir; + config_path.push("config.toml"); + let cli = Cli { + common: CommonCliArgs { + base_path: data_dir_str, + config: config_path.into_os_string().into_string().unwrap(), + log_config: None, + log_level: None, + config_property_overrides: vec![ + ( + "miner.base_node_grpc_address".to_string(), + format!("/ip4/127.0.0.1/tcp/{}", node), + ), + ( + "miner.wallet_grpc_address".to_string(), + format!("/ip4/127.0.0.1/tcp/{}", wallet), + ), + ("miner.num_mining_threads".to_string(), "1".to_string()), + ("miner.mine_on_tip_only".to_string(), "false".to_string()), + ], + }, + mine_until_height: None, + miner_max_blocks: blocks, + miner_min_diff, + miner_max_diff, + }; + run_miner(cli).await.unwrap(); + } +} + +#[allow(dead_code)] +pub async fn mine_blocks(world: &mut TariWorld, miner_name: String, num_blocks: u64) { + let mut base_client = create_base_node_client(world, &miner_name).await; + let mut wallet_client = create_wallet_client(world, &miner_name).await; + + for _ in 0..num_blocks { + mine_block(&mut base_client, &mut wallet_client).await; + tokio::time::sleep(Duration::from_millis(100)).await; + } + + // Give some time for the base node and wallet to sync the new blocks + tokio::time::sleep(Duration::from_secs(5)).await; +} + +pub async fn mine_blocks_without_wallet(base_client: &mut BaseNodeClient, num_blocks: u64, weight: u64) { + for _ in 0..num_blocks { + mine_block_without_wallet(base_client, weight).await; + tokio::time::sleep(Duration::from_millis(100)).await; + } + + // Give some time for the base node and wallet to sync the new blocks + tokio::time::sleep(Duration::from_secs(5)).await; +} + +async fn create_base_node_client(world: &TariWorld, miner_name: &String) -> BaseNodeClient { + let miner = world.miners.get(miner_name).unwrap(); + let base_node_grpc_port = world.base_nodes.get(&miner.base_node_name).unwrap().grpc_port; + let base_node_grpc_url = format!("http://127.0.0.1:{}", base_node_grpc_port); + eprintln!("Base node GRPC at {}", base_node_grpc_url); + BaseNodeClient::connect(base_node_grpc_url).await.unwrap() +} + +async fn create_wallet_client(world: &TariWorld, miner_name: &String) -> WalletGrpcClient { + let miner = world.miners.get(miner_name).unwrap(); + let wallet_grpc_port = world.wallets.get(&miner.wallet_name).unwrap().grpc_port; + let wallet_addr = format!("http://127.0.0.1:{}", wallet_grpc_port); + eprintln!("Wallet GRPC at {}", wallet_addr); + let channel = Endpoint::from_str(&wallet_addr).unwrap().connect().await.unwrap(); + WalletClient::with_interceptor( + channel, + ClientAuthenticationInterceptor::create(&GrpcAuthentication::default()).unwrap(), + ) +} + +pub async fn mine_block(base_client: &mut BaseNodeClient, wallet_client: &mut WalletGrpcClient) { + let block_template = create_block_template_with_coinbase(base_client, wallet_client).await; + + // Ask the base node for a valid block using the template + let block_result = base_client + .get_new_block(block_template.clone()) + .await + .unwrap() + .into_inner(); + let block = block_result.block.unwrap(); + + // We don't need to mine, as Localnet blocks have difficulty 1s + let _sumbmit_res = base_client.submit_block(block).await.unwrap(); + println!( + "Block successfully mined at height {:?}", + block_template.header.unwrap().height + ); +} + +async fn mine_block_without_wallet(base_client: &mut BaseNodeClient, weight: u64) { + let (block_template, _unblinded_output) = + create_block_template_with_coinbase_without_wallet(base_client, weight).await; + mine_block_without_wallet_with_template(base_client, block_template.new_block_template.unwrap()).await; +} + +async fn mine_block_without_wallet_with_template(base_client: &mut BaseNodeClient, block_template: NewBlockTemplate) { + // Ask the base node for a valid block using the template + let block_result = base_client + .get_new_block(block_template.clone()) + .await + .unwrap() + .into_inner(); + let block = block_result.block.unwrap(); + + // We don't need to mine, as Localnet blocks have difficulty 1s + let _submit_res = base_client.submit_block(block).await.unwrap(); + println!( + "Block successfully mined at height {:?}", + block_template.header.unwrap().height + ); +} + +async fn create_block_template_with_coinbase( + base_client: &mut BaseNodeClient, + wallet_client: &mut WalletGrpcClient, +) -> NewBlockTemplate { + // get the block template from the base node + let template_req = NewBlockTemplateRequest { + algo: Some(PowAlgo { + pow_algo: PowAlgos::Sha3.into(), + }), + max_weight: 0, + }; + + let template_res = base_client + .get_new_block_template(template_req) + .await + .unwrap() + .into_inner(); + + let mut block_template = template_res.new_block_template.clone().unwrap(); + + // add the coinbase outputs and kernels to the block template + let (output, kernel) = get_coinbase_outputs_and_kernels(wallet_client, template_res).await; + let body = block_template.body.as_mut().unwrap(); + + body.outputs.push(output); + body.kernels.push(kernel); + + block_template +} + +async fn create_block_template_with_coinbase_without_wallet( + base_client: &mut BaseNodeClient, + weight: u64, +) -> (NewBlockTemplateResponse, UnblindedOutput) { + // get the block template from the base node + let template_req = NewBlockTemplateRequest { + algo: Some(PowAlgo { + pow_algo: PowAlgos::Sha3.into(), + }), + max_weight: weight, + }; + + let mut template_res = base_client + .get_new_block_template(template_req) + .await + .unwrap() + .into_inner(); + + // let mut block_template = template_res.new_block_template.clone().unwrap(); + + // add the coinbase outputs and kernels to the block template + let (output, kernel, unblinded_output) = get_coinbase_without_wallet_client(template_res.clone()); + // let body = block_template.body.as_mut().unwrap(); + + template_res + .new_block_template + .as_mut() + .unwrap() + .body + .as_mut() + .unwrap() + .outputs + .push(output); + template_res + .new_block_template + .as_mut() + .unwrap() + .body + .as_mut() + .unwrap() + .kernels + .push(kernel); + + (template_res, unblinded_output) +} + +async fn get_coinbase_outputs_and_kernels( + wallet_client: &mut WalletGrpcClient, + template_res: NewBlockTemplateResponse, +) -> (TransactionOutput, TransactionKernel) { + let coinbase_req = coinbase_request(&template_res); + let coinbase_res = wallet_client.get_coinbase(coinbase_req).await.unwrap().into_inner(); + extract_outputs_and_kernels(coinbase_res) +} + +fn get_coinbase_without_wallet_client( + template_res: NewBlockTemplateResponse, +) -> (TransactionOutput, TransactionKernel, UnblindedOutput) { + let coinbase_req = coinbase_request(&template_res); + generate_coinbase(coinbase_req) +} + +pub fn generate_coinbase(coinbase_req: GetCoinbaseRequest) -> (TransactionOutput, TransactionKernel, UnblindedOutput) { + let reward = coinbase_req.reward; + let height = coinbase_req.height; + let fee = coinbase_req.fee; + let extra = coinbase_req.extra; + + let spending_key = PrivateKey::random(&mut OsRng); + let script_private_key = PrivateKey::random(&mut OsRng); + let nonce = PrivateKey::random(&mut OsRng); + + let consensus_manager = ConsensusManager::builder(Network::LocalNet).build(); + let consensus_constants = consensus_manager.consensus_constants(height); + + let (tx, ubutxo) = CoinbaseBuilder::new(CryptoFactories::default()) + .with_block_height(height) + .with_fees(fee.into()) + .with_spend_key(spending_key) + .with_script_key(script_private_key) + .with_nonce(nonce) + .with_extra(extra) + .build_with_reward(consensus_manager.clone(), consensus_constants, reward.into()) + .unwrap(); + + let tx_out = tx.body().outputs().first().unwrap().clone(); + let tx_krnl = tx.body().kernels().first().unwrap().clone(); + + (tx_out.try_into().unwrap(), tx_krnl.into(), ubutxo) +} + +fn coinbase_request(template_response: &NewBlockTemplateResponse) -> GetCoinbaseRequest { + let template = template_response.new_block_template.as_ref().unwrap(); + let miner_data = template_response.miner_data.as_ref().unwrap(); + let fee = miner_data.total_fees; + let reward = miner_data.reward; + let height = template.header.as_ref().unwrap().height; + GetCoinbaseRequest { + reward, + fee, + height, + extra: vec![], + } +} + +fn extract_outputs_and_kernels(coinbase: GetCoinbaseResponse) -> (TransactionOutput, TransactionKernel) { + let transaction_body = coinbase.transaction.unwrap().body.unwrap(); + let output = transaction_body.outputs.get(0).cloned().unwrap(); + let kernel = transaction_body.kernels.get(0).cloned().unwrap(); + (output, kernel) +} + +pub async fn mine_block_with_coinbase_on_node(world: &mut TariWorld, base_node: String, coinbase_name: String) { + let mut client = world + .base_nodes + .get(&base_node) + .unwrap() + .get_grpc_client() + .await + .unwrap(); + let (template, unblinded_output) = create_block_template_with_coinbase_without_wallet(&mut client, 0).await; + world.utxos.insert(coinbase_name, unblinded_output); + mine_block_without_wallet_with_template(&mut client, template.new_block_template.unwrap()).await; +} + +pub async fn mine_block_before_submit(client: &mut BaseNodeClient) -> Block { + let (template, _unblinded_output) = create_block_template_with_coinbase_without_wallet(client, 0).await; + + let new_block = client + .get_new_block(template.new_block_template.unwrap()) + .await + .unwrap() + .into_inner(); + + new_block.block.unwrap() +} diff --git a/integration_tests/tests/utils/mod.rs b/integration_tests/tests/utils/mod.rs new file mode 100644 index 0000000000..ebf1b64492 --- /dev/null +++ b/integration_tests/tests/utils/mod.rs @@ -0,0 +1,83 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{net::TcpListener, ops::Range, time::Duration}; + +use rand::Rng; + +use crate::TariWorld; + +pub mod base_node_process; +pub mod ffi; +pub mod merge_mining_proxy; +pub mod miner; +pub mod transaction; +pub mod wallet; +pub mod wallet_ffi; +pub mod wallet_process; + +pub fn get_port(range: Range) -> Option { + let min = range.clone().min().expect("A minimum possible port number"); + let max = range.max().expect("A maximum possible port number"); + + loop { + let port = rand::thread_rng().gen_range(min, max); + + if TcpListener::bind(("127.0.0.1", port)).is_ok() { + return Some(u64::from(port)); + } + } +} + +pub async fn wait_for_service(port: u64) { + // The idea is that if the port is taken it means the service is running. + // If it's not taken the port hasn't come up yet + let max_tries = 40; + let mut attempts = 0; + + loop { + if TcpListener::bind(("127.0.0.1", port as u16)).is_err() { + return; + } + + if attempts >= max_tries { + panic!("Service on port {} never started", port); + } + + tokio::time::sleep(Duration::from_millis(250)).await; + attempts += 1; + } +} + +pub async fn get_peer_addresses(world: &TariWorld, peers: &Vec) -> Vec { + let mut peer_addresses = vec![]; + for peer in peers { + let peer = world.base_nodes.get(peer.as_str()).unwrap(); + peer_addresses.push(format!( + "{}::{}", + peer.identity.public_key(), + peer.identity.public_address() + )); + } + + peer_addresses +} diff --git a/integration_tests/tests/utils/transaction.rs b/integration_tests/tests/utils/transaction.rs new file mode 100644 index 0000000000..9f6b8a3312 --- /dev/null +++ b/integration_tests/tests/utils/transaction.rs @@ -0,0 +1,208 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::default::Default; + +use tari_common::configuration::Network; +use tari_common_types::types::{Commitment, PrivateKey, Signature}; +use tari_core::{ + consensus::ConsensusManager, + transactions::{ + tari_amount::MicroTari, + test_helpers::TestParams, + transaction_components::{ + KernelBuilder, + Transaction, + TransactionBuilder, + TransactionInput, + TransactionKernel, + TransactionOutput, + UnblindedOutput, + UnblindedOutputBuilder, + }, + transaction_protocol::TransactionMetadata, + CryptoFactories, + }, +}; +use tari_crypto::{ + keys::PublicKey, + ristretto::{ + pedersen::extended_commitment_factory::ExtendedPedersenCommitmentFactory, + RistrettoPublicKey, + RistrettoSecretKey, + }, +}; +use tari_script::{inputs, script}; + +#[derive(Clone)] +struct TestTransactionBuilder { + amount: MicroTari, + factories: CryptoFactories, + fee: MicroTari, + inputs_max_height: u64, + inputs: Vec<(TransactionInput, UnblindedOutput)>, + keys: TestParams, + lock_height: u64, + output: Option<(TransactionOutput, UnblindedOutput)>, +} + +impl TestTransactionBuilder { + pub fn new() -> Self { + Self { + amount: MicroTari(0), + factories: CryptoFactories::default(), + fee: MicroTari(0), + inputs_max_height: 0, + inputs: vec![], + keys: TestParams::new(), + lock_height: 0, + output: None, + } + } + + pub fn change_fee(&mut self, fee: MicroTari) -> &mut Self { + self.fee = fee; + self + } + + pub fn update_inputs_max_height(&mut self, height: u64) -> &mut Self { + self.inputs_max_height = height; + self + } + + fn update_amount(&mut self, amount: MicroTari) { + self.amount += amount + } + + pub fn add_input(&mut self, u: UnblindedOutput) -> &mut Self { + self.update_amount(u.value); + + if u.features.maturity > self.inputs_max_height { + self.update_inputs_max_height(u.features.maturity); + } + + self.inputs.push(( + u.as_transaction_input(&ExtendedPedersenCommitmentFactory::default()) + .expect("The Unblinded output to convert to an Input"), + u, + )); + + self + } + + pub fn build(mut self) -> (Transaction, UnblindedOutput) { + self.create_utxo(); + + let (script_offset_pvt, offset, kernel) = &self.build_kernel(); + + let output = self.output.clone().unwrap(); + + let mut tx_builder = TransactionBuilder::new(); + tx_builder + .add_inputs(&mut self.inputs.iter().map(|f| f.0.clone()).collect()) + .add_output(self.output.unwrap().0) + .add_offset(offset.clone()) + .add_script_offset(script_offset_pvt.clone()) + .with_kernel(kernel.clone()); + + let rules = ConsensusManager::builder(Network::LocalNet).build(); + let tx = tx_builder + .build(rules, &self.factories, None, self.inputs_max_height) + .unwrap(); + (tx, output.1) + } + + pub fn build_kernel(&self) -> (PrivateKey, RistrettoSecretKey, TransactionKernel) { + let input = &self.inputs[0].1.clone(); + let output = &self.output.clone().unwrap().1; + + let fee = self.fee; + let nonce = PrivateKey::default() + self.keys.nonce.clone(); + let offset = PrivateKey::default() + self.keys.offset.clone(); + + let script_offset_pvt = input.script_private_key.clone() - self.keys.sender_offset_private_key.clone(); + let excess_blinding_factor = output.spending_key.clone() - input.spending_key.clone(); + + let tx_meta = TransactionMetadata::new(fee, 0); + + let public_nonce = PublicKey::from_secret_key(&nonce); + let offset_blinding_factor = &excess_blinding_factor - &offset; + let excess = PublicKey::from_secret_key(&offset_blinding_factor); + let e = TransactionKernel::build_kernel_challenge_from_tx_meta(&public_nonce, &excess, &tx_meta); + let k = offset_blinding_factor; + let r = nonce; + let s = Signature::sign_raw(&k, r, &e).unwrap(); + + let kernel = KernelBuilder::new() + .with_fee(self.fee) + .with_lock_height(self.lock_height) + .with_excess(&Commitment::from_public_key(&excess)) + .with_signature(&s) + .build() + .unwrap(); + + (script_offset_pvt, offset, kernel) + } + + fn calculate_spendable(&self) -> MicroTari { + MicroTari(self.amount.0 - self.fee.0) + } + + fn create_utxo(&mut self) { + let input_data: RistrettoPublicKey = PublicKey::from_secret_key(&self.keys.script_private_key); + + let mut builder = UnblindedOutputBuilder::new(self.calculate_spendable(), self.keys.spend_key.clone()) + .with_features(Default::default()) + .with_script(script!(Nop)) + .with_script_private_key(self.keys.script_private_key.clone()) + .with_input_data(inputs!(input_data)); + builder.with_sender_offset_public_key(self.keys.sender_offset_public_key.clone()); + builder + .sign_as_sender_and_receiver(&self.keys.sender_offset_private_key.clone()) + .expect("sign as sender and receiver"); + let unblinded = builder.try_build().expect("Get output from unblinded output"); + let utxo = unblinded + .as_transaction_output(&self.factories) + .expect("unblinded into output"); + + self.output = Some((utxo, unblinded)); + } +} + +pub fn build_transaction_with_output_and_fee(utxos: Vec, fee: u64) -> (Transaction, UnblindedOutput) { + let mut builder = TestTransactionBuilder::new(); + for unblinded_output in utxos { + builder.add_input(unblinded_output); + } + builder.change_fee(MicroTari(fee)); + + builder.build() +} + +pub fn build_transaction_with_output(utxos: Vec) -> (Transaction, UnblindedOutput) { + let mut builder = TestTransactionBuilder::new(); + for unblinded_output in utxos { + builder.add_input(unblinded_output); + } + + builder.build() +} diff --git a/integration_tests/helpers/types.js b/integration_tests/tests/utils/wallet.rs similarity index 77% rename from integration_tests/helpers/types.js rename to integration_tests/tests/utils/wallet.rs index ee40461b63..e310a4ca5f 100644 --- a/integration_tests/helpers/types.js +++ b/integration_tests/tests/utils/wallet.rs @@ -1,4 +1,4 @@ -// Copyright 2021, The Tari Project +// Copyright 2021. The Tari Project // // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the // following conditions are met: @@ -20,23 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -const PowAlgo = { MONERO: 0, SHA3: 1 }; +use async_trait::async_trait; -const ConnectivityStatus = { - INITIALIZING: "Initializing", - ONLINE: "Online", - DEGRADED: "Degraded", - OFFLINE: "Offline", -}; - -const PaymentType = { - STANDARD_MIMBLEWIMBLE: 0, - ONE_SIDED: 1, - ONE_SIDED_TO_STEALTH_ADDRESS: 2, -}; - -const OutputType = { - STANDARD: 0, - COINBASE: 1, -}; -module.exports = { PowAlgo, ConnectivityStatus, PaymentType, OutputType }; +#[async_trait] +pub trait WalletClient: Send + Sync {} diff --git a/integration_tests/tests/utils/wallet_ffi.rs b/integration_tests/tests/utils/wallet_ffi.rs new file mode 100644 index 0000000000..a5d8263ef4 --- /dev/null +++ b/integration_tests/tests/utils/wallet_ffi.rs @@ -0,0 +1,216 @@ +// Copyright 2021. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ + ffi::CString, + ptr::null, + sync::{Arc, Mutex}, + time::SystemTime, +}; + +use chrono::{DateTime, Utc}; +use indexmap::IndexMap; +use libc::c_void; + +// use tari_wallet_ffi::*; +use super::{ + ffi::{ + Balance, + Callbacks, + CompletedTransactions, + Contact, + Contacts, + ContactsLivenessData, + FeePerGramStats, + PendingInboundTransactions, + PendingOutboundTransactions, + PublicKeys, + WalletAddress, + }, + get_port, +}; +use crate::{ + utils::ffi::{self}, + TariWorld, +}; + +#[derive(Debug)] +pub struct WalletFFI { + pub name: String, + pub port: u64, + // pub grpc_port: u64, + // pub temp_dir_path: String, + pub wallet: Arc>, +} + +impl WalletFFI { + fn spawn(name: String, seed_words_ptr: *const c_void) -> Self { + let port = get_port(18000..18499).unwrap(); + let transport_config = + ffi::TransportConfig::create_tcp(CString::new(format!("/ip4/127.0.0.1/tcp/{}", port)).unwrap().into_raw()); + let now: DateTime = SystemTime::now().into(); + let base_dir = format!("./temp/base_nodes/{}", now.format("%Y%m%d-%H%M%S")); + let comms_config = ffi::CommsConfig::create(port, transport_config, base_dir.clone()); + let log_path = format!("{}/log/wallet.log", base_dir); + let wallet = ffi::Wallet::create(comms_config, log_path, seed_words_ptr); + Self { name, port, wallet } + } + + pub fn identify(&self) -> String { + let tari_address = self.get_address(); + let key = tari_address.address(); + key.get_as_hex() + } + + pub fn get_emoji_id(&self) -> String { + let tari_address = self.get_address(); + let emoji_id = tari_address.emoji_id(); + emoji_id.as_string() + } + + pub fn add_base_node(&self, public_key: String, address: String) { + let node_public_key = ffi::PublicKey::from_hex(public_key); + self.wallet.lock().unwrap().add_base_node_peer(node_public_key, address); + } + + pub fn destroy(&mut self) { + self.wallet.lock().unwrap().destroy(); + } + + pub fn get_address(&self) -> WalletAddress { + self.wallet.lock().unwrap().get_address() + } + + pub fn connected_public_keys(&self) -> PublicKeys { + self.wallet.lock().unwrap().connected_public_keys() + } + + pub fn get_balance(&self) -> Balance { + self.wallet.lock().unwrap().get_balance() + } + + pub fn upsert_contact(&self, contact: Contact) -> bool { + self.wallet.lock().unwrap().upsert_contact(contact) + } + + pub fn get_contacts(&self) -> Contacts { + self.wallet.lock().unwrap().get_contacts() + } + + pub fn remove_contact(&self, contact_to_remove: Contact) -> bool { + self.wallet.lock().unwrap().remove_contact(contact_to_remove) + } + + pub fn get_pending_inbound_transactions(&self) -> PendingInboundTransactions { + self.wallet.lock().unwrap().get_pending_inbound_transactions() + } + + pub fn get_pending_outbound_transactions(&self) -> PendingOutboundTransactions { + self.wallet.lock().unwrap().get_pending_outbound_transactions() + } + + pub fn get_completed_transactions(&self) -> CompletedTransactions { + self.wallet.lock().unwrap().get_completed_transactions() + } + + pub fn cancel_pending_transaction(&self, transaction_id: u64) -> bool { + self.wallet.lock().unwrap().cancel_pending_transaction(transaction_id) + } + + pub fn get_counters(&self) -> &mut Callbacks { + let callback = Callbacks::instance(); + callback + } + + pub fn start_txo_validation(&self) -> u64 { + self.wallet.lock().unwrap().start_txo_validation() + } + + pub fn start_transaction_validation(&self) -> u64 { + self.wallet.lock().unwrap().start_transaction_validation() + } + + pub fn get_liveness_data(&self) -> Arc>> { + self.wallet.lock().unwrap().get_liveness_data() + } + + pub fn send_transaction( + &self, + dest: String, + amount: u64, + fee_per_gram: u64, + message: String, + one_sided: bool, + ) -> u64 { + self.wallet + .lock() + .unwrap() + .send_transaction(dest, amount, fee_per_gram, message, one_sided) + } + + pub fn restart(&mut self) { + self.wallet.lock().unwrap().destroy(); + let port = get_port(18000..18499).unwrap(); + let transport_config = + ffi::TransportConfig::create_tcp(CString::new(format!("/ip4/127.0.0.1/tcp/{}", port)).unwrap().into_raw()); + let now: DateTime = SystemTime::now().into(); + let base_dir = format!("./temp/base_nodes/{}", now.format("%Y%m%d-%H%M%S")); + let comms_config = ffi::CommsConfig::create(port, transport_config, base_dir.clone()); + let log_path = format!("{}/log/wallet.log", base_dir); + self.wallet = ffi::Wallet::create(comms_config, log_path, null()); + } + + pub fn get_fee_per_gram_stats(&self, count: u32) -> FeePerGramStats { + self.wallet.lock().unwrap().get_fee_per_gram_stats(count) + } +} + +pub fn spawn_wallet_ffi(world: &mut TariWorld, wallet_name: String, seed_words_ptr: *const c_void) { + let wallet_ffi = WalletFFI::spawn(wallet_name.clone(), seed_words_ptr); + world.ffi_wallets.insert(wallet_name, wallet_ffi); +} + +pub fn get_mnemonic_word_list_for_language(language: String) -> ffi::SeedWords { + let language = match language.as_str() { + "CHINESE_SIMPLIFIED" => "ChineseSimplified", + "ENGLISH" => "English", + "FRENCH" => "French", + "ITALIAN" => "Italian", + "JAPANESE" => "Japanese", + "KOREAN" => "Korean", + "SPANISH" => "Spanish", + _ => panic!("Unknown language {}", language), + }; + ffi::SeedWords::get_mnemonic_word_list_for_language(language.to_string()) +} + +pub fn create_contact(alias: String, address: String) -> ffi::Contact { + ffi::Contact::create(alias, address) +} + +pub fn create_seed_words(words: Vec<&str>) -> ffi::SeedWords { + let seed_words = ffi::SeedWords::create(); + for word in words { + seed_words.push_word(word.to_string()); + } + seed_words +} diff --git a/integration_tests/tests/utils/wallet_process.rs b/integration_tests/tests/utils/wallet_process.rs new file mode 100644 index 0000000000..081ee19945 --- /dev/null +++ b/integration_tests/tests/utils/wallet_process.rs @@ -0,0 +1,229 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{path::PathBuf, str::FromStr, thread, time::Duration}; + +use tari_app_grpc::tari_rpc::SetBaseNodeRequest; +use tari_app_utilities::common_cli_args::CommonCliArgs; +use tari_common::configuration::CommonConfig; +use tari_comms::multiaddr::Multiaddr; +use tari_comms_dht::DhtConfig; +use tari_console_wallet::{run_wallet_with_cli, Cli}; +use tari_p2p::{auto_update::AutoUpdateConfig, Network, PeerSeedsConfig, TransportType}; +use tari_shutdown::Shutdown; +use tari_wallet::{transaction_service::config::TransactionRoutingMechanism, WalletConfig}; +use tari_wallet_grpc_client::WalletGrpcClient; +use tempfile::tempdir; +use tokio::runtime; +use tonic::transport::Channel; + +use crate::{ + get_peer_addresses, + utils::{get_port, wait_for_service}, + TariWorld, +}; + +#[derive(Clone, Debug)] +pub struct WalletProcess { + pub name: String, + pub port: u64, + pub grpc_port: u64, + pub temp_dir_path: PathBuf, + pub kill_signal: Shutdown, +} + +impl Drop for WalletProcess { + fn drop(&mut self) { + self.kill(); + } +} + +#[allow(clippy::too_many_lines)] +pub async fn spawn_wallet( + world: &mut TariWorld, + wallet_name: String, + base_node_name: Option, + peer_seeds: Vec, + routing_mechanism: Option, + cli: Option, +) { + let port: u64; + let grpc_port: u64; + let temp_dir_path: PathBuf; + + if let Some(wallet_ps) = world.wallets.get(&wallet_name) { + port = wallet_ps.port; + grpc_port = wallet_ps.grpc_port; + temp_dir_path = wallet_ps.temp_dir_path.clone(); + } else { + // each spawned wallet will use different ports + port = get_port(18000..18499).unwrap(); + grpc_port = get_port(18500..18999).unwrap(); + // create a new temporary directory + temp_dir_path = tempdir().unwrap().path().to_path_buf(); + }; + + let base_node = base_node_name.map(|name| { + let pubkey = world.base_nodes.get(&name).unwrap().identity.public_key().clone(); + let port = world.base_nodes.get(&name).unwrap().port; + let set_base_node_request = SetBaseNodeRequest { + net_address: format! {"/ip4/127.0.0.1/tcp/{}", port}, + public_key_hex: pubkey.to_string(), + }; + + (pubkey, port, set_base_node_request) + }); + + let peer_addresses = get_peer_addresses(world, &peer_seeds).await; + + let base_node_cloned = base_node.clone(); + let shutdown = Shutdown::new(); + let mut send_to_thread_shutdown = shutdown.clone(); + + let temp_dir = temp_dir_path.clone(); + + thread::spawn(move || { + let mut wallet_config = tari_console_wallet::ApplicationConfig { + common: CommonConfig::default(), + auto_update: AutoUpdateConfig::default(), + wallet: WalletConfig::default(), + peer_seeds: PeerSeedsConfig { + peer_seeds: peer_addresses.into(), + ..Default::default() + }, + }; + + eprintln!("Using wallet temp_dir: {}", temp_dir_path.clone().display()); + + wallet_config.wallet.identity_file = Some(temp_dir_path.clone().join("wallet_id.json")); + wallet_config.wallet.network = Network::LocalNet; + wallet_config.wallet.password = Some("test".into()); + wallet_config.wallet.grpc_enabled = true; + wallet_config.wallet.grpc_address = + Some(Multiaddr::from_str(&format!("/ip4/127.0.0.1/tcp/{}", grpc_port)).unwrap()); + wallet_config.wallet.data_dir = temp_dir_path.clone().join("data").join("wallet"); + wallet_config.wallet.db_file = temp_dir_path.clone().join("db").join("console_wallet.db"); + wallet_config.wallet.contacts_auto_ping_interval = Duration::from_secs(5); + wallet_config.wallet.p2p.transport.transport_type = TransportType::Tcp; + wallet_config.wallet.p2p.transport.tcp.listener_address = + Multiaddr::from_str(&format!("/ip4/127.0.0.1/tcp/{}", port)).unwrap(); + wallet_config.wallet.p2p.public_address = Some(wallet_config.wallet.p2p.transport.tcp.listener_address.clone()); + wallet_config.wallet.p2p.datastore_path = temp_dir_path.clone().join("peer_db").join("wallet"); + wallet_config.wallet.p2p.dht = DhtConfig::default_local_test(); + wallet_config.wallet.p2p.allow_test_addresses = true; + if let Some(mech) = routing_mechanism { + wallet_config + .wallet + .transaction_service_config + .transaction_routing_mechanism = mech; + } + + // FIXME: wallet doesn't pick up the custom base node for some reason atm + wallet_config.wallet.custom_base_node = + base_node_cloned.map(|(pubkey, port, _)| format!("{}::/ip4/127.0.0.1/tcp/{}", pubkey, port)); + + let rt = runtime::Builder::new_multi_thread().enable_all().build().unwrap(); + + let mut cli = cli.unwrap_or_else(get_default_cli); + // We expect only file_name to be passed from cucumber.rs, now we put it in the right directory. + if let Some(file_name) = cli.seed_words_file_name { + cli.seed_words_file_name = Some(temp_dir_path.join(file_name)); + } + + if let Err(e) = run_wallet_with_cli(&mut send_to_thread_shutdown, rt, &mut wallet_config, cli) { + panic!("{:?}", e); + } + }); + + // make the new wallet able to be referenced by other processes + world.wallets.insert(wallet_name.clone(), WalletProcess { + name: wallet_name.clone(), + port, + grpc_port, + temp_dir_path: temp_dir, + kill_signal: shutdown, + }); + + tokio::time::sleep(Duration::from_secs(5)).await; + + wait_for_service(port).await; + wait_for_service(grpc_port).await; + + // TODO: fix the wallet configuration so the base node is correctly setted on startup insted of afterwards + if let Some((_, _, hacky_request)) = base_node { + let mut wallet_client = create_wallet_client(world, wallet_name) + .await + .expect("wallet grpc client"); + + let _resp = wallet_client.set_base_node(hacky_request).await.unwrap(); + } + + tokio::time::sleep(Duration::from_secs(2)).await; +} + +pub fn get_default_cli() -> Cli { + Cli { + // CommonCliArgs are ignored in test, it's used only to override the config in the main.rs of the wallet. + common: CommonCliArgs { + base_path: Default::default(), + config: Default::default(), + log_config: None, + log_level: None, + config_property_overrides: vec![], + }, + password: None, + change_password: false, + recovery: false, + seed_words: None, + seed_words_file_name: None, + non_interactive_mode: true, + input_file: None, + command: None, + wallet_notify: None, + command_mode_auto_exit: false, + network: None, + grpc_enabled: true, + grpc_address: None, + command2: None, + } +} + +pub async fn create_wallet_client(world: &TariWorld, wallet_name: String) -> anyhow::Result> { + let wallet_grpc_port = world.wallets.get(&wallet_name).unwrap().grpc_port; + let wallet_addr = format!("http://127.0.0.1:{}", wallet_grpc_port); + + eprintln!("Wallet GRPC at {}", wallet_addr); + + Ok(WalletGrpcClient::connect(wallet_addr.as_str()).await?) +} + +impl WalletProcess { + #[allow(dead_code)] + pub async fn get_grpc_client(&self) -> anyhow::Result> { + let wallet_addr = format!("http://127.0.0.1:{}", self.grpc_port); + Ok(WalletGrpcClient::connect(wallet_addr.as_str()).await?) + } + + pub fn kill(&mut self) { + self.kill_signal.trigger(); + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index e53cb68238..5cb4a98c22 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -8,9 +8,9 @@ # AND update the action-buildlibs dockerfile. -# Hours spent updating the Rust Toolchain = 7 +# Hours spent updating the Rust Toolchain = 10 # other places to check: # - the CI files in .github folder # - the Makefile in base_layer/key_manager/Makefile [toolchain] -channel = "nightly-2022-05-01" +channel = "nightly-2022-11-03"